diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8139ab7f52..799572c06d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -85,14 +85,6 @@ android { } } - // START Non-FOSS component - if (project.file("google-services.json").exists()) { - sourceSets.getByName("main").kotlin.directories.add("src/play/java") - } - // END Non-FOSS component - if (!project.file("google-services.json").exists()){ - sourceSets.getByName("main").kotlin.directories.add("src/foss/java") - } } dependencies { @@ -123,7 +115,14 @@ dependencies { implementation(libs.androidx.window) coreLibraryDesugaring(libs.desugar.jdk.libs) implementation(libs.compose.webview) - implementation(projects.shared) + implementation(projects.presentation.features) + implementation(projects.core.humanizer) + implementation(projects.modules.draft.presentation) + implementation(projects.modules.ai.data) + implementation(projects.foundation.network) + implementation(projects.foundation.filesystem) + implementation(projects.social.rss) + implementation(projects.social.rss.model) implementation(projects.composeUi) implementation(libs.androidx.splash) implementation(libs.materialKolor) @@ -142,9 +141,6 @@ dependencies { implementation(platform(libs.firebase.bom)) implementation(libs.firebase.crashlytics.ktx) implementation(libs.firebase.analytics.ktx) - implementation(libs.kotlinx.coroutines.play.services) - implementation("com.google.mlkit:genai-prompt:1.0.0-beta2") - implementation("com.google.mlkit:genai-summarization:1.0.0-beta1") } // END Non-FOSS component diff --git a/app/src/foss/java/dev/dimension/flare/di/AiModule.kt b/app/src/foss/java/dev/dimension/flare/di/AiModule.kt deleted file mode 100644 index 759f276018..0000000000 --- a/app/src/foss/java/dev/dimension/flare/di/AiModule.kt +++ /dev/null @@ -1,10 +0,0 @@ -package dev.dimension.flare.di - -import dev.dimension.flare.common.FossOnDeviceAI -import dev.dimension.flare.common.OnDeviceAI -import org.koin.dsl.module - -val aiModule = - module { - single { FossOnDeviceAI(get()) } - } diff --git a/app/src/main/java/dev/dimension/flare/App.kt b/app/src/main/java/dev/dimension/flare/App.kt index f1a918c236..0f35c0fa2d 100644 --- a/app/src/main/java/dev/dimension/flare/App.kt +++ b/app/src/main/java/dev/dimension/flare/App.kt @@ -16,7 +16,6 @@ import dev.dimension.flare.common.AnimatedPngDecoder import dev.dimension.flare.common.AnimatedWebPDecoder import dev.dimension.flare.data.network.ktorClient import dev.dimension.flare.di.KoinHelper -import dev.dimension.flare.di.aiModule import dev.dimension.flare.di.androidModule import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin @@ -28,7 +27,7 @@ class App : super.onCreate() startKoin { androidContext(this@App) - modules(KoinHelper.modules() + androidModule + aiModule) + modules(KoinHelper.modules() + androidModule) } } diff --git a/app/src/main/java/dev/dimension/flare/common/ComposeInAppNotification.kt b/app/src/main/java/dev/dimension/flare/common/ComposeInAppNotification.kt index aece1754cd..8fde93e00d 100644 --- a/app/src/main/java/dev/dimension/flare/common/ComposeInAppNotification.kt +++ b/app/src/main/java/dev/dimension/flare/common/ComposeInAppNotification.kt @@ -2,7 +2,7 @@ package dev.dimension.flare.common import androidx.annotation.StringRes import dev.dimension.flare.R -import dev.dimension.flare.data.repository.LoginExpiredException +import dev.dimension.flare.model.LoginExpiredException import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/app/src/main/java/dev/dimension/flare/ui/route/Route.kt b/app/src/main/java/dev/dimension/flare/ui/route/Route.kt index 1bc5f74489..38a36b068c 100644 --- a/app/src/main/java/dev/dimension/flare/ui/route/Route.kt +++ b/app/src/main/java/dev/dimension/flare/ui/route/Route.kt @@ -3,6 +3,7 @@ package dev.dimension.flare.ui.route import androidx.compose.runtime.Immutable import androidx.navigation3.runtime.NavKey import dev.dimension.flare.data.model.tab.TimelineSourceRef +import dev.dimension.flare.data.model.tab.xqtDeviceFollowTimelineSource import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import kotlinx.collections.immutable.ImmutableMap @@ -470,7 +471,7 @@ internal sealed interface Route : NavKey { (deeplinkRoute.accountType as? AccountType.Specific)?.accountKey ?: return null Route.Timeline( - source = TimelineSourceRef.xqtDeviceFollow(accountKey), + source = xqtDeviceFollowTimelineSource(accountKey), ) } diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/compose/ComposeScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/compose/ComposeScreen.kt index 263a437de1..75c58c52b4 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/compose/ComposeScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/compose/ComposeScreen.kt @@ -89,7 +89,7 @@ import compose.icons.fontawesomeicons.solid.SquarePollHorizontal import compose.icons.fontawesomeicons.solid.TriangleExclamation import compose.icons.fontawesomeicons.solid.Xmark import dev.dimension.flare.R -import dev.dimension.flare.common.FileItem +import dev.dimension.flare.data.io.FileItem import dev.dimension.flare.data.datasource.microblog.ComposeConfig import dev.dimension.flare.data.datasource.microblog.ComposeData import dev.dimension.flare.data.model.PostActionStyle diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/home/ChangeLogState.kt b/app/src/main/java/dev/dimension/flare/ui/screen/home/ChangeLogState.kt index aa00e8a12e..d84241225b 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/home/ChangeLogState.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/home/ChangeLogState.kt @@ -10,7 +10,7 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.fromHtml import dev.dimension.flare.BuildConfig import dev.dimension.flare.R -import dev.dimension.flare.data.repository.SettingsRepository +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.collectAsUiState import dev.dimension.flare.ui.model.map @@ -20,7 +20,7 @@ import org.koin.compose.koinInject @Composable internal fun changeLogPresenter( context: Context = koinInject(), - repository: SettingsRepository = koinInject(), + repository: AppDataStore = koinInject(), ): ChangeLogState { val scope = rememberCoroutineScope() val appSettings by repository.appSettings.collectAsUiState() diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/list/CreateListDialog.kt b/app/src/main/java/dev/dimension/flare/ui/screen/list/CreateListDialog.kt index ccecece83f..0f6e56dd91 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/list/CreateListDialog.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/list/CreateListDialog.kt @@ -37,7 +37,7 @@ import compose.icons.FontAwesomeIcons import compose.icons.fontawesomeicons.Solid import compose.icons.fontawesomeicons.solid.Rss import dev.dimension.flare.R -import dev.dimension.flare.common.FileItem +import dev.dimension.flare.data.io.FileItem import dev.dimension.flare.data.datasource.microblog.list.ListMetaData import dev.dimension.flare.data.datasource.microblog.list.ListMetaDataType import dev.dimension.flare.model.AccountType diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/list/EditListScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/list/EditListScreen.kt index 118bc18a98..52d975bf8b 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/list/EditListScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/list/EditListScreen.kt @@ -48,7 +48,7 @@ import compose.icons.fontawesomeicons.solid.Rss import compose.icons.fontawesomeicons.solid.Trash import compose.icons.fontawesomeicons.solid.UserPen import dev.dimension.flare.R -import dev.dimension.flare.common.FileItem +import dev.dimension.flare.data.io.FileItem import dev.dimension.flare.common.onEmpty import dev.dimension.flare.common.onError import dev.dimension.flare.common.onLoading diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/list/ListEntryBuilder.kt b/app/src/main/java/dev/dimension/flare/ui/screen/list/ListEntryBuilder.kt index 2c583d7470..c02cb0bdca 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/list/ListEntryBuilder.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/list/ListEntryBuilder.kt @@ -10,7 +10,7 @@ import dev.dimension.flare.data.model.IconType import dev.dimension.flare.data.model.tab.SourceTimelineTabItemV2 import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.model.UiText -import dev.dimension.flare.ui.presenter.list.ListTimelinePresenter +import dev.dimension.flare.ui.presenter.list.createListTimeline import dev.dimension.flare.ui.route.Route import dev.dimension.flare.ui.screen.home.TimelineScreen @@ -63,7 +63,7 @@ internal fun EntryProviderScope.listEntryBuilder( title = UiText.Raw(args.title), icon = IconType.Material(UiIcon.List), createPresenter = { - ListTimelinePresenter( + createListTimeline( accountType = args.accountType, listId = args.listId, ) diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/misskey/MisskeyEntryBuilder.kt b/app/src/main/java/dev/dimension/flare/ui/screen/misskey/MisskeyEntryBuilder.kt index 104cc38343..8849296742 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/misskey/MisskeyEntryBuilder.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/misskey/MisskeyEntryBuilder.kt @@ -25,8 +25,8 @@ import dev.dimension.flare.ui.component.FAIcon import dev.dimension.flare.ui.component.FlareScaffold import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.model.UiText -import dev.dimension.flare.ui.presenter.list.AntennasTimelinePresenter -import dev.dimension.flare.ui.presenter.list.ChannelTimelinePresenter +import dev.dimension.flare.ui.presenter.list.createMisskeyAntennaTimeline +import dev.dimension.flare.ui.presenter.list.createMisskeyChannelTimeline import dev.dimension.flare.ui.route.Route import dev.dimension.flare.ui.screen.home.TimelineScreen @@ -64,7 +64,7 @@ internal fun EntryProviderScope.misskeyEntryBuilder( title = UiText.Raw(args.title), icon = IconType.Material(UiIcon.Rss), createPresenter = { - AntennasTimelinePresenter( + createMisskeyAntennaTimeline( accountType = args.accountType, id = args.antennaId, ) @@ -104,7 +104,7 @@ internal fun EntryProviderScope.misskeyEntryBuilder( title = UiText.Raw(args.title), icon = IconType.Material(UiIcon.List), createPresenter = { - ChannelTimelinePresenter( + createMisskeyChannelTimeline( accountType = args.accountType, id = args.channelId, ) diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssDetailScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssDetailScreen.kt index aaf42eea5c..21ca098abf 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssDetailScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssDetailScreen.kt @@ -48,8 +48,8 @@ import compose.icons.fontawesomeicons.solid.Language import compose.icons.fontawesomeicons.solid.ShareNodes import dev.dimension.flare.R import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.network.rss.DocumentData -import dev.dimension.flare.data.repository.SettingsRepository import dev.dimension.flare.ui.component.BackButton import dev.dimension.flare.ui.component.DateTimeText import dev.dimension.flare.ui.component.FAIcon @@ -362,7 +362,7 @@ private fun presenter( url: String, descriptionHtml: String? = null, descriptionTitle: String? = null, - settingsRepository: SettingsRepository = koinInject(), + appDataStore: AppDataStore = koinInject(), ) = run { val state = remember(url, descriptionHtml) { @@ -377,10 +377,10 @@ private fun presenter( ) } val enableTldr by remember { - settingsRepository.appSettings.map { it.aiConfig.tldr } + appDataStore.appSettings.map { it.aiConfig.tldr } }.collectAsUiState() val preTranslate by remember { - settingsRepository.appSettings.map { it.translateConfig.preTranslate } + appDataStore.appSettings.map { it.translateConfig.preTranslate } }.collectAsUiState() var showTldr by remember { mutableStateOf(false) } var tldrRefreshKey by remember { mutableIntStateOf(0) } diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssEntryBuilder.kt b/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssEntryBuilder.kt index cfa18f1cb5..cc54e7be9b 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssEntryBuilder.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssEntryBuilder.kt @@ -26,7 +26,7 @@ import dev.dimension.flare.ui.component.FAIcon import dev.dimension.flare.ui.component.FlareScaffold import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.model.UiText -import dev.dimension.flare.ui.presenter.home.rss.RssTimelinePresenter +import dev.dimension.flare.ui.presenter.home.rss.createRssTimeline import dev.dimension.flare.ui.route.Route import dev.dimension.flare.ui.screen.home.TimelineScreen @@ -81,7 +81,7 @@ internal fun EntryProviderScope.rssEntryBuilder( IconType.Url(args.favIcon) } ?: IconType.Material(UiIcon.Rss), createPresenter = { - RssTimelinePresenter(args.url) + createRssTimeline(args.url) }, ) }, diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssSourceEditSheet.kt b/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssSourceEditSheet.kt index ee721b98cd..dcd0f9dd55 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssSourceEditSheet.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssSourceEditSheet.kt @@ -49,8 +49,8 @@ import compose.icons.fontawesomeicons.solid.CircleCheck import compose.icons.fontawesomeicons.solid.CircleChevronDown import compose.icons.fontawesomeicons.solid.CircleXmark import dev.dimension.flare.R -import dev.dimension.flare.data.database.app.model.RssDisplayMode import dev.dimension.flare.data.database.app.model.SubscriptionType +import dev.dimension.flare.model.RssDisplayMode import dev.dimension.flare.ui.component.FAIcon import dev.dimension.flare.ui.component.NetworkImage import dev.dimension.flare.ui.model.UiRssSource diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AccountsScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AccountsScreen.kt index 6f478dc524..eaebcd1db9 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AccountsScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AccountsScreen.kt @@ -56,7 +56,7 @@ import compose.icons.fontawesomeicons.solid.List import compose.icons.fontawesomeicons.solid.Plus import compose.icons.fontawesomeicons.solid.Trash import dev.dimension.flare.R -import dev.dimension.flare.data.repository.LoginExpiredException +import dev.dimension.flare.model.LoginExpiredException import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType import dev.dimension.flare.ui.component.AvatarComponent diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceCommon.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceCommon.kt index 60bf415d0e..b210f0ced8 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceCommon.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceCommon.kt @@ -17,9 +17,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.model.appearance.AppearanceKey import dev.dimension.flare.data.model.appearance.AppearancePatch -import dev.dimension.flare.data.repository.SettingsRepository import dev.dimension.flare.ui.component.FlareDropdownMenu import dev.dimension.flare.ui.component.platform.isBigScreen import dev.dimension.flare.ui.model.UiState @@ -116,9 +116,9 @@ internal interface AppearanceSettingsUpdater : AppearanceState { internal fun appearancePresenter(): AppearanceSettingsUpdater = run { val scope = rememberCoroutineScope() - val settingsRepository = koinInject() + val appDataStore = koinInject() val appearanceState = remember { AppearancePresenter() }.invoke() - val appearance by settingsRepository.appearancePatch.collectAsUiState() + val appearance by appDataStore.appearancePatch.collectAsUiState() object : AppearanceSettingsUpdater, AppearanceState by appearanceState { override val appearance: UiState = appearance @@ -128,19 +128,19 @@ internal fun appearancePresenter(): AppearanceSettingsUpdater = value: T, ) { scope.launch { - settingsRepository.updateAppearance(key, value) + appDataStore.updateAppearance(key, value) } } override fun clear(key: AppearanceKey<*>) { scope.launch { - settingsRepository.clearAppearance(key) + appDataStore.clearAppearance(key) } } override fun updateFontScale(fontSizeDiff: Float) { scope.launch { - settingsRepository.updateAppearance { + appDataStore.updateAppearance { set(dev.dimension.flare.data.model.appearance.AppearanceKeys.FontSizeDiff, fontSizeDiff) .set(dev.dimension.flare.data.model.appearance.AppearanceKeys.LineHeightDiff, fontSizeDiff * 2) } diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/ColorPickerDialog.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/ColorPickerDialog.kt index 725c837c0c..9f64bc583c 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/ColorPickerDialog.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/ColorPickerDialog.kt @@ -25,8 +25,8 @@ import com.github.skydoves.colorpicker.compose.ColorEnvelope import com.github.skydoves.colorpicker.compose.HsvColorPicker import com.github.skydoves.colorpicker.compose.rememberColorPickerController import dev.dimension.flare.R +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.model.appearance.AppearanceKeys -import dev.dimension.flare.data.repository.SettingsRepository import dev.dimension.flare.ui.component.LocalGlobalAppearance import dev.dimension.flare.ui.component.LocalTimelineAppearance import kotlinx.coroutines.CoroutineScope @@ -100,7 +100,7 @@ internal fun ColorPickerDialog(onBack: () -> Unit) { @Composable private fun presenter( initialColor: ULong, - settingsRepository: SettingsRepository = koinInject(), + appDataStore: AppDataStore = koinInject(), coroutineScope: CoroutineScope = koinInject(), ) = run { var selectedColor by remember { mutableStateOf(Color(initialColor)) } @@ -112,7 +112,7 @@ private fun presenter( fun confirm() { coroutineScope.launch { - settingsRepository.updateAppearance(AppearanceKeys.ColorSeed, selectedColor.value) + appDataStore.updateAppearance(AppearanceKeys.ColorSeed, selectedColor.value) } } } diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/status/action/StatusShareSheet.kt b/app/src/main/java/dev/dimension/flare/ui/screen/status/action/StatusShareSheet.kt index f4a95f1f7d..2c16d81866 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/status/action/StatusShareSheet.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/status/action/StatusShareSheet.kt @@ -78,6 +78,7 @@ import compose.icons.fontawesomeicons.solid.Download import compose.icons.fontawesomeicons.solid.Image import compose.icons.fontawesomeicons.solid.Link import dev.dimension.flare.R +import dev.dimension.flare.data.io.sanitizeFileName import dev.dimension.flare.data.model.VideoAutoplay import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey @@ -539,8 +540,6 @@ private fun View.findCaptureHostView(): ViewGroup? = ?: rootView as? ViewGroup ?: parent as? ViewGroup -private fun String.sanitizeForFileName(): String = replace(Regex("[^A-Za-z0-9._-]"), "_") - private fun saveBitmapToDownloads( context: Context, bitmap: Bitmap, @@ -551,7 +550,7 @@ private fun saveBitmapToDownloads( bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream) stream.toByteArray() } - val fileName = "status_${statusKey.sanitizeForFileName()}_${System.currentTimeMillis()}.png" + val fileName = "status_${statusKey.sanitizeFileName()}_${System.currentTimeMillis()}.png" saveByteArrayToDownloads( context = context, byteArray = bytes, @@ -571,7 +570,11 @@ private fun shareBitmapAsImage( if (bitmap.width <= 0 || bitmap.height <= 0) { return null } - val file = File(context.cacheDir, "status_share_${statusKey.sanitizeForFileName()}_${System.currentTimeMillis()}.png") + val file = + File( + context.cacheDir, + "status_share_${statusKey.sanitizeFileName()}_${System.currentTimeMillis()}.png", + ) FileOutputStream(file).use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) } diff --git a/app/src/play/java/dev/dimension/flare/di/AiModule.kt b/app/src/play/java/dev/dimension/flare/di/AiModule.kt deleted file mode 100644 index 53fd45d1cc..0000000000 --- a/app/src/play/java/dev/dimension/flare/di/AiModule.kt +++ /dev/null @@ -1,10 +0,0 @@ -package dev.dimension.flare.di - -import dev.dimension.flare.common.AndroidOnDeviceAI -import dev.dimension.flare.common.OnDeviceAI -import org.koin.dsl.module - -val aiModule = - module { - single { AndroidOnDeviceAI(get()) } - } diff --git a/build-logic/src/main/kotlin/dev/dimension/flare/buildlogic/FlareConventionSupport.kt b/build-logic/src/main/kotlin/dev/dimension/flare/buildlogic/FlareConventionSupport.kt index 75cf770c39..17df63cead 100644 --- a/build-logic/src/main/kotlin/dev/dimension/flare/buildlogic/FlareConventionSupport.kt +++ b/build-logic/src/main/kotlin/dev/dimension/flare/buildlogic/FlareConventionSupport.kt @@ -7,6 +7,7 @@ import org.gradle.api.JavaVersion import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.api.tasks.testing.AbstractTestTask import org.gradle.jvm.toolchain.JavaLanguageVersion import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.getByName @@ -45,20 +46,34 @@ class FlareRootConventionsPlugin : Plugin { version.set(ktlintCliVersion) filter { exclude { element -> element.file.path.contains("build", ignoreCase = true) } - if (subproject.path == ":shared") { - exclude { element -> - element.file.absolutePath.contains("data/network/misskey/api/", ignoreCase = true) - } - exclude { element -> - element.file.absolutePath.contains("data/network/xqt/", ignoreCase = true) - } - } + exclude { element -> element.file.absolutePath.contains("data/network/misskey/api/", ignoreCase = true) } + exclude { element -> element.file.absolutePath.contains("data/network/xqt/", ignoreCase = true) } } } + subproject.configureKspKtlintOrdering() + subproject.tasks + .matching { it.name == "wasmJsBrowserTest" } + .withType(AbstractTestTask::class.java) + .configureEach { + failOnNoDiscoveredTests.set(false) + onlyIf("Chrome is available for WASM browser tests") { + chromeBrowserIsAvailable(subproject) + } + } } } } +private fun chromeBrowserIsAvailable(project: Project): Boolean = + project.providers.environmentVariable("CHROME_BIN").orNull?.isNotBlank() == true || + listOf( + "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", + "/usr/bin/google-chrome", + "/usr/bin/google-chrome-stable", + "/usr/bin/chromium", + "/usr/bin/chromium-browser", + ).any { project.file(it).exists() } + class FlareAndroidApplicationSpec internal constructor( private val project: Project, ) { @@ -225,7 +240,14 @@ class FlareModuleSpec internal constructor( } if (FlarePlatform.WEB in selectedPlatforms && !kotlin.hasTarget("wasmJs")) { kotlin.wasmJs { - browser() + browser { + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.rootProject.file("karma.config.d")) + } + } + } } } @@ -254,6 +276,24 @@ class FlareModuleSpec internal constructor( } } +private fun Project.configureKspKtlintOrdering() { + pluginManager.withPlugin("com.google.devtools.ksp") { + val commonMetadataKsp = tasks.matching { it.name == "kspCommonMainKotlinMetadata" } + tasks + .matching { + it.name == "runKtlintCheckOverCommonMainSourceSet" || + it.name == "runKtlintFormatOverCommonMainSourceSet" + }.configureEach { + dependsOn(commonMetadataKsp) + } + tasks.configureEach { + if (name != "kspCommonMainKotlinMetadata" && name.startsWith("ksp")) { + dependsOn(commonMetadataKsp) + } + } + } +} + fun KotlinMultiplatformExtension.flare(configure: FlareModuleSpec.() -> Unit) { FlareModuleSpec(this).apply(configure).apply() } diff --git a/build.gradle.kts b/build.gradle.kts index 5a1a5f21fa..9e6cb4d059 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,7 @@ +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.artifacts.ProjectDependency + plugins { id("dev.dimension.flare.root-conventions") alias(libs.plugins.android.library) apply false @@ -14,3 +18,240 @@ plugins { alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.nucleus) apply false } + +val validateModuleBoundaries by tasks.registering { + group = "verification" + description = "Validates modularization dependency and source boundary rules." + + doLast { + val violations = mutableListOf() + + fun Project.projectDependencyPaths(): Set { + val paths = linkedSetOf() + configurations.forEach { configuration -> + configuration.dependencies.forEach { dependency -> + if (dependency is ProjectDependency) { + paths += dependency.path + } + } + } + return paths + } + + fun Project.kotlinSourceFiles() = + fileTree(projectDir) { + include("src/**/*.kt") + exclude("build/**") + }.files + + rootProject.findProject(":shared")?.let { + violations += ":shared is still included in settings.gradle.kts" + } + + val sharedProductionFiles = + fileTree(rootProject.layout.projectDirectory.dir("shared")) { + include("src/**/*.kt") + include("*.gradle.kts") + exclude("build/**") + }.files + if (sharedProductionFiles.isNotEmpty()) { + violations += + "shared still contains production files: " + + sharedProductionFiles.joinToString { it.relativeTo(rootDir).path } + } + + val socialPlatformModules = + subprojects + .map { it.path } + .filter { + it.startsWith(":social:") && + it !in setOf(":social:api", ":social:microblog", ":social:model", ":social:nodeinfo") + } + .toSet() + + subprojects.forEach { project -> + val dependencyPaths = project.projectDependencyPaths() + + dependencyPaths + .filter { it == ":shared" } + .forEach { violations += "${project.path} depends on removed module $it" } + + if (project.path.startsWith(":core:")) { + dependencyPaths + .filter { + it == ":network" || + it.startsWith(":storage:") || + it.startsWith(":capability:") || + it.startsWith(":social:") || + (it.startsWith(":presentation:") && it != ":presentation:model") || + it == ":compose-ui" + }.forEach { violations += "${project.path} must not depend on $it" } + } + + if (project.path.startsWith(":presentation:") || project.path.startsWith(":web:")) { + if (":compose-ui" in dependencyPaths) { + violations += "${project.path} must not depend on :compose-ui" + } + } + + if (project.path.startsWith(":web:") && ":social:nostr" in dependencyPaths) { + violations += "${project.path} must not export Nostr on Web" + } + + if (project.path == ":storage:database") { + dependencyPaths + .filter { + it.startsWith(":social:") || + it.startsWith(":capability:") || + (it.startsWith(":presentation:") && it != ":presentation:model") || + it == ":compose-ui" + }.forEach { violations += "${project.path} must not depend on $it" } + } + + if (project.path.startsWith(":storage:") && project.path != ":storage:database") { + dependencyPaths + .filter { + it.startsWith(":social:") || + it.startsWith(":capability:") || + it.startsWith(":presentation:") || + it == ":compose-ui" + }.forEach { violations += "${project.path} must not depend on $it" } + } + + if (project.path.startsWith(":capability:")) { + dependencyPaths + .filter { + (it.startsWith(":presentation:") && it != ":presentation:model") || + it == ":compose-ui" + }.forEach { violations += "${project.path} must not depend on $it" } + } + + if (project.path.startsWith(":social:")) { + dependencyPaths + .filter { + it.startsWith(":capability:") || + (it.startsWith(":presentation:") && it != ":presentation:model") || + it == ":compose-ui" + }.forEach { violations += "${project.path} must not depend on $it" } + } + + if (project.path in socialPlatformModules) { + dependencyPaths + .filter { it in socialPlatformModules } + .forEach { violations += "${project.path} must not depend on sibling platform module $it" } + } + + project.kotlinSourceFiles().forEach { file -> + file.useLines { lines -> + lines.forEachIndexed { index, line -> + if (line.matches(Regex("""^\s*package\s+dev\.dimension\.flare\.shared(\.|$).*"""))) { + violations += + "${file.relativeTo(rootDir).path}:${index + 1} must not use removed shared package: ${line.trim()}" + } + } + } + } + } + + val projectDependencyGraph = + subprojects.associate { project -> + project.path to + project + .projectDependencyPaths() + .filter { it != project.path && rootProject.findProject(it) != null } + .toSet() + } + val visitState = mutableMapOf() + val dependencyStack = mutableListOf() + val cycles = linkedSetOf() + + fun visitProject(path: String) { + visitState[path] = 1 + dependencyStack += path + projectDependencyGraph[path].orEmpty().forEach { dependency -> + when (visitState[dependency]) { + 1 -> { + val cycleStart = dependencyStack.indexOf(dependency) + if (cycleStart >= 0) { + cycles += (dependencyStack.drop(cycleStart) + dependency).joinToString(" -> ") + } + } + + 2 -> Unit + else -> visitProject(dependency) + } + } + dependencyStack.removeAt(dependencyStack.lastIndex) + visitState[path] = 2 + } + + projectDependencyGraph.keys.forEach { path -> + if (visitState[path] == null) { + visitProject(path) + } + } + cycles.forEach { cycle -> + violations += "Project dependency cycle: $cycle" + } + + val forbiddenRegistryBypassPatterns = + listOf( + Regex("""\bPlatformType\.(spec|icon|agreementUrl)\b""") to + "use SocialPlatformRegistry metadata instead of PlatformType extensions", + Regex("""\b(fun|val)\s+PlatformType\.(spec|icon|agreementUrl)\b""") to + "do not reintroduce PlatformType metadata extensions", + Regex("""\bfun\s+UiAccount\.createDataSource\b""") to + "use SocialPlatformRegistry.createDataSource(account) instead of UiAccount.createDataSource()", + Regex("""\.\s*createDataSource\s*\(\s*\)""") to + "use SocialPlatformRegistry.createDataSource(account) instead of zero-argument createDataSource()", + Regex("""\bPlatformType\.entries\b""") to + "use SocialPlatformRegistry for platform iteration", + Regex("""\bPlatformType\.values\s*\(""") to + "use SocialPlatformRegistry for platform iteration", + ) + subprojects.forEach { project -> + project.kotlinSourceFiles().forEach { file -> + file.useLines { lines -> + lines.forEachIndexed { index, line -> + forbiddenRegistryBypassPatterns.forEach { (pattern, message) -> + if (pattern.containsMatchIn(line)) { + violations += + "${file.relativeTo(rootDir).path}:${index + 1} $message: ${line.trim()}" + } + } + } + } + } + } + + val forbiddenCoreImport = + Regex("""^import\s+dev\.dimension\.flare\.(data|social|ui\.presenter|ui\.component|ui\.screen)\.""") + subprojects + .filter { it.path.startsWith(":core:") } + .forEach { project -> + project.kotlinSourceFiles().forEach { file -> + file.useLines { lines -> + lines.forEachIndexed { index, line -> + if (forbiddenCoreImport.containsMatchIn(line)) { + violations += + "${file.relativeTo(rootDir).path}:${index + 1} forbidden core import: ${line.trim()}" + } + } + } + } + } + + if (violations.isNotEmpty()) { + throw GradleException( + violations.joinToString( + separator = "\n", + prefix = "Module boundary violations:\n", + ), + ) + } + } +} + +tasks.matching { it.name == "check" }.configureEach { + dependsOn(validateModuleBoundaries) +} diff --git a/compose-ui/build.gradle.kts b/compose-ui/build.gradle.kts index 8195d73b61..e4a28295d4 100644 --- a/compose-ui/build.gradle.kts +++ b/compose-ui/build.gradle.kts @@ -27,20 +27,29 @@ kotlin { android { experimentalProperties["android.experimental.kmp.enableAndroidResources"] = true } + + compilerOptions { + allWarningsAsErrors.set(false) + } + listOf("iosArm64", "iosSimulatorArm64") .map { targetName -> targets.getByName(targetName) as KotlinNativeTarget } .forEach { appleTarget -> appleTarget.binaries.framework { baseName = "KotlinSharedUI" isStatic = true - export(projects.shared) + export(projects.modules.ai.data) + export(projects.presentation.features) } } sourceSets { val commonMain by getting { dependencies { - implementation(projects.shared) + implementation(projects.presentation.features) + implementation(projects.core.humanizer) + implementation(projects.ui.presenterRuntime) + implementation(projects.social.misskey) implementation(compose("org.jetbrains.compose.ui:ui")) implementation(compose("org.jetbrains.compose.runtime:runtime")) implementation(compose("org.jetbrains.compose.foundation:foundation")) @@ -95,7 +104,8 @@ kotlin { } val iosMain by getting { dependencies { - api(projects.shared) + api(projects.modules.ai.data) + api(projects.presentation.features) implementation(libs.cupertino) api(compose("org.jetbrains.compose.ui:ui-util")) implementation(libs.lifecycle.viewmodel.compose) diff --git a/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/ErrorContent.kt b/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/ErrorContent.kt index 5320c69084..3cba249d28 100644 --- a/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/ErrorContent.kt +++ b/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/ErrorContent.kt @@ -21,8 +21,8 @@ import dev.dimension.flare.compose.ui.login_expired_message import dev.dimension.flare.compose.ui.permission_denied_message import dev.dimension.flare.compose.ui.permission_denied_title import dev.dimension.flare.compose.ui.status_loadmore_error -import dev.dimension.flare.data.repository.LoginExpiredException -import dev.dimension.flare.data.repository.RequireReLoginException +import dev.dimension.flare.model.LoginExpiredException +import dev.dimension.flare.model.RequireReLoginException import dev.dimension.flare.ui.component.platform.PlatformText import dev.dimension.flare.ui.route.DeeplinkRoute import dev.dimension.flare.ui.route.toUri diff --git a/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/TimelineItemPresenter.kt b/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/TimelineItemPresenter.kt index f429dc5d97..048439c679 100644 --- a/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/TimelineItemPresenter.kt +++ b/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/TimelineItemPresenter.kt @@ -4,10 +4,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.isRefreshing +import dev.dimension.flare.data.model.tab.TimelineResolver import dev.dimension.flare.data.model.tab.TimelineTabItemV2 import dev.dimension.flare.ui.model.UiTimelineV2 import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent +import org.koin.core.component.inject public class TimelineItemPresenter( private val timelineTabItem: TimelineTabItemV2, @@ -23,8 +25,10 @@ public class TimelineItemPresenter( public val isRefreshing: Boolean } + private val timelineResolver: TimelineResolver by inject() + private val timelinePresenter by lazy { - timelineTabItem.createPresenter() + timelineResolver.createPresenter(timelineTabItem) } @Composable diff --git a/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/screen/login/ServiceSelectionScreenContent.kt b/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/screen/login/ServiceSelectionScreenContent.kt index 934ec0462a..dedf149d7f 100644 --- a/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/screen/login/ServiceSelectionScreenContent.kt +++ b/compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/screen/login/ServiceSelectionScreenContent.kt @@ -76,8 +76,6 @@ import dev.dimension.flare.compose.ui.service_select_welcome_list_hint import dev.dimension.flare.compose.ui.service_select_welcome_message import dev.dimension.flare.compose.ui.service_select_welcome_title import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.model.agreementUrl -import dev.dimension.flare.model.icon import dev.dimension.flare.ui.component.FAIcon import dev.dimension.flare.ui.component.NetworkImage import dev.dimension.flare.ui.component.placeholder @@ -92,6 +90,7 @@ import dev.dimension.flare.ui.component.platform.PlatformTextField import dev.dimension.flare.ui.component.status.AdaptiveCard import dev.dimension.flare.ui.component.status.LazyStatusVerticalStaggeredGrid import dev.dimension.flare.ui.component.toImageVector +import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.model.UiInstance import dev.dimension.flare.ui.model.isSuccess import dev.dimension.flare.ui.model.onError @@ -186,7 +185,7 @@ public fun ServiceSelectionScreenContent( state.detectedPlatformType .onSuccess { FAIcon( - imageVector = it.platformType.icon.toImageVector(), + imageVector = state.platformIcon(it.platformType).toImageVector(), contentDescription = null, modifier = Modifier.size(24.dp), ) @@ -545,8 +544,7 @@ public fun ServiceSelectionScreenContent( } } LoginAgreement( - platformType = nodeData.platformType, - host = nodeData.host, + agreementUrl = state.agreementUrl(nodeData.platformType, nodeData.host), openUri = openUri, ) } @@ -583,6 +581,7 @@ public fun ServiceSelectionScreenContent( val instance = get(it) ServiceSelectItem( instance = instance, + platformIcon = instance?.type?.let(state::platformIcon), index = it, totalCount = itemCount, onClick = { @@ -598,6 +597,7 @@ public fun ServiceSelectionScreenContent( index = it, totalCount = 10, instance = null, + platformIcon = null, onClick = {}, ) } @@ -784,6 +784,7 @@ private fun NostrLoginContent(state: SelectionPresenter.State) { @Composable private fun ServiceSelectItem( instance: UiInstance?, + platformIcon: UiIcon?, onClick: () -> Unit, index: Int, totalCount: Int, @@ -824,9 +825,9 @@ private fun ServiceSelectItem( Modifier .size(24.dp), ) - } else if (instance != null) { + } else if (platformIcon != null) { FAIcon( - imageVector = instance.type.icon.toImageVector(), + imageVector = platformIcon.toImageVector(), contentDescription = null, modifier = Modifier.size(24.dp), ) @@ -857,17 +858,16 @@ private fun ServiceSelectItem( @Composable private fun LoginAgreement( - platformType: PlatformType, - host: String, + agreementUrl: String?, openUri: (String) -> Unit, modifier: Modifier = Modifier, ) { - val url = platformType.agreementUrl(host) ?: return + val url = agreementUrl ?: return val linkText = stringResource(Res.string.eula_privacy_policy) val fullText = stringResource(Res.string.login_agreement, linkText) val color = PlatformTheme.colorScheme.primary val annotatedString = - remember(platformType, host, url, linkText, fullText, color) { + remember(url, linkText, fullText, color) { buildAnnotatedString { append(fullText) val startIndex = fullText.indexOf(linkText) diff --git a/compose-ui/src/iosMain/kotlin/dev/dimension/flare/ui/theme/FlareTheme.kt b/compose-ui/src/iosMain/kotlin/dev/dimension/flare/ui/theme/FlareTheme.kt index b66cc46c07..6749f4bad8 100644 --- a/compose-ui/src/iosMain/kotlin/dev/dimension/flare/ui/theme/FlareTheme.kt +++ b/compose-ui/src/iosMain/kotlin/dev/dimension/flare/ui/theme/FlareTheme.kt @@ -6,11 +6,11 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import com.slapps.cupertino.theme.CupertinoTheme +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.datastore.model.AppSettings import dev.dimension.flare.data.model.VideoAutoplay import dev.dimension.flare.data.model.appearance.GlobalAppearance import dev.dimension.flare.data.model.appearance.TimelineAppearance -import dev.dimension.flare.data.repository.SettingsRepository import dev.dimension.flare.ui.component.LocalGlobalAppearance import dev.dimension.flare.ui.component.LocalTimelineAppearance import org.koin.compose.koinInject @@ -26,18 +26,18 @@ internal fun FlareTheme(content: @Composable () -> Unit) { @Composable internal fun ProvideThemeSettings(content: @Composable () -> Unit) { - val settingsRepository = koinInject() - val globalAppearance by settingsRepository.globalAppearance.collectAsState( + val appDataStore = koinInject() + val globalAppearance by appDataStore.globalAppearance.collectAsState( GlobalAppearance(), ) - val baseTimelineAppearance by settingsRepository.timelineAppearance.collectAsState( + val baseTimelineAppearance by appDataStore.timelineAppearance.collectAsState( TimelineAppearance(), ) val timelineAppearance = remember(baseTimelineAppearance) { baseTimelineAppearance.copy(videoAutoplay = VideoAutoplay.NEVER) } - val appSettings by settingsRepository.appSettings.collectAsState(AppSettings("")) + val appSettings by appDataStore.appSettings.collectAsState(AppSettings("")) CompositionLocalProvider( LocalGlobalAppearance provides globalAppearance, LocalTimelineAppearance provides diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts new file mode 100644 index 0000000000..36cf0a1086 --- /dev/null +++ b/core/common/build.gradle.kts @@ -0,0 +1,34 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.core.common" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + sourceSets { + val commonMain by getting { + dependencies { + api(libs.kotlinx.coroutines.core) + api(libs.kotlinx.immutable) + api(libs.kotlinx.serialization.json) + api(libs.kotlinx.serialization.protobuf) + api(libs.paging.common) + api(libs.xmlUtil) + } + } + } +} diff --git a/core/common/src/androidMain/kotlin/dev/dimension/flare/common/BuildConfig.android.kt b/core/common/src/androidMain/kotlin/dev/dimension/flare/common/BuildConfig.android.kt new file mode 100644 index 0000000000..a32da5493c --- /dev/null +++ b/core/common/src/androidMain/kotlin/dev/dimension/flare/common/BuildConfig.android.kt @@ -0,0 +1,6 @@ +package dev.dimension.flare.common + +public actual object BuildConfig { + public actual val debug: Boolean + get() = false +} diff --git a/shared/src/androidMain/kotlin/dev/dimension/flare/common/Locale.android.kt b/core/common/src/androidMain/kotlin/dev/dimension/flare/common/Locale.android.kt similarity index 62% rename from shared/src/androidMain/kotlin/dev/dimension/flare/common/Locale.android.kt rename to core/common/src/androidMain/kotlin/dev/dimension/flare/common/Locale.android.kt index c5b212248e..d077a38bb1 100644 --- a/shared/src/androidMain/kotlin/dev/dimension/flare/common/Locale.android.kt +++ b/core/common/src/androidMain/kotlin/dev/dimension/flare/common/Locale.android.kt @@ -2,7 +2,7 @@ package dev.dimension.flare.common import java.util.Locale -internal actual object Locale { - actual val language: String +public actual object Locale { + public actual val language: String get() = Locale.getDefault().toLanguageTag() } diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/common/BuildConfig.apple.kt b/core/common/src/appleMain/kotlin/dev/dimension/flare/common/BuildConfig.apple.kt similarity index 69% rename from shared/src/appleMain/kotlin/dev/dimension/flare/common/BuildConfig.apple.kt rename to core/common/src/appleMain/kotlin/dev/dimension/flare/common/BuildConfig.apple.kt index ecc121ed93..81c9390ec3 100644 --- a/shared/src/appleMain/kotlin/dev/dimension/flare/common/BuildConfig.apple.kt +++ b/core/common/src/appleMain/kotlin/dev/dimension/flare/common/BuildConfig.apple.kt @@ -2,9 +2,8 @@ package dev.dimension.flare.common import kotlin.experimental.ExperimentalNativeApi - -internal actual object BuildConfig { +public actual object BuildConfig { @OptIn(ExperimentalNativeApi::class) - actual val debug: Boolean + public actual val debug: Boolean get() = Platform.isDebugBinary -} \ No newline at end of file +} diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/common/Locale.apple.kt b/core/common/src/appleMain/kotlin/dev/dimension/flare/common/Locale.apple.kt similarity index 77% rename from shared/src/appleMain/kotlin/dev/dimension/flare/common/Locale.apple.kt rename to core/common/src/appleMain/kotlin/dev/dimension/flare/common/Locale.apple.kt index 251d2458ad..4b9dc760fa 100644 --- a/shared/src/appleMain/kotlin/dev/dimension/flare/common/Locale.apple.kt +++ b/core/common/src/appleMain/kotlin/dev/dimension/flare/common/Locale.apple.kt @@ -4,7 +4,7 @@ import platform.Foundation.NSLocale import platform.Foundation.currentLocale import platform.Foundation.localeIdentifier -internal actual object Locale { - actual val language: String +public actual object Locale { + public actual val language: String get() = NSLocale.currentLocale.localeIdentifier.replace('_', '-') } diff --git a/core/common/src/commonMain/kotlin/dev/dimension/flare/common/BuildConfig.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/BuildConfig.kt new file mode 100644 index 0000000000..ad054c83a1 --- /dev/null +++ b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/BuildConfig.kt @@ -0,0 +1,5 @@ +package dev.dimension.flare.common + +public expect object BuildConfig { + public val debug: Boolean +} diff --git a/core/common/src/commonMain/kotlin/dev/dimension/flare/common/CacheData.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/CacheData.kt new file mode 100644 index 0000000000..68edff4747 --- /dev/null +++ b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/CacheData.kt @@ -0,0 +1,103 @@ +package dev.dimension.flare.common + +import androidx.paging.LoadState +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.transform +import kotlinx.coroutines.withContext + +public class Cacheable( + fetchSource: suspend () -> Unit, + cacheSource: () -> Flow, +) : CacheData( + fetchSource = fetchSource, + cacheSource = cacheSource, + ) + +@Suppress("UNCHECKED_CAST") +public class MemCacheable( + private val key: String, + fetchSource: suspend () -> T, +) : CacheData( + fetchSource = { + update(key, fetchSource.invoke()) + }, + cacheSource = { + subscribe(key) + }, + ) { + public companion object { + private val caches = mutableMapOf>() + + public fun update( + key: String, + value: T, + ) { + caches[key]?.value = value + } + + public fun updateWith( + key: String, + update: (T) -> T, + ) { + if (caches.containsKey(key)) { + caches[key]?.value = update(caches[key]?.value as T) + } + } + + public fun subscribe(key: String): Flow = + caches + .getOrPut(key) { + MutableStateFlow(null) + }.filterNotNull() as Flow + } +} + +public sealed class CacheData( + private val fetchSource: suspend () -> Unit, + private val cacheSource: () -> Flow, +) { + private val refreshFlow = MutableStateFlow(0) + private val cacheFlow by lazy { + cacheSource.invoke() + } + public val refreshState: Flow = + refreshFlow + .transform { + emit(LoadState.Loading) + emit( + try { + withContext(PlatformDispatchers.IO) { + fetchSource.invoke() + } + LoadState.NotLoading(true) + } catch (e: Exception) { + LoadState.Error(e) + }, + ) + }.catch { emit(LoadState.Error(it)) } + + public val data: Flow> = + cacheFlow + .map> { + CacheState.Success(it) + }.onStart { + emit(CacheState.Empty()) + } + + public fun refresh() { + refreshFlow.value++ + } +} + +public sealed class CacheState { + public class Empty : CacheState() + + public data class Success( + val data: T, + ) : CacheState() +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/DebugRepository.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/DebugRepository.kt similarity index 77% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/DebugRepository.kt rename to core/common/src/commonMain/kotlin/dev/dimension/flare/common/DebugRepository.kt index 5c50cdd7e3..127282f139 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/DebugRepository.kt +++ b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/DebugRepository.kt @@ -1,24 +1,23 @@ -package dev.dimension.flare.data.repository +package dev.dimension.flare.common import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import kotlin.coroutines.cancellation.CancellationException -internal object DebugRepository { +public object DebugRepository { private const val MAX_MESSAGES = 25 private const val DEBUG_MAX_MESSAGES = 1000 private val _messages = MutableStateFlow>(emptyList()) private val _enabled = MutableStateFlow(false) - private val scope = CoroutineScope(Dispatchers.IO) + private val scope = CoroutineScope(PlatformDispatchers.IO) - val enabled get() = _enabled.asSharedFlow() - val messages get() = _messages.asSharedFlow() + public val enabled: SharedFlow get() = _enabled.asSharedFlow() + public val messages: SharedFlow> get() = _messages.asSharedFlow() - fun setEnabled(enabled: Boolean) { + public fun setEnabled(enabled: Boolean) { _enabled.value = enabled if (!enabled) { _messages.value = _messages.value.takeLast(MAX_MESSAGES) @@ -28,7 +27,7 @@ internal object DebugRepository { private val messageLimit: Int get() = if (_enabled.value) DEBUG_MAX_MESSAGES else MAX_MESSAGES - fun log(message: String) { + public fun log(message: String) { if (_enabled.value) { scope.launch { _messages.value = (_messages.value + message).takeLast(messageLimit) @@ -36,7 +35,7 @@ internal object DebugRepository { } } - fun error(exception: Throwable) { + public fun error(exception: Throwable) { if (exception is CancellationException) { // Ignore cancellation exceptions return @@ -53,13 +52,13 @@ internal object DebugRepository { } } - fun clear() { + public fun clear() { scope.launch { _messages.value = emptyList() } } - fun printToString(): String = _messages.value.joinToString(separator = "\n") + public fun printToString(): String = _messages.value.joinToString(separator = "\n") } /** @@ -74,7 +73,7 @@ internal object DebugRepository { * @param block The function to execute * @return A [Result] object containing either the successful result or the failure exception */ -internal inline fun tryRun(block: () -> R): Result = +public inline fun tryRun(block: () -> R): Result = try { Result.success(block()) } catch (e: Exception) { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/FlowExt.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/FlowExt.kt similarity index 84% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/FlowExt.kt rename to core/common/src/commonMain/kotlin/dev/dimension/flare/common/FlowExt.kt index 09ef0a3d2e..08e61fef15 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/FlowExt.kt +++ b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/FlowExt.kt @@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf @OptIn(ExperimentalCoroutinesApi::class) -internal inline fun Flow>>.combineLatestFlowLists(): Flow> = +public inline fun Flow>>.combineLatestFlowLists(): Flow> = flatMapLatest { flows -> if (flows.isEmpty()) { flowOf(emptyList()) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/ImmutableListWrapper.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/ImmutableListWrapper.kt similarity index 73% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/ImmutableListWrapper.kt rename to core/common/src/commonMain/kotlin/dev/dimension/flare/common/ImmutableListWrapper.kt index 662c346b19..b1dea9e771 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/ImmutableListWrapper.kt +++ b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/ImmutableListWrapper.kt @@ -1,9 +1,7 @@ package dev.dimension.flare.common -import androidx.compose.runtime.Immutable import kotlinx.collections.immutable.ImmutableList -@Immutable public data class ImmutableListWrapper( private val data: ImmutableList, ) { @@ -19,4 +17,4 @@ public data class ImmutableListWrapper( public fun toImmutableList(): ImmutableList = data } -internal fun ImmutableList.toImmutableListWrapper(): ImmutableListWrapper = ImmutableListWrapper(this) +public fun ImmutableList.toImmutableListWrapper(): ImmutableListWrapper = ImmutableListWrapper(this) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/InAppNotification.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/InAppNotification.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/InAppNotification.kt rename to core/common/src/commonMain/kotlin/dev/dimension/flare/common/InAppNotification.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/JSON.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/JSON.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/JSON.kt rename to core/common/src/commonMain/kotlin/dev/dimension/flare/common/JSON.kt index 4d155ed82e..7213b055d5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/JSON.kt +++ b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/JSON.kt @@ -26,26 +26,26 @@ private val jsonWithEncodeDefault = encodeDefaults = true } -internal val JSON get() = json -internal val JSON_WITH_ENCODE_DEFAULT get() = jsonWithEncodeDefault +public val JSON: Json get() = json +public val JSON_WITH_ENCODE_DEFAULT: Json get() = jsonWithEncodeDefault -internal inline fun T.encodeJson(): String = JSON.encodeToString(this) +public inline fun T.encodeJson(): String = JSON.encodeToString(this) @OptIn(ExperimentalObjCRefinement::class) @HiddenFromObjC public fun T.encodeJson(serializer: KSerializer): String = JSON.encodeToString(serializer, this) -internal inline fun String.decodeJson(): T = JSON.decodeFromString(this) +public inline fun String.decodeJson(): T = JSON.decodeFromString(this) @OptIn(ExperimentalObjCRefinement::class) @HiddenFromObjC public fun String.decodeJson(serializer: KSerializer): T = JSON.decodeFromString(serializer, this) -internal val JsonElement.jsonObjectOrNull: JsonObject? +public val JsonElement.jsonObjectOrNull: JsonObject? get() = if (this is JsonObject) this else null @OptIn(ExperimentalSerializationApi::class) -internal class SafePolymorphicSerializer( +public class SafePolymorphicSerializer( private val baseSerializer: KSerializer, private val discriminator: String, ) : KSerializer { diff --git a/core/common/src/commonMain/kotlin/dev/dimension/flare/common/Locale.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/Locale.kt new file mode 100644 index 0000000000..ea4f1a9bac --- /dev/null +++ b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/Locale.kt @@ -0,0 +1,5 @@ +package dev.dimension.flare.common + +public expect object Locale { + public val language: String +} diff --git a/core/common/src/commonMain/kotlin/dev/dimension/flare/common/PlatformDispatchers.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/PlatformDispatchers.kt new file mode 100644 index 0000000000..782caec528 --- /dev/null +++ b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/PlatformDispatchers.kt @@ -0,0 +1,13 @@ +package dev.dimension.flare.common + +import kotlinx.coroutines.CoroutineDispatcher + +/** + * Platform-aware dispatcher aliases for shared code. + */ +public expect object PlatformDispatchers { + /** + * Uses the real IO dispatcher on platforms that provide it, and a Web-safe dispatcher otherwise. + */ + public val IO: CoroutineDispatcher +} diff --git a/core/common/src/commonMain/kotlin/dev/dimension/flare/common/Protobuf.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/Protobuf.kt new file mode 100644 index 0000000000..1c1ae20072 --- /dev/null +++ b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/Protobuf.kt @@ -0,0 +1,27 @@ +package dev.dimension.flare.common + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.decodeFromHexString +import kotlinx.serialization.encodeToByteArray +import kotlinx.serialization.encodeToHexString +import kotlinx.serialization.protobuf.ProtoBuf + +@OptIn(ExperimentalSerializationApi::class) +public inline fun T.encodeProtobuf(): ByteArray = ProtoBuf.encodeToByteArray(this) + +@OptIn(ExperimentalSerializationApi::class) +public inline fun ByteArray.decodeProtobuf(): T = ProtoBuf.decodeFromByteArray(this) + +@OptIn(ExperimentalSerializationApi::class) +public inline fun String.decodeProtobuf(): T = ProtoBuf.decodeFromHexString(this) + +@OptIn(ExperimentalSerializationApi::class) +public inline fun String.decodeProtobuf(serializer: KSerializer): T = ProtoBuf.decodeFromHexString(serializer, this) + +@OptIn(ExperimentalSerializationApi::class) +public inline fun T.encodeProtobufToString(): String = ProtoBuf.encodeToHexString(this) + +@OptIn(ExperimentalSerializationApi::class) +public inline fun T.encodeProtobufToString(serializer: KSerializer): String = ProtoBuf.encodeToHexString(serializer, this) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/SerializableImmutableList.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/SerializableImmutableList.kt similarity index 86% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/SerializableImmutableList.kt rename to core/common/src/commonMain/kotlin/dev/dimension/flare/common/SerializableImmutableList.kt index 31f65267ef..d27a449f31 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/SerializableImmutableList.kt +++ b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/SerializableImmutableList.kt @@ -15,11 +15,11 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder // https://github.com/Kotlin/kotlinx.collections.immutable/issues/63 -internal typealias SerializableImmutableList = +public typealias SerializableImmutableList = @Serializable(ImmutableListSerializer::class) ImmutableList -internal class ImmutableListSerializer( +public class ImmutableListSerializer( private val dataSerializer: KSerializer, ) : KSerializer> { @OptIn(SealedSerializationApi::class) @@ -32,16 +32,16 @@ internal class ImmutableListSerializer( override fun serialize( encoder: Encoder, value: ImmutableList, - ) = ListSerializer(dataSerializer).serialize(encoder, value.toList()) + ): Unit = ListSerializer(dataSerializer).serialize(encoder, value.toList()) override fun deserialize(decoder: Decoder): ImmutableList = ListSerializer(dataSerializer).deserialize(decoder).toPersistentList() } -internal typealias SerializableImmutableMap = +public typealias SerializableImmutableMap = @Serializable(ImmutableMapSerializer::class) ImmutableMap -internal class ImmutableMapSerializer( +public class ImmutableMapSerializer( private val keySerializer: KSerializer, private val valueSerializer: KSerializer, ) : KSerializer> { @@ -55,7 +55,7 @@ internal class ImmutableMapSerializer( override fun serialize( encoder: Encoder, value: ImmutableMap, - ) = MapSerializer(keySerializer, valueSerializer).serialize(encoder, value.toMap()) + ): Unit = MapSerializer(keySerializer, valueSerializer).serialize(encoder, value.toMap()) override fun deserialize(decoder: Decoder): ImmutableMap = MapSerializer(keySerializer, valueSerializer).deserialize(decoder).toPersistentMap() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/SnowflakeIdGenerator.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/SnowflakeIdGenerator.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/SnowflakeIdGenerator.kt rename to core/common/src/commonMain/kotlin/dev/dimension/flare/common/SnowflakeIdGenerator.kt index d7e4e02fa2..f167dd3a38 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/SnowflakeIdGenerator.kt +++ b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/SnowflakeIdGenerator.kt @@ -1,4 +1,5 @@ package dev.dimension.flare.common + import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlin.time.Clock @@ -11,7 +12,7 @@ import kotlin.time.Clock * * 64‑bit layout: 41 timestamp | 5 datacenter | 5 worker | 12 sequence * */ -internal data object SnowflakeIdGenerator { +public data object SnowflakeIdGenerator { // ─────────────────────────────── config (edit to suit) ────────────────── private const val DATA_CENTER_ID: Int = 1 // 0‒31 private const val WORKER_ID: Int = 1 // 0‒31 @@ -46,7 +47,7 @@ internal data object SnowflakeIdGenerator { * * **Suspend**s while waiting to enter the critical section ‒ no CPU busy‑wait. */ - suspend fun nextId(): Long = + public suspend fun nextId(): Long = mutex.withLock { var ts = currentMillis() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/XML.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/XML.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/XML.kt rename to core/common/src/commonMain/kotlin/dev/dimension/flare/common/XML.kt index f47421abf1..8395219ef8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/XML.kt +++ b/core/common/src/commonMain/kotlin/dev/dimension/flare/common/XML.kt @@ -2,7 +2,7 @@ package dev.dimension.flare.common import nl.adaptivity.xmlutil.serialization.XML -internal val Xml = +public val Xml: XML = XML { defaultPolicy { autoPolymorphic = true diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/shared/image/ImageCompressor.kt b/core/common/src/commonMain/kotlin/dev/dimension/flare/media/ImageCompressor.kt similarity index 81% rename from shared/src/commonMain/kotlin/dev/dimension/flare/shared/image/ImageCompressor.kt rename to core/common/src/commonMain/kotlin/dev/dimension/flare/media/ImageCompressor.kt index b418a87bab..af1b301568 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/shared/image/ImageCompressor.kt +++ b/core/common/src/commonMain/kotlin/dev/dimension/flare/media/ImageCompressor.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare.shared.image +package dev.dimension.flare.media public interface ImageCompressor { public suspend fun compress( diff --git a/core/common/src/jvmMain/kotlin/dev/dimension/flare/common/BuildConfig.jvm.kt b/core/common/src/jvmMain/kotlin/dev/dimension/flare/common/BuildConfig.jvm.kt new file mode 100644 index 0000000000..a32da5493c --- /dev/null +++ b/core/common/src/jvmMain/kotlin/dev/dimension/flare/common/BuildConfig.jvm.kt @@ -0,0 +1,6 @@ +package dev.dimension.flare.common + +public actual object BuildConfig { + public actual val debug: Boolean + get() = false +} diff --git a/shared/src/jvmMain/kotlin/dev/dimension/flare/common/Locale.jvm.kt b/core/common/src/jvmMain/kotlin/dev/dimension/flare/common/Locale.jvm.kt similarity index 62% rename from shared/src/jvmMain/kotlin/dev/dimension/flare/common/Locale.jvm.kt rename to core/common/src/jvmMain/kotlin/dev/dimension/flare/common/Locale.jvm.kt index c5b212248e..d077a38bb1 100644 --- a/shared/src/jvmMain/kotlin/dev/dimension/flare/common/Locale.jvm.kt +++ b/core/common/src/jvmMain/kotlin/dev/dimension/flare/common/Locale.jvm.kt @@ -2,7 +2,7 @@ package dev.dimension.flare.common import java.util.Locale -internal actual object Locale { - actual val language: String +public actual object Locale { + public actual val language: String get() = Locale.getDefault().toLanguageTag() } diff --git a/core/common/src/nonWebMain/kotlin/dev/dimension/flare/common/PlatformDispatchers.nonWeb.kt b/core/common/src/nonWebMain/kotlin/dev/dimension/flare/common/PlatformDispatchers.nonWeb.kt new file mode 100644 index 0000000000..6953d94b35 --- /dev/null +++ b/core/common/src/nonWebMain/kotlin/dev/dimension/flare/common/PlatformDispatchers.nonWeb.kt @@ -0,0 +1,9 @@ +package dev.dimension.flare.common + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO + +public actual object PlatformDispatchers { + public actual val IO: CoroutineDispatcher = Dispatchers.IO +} diff --git a/core/common/src/wasmJsMain/kotlin/dev/dimension/flare/common/BuildConfig.wasmJs.kt b/core/common/src/wasmJsMain/kotlin/dev/dimension/flare/common/BuildConfig.wasmJs.kt new file mode 100644 index 0000000000..40c88543c2 --- /dev/null +++ b/core/common/src/wasmJsMain/kotlin/dev/dimension/flare/common/BuildConfig.wasmJs.kt @@ -0,0 +1,5 @@ +package dev.dimension.flare.common + +public actual object BuildConfig { + public actual val debug: Boolean = false +} diff --git a/core/common/src/wasmJsMain/kotlin/dev/dimension/flare/common/Locale.wasmJs.kt b/core/common/src/wasmJsMain/kotlin/dev/dimension/flare/common/Locale.wasmJs.kt new file mode 100644 index 0000000000..7684b09cea --- /dev/null +++ b/core/common/src/wasmJsMain/kotlin/dev/dimension/flare/common/Locale.wasmJs.kt @@ -0,0 +1,5 @@ +package dev.dimension.flare.common + +public actual object Locale { + public actual val language: String = "en" +} diff --git a/core/common/src/wasmJsMain/kotlin/dev/dimension/flare/common/PlatformDispatchers.wasmJs.kt b/core/common/src/wasmJsMain/kotlin/dev/dimension/flare/common/PlatformDispatchers.wasmJs.kt new file mode 100644 index 0000000000..443dbec567 --- /dev/null +++ b/core/common/src/wasmJsMain/kotlin/dev/dimension/flare/common/PlatformDispatchers.wasmJs.kt @@ -0,0 +1,8 @@ +package dev.dimension.flare.common + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +public actual object PlatformDispatchers { + public actual val IO: CoroutineDispatcher = Dispatchers.Default +} diff --git a/core/humanizer/build.gradle.kts b/core/humanizer/build.gradle.kts new file mode 100644 index 0000000000..aac7612ecd --- /dev/null +++ b/core/humanizer/build.gradle.kts @@ -0,0 +1,45 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.core.humanizer" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + sourceSets { + val commonMain by getting { + dependencies { + api(dependencies.platform(libs.koin.bom)) + api(libs.koin.core) + } + } + val androidMain by getting { + dependencies { + implementation(libs.kotlinx.datetime) + } + } + val jvmMain by getting { + dependencies { + implementation(libs.kotlinx.datetime) + implementation(libs.prettytime) + } + } + val appleMain by getting { + dependencies { + implementation(libs.kotlinx.datetime) + } + } + } +} diff --git a/shared/src/androidMain/kotlin/dev/dimension/flare/ui/humanizer/AndroidFormatter.kt b/core/humanizer/src/androidMain/kotlin/dev/dimension/flare/ui/humanizer/AndroidFormatter.kt similarity index 98% rename from shared/src/androidMain/kotlin/dev/dimension/flare/ui/humanizer/AndroidFormatter.kt rename to core/humanizer/src/androidMain/kotlin/dev/dimension/flare/ui/humanizer/AndroidFormatter.kt index ed9cdd089c..c65138f011 100644 --- a/shared/src/androidMain/kotlin/dev/dimension/flare/ui/humanizer/AndroidFormatter.kt +++ b/core/humanizer/src/androidMain/kotlin/dev/dimension/flare/ui/humanizer/AndroidFormatter.kt @@ -11,7 +11,7 @@ import java.util.Locale import kotlin.time.Clock import kotlin.time.Instant -internal class AndroidFormatter( +public class AndroidFormatter public constructor( private val context: Context, ) : PlatformFormatter { override fun formatNumber(number: Long): String { diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/ui/humanizer/AppleFormatter.kt b/core/humanizer/src/appleMain/kotlin/dev/dimension/flare/ui/humanizer/AppleFormatter.kt similarity index 98% rename from shared/src/appleMain/kotlin/dev/dimension/flare/ui/humanizer/AppleFormatter.kt rename to core/humanizer/src/appleMain/kotlin/dev/dimension/flare/ui/humanizer/AppleFormatter.kt index 0a65f342cf..ad9dd01910 100644 --- a/shared/src/appleMain/kotlin/dev/dimension/flare/ui/humanizer/AppleFormatter.kt +++ b/core/humanizer/src/appleMain/kotlin/dev/dimension/flare/ui/humanizer/AppleFormatter.kt @@ -19,7 +19,7 @@ public interface SwiftFormatter { public fun formatNumber(number: Long): String } -internal class AppleFormatter( +public class AppleFormatter public constructor( private val formatter: SwiftFormatter, ) : PlatformFormatter { override fun formatNumber(number: Long): String = formatter.formatNumber(number) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/humanizer/Instant.kt b/core/humanizer/src/commonMain/kotlin/dev/dimension/flare/ui/humanizer/Instant.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/humanizer/Instant.kt rename to core/humanizer/src/commonMain/kotlin/dev/dimension/flare/ui/humanizer/Instant.kt diff --git a/core/humanizer/src/commonMain/kotlin/dev/dimension/flare/ui/humanizer/Number.kt b/core/humanizer/src/commonMain/kotlin/dev/dimension/flare/ui/humanizer/Number.kt new file mode 100644 index 0000000000..4eaf244772 --- /dev/null +++ b/core/humanizer/src/commonMain/kotlin/dev/dimension/flare/ui/humanizer/Number.kt @@ -0,0 +1,33 @@ +package dev.dimension.flare.ui.humanizer + +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import kotlin.math.round +import kotlin.time.Instant + +public fun Float.humanizePercentage(): String { + val roundedNumber = round(this * 100 * 100).toDouble() / 100 + return "$roundedNumber%" +} + +public object Formatter : KoinComponent { + public val platformFormatter: PlatformFormatter by inject() + + public fun Long.humanize(): String = platformFormatter.formatNumber(number = this) + + public fun Instant.relative(): String = platformFormatter.formatRelativeInstant(this) + + public fun Instant.full(): String = platformFormatter.formatFullInstant(this) + + public fun Instant.absolute(): String = platformFormatter.formatAbsoluteInstant(this) +} + +public interface PlatformFormatter { + public fun formatNumber(number: Long): String + + public fun formatRelativeInstant(instant: Instant): String + + public fun formatFullInstant(instant: Instant): String + + public fun formatAbsoluteInstant(instant: Instant): String +} diff --git a/shared/src/jvmMain/kotlin/dev/dimension/flare/ui/humanizer/JVMFormatter.kt b/core/humanizer/src/jvmMain/kotlin/dev/dimension/flare/ui/humanizer/JVMFormatter.kt similarity index 98% rename from shared/src/jvmMain/kotlin/dev/dimension/flare/ui/humanizer/JVMFormatter.kt rename to core/humanizer/src/jvmMain/kotlin/dev/dimension/flare/ui/humanizer/JVMFormatter.kt index fe18915a64..923edeaa8f 100644 --- a/shared/src/jvmMain/kotlin/dev/dimension/flare/ui/humanizer/JVMFormatter.kt +++ b/core/humanizer/src/jvmMain/kotlin/dev/dimension/flare/ui/humanizer/JVMFormatter.kt @@ -22,7 +22,7 @@ public fun updateTimeFormatterLocale(locale: Locale) { prettyTime.setLocale(locale) } -internal class JVMFormatter : PlatformFormatter { +public class JVMFormatter : PlatformFormatter { override fun formatNumber(number: Long): String { val fmt = NumberFormat.getCompactNumberInstance( diff --git a/core/humanizer/src/wasmJsMain/kotlin/dev/dimension/flare/ui/humanizer/WebFormatter.kt b/core/humanizer/src/wasmJsMain/kotlin/dev/dimension/flare/ui/humanizer/WebFormatter.kt new file mode 100644 index 0000000000..b5590f799b --- /dev/null +++ b/core/humanizer/src/wasmJsMain/kotlin/dev/dimension/flare/ui/humanizer/WebFormatter.kt @@ -0,0 +1,13 @@ +package dev.dimension.flare.ui.humanizer + +import kotlin.time.Instant + +public data object WebFormatter : PlatformFormatter { + override fun formatNumber(number: Long): String = number.toString() + + override fun formatRelativeInstant(instant: Instant): String = instant.toString() + + override fun formatFullInstant(instant: Instant): String = instant.toString() + + override fun formatAbsoluteInstant(instant: Instant): String = instant.toString() +} diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts new file mode 100644 index 0000000000..351ed95d82 --- /dev/null +++ b/core/model/build.gradle.kts @@ -0,0 +1,37 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.core.model" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + sourceSets { + val commonMain by getting { + dependencies { + implementation(projects.core.humanizer) + implementation(dependencies.platform(libs.compose.bom)) + implementation(libs.compose.runtime) + api(libs.kotlinx.serialization.json) + } + } + val appleMain by getting { + dependencies { + implementation(libs.kotlinx.datetime) + } + } + } +} diff --git a/core/model/src/androidJvmMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.androidJvm.kt b/core/model/src/androidJvmMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.androidJvm.kt new file mode 100644 index 0000000000..f19d3dea9c --- /dev/null +++ b/core/model/src/androidJvmMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.androidJvm.kt @@ -0,0 +1,7 @@ +package dev.dimension.flare.ui.render + +import kotlin.time.Instant + +public actual typealias PlatformDateTime = Instant + +public actual fun Instant.toPlatform(): PlatformDateTime = this diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.apple.kt b/core/model/src/appleMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.apple.kt similarity index 52% rename from shared/src/appleMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.apple.kt rename to core/model/src/appleMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.apple.kt index 2d37cb9731..20e0c80ac8 100644 --- a/shared/src/appleMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.apple.kt +++ b/core/model/src/appleMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.apple.kt @@ -4,6 +4,6 @@ import kotlinx.datetime.toNSDate import platform.Foundation.NSDate import kotlin.time.Instant -internal actual fun Instant.toPlatform(): PlatformDateTime = toNSDate() +public actual fun Instant.toPlatform(): PlatformDateTime = toNSDate() -internal actual typealias PlatformDateTime = NSDate +public actual typealias PlatformDateTime = NSDate diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/IconType.kt b/core/model/src/commonMain/kotlin/dev/dimension/flare/data/model/IconType.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/model/IconType.kt rename to core/model/src/commonMain/kotlin/dev/dimension/flare/data/model/IconType.kt index f9021dbcff..580cc76318 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/IconType.kt +++ b/core/model/src/commonMain/kotlin/dev/dimension/flare/data/model/IconType.kt @@ -1,6 +1,5 @@ package dev.dimension.flare.data.model -import androidx.compose.runtime.Immutable import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiIcon import kotlinx.serialization.SerialName @@ -8,32 +7,27 @@ import kotlinx.serialization.Serializable @Serializable public sealed class IconType { - @Immutable @Serializable public data class Avatar( @SerialName("userKey") val accountKey: MicroBlogKey, ) : IconType() - @Immutable @Serializable public data class Url( val url: String, ) : IconType() - @Immutable @Serializable public data class FavIcon( val host: String, ) : IconType() - @Immutable @Serializable public data class Material( val icon: UiIcon, ) : IconType() - @Immutable @Serializable public data class Mixed( val icon: UiIcon, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/model/MicroBlogKey.kt b/core/model/src/commonMain/kotlin/dev/dimension/flare/model/MicroBlogKey.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/model/MicroBlogKey.kt rename to core/model/src/commonMain/kotlin/dev/dimension/flare/model/MicroBlogKey.kt index 6e49a1c6ef..3344610b3d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/model/MicroBlogKey.kt +++ b/core/model/src/commonMain/kotlin/dev/dimension/flare/model/MicroBlogKey.kt @@ -2,6 +2,7 @@ package dev.dimension.flare.model import androidx.compose.runtime.Immutable import kotlinx.serialization.Serializable +import kotlin.io.encoding.Base64 @Serializable @Immutable @@ -37,6 +38,12 @@ public data class MicroBlogKey( private fun isSpecialChar(ch: Char): Boolean = ch == '\\' || ch == '@' || ch == ',' public companion object { + public fun fromRss(url: String): MicroBlogKey = + MicroBlogKey( + id = Base64.encode(url.encodeToByteArray()), + host = "RSS", + ) + public fun valueOf(str: String): MicroBlogKey { var escaping = false var idFinished = false diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/model/PlatformType.kt b/core/model/src/commonMain/kotlin/dev/dimension/flare/model/PlatformType.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/model/PlatformType.kt rename to core/model/src/commonMain/kotlin/dev/dimension/flare/model/PlatformType.kt index 484fd2b30f..28b504236c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/model/PlatformType.kt +++ b/core/model/src/commonMain/kotlin/dev/dimension/flare/model/PlatformType.kt @@ -1,7 +1,6 @@ package dev.dimension.flare.model import androidx.compose.runtime.Immutable -import dev.dimension.flare.ui.model.UiIcon import kotlinx.serialization.Serializable import kotlin.io.encoding.Base64 @@ -19,12 +18,6 @@ public enum class PlatformType { Nostr, } -@Immutable -public data class PlatformTypeMetadata( - val displayName: String, - val icon: UiIcon, -) - public val xqtOldHost: String = buildString { append(Base64.decode("dHc=").decodeToString()) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiIcon.kt b/core/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiIcon.kt similarity index 82% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiIcon.kt rename to core/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiIcon.kt index 2863c52a57..63b0fee57b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiIcon.kt +++ b/core/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiIcon.kt @@ -1,11 +1,8 @@ package dev.dimension.flare.ui.model -import androidx.compose.runtime.Immutable -import dev.dimension.flare.data.model.IconType import kotlinx.serialization.Serializable @Serializable -@Immutable public enum class UiIcon { Home, Notification, @@ -61,5 +58,3 @@ public enum class UiIcon { Translate, UnFavourite, } - -public fun UiIcon.asType(): IconType = IconType.Material(this) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiStrings.kt b/core/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiStrings.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiStrings.kt rename to core/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiStrings.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.kt b/core/model/src/commonMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.kt similarity index 81% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.kt rename to core/model/src/commonMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.kt index 5103ce3f84..d814743431 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.kt +++ b/core/model/src/commonMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.kt @@ -1,6 +1,5 @@ package dev.dimension.flare.ui.render -import androidx.compose.runtime.Immutable import dev.dimension.flare.ui.humanizer.Formatter.absolute import dev.dimension.flare.ui.humanizer.Formatter.full import dev.dimension.flare.ui.humanizer.Formatter.relative @@ -10,11 +9,10 @@ import kotlin.time.Instant public expect class PlatformDateTime -internal expect fun Instant.toPlatform(): PlatformDateTime +public expect fun Instant.toPlatform(): PlatformDateTime @Serializable(with = UiDateTimeSerializer::class) -@Immutable -public data class UiDateTime internal constructor( +public data class UiDateTime public constructor( val value: Instant, ) { val platformValue: PlatformDateTime by lazy { @@ -33,9 +31,9 @@ public data class UiDateTime internal constructor( public fun Instant.toUi(): UiDateTime = UiDateTime(this) -internal operator fun UiDateTime.compareTo(other: UiDateTime): Int = value.compareTo(other.value) +public operator fun UiDateTime.compareTo(other: UiDateTime): Int = value.compareTo(other.value) -internal object UiDateTimeSerializer : kotlinx.serialization.KSerializer { +public object UiDateTimeSerializer : kotlinx.serialization.KSerializer { override val descriptor: kotlinx.serialization.descriptors.SerialDescriptor get() = kotlinx.serialization.descriptors.PrimitiveSerialDescriptor( diff --git a/core/model/src/wasmJsMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.wasmJs.kt b/core/model/src/wasmJsMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.wasmJs.kt new file mode 100644 index 0000000000..f19d3dea9c --- /dev/null +++ b/core/model/src/wasmJsMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.wasmJs.kt @@ -0,0 +1,7 @@ +package dev.dimension.flare.ui.render + +import kotlin.time.Instant + +public actual typealias PlatformDateTime = Instant + +public actual fun Instant.toPlatform(): PlatformDateTime = this diff --git a/desktopApp/build.gradle.kts b/desktopApp/build.gradle.kts index b34ef4eb53..4c55446d13 100644 --- a/desktopApp/build.gradle.kts +++ b/desktopApp/build.gradle.kts @@ -14,7 +14,13 @@ plugins { } dependencies { - implementation(projects.shared) + implementation(projects.presentation.features) + implementation(projects.core.humanizer) + implementation(projects.modules.draft.presentation) + implementation(projects.foundation.network) + implementation(projects.foundation.filesystem) + implementation(projects.social.rss) + implementation(projects.social.rss.model) implementation(projects.composeUi) implementation(compose("org.jetbrains.compose.runtime:runtime")) diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/component/AccountItem.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/component/AccountItem.kt index 484489fd4b..406c8a0cd3 100644 --- a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/component/AccountItem.kt +++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/component/AccountItem.kt @@ -11,9 +11,9 @@ import compose.icons.fontawesomeicons.solid.FaceSadTear import dev.dimension.flare.Res import dev.dimension.flare.account_item_error_message import dev.dimension.flare.account_item_error_title -import dev.dimension.flare.data.repository.LoginExpiredException import dev.dimension.flare.login_expired import dev.dimension.flare.login_expired_relogin +import dev.dimension.flare.model.LoginExpiredException import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.UiState diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/component/ComposeInAppNotification.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/component/ComposeInAppNotification.kt index 0b4c38b5c7..b2487322bf 100644 --- a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/component/ComposeInAppNotification.kt +++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/component/ComposeInAppNotification.kt @@ -23,7 +23,7 @@ import dev.dimension.flare.common.InAppNotification import dev.dimension.flare.common.Message import dev.dimension.flare.compose_notification_error import dev.dimension.flare.compose_notification_success -import dev.dimension.flare.data.repository.LoginExpiredException +import dev.dimension.flare.model.LoginExpiredException import dev.dimension.flare.notification_login_expired import io.github.composefluent.component.InfoBar import io.github.composefluent.component.InfoBarSeverity diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Route.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Route.kt index 66c3e65ac2..a52e71e21a 100644 --- a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Route.kt +++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Route.kt @@ -3,6 +3,7 @@ package dev.dimension.flare.ui.route import androidx.navigation3.runtime.NavKey import dev.dimension.flare.data.model.tab.TimelineSourceRef import dev.dimension.flare.data.model.tab.TimelineTabItemV2 +import dev.dimension.flare.data.model.tab.xqtDeviceFollowTimelineSource import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiRssSource @@ -302,7 +303,7 @@ internal sealed interface Route : NavKey { is DeeplinkRoute.Timeline.XQTDeviceFollow -> { val accountKey = (deeplinkRoute.accountType as? AccountType.Specific)?.accountKey ?: return null Route.TimelineSource( - source = TimelineSourceRef.xqtDeviceFollow(accountKey), + source = xqtDeviceFollowTimelineSource(accountKey), ) } diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Router.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Router.kt index 42e63a7086..f4d958adb0 100644 --- a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Router.kt +++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Router.kt @@ -38,12 +38,12 @@ import dev.dimension.flare.ui.model.asType import dev.dimension.flare.ui.presenter.compose.ComposeStatus.Quote import dev.dimension.flare.ui.presenter.compose.ComposeStatus.Reply import dev.dimension.flare.ui.presenter.compose.ComposeStatus.VVOComment -import dev.dimension.flare.ui.presenter.home.bluesky.BlueskyFeedTimelinePresenter -import dev.dimension.flare.ui.presenter.home.rss.RssTimelinePresenter -import dev.dimension.flare.ui.presenter.home.rss.SubscriptionTimelinePresenter -import dev.dimension.flare.ui.presenter.list.AntennasTimelinePresenter -import dev.dimension.flare.ui.presenter.list.ChannelTimelinePresenter -import dev.dimension.flare.ui.presenter.list.ListTimelinePresenter +import dev.dimension.flare.ui.presenter.home.bluesky.createBlueskyFeedTimeline +import dev.dimension.flare.ui.presenter.home.rss.createRssTimeline +import dev.dimension.flare.ui.presenter.home.rss.createSubscriptionTimeline +import dev.dimension.flare.ui.presenter.list.createListTimeline +import dev.dimension.flare.ui.presenter.list.createMisskeyAntennaTimeline +import dev.dimension.flare.ui.presenter.list.createMisskeyChannelTimeline import dev.dimension.flare.ui.route.FluentDialogSceneStrategy.Companion.dialog import dev.dimension.flare.ui.route.Route.EditRssSource import dev.dimension.flare.ui.route.Route.Profile @@ -454,7 +454,7 @@ internal fun Router( title = UiText.Raw(it.title), icon = UiIcon.List.asType(), createPresenter = { - ListTimelinePresenter( + createListTimeline( accountType = args.accountType, listId = it.id, ) @@ -481,7 +481,7 @@ internal fun Router( title = UiText.Raw(it.title), icon = UiIcon.Feeds.asType(), createPresenter = { - BlueskyFeedTimelinePresenter( + createBlueskyFeedTimeline( accountType = args.accountType, uri = it.id, ) @@ -907,7 +907,7 @@ internal fun Router( title = UiText.Raw(it.title), icon = UiIcon.Rss.asType(), createPresenter = { - AntennasTimelinePresenter( + createMisskeyAntennaTimeline( accountType = args.accountType, id = it.id, ) @@ -1053,7 +1053,7 @@ internal fun Router( title = UiText.Raw(args.title), icon = UiIcon.Channel.asType(), createPresenter = { - ChannelTimelinePresenter( + createMisskeyChannelTimeline( accountType = args.accountType, id = args.channelId, ) @@ -1073,8 +1073,8 @@ private fun UiRssSource.toTimelineTabItem(): TimelineTabItemV2 = icon = favIcon?.let { IconType.Url(it) } ?: UiIcon.Rss.asType(), createPresenter = { when (type) { - SubscriptionType.RSS -> RssTimelinePresenter(url) - else -> SubscriptionTimelinePresenter(type, url) + SubscriptionType.RSS -> createRssTimeline(url) + else -> createSubscriptionTimeline(type, url) } }, ) diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/compose/ComposeDialog.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/compose/ComposeDialog.kt index 145f86d100..759e7a23c7 100644 --- a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/compose/ComposeDialog.kt +++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/compose/ComposeDialog.kt @@ -59,7 +59,7 @@ import compose.icons.fontawesomeicons.solid.TriangleExclamation import compose.icons.fontawesomeicons.solid.Xmark import dev.dimension.flare.Res import dev.dimension.flare.cancel -import dev.dimension.flare.common.FileItem +import dev.dimension.flare.data.io.FileItem import dev.dimension.flare.common.ImageClipboardManager import dev.dimension.flare.common.ImageDragAndDropTarget import dev.dimension.flare.compose_close_confirm_message diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/rss/EditRssSourceScreen.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/rss/EditRssSourceScreen.kt index 0fe1a2746e..c9700f9aa0 100644 --- a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/rss/EditRssSourceScreen.kt +++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/rss/EditRssSourceScreen.kt @@ -33,13 +33,13 @@ import compose.icons.fontawesomeicons.solid.CircleXmark import dev.dimension.flare.Res import dev.dimension.flare.add_rss_source import dev.dimension.flare.cancel -import dev.dimension.flare.data.database.app.model.RssDisplayMode import dev.dimension.flare.data.database.app.model.SubscriptionType import dev.dimension.flare.edit_rss_source import dev.dimension.flare.mastodon_available_timelines import dev.dimension.flare.mastodon_federated_timeline import dev.dimension.flare.mastodon_local_timeline import dev.dimension.flare.mastodon_trending_statuses +import dev.dimension.flare.model.RssDisplayMode import dev.dimension.flare.ok import dev.dimension.flare.opml_import import dev.dimension.flare.rss_sources_description_only diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/rss/RssDetailScreen.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/rss/RssDetailScreen.kt index ebfca15e8f..776fbe749e 100644 --- a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/rss/RssDetailScreen.kt +++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/rss/RssDetailScreen.kt @@ -38,8 +38,8 @@ import dev.dimension.flare.LocalWindowPadding import dev.dimension.flare.Res import dev.dimension.flare.common.encodeJson import dev.dimension.flare.copied_to_clipboard +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.network.rss.DocumentData -import dev.dimension.flare.data.repository.SettingsRepository import dev.dimension.flare.rss_detail_open_in_browser import dev.dimension.flare.rss_detail_tldr import dev.dimension.flare.rss_detail_tldr_error @@ -347,17 +347,17 @@ private fun presenter( url: String, descriptionHtml: String? = null, descriptionTitle: String? = null, - settingsRepository: SettingsRepository = koinInject(), + appDataStore: AppDataStore = koinInject(), ) = run { val state = remember(url, descriptionHtml) { RssDetailPresenter(url, descriptionHtml, descriptionTitle) }.invoke() val enableTldr by remember { - settingsRepository.appSettings.map { it.aiConfig.tldr } + appDataStore.appSettings.map { it.aiConfig.tldr } }.collectAsUiState() val preTranslate by remember { - settingsRepository.appSettings.map { it.translateConfig.preTranslate } + appDataStore.appSettings.map { it.translateConfig.preTranslate } }.collectAsUiState() var showTldr by remember { mutableStateOf(false) } var tldrRefreshKey by remember { mutableIntStateOf(0) } diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/settings/SettingsScreen.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/settings/SettingsScreen.kt index e318e8888b..80b2f3eb50 100644 --- a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/settings/SettingsScreen.kt +++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/settings/SettingsScreen.kt @@ -56,6 +56,7 @@ import dev.dimension.flare.action_import import dev.dimension.flare.add_account import dev.dimension.flare.app_name import dev.dimension.flare.cancel +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.model.AvatarShape import dev.dimension.flare.data.model.PostActionStyle import dev.dimension.flare.data.model.Theme @@ -63,7 +64,6 @@ import dev.dimension.flare.data.model.TimelineDisplayMode import dev.dimension.flare.data.model.VideoAutoplay import dev.dimension.flare.data.model.appearance.AppearanceKey import dev.dimension.flare.data.model.appearance.AppearanceKeys -import dev.dimension.flare.data.repository.SettingsRepository import dev.dimension.flare.delete import dev.dimension.flare.edit import dev.dimension.flare.home_login @@ -2177,7 +2177,7 @@ private fun presenter( onImportFilePicker: () -> File?, ) = run { val scope = rememberCoroutineScope() - val settingsRepository = koinInject() + val appDataStore = koinInject() val accountState = accountsPresenter() val appearanceState = appearancePresenter() val storageState = storagePresenter(onExportFilePicker, onImportFilePicker) @@ -2197,7 +2197,7 @@ private fun presenter( fun setLanguage(tag: String) { scope.launch { - settingsRepository.updateAppSettings { + appDataStore.updateAppSettings { copy(language = tag) } } @@ -2340,7 +2340,7 @@ private fun appearancePresenter() = run { var expanded by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() - val settingsRepository = koinInject() + val appDataStore = koinInject() object { val expanded = expanded @@ -2353,7 +2353,7 @@ private fun appearancePresenter() = value: T, ) { scope.launch { - settingsRepository.updateAppearance(key, value) + appDataStore.updateAppearance(key, value) } } } diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/status/action/StatusShareSheet.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/status/action/StatusShareSheet.kt index 41fee58b23..35191d599d 100644 --- a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/status/action/StatusShareSheet.kt +++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/status/action/StatusShareSheet.kt @@ -34,6 +34,7 @@ import compose.icons.fontawesomeicons.solid.Image import compose.icons.fontawesomeicons.solid.Link import dev.dimension.flare.Res import dev.dimension.flare.cancel +import dev.dimension.flare.data.io.sanitizeFileName import dev.dimension.flare.copied_to_clipboard import dev.dimension.flare.data.model.VideoAutoplay import dev.dimension.flare.model.AccountType @@ -224,7 +225,7 @@ internal fun StatusShareSheet( onClick = { scope.launch { val image = capturePreviewImage(previewGraphicsLayer) ?: return@launch - saveImageWithDialog(window, image, "status_${statusKey.toString().replace(Regex("[^A-Za-z0-9._-]"), "_")}.png") + saveImageWithDialog(window, image, "status_${statusKey.toString().sanitizeFileName()}.png") } }, ) { diff --git a/foundation/database/build.gradle.kts b/foundation/database/build.gradle.kts new file mode 100644 index 0000000000..7637cb8d9d --- /dev/null +++ b/foundation/database/build.gradle.kts @@ -0,0 +1,80 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ksp) + alias(libs.plugins.room) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.foundation.database" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + ksp(libs.room.compiler) + } + + compilerOptions { + allWarningsAsErrors.set(false) + } + + sourceSets { + all { + languageSettings { + optIn("kotlin.uuid.ExperimentalUuidApi") + } + } + + val commonMain by getting { + dependencies { + implementation(projects.core.common) + api(projects.core.model) + api(projects.modules.account.model) + api(projects.modules.translation.model) + api(projects.social.model) + api(projects.social.rss.model) + api(libs.kotlinx.coroutines.core) + api(libs.paging.common) + api(libs.room.runtime) + api(libs.room.paging) + api(libs.sqlite) + implementation(libs.sqlite.async) + implementation(libs.kotlinx.serialization.json) + api(dependencies.platform(libs.koin.bom)) + api(libs.koin.core) + } + } + + val nonWebMain by getting { + dependencies { + api(libs.sqlite.bundled) + } + } + + val jvmMain by getting { + dependencies { + implementation(projects.foundation.filesystem) + } + } + + val wasmJsMain by getting { + dependencies { + implementation(libs.sqlite.web) + implementation(libs.kotlinx.browser) + implementation(npm("@androidx/sqlite-web-worker", file("sqlite-web-worker"))) + } + } + } +} + +room3 { + schemaDirectory("$projectDir/schemas") +} diff --git a/shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/10.json b/foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/10.json similarity index 100% rename from shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/10.json rename to foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/10.json diff --git a/shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/3.json b/foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/3.json similarity index 100% rename from shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/3.json rename to foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/3.json diff --git a/shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/4.json b/foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/4.json similarity index 100% rename from shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/4.json rename to foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/4.json diff --git a/shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/5.json b/foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/5.json similarity index 100% rename from shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/5.json rename to foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/5.json diff --git a/shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/6.json b/foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/6.json similarity index 100% rename from shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/6.json rename to foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/6.json diff --git a/shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/7.json b/foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/7.json similarity index 100% rename from shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/7.json rename to foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/7.json diff --git a/shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/8.json b/foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/8.json similarity index 100% rename from shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/8.json rename to foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/8.json diff --git a/shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/9.json b/foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/9.json similarity index 100% rename from shared/schemas/dev.dimension.flare.data.database.app.AppDatabase/9.json rename to foundation/database/schemas/dev.dimension.flare.data.database.app.AppDatabase/9.json diff --git a/foundation/database/sqlite-web-worker/package.json b/foundation/database/sqlite-web-worker/package.json new file mode 100644 index 0000000000..4a515418d3 --- /dev/null +++ b/foundation/database/sqlite-web-worker/package.json @@ -0,0 +1,8 @@ +{ + "name": "@androidx/sqlite-web-worker", + "version": "0.0.0", + "license": "Apache-2.0", + "dependencies": { + "@sqlite.org/sqlite-wasm": "^3.50.1-build1" + } +} diff --git a/foundation/database/sqlite-web-worker/worker.js b/foundation/database/sqlite-web-worker/worker.js new file mode 100644 index 0000000000..0044914228 --- /dev/null +++ b/foundation/database/sqlite-web-worker/worker.js @@ -0,0 +1,170 @@ +/** + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import sqlite3InitModule from '@sqlite.org/sqlite-wasm'; + +let sqlite3 = null; + +const databases = new Map(); +const statements = new Map(); + +let nextDatabaseId = 0; +let nextStatementId = 0; + +function createDatabase(fileName) { + if (typeof sqlite3.oo1.OpfsDb === 'function') { + return new sqlite3.oo1.OpfsDb(fileName); + } + return new sqlite3.oo1.DB(fileName || ':memory:', 'c'); +} + +function openRequest(id, requestData) { + try { + const newDatabaseId = nextDatabaseId++; + const newDatabase = createDatabase(requestData.fileName); + databases.set(newDatabaseId, newDatabase); + postMessage({ id, data: { databaseId: newDatabaseId } }); + } catch (error) { + postMessage({ id, error: error.message }); + } +} + +function prepareRequest(id, requestData) { + try { + const newStatementId = nextStatementId++; + const resultData = { + statementId: newStatementId, + parameterCount: 0, + columnNames: [], + }; + const database = databases.get(requestData.databaseId); + if (!database) { + postMessage({ id, error: `Invalid database ID: ${requestData.databaseId}` }); + return; + } + const statement = database.prepare(requestData.sql); + statements.set(newStatementId, statement); + resultData.parameterCount = sqlite3.capi.sqlite3_bind_parameter_count(statement); + for (let i = 0; i < statement.columnCount; i++) { + resultData.columnNames.push(sqlite3.capi.sqlite3_column_name(statement, i)); + } + postMessage({ id, data: resultData }); + } catch (error) { + postMessage({ id, error: error.message }); + } +} + +function stepRequest(id, requestData) { + const statement = statements.get(requestData.statementId); + if (!statement) { + postMessage({ id, error: `Invalid statement ID: ${requestData.statementId}` }); + return; + } + try { + const resultData = { + rows: [], + columnTypes: [], + }; + statement.reset(); + statement.clearBindings(); + for (let i = 0; i < requestData.bindings.length; i++) { + statement.bind(i + 1, requestData.bindings[i]); + } + while (statement.step()) { + if (!resultData.columnTypes.length) { + for (let i = 0; i < statement.columnCount; i++) { + resultData.columnTypes.push(sqlite3.capi.sqlite3_column_type(statement, i)); + } + } + resultData.rows.push(statement.get([])); + } + postMessage({ id, data: resultData }); + } catch (error) { + postMessage({ id, error: error.message }); + } +} + +function closeRequest(id, requestData) { + if (requestData.statementId !== undefined && requestData.statementId != null) { + const statement = statements.get(requestData.statementId); + if (!statement) { + postMessage({ id, error: `Invalid statement ID: ${requestData.statementId}` }); + return; + } + try { + statement.finalize(); + statements.delete(requestData.statementId); + } catch (error) { + postMessage({ id, error: error.message }); + } + } + + if (requestData.databaseId !== undefined && requestData.databaseId != null) { + const database = databases.get(requestData.databaseId); + if (!database) { + postMessage({ id, error: `Invalid database ID: ${requestData.databaseId}` }); + return; + } + try { + database.close(); + databases.delete(requestData.databaseId); + } catch (error) { + postMessage({ id, error: error.message }); + } + } +} + +const commandMap = { + open: openRequest, + prepare: prepareRequest, + step: stepRequest, + close: closeRequest, +}; + +function handleMessage(e) { + const requestMsg = e.data; + if (!Object.hasOwn(requestMsg, 'data') && requestMsg.data == null) { + postMessage({ id: requestMsg.id, error: "Invalid request, missing 'data'." }); + return; + } + if (!Object.hasOwn(requestMsg.data, 'cmd') && requestMsg.data.cmd == null) { + postMessage({ id: requestMsg.id, error: "Invalid request, missing 'cmd'." }); + return; + } + const command = requestMsg.data.cmd; + const requestHandler = commandMap[command]; + if (requestHandler) { + requestHandler(requestMsg.id, requestMsg.data); + } else { + postMessage({ id: requestMsg.id, error: `Invalid request, unknown command: '${command}'.` }); + } +} + +const messageQueue = []; +onmessage = (e) => { + if (!sqlite3) { + messageQueue.push(e); + } else { + handleMessage(e); + } +}; + +sqlite3InitModule().then((instance) => { + sqlite3 = instance; + while (messageQueue.length > 0) { + handleMessage(messageQueue.shift()); + } +}); diff --git a/shared/src/androidMain/kotlin/dev/dimension/flare/data/database/DriverFactory.android.kt b/foundation/database/src/androidMain/kotlin/dev/dimension/flare/data/database/DriverFactory.android.kt similarity index 83% rename from shared/src/androidMain/kotlin/dev/dimension/flare/data/database/DriverFactory.android.kt rename to foundation/database/src/androidMain/kotlin/dev/dimension/flare/data/database/DriverFactory.android.kt index 99d25fb49f..78bdc2d308 100644 --- a/shared/src/androidMain/kotlin/dev/dimension/flare/data/database/DriverFactory.android.kt +++ b/foundation/database/src/androidMain/kotlin/dev/dimension/flare/data/database/DriverFactory.android.kt @@ -5,10 +5,11 @@ import androidx.room3.Room import androidx.room3.RoomDatabase import java.io.File -internal actual class DriverFactory( - private val context: Context, +public actual class DriverFactory( + @PublishedApi + internal val context: Context, ) { - actual inline fun createBuilder( + public actual inline fun createBuilder( name: String, isCache: Boolean, ): RoomDatabase.Builder { @@ -25,7 +26,7 @@ internal actual class DriverFactory( ) } - actual fun deleteDatabase( + public actual fun deleteDatabase( name: String, isCache: Boolean, ) { diff --git a/shared/src/jvmTest/kotlin/dev/dimension/flare/DatabaseHelper.jvm.kt b/foundation/database/src/androidMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.android.kt similarity index 69% rename from shared/src/jvmTest/kotlin/dev/dimension/flare/DatabaseHelper.jvm.kt rename to foundation/database/src/androidMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.android.kt index 7f245d18df..e12a81bed9 100644 --- a/shared/src/jvmTest/kotlin/dev/dimension/flare/DatabaseHelper.jvm.kt +++ b/foundation/database/src/androidMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.android.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare +package dev.dimension.flare.data.database import androidx.room3.Room import androidx.room3.RoomDatabase @@ -6,12 +6,11 @@ import dev.dimension.flare.data.database.app.AppDatabase import dev.dimension.flare.data.database.cache.CacheDatabase import kotlin.reflect.KClass +@PublishedApi @Suppress("UNCHECKED_CAST") -internal actual fun Room.memoryDatabaseBuilder(databaseClass: KClass): RoomDatabase.Builder = +internal actual fun Room.platformMemoryDatabaseBuilder(databaseClass: KClass): RoomDatabase.Builder = when (databaseClass) { AppDatabase::class -> Room.inMemoryDatabaseBuilder() CacheDatabase::class -> Room.inMemoryDatabaseBuilder() - else -> error("Unsupported test database: ${databaseClass.qualifiedName}") + else -> error("Unsupported test database: ${databaseClass.simpleName}") } as RoomDatabase.Builder - -actual open class RobolectricTest actual constructor() diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/data/database/DriverFactory.apple.kt b/foundation/database/src/appleMain/kotlin/dev/dimension/flare/data/database/DriverFactory.apple.kt similarity index 93% rename from shared/src/appleMain/kotlin/dev/dimension/flare/data/database/DriverFactory.apple.kt rename to foundation/database/src/appleMain/kotlin/dev/dimension/flare/data/database/DriverFactory.apple.kt index 35a12be7fe..0ce31fd7d3 100644 --- a/shared/src/appleMain/kotlin/dev/dimension/flare/data/database/DriverFactory.apple.kt +++ b/foundation/database/src/appleMain/kotlin/dev/dimension/flare/data/database/DriverFactory.apple.kt @@ -11,8 +11,8 @@ import platform.Foundation.NSSearchPathForDirectoriesInDomains import platform.Foundation.NSUserDomainMask import platform.Foundation.stringWithString -internal actual class DriverFactory { - actual inline fun createBuilder( +public actual class DriverFactory { + public actual inline fun createBuilder( name: String, isCache: Boolean, ): RoomDatabase.Builder { @@ -30,7 +30,7 @@ internal actual class DriverFactory { } @OptIn(ExperimentalForeignApi::class) - actual fun deleteDatabase( + public actual fun deleteDatabase( name: String, isCache: Boolean, ) { @@ -42,6 +42,7 @@ internal actual class DriverFactory { } } + @PublishedApi internal fun databaseDirPath(): String = iosDirPath("databases") @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class, kotlinx.cinterop.UnsafeNumber::class) diff --git a/shared/src/appleTest/kotlin/dev/dimension/flare/DatabaseHelper.apple.kt b/foundation/database/src/appleMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.apple.kt similarity index 69% rename from shared/src/appleTest/kotlin/dev/dimension/flare/DatabaseHelper.apple.kt rename to foundation/database/src/appleMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.apple.kt index 7f245d18df..e12a81bed9 100644 --- a/shared/src/appleTest/kotlin/dev/dimension/flare/DatabaseHelper.apple.kt +++ b/foundation/database/src/appleMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.apple.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare +package dev.dimension.flare.data.database import androidx.room3.Room import androidx.room3.RoomDatabase @@ -6,12 +6,11 @@ import dev.dimension.flare.data.database.app.AppDatabase import dev.dimension.flare.data.database.cache.CacheDatabase import kotlin.reflect.KClass +@PublishedApi @Suppress("UNCHECKED_CAST") -internal actual fun Room.memoryDatabaseBuilder(databaseClass: KClass): RoomDatabase.Builder = +internal actual fun Room.platformMemoryDatabaseBuilder(databaseClass: KClass): RoomDatabase.Builder = when (databaseClass) { AppDatabase::class -> Room.inMemoryDatabaseBuilder() CacheDatabase::class -> Room.inMemoryDatabaseBuilder() - else -> error("Unsupported test database: ${databaseClass.qualifiedName}") + else -> error("Unsupported test database: ${databaseClass.simpleName}") } as RoomDatabase.Builder - -actual open class RobolectricTest actual constructor() diff --git a/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/DatabaseDriverProvider.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/DatabaseDriverProvider.kt new file mode 100644 index 0000000000..cfeea66a63 --- /dev/null +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/DatabaseDriverProvider.kt @@ -0,0 +1,5 @@ +package dev.dimension.flare.data.database + +import androidx.sqlite.SQLiteDriver + +public expect fun createDatabaseDriver(): SQLiteDriver diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/DriverFactory.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/DriverFactory.kt similarity index 62% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/DriverFactory.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/DriverFactory.kt index 311a3ccd41..7dc7d675f4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/DriverFactory.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/DriverFactory.kt @@ -2,13 +2,13 @@ package dev.dimension.flare.data.database import androidx.room3.RoomDatabase -internal expect class DriverFactory { - inline fun createBuilder( +public expect class DriverFactory { + public inline fun createBuilder( name: String, isCache: Boolean = false, ): RoomDatabase.Builder - fun deleteDatabase( + public fun deleteDatabase( name: String, isCache: Boolean, ) diff --git a/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.kt new file mode 100644 index 0000000000..f63ea1fc48 --- /dev/null +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.kt @@ -0,0 +1,15 @@ +package dev.dimension.flare.data.database + +import androidx.room3.Room +import androidx.room3.RoomDatabase +import androidx.sqlite.SQLiteDriver +import kotlin.reflect.KClass + +public inline fun Room.memoryDatabaseBuilder( + databaseDriver: SQLiteDriver = createDatabaseDriver(), +): RoomDatabase.Builder = + platformMemoryDatabaseBuilder(T::class) + .setDriver(databaseDriver) + +@PublishedApi +internal expect fun Room.platformMemoryDatabaseBuilder(databaseClass: KClass): RoomDatabase.Builder diff --git a/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/ProvideDatabase.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/ProvideDatabase.kt new file mode 100644 index 0000000000..8e4af9e5cd --- /dev/null +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/ProvideDatabase.kt @@ -0,0 +1,42 @@ +package dev.dimension.flare.data.database + +import androidx.sqlite.SQLiteDriver +import dev.dimension.flare.common.PlatformDispatchers +import dev.dimension.flare.data.database.app.AppDatabase +import dev.dimension.flare.data.database.cache.CacheDatabase + +public fun provideAppDatabase(driverFactory: DriverFactory): AppDatabase = + provideAppDatabase( + driverFactory = driverFactory, + databaseDriver = createDatabaseDriver(), + ) + +public fun provideAppDatabase( + driverFactory: DriverFactory, + databaseDriver: SQLiteDriver, +): AppDatabase = + driverFactory + .createBuilder("app.db") + .addMigrations(AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10) + .setDriver(databaseDriver) + .setQueryCoroutineContext(PlatformDispatchers.IO) + .build() + +public const val CACHE_DATABASE_NAME: String = "cache.db" + +public fun provideCacheDatabase(driverFactory: DriverFactory): CacheDatabase = + provideCacheDatabase( + driverFactory = driverFactory, + databaseDriver = createDatabaseDriver(), + ) + +public fun provideCacheDatabase( + driverFactory: DriverFactory, + databaseDriver: SQLiteDriver, +): CacheDatabase = + driverFactory + .createBuilder(CACHE_DATABASE_NAME, isCache = true) + .fallbackToDestructiveMigration(dropAllTables = true) + .setDriver(databaseDriver) + .setQueryCoroutineContext(PlatformDispatchers.IO) + .build() diff --git a/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/AccountTypeConverter.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/AccountTypeConverter.kt new file mode 100644 index 0000000000..e55c96e4da --- /dev/null +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/AccountTypeConverter.kt @@ -0,0 +1,51 @@ +package dev.dimension.flare.data.database.adapter + +import androidx.room3.TypeConverter +import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.common.decodeProtobuf +import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.common.encodeProtobuf +import dev.dimension.flare.model.DbAccountType +import dev.dimension.flare.ui.model.UiDMItem +import dev.dimension.flare.ui.model.UiDMRoom +import dev.dimension.flare.ui.model.UiProfile +import dev.dimension.flare.ui.model.UiRelation +import dev.dimension.flare.ui.model.UiTimelineV2 + +public class AccountTypeConverter { + @TypeConverter + public fun fromString(value: String): DbAccountType = value.decodeJson() + + @TypeConverter + public fun fromEnum(value: DbAccountType): String = value.encodeJson() + + @TypeConverter + public fun fromUiProfile(value: UiProfile): ByteArray = value.encodeProtobuf() + + @TypeConverter + public fun toUiProfile(value: ByteArray): UiProfile = value.decodeProtobuf() + + @TypeConverter + public fun fromUiTimelineV2(value: UiTimelineV2): ByteArray = value.encodeProtobuf() + + @TypeConverter + public fun toUiTimelineV2(value: ByteArray): UiTimelineV2 = value.decodeProtobuf() + + @TypeConverter + public fun fromUiDMRoom(value: UiDMRoom): ByteArray = value.encodeProtobuf() + + @TypeConverter + public fun toUiDMRoom(value: ByteArray): UiDMRoom = value.decodeProtobuf() + + @TypeConverter + public fun fromUiDMItem(value: UiDMItem): ByteArray = value.encodeProtobuf() + + @TypeConverter + public fun toUiDMItem(value: ByteArray): UiDMItem = value.decodeProtobuf() + + @TypeConverter + public fun fromUiRelation(value: UiRelation): ByteArray = value.encodeProtobuf() + + @TypeConverter + public fun toUiRelation(value: ByteArray): UiRelation = value.decodeProtobuf() +} diff --git a/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/MicroBlogKeyConverter.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/MicroBlogKeyConverter.kt new file mode 100644 index 0000000000..6bc50b57a7 --- /dev/null +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/MicroBlogKeyConverter.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.data.database.adapter + +import dev.dimension.flare.model.MicroBlogKey + +public class MicroBlogKeyConverter { + @androidx.room3.TypeConverter + public fun fromString(value: String): MicroBlogKey = MicroBlogKey.valueOf(value) + + @androidx.room3.TypeConverter + public fun fromEnum(value: MicroBlogKey): String = value.toString() +} diff --git a/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/PlatformTypeConverter.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/PlatformTypeConverter.kt new file mode 100644 index 0000000000..d0d458b9c3 --- /dev/null +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/PlatformTypeConverter.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.data.database.adapter + +import dev.dimension.flare.model.PlatformType + +public class PlatformTypeConverter { + @androidx.room3.TypeConverter + public fun fromString(value: String): PlatformType = PlatformType.valueOf(value) + + @androidx.room3.TypeConverter + public fun fromEnum(value: PlatformType): String = value.name +} diff --git a/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/SubscriptionTypeConverter.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/SubscriptionTypeConverter.kt new file mode 100644 index 0000000000..c44665b1a2 --- /dev/null +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/SubscriptionTypeConverter.kt @@ -0,0 +1,20 @@ +package dev.dimension.flare.data.database.adapter + +import dev.dimension.flare.data.database.app.model.SubscriptionType +import dev.dimension.flare.model.RssDisplayMode + +public class SubscriptionTypeConverter { + @androidx.room3.TypeConverter + public fun fromString(value: String): SubscriptionType = SubscriptionType.valueOf(value) + + @androidx.room3.TypeConverter + public fun fromEnum(value: SubscriptionType): String = value.name +} + +public class RssDisplayModeConverter { + @androidx.room3.TypeConverter + public fun fromString(value: String): RssDisplayMode = RssDisplayMode.valueOf(value) + + @androidx.room3.TypeConverter + public fun fromEnum(value: RssDisplayMode): String = value.name +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/AppDatabase.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/AppDatabase.kt similarity index 79% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/AppDatabase.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/AppDatabase.kt index c334ee1a44..188fa7b85e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/AppDatabase.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/AppDatabase.kt @@ -8,7 +8,7 @@ import androidx.room3.RoomDatabaseConstructor import androidx.room3.TypeConverters import androidx.room3.migration.Migration import androidx.sqlite.SQLiteConnection -import androidx.sqlite.execSQL +import androidx.sqlite.async.executeSQL import dev.dimension.flare.data.database.app.dao.AccountDao import dev.dimension.flare.data.database.app.dao.ApplicationDao import dev.dimension.flare.data.database.app.dao.DraftDao @@ -60,38 +60,38 @@ import dev.dimension.flare.data.database.app.dao.SearchHistoryDao dev.dimension.flare.data.database.adapter.RssDisplayModeConverter::class, ) @ConstructedBy(AppDatabaseConstructor::class) -internal abstract class AppDatabase : RoomDatabase() { - abstract fun accountDao(): AccountDao +public abstract class AppDatabase : RoomDatabase() { + public abstract fun accountDao(): AccountDao - abstract fun applicationDao(): ApplicationDao + public abstract fun applicationDao(): ApplicationDao - abstract fun draftDao(): DraftDao + public abstract fun draftDao(): DraftDao - abstract fun keywordFilterDao(): KeywordFilterDao + public abstract fun keywordFilterDao(): KeywordFilterDao - abstract fun searchHistoryDao(): SearchHistoryDao + public abstract fun searchHistoryDao(): SearchHistoryDao - abstract fun rssSourceDao(): RssSourceDao + public abstract fun rssSourceDao(): RssSourceDao - companion object { - val MIGRATION_8_9 = + public companion object { + public val MIGRATION_8_9: Migration = object : Migration(8, 9) { override suspend fun migrate(connection: SQLiteConnection) { - connection.execSQL( + connection.executeSQL( "ALTER TABLE DbRssSources ADD COLUMN displayMode TEXT NOT NULL DEFAULT 'FULL_CONTENT'", ) - connection.execSQL( + connection.executeSQL( "UPDATE DbRssSources SET displayMode = 'OPEN_IN_BROWSER' WHERE openInBrowser = 1", ) - connection.execSQL( + connection.executeSQL( "ALTER TABLE DbRssSources DROP COLUMN openInBrowser", ) } } - val MIGRATION_9_10 = + public val MIGRATION_9_10: Migration = object : Migration(9, 10) { override suspend fun migrate(connection: SQLiteConnection) { - connection.execSQL( + connection.executeSQL( "ALTER TABLE DbKeywordFilter ADD COLUMN is_regex INTEGER NOT NULL DEFAULT 0", ) } @@ -101,6 +101,6 @@ internal abstract class AppDatabase : RoomDatabase() { // The Room compiler generates the `actual` implementations. @Suppress("NO_ACTUAL_FOR_EXPECT") -internal expect object AppDatabaseConstructor : RoomDatabaseConstructor { +public expect object AppDatabaseConstructor : RoomDatabaseConstructor { override fun initialize(): AppDatabase } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/AccountDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/AccountDao.kt similarity index 68% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/AccountDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/AccountDao.kt index 9c273c120b..969790d082 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/AccountDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/AccountDao.kt @@ -9,46 +9,46 @@ import dev.dimension.flare.model.MicroBlogKey import kotlinx.coroutines.flow.Flow @Dao -internal interface AccountDao { +public interface AccountDao { @Query("SELECT * FROM DbAccount ORDER BY last_active DESC LIMIT 1") - fun activeAccount(): Flow + public fun activeAccount(): Flow @Query("SELECT * FROM DbAccount ORDER BY sort_id") - fun sortedAccounts(): Flow> + public fun sortedAccounts(): Flow> @Query("SELECT * FROM DbAccount") - fun allAccounts(): Flow> + public fun allAccounts(): Flow> @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(account: DbAccount) + public suspend fun insert(account: DbAccount) @Query("UPDATE DbAccount SET last_active = :lastActive WHERE account_key = :accountKey") - suspend fun setLastActive( + public suspend fun setLastActive( accountKey: MicroBlogKey, lastActive: Long, ) @Query("SELECT * FROM DbAccount WHERE account_key = :accountKey") - fun get(accountKey: MicroBlogKey): Flow + public fun get(accountKey: MicroBlogKey): Flow @Query("SELECT * FROM DbAccount WHERE account_key = :accountKey") - suspend fun getAccount(accountKey: MicroBlogKey): DbAccount? + public suspend fun getAccount(accountKey: MicroBlogKey): DbAccount? @Query("DELETE FROM DbAccount WHERE account_key = :accountKey") - suspend fun delete(accountKey: MicroBlogKey) + public suspend fun delete(accountKey: MicroBlogKey) @Query("UPDATE DbAccount SET credential_json = :credentialJson WHERE account_key = :accountKey") - suspend fun setCredential( + public suspend fun setCredential( accountKey: MicroBlogKey, credentialJson: String, ) @Query("UPDATE DbAccount SET sort_id = :sortId WHERE account_key = :accountKey") - suspend fun setSortId( + public suspend fun setSortId( accountKey: MicroBlogKey, sortId: Long, ) @Query("SELECT MAX(sort_id) FROM DbAccount") - suspend fun getMaxSortId(): Long? + public suspend fun getMaxSortId(): Long? } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/ApplicationDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/ApplicationDao.kt similarity index 73% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/ApplicationDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/ApplicationDao.kt index 58d5f94374..747bd08752 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/ApplicationDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/ApplicationDao.kt @@ -9,35 +9,35 @@ import dev.dimension.flare.model.PlatformType import kotlinx.coroutines.flow.Flow @Dao -internal interface ApplicationDao { +public interface ApplicationDao { @Query("SELECT * FROM DbApplication") - fun allApplication(): Flow> + public fun allApplication(): Flow> @Query("SELECT * FROM DbApplication WHERE host = :host") - fun get(host: String): Flow + public fun get(host: String): Flow @Query("SELECT * FROM DbApplication WHERE has_pending_oauth_request = 1") - fun getPending(): Flow> + public fun getPending(): Flow> @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(application: DbApplication) + public suspend fun insert(application: DbApplication) @Query("UPDATE DbApplication SET credential_json = :credentialJson, platform_type = :platformType WHERE host = :host") - suspend fun update( + public suspend fun update( host: String, credentialJson: String, platformType: PlatformType, ) @Query("UPDATE DbApplication SET has_pending_oauth_request = :hasPendingOauthRequest WHERE host = :host") - suspend fun updatePending( + public suspend fun updatePending( host: String, hasPendingOauthRequest: Long, ) @Query("DELETE FROM DbApplication WHERE host = :host") - suspend fun delete(host: String) + public suspend fun delete(host: String) @Query("UPDATE DbApplication SET has_pending_oauth_request = 0 WHERE has_pending_oauth_request = 1") - suspend fun clearPending() + public suspend fun clearPending() } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/DraftDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/DraftDao.kt similarity index 79% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/DraftDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/DraftDao.kt index c2025a95bb..f2e74a27ce 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/DraftDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/DraftDao.kt @@ -14,30 +14,30 @@ import dev.dimension.flare.data.database.app.model.DraftTargetStatus import kotlinx.coroutines.flow.Flow @Dao -internal interface DraftDao { +public interface DraftDao { @Upsert - suspend fun upsertGroup(group: DbDraftGroup) + public suspend fun upsertGroup(group: DbDraftGroup) @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertTargets(targets: List) + public suspend fun insertTargets(targets: List) @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertMedias(medias: List) + public suspend fun insertMedias(medias: List) @Query("DELETE FROM DbDraftMedia WHERE group_id = :groupId") - suspend fun deleteMediasByGroup(groupId: String) + public suspend fun deleteMediasByGroup(groupId: String) @Query("DELETE FROM DbDraftTarget WHERE group_id = :groupId") - suspend fun deleteTargetsByGroup(groupId: String) + public suspend fun deleteTargetsByGroup(groupId: String) @Query("DELETE FROM DbDraftTarget WHERE target_id = :targetId") - suspend fun deleteTarget(targetId: String) + public suspend fun deleteTarget(targetId: String) @Query("DELETE FROM DbDraftGroup WHERE group_id = :groupId") - suspend fun deleteGroup(groupId: String) + public suspend fun deleteGroup(groupId: String) @Query("SELECT COUNT(*) FROM DbDraftTarget WHERE group_id = :groupId") - suspend fun countTargets(groupId: String): Int + public suspend fun countTargets(groupId: String): Int @Transaction @Query( @@ -51,7 +51,7 @@ internal interface DraftDao { ORDER BY updated_at DESC """, ) - fun visibleDraftGroups(): Flow> + public fun visibleDraftGroups(): Flow> @Transaction @Query( @@ -61,7 +61,7 @@ internal interface DraftDao { LIMIT 1 """, ) - fun draftGroup(groupId: String): Flow + public fun draftGroup(groupId: String): Flow @Transaction @Query( @@ -71,7 +71,7 @@ internal interface DraftDao { LIMIT 1 """, ) - suspend fun getDraftGroup(groupId: String): DbDraftGroupWithRelations? + public suspend fun getDraftGroup(groupId: String): DbDraftGroupWithRelations? @Query( """ @@ -80,7 +80,7 @@ internal interface DraftDao { LIMIT 1 """, ) - suspend fun getGroup(groupId: String): DbDraftGroup? + public suspend fun getGroup(groupId: String): DbDraftGroup? @Transaction @Query( @@ -94,7 +94,7 @@ internal interface DraftDao { ORDER BY updated_at DESC """, ) - fun sendingDraftGroups(): Flow> + public fun sendingDraftGroups(): Flow> @Query( """ @@ -107,7 +107,7 @@ internal interface DraftDao { WHERE target_id = :targetId """, ) - suspend fun updateTargetStatus( + public suspend fun updateTargetStatus( targetId: String, status: DraftTargetStatus, errorMessage: String?, @@ -123,7 +123,7 @@ internal interface DraftDao { WHERE group_id = :groupId """, ) - suspend fun touchGroup( + public suspend fun touchGroup( groupId: String, updatedAt: Long, ) @@ -135,7 +135,7 @@ internal interface DraftDao { AND status = :status """, ) - suspend fun countTargetsByStatus( + public suspend fun countTargetsByStatus( groupId: String, status: DraftTargetStatus, ): Int @@ -150,7 +150,7 @@ internal interface DraftDao { AND (last_attempt_at IS NULL OR last_attempt_at < :expiredBefore) """, ) - suspend fun resetSendingTargets( + public suspend fun resetSendingTargets( fromStatus: DraftTargetStatus, toStatus: DraftTargetStatus, expiredBefore: Long, @@ -169,7 +169,7 @@ internal interface DraftDao { ) """, ) - suspend fun touchExpiredSendingGroups( + public suspend fun touchExpiredSendingGroups( fromStatus: DraftTargetStatus, expiredBefore: Long, updatedAt: Long, @@ -184,7 +184,7 @@ internal interface DraftDao { WHERE status = :fromStatus """, ) - suspend fun updateTargetsByStatus( + public suspend fun updateTargetsByStatus( fromStatus: DraftTargetStatus, toStatus: DraftTargetStatus, errorMessage: String?, @@ -201,7 +201,7 @@ internal interface DraftDao { ) """, ) - suspend fun touchGroupsByTargetStatus( + public suspend fun touchGroupsByTargetStatus( status: DraftTargetStatus, updatedAt: Long, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/KeywordFilterDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/KeywordFilterDao.kt similarity index 74% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/KeywordFilterDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/KeywordFilterDao.kt index 401d121c97..56e5884ea3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/KeywordFilterDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/KeywordFilterDao.kt @@ -8,20 +8,20 @@ import dev.dimension.flare.data.database.app.model.DbKeywordFilter import kotlinx.coroutines.flow.Flow @Dao -internal interface KeywordFilterDao { +public interface KeywordFilterDao { @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(keywordFilter: DbKeywordFilter) + public suspend fun insert(keywordFilter: DbKeywordFilter) @Query("SELECT * FROM DbKeywordFilter") - fun selectAll(): Flow> + public fun selectAll(): Flow> @Query("SELECT * FROM DbKeywordFilter WHERE expired_at = 0 OR expired_at > :currentTime") - fun selectAllNotExpired(currentTime: Long): Flow> + public fun selectAllNotExpired(currentTime: Long): Flow> @Query( "SELECT * FROM DbKeywordFilter WHERE (expired_at = 0 OR expired_at > :currentTime) AND (for_timeline = :forTimeline OR for_notification = :forNotification OR for_search = :forSearch)", ) - fun selectNotExpiredFor( + public fun selectNotExpiredFor( currentTime: Long, forTimeline: Long, forNotification: Long, @@ -29,18 +29,18 @@ internal interface KeywordFilterDao { ): Flow> @Query("SELECT * FROM DbKeywordFilter WHERE keyword = :keyword") - fun selectByKeyword(keyword: String): Flow + public fun selectByKeyword(keyword: String): Flow @Query("DELETE FROM DbKeywordFilter WHERE keyword = :keyword") - suspend fun deleteByKeyword(keyword: String) + public suspend fun deleteByKeyword(keyword: String) @Query("DELETE FROM DbKeywordFilter") - suspend fun deleteAll() + public suspend fun deleteAll() @Query( "UPDATE DbKeywordFilter SET for_timeline = :forTimeline, for_notification = :forNotification, for_search = :forSearch, expired_at = :expiredAt, is_regex = :isRegex WHERE keyword = :keyword", ) - suspend fun update( + public suspend fun update( keyword: String, forTimeline: Long, forNotification: Long, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/RssSourceDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/RssSourceDao.kt similarity index 68% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/RssSourceDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/RssSourceDao.kt index 0b5933a901..d7215ac929 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/RssSourceDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/RssSourceDao.kt @@ -8,27 +8,27 @@ import dev.dimension.flare.data.database.app.model.SubscriptionType import kotlinx.coroutines.flow.Flow @Dao -internal interface RssSourceDao { +public interface RssSourceDao { @Insert(onConflict = androidx.room3.OnConflictStrategy.REPLACE) - suspend fun insert(data: DbRssSources) + public suspend fun insert(data: DbRssSources) @Insert(onConflict = androidx.room3.OnConflictStrategy.REPLACE) - suspend fun insertAll(data: List) + public suspend fun insertAll(data: List) @Query("SELECT * FROM DbRssSources") - fun getAll(): Flow> + public fun getAll(): Flow> @Query("DELETE FROM DbRssSources WHERE id = :id") - suspend fun delete(id: Int) + public suspend fun delete(id: Int) @Query("SELECT * FROM DbRssSources WHERE id = :id") - fun get(id: Int): Flow + public fun get(id: Int): Flow @Query("SELECT * FROM DbRssSources WHERE url = :url") - suspend fun getByUrl(url: String): List + public suspend fun getByUrl(url: String): List @Query("SELECT * FROM DbRssSources WHERE url = :url AND type = :type") - suspend fun getByUrlAndType( + public suspend fun getByUrlAndType( url: String, type: SubscriptionType, ): List diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/SearchHistoryDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/SearchHistoryDao.kt similarity index 69% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/SearchHistoryDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/SearchHistoryDao.kt index 81a379baa2..2635a369e0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/SearchHistoryDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/dao/SearchHistoryDao.kt @@ -8,16 +8,16 @@ import dev.dimension.flare.data.database.app.model.DbSearchHistory import kotlinx.coroutines.flow.Flow @Dao -internal interface SearchHistoryDao { +public interface SearchHistoryDao { @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(searchHistory: DbSearchHistory) + public suspend fun insert(searchHistory: DbSearchHistory) @Query("SELECT * FROM DbSearchHistory ORDER BY created_at DESC") - fun select(): Flow> + public fun select(): Flow> @Query("DELETE FROM DbSearchHistory WHERE search = :search") - suspend fun delete(search: String) + public suspend fun delete(search: String) @Query("DELETE FROM DbSearchHistory") - suspend fun deleteAll() + public suspend fun deleteAll() } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/AppDatabaseExport.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/AppDatabaseExport.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/AppDatabaseExport.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/AppDatabaseExport.kt index 2ed8ffa120..b8e3e7daf0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/AppDatabaseExport.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/AppDatabaseExport.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.database.app.model import kotlinx.serialization.Serializable @Serializable -internal data class AppDatabaseExport( +public data class AppDatabaseExport( val accounts: List = emptyList(), val applications: List = emptyList(), val keywordFilters: List = emptyList(), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbAccount.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbAccount.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbAccount.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbAccount.kt index eb1b25ed1b..329b3eb186 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbAccount.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbAccount.kt @@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable @Serializable @Entity -internal data class DbAccount( +public data class DbAccount( @PrimaryKey val account_key: MicroBlogKey, val credential_json: String, val platform_type: PlatformType, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbApplication.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbApplication.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbApplication.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbApplication.kt index dd3eef5bb5..0b7461673f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbApplication.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbApplication.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable @Serializable @Entity -internal data class DbApplication( +public data class DbApplication( @PrimaryKey val host: String, val credential_json: String, val platform_type: PlatformType, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbDraft.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbDraft.kt similarity index 77% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbDraft.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbDraft.kt index 15005b3ab4..315482b01f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbDraft.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbDraft.kt @@ -10,15 +10,14 @@ import androidx.room3.TypeConverter import dev.dimension.flare.common.decodeJson import dev.dimension.flare.common.encodeJson import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.UiTimelineV2 import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlin.uuid.Uuid @Serializable -internal data class DraftContent( +public data class DraftContent( val text: String, - val visibility: UiTimelineV2.Post.Visibility, + val visibility: DraftVisibility, val language: List, val sensitive: Boolean, val spoilerText: String? = null, @@ -27,14 +26,14 @@ internal data class DraftContent( val reference: DraftReference? = null, ) { @Serializable - data class DraftPoll( + public data class DraftPoll( val options: List, val expiredAfter: Long, val multiple: Boolean, ) @Serializable - data class DraftReference( + public data class DraftReference( val type: DraftReferenceType, val statusKey: MicroBlogKey, val rootId: String? = null, @@ -42,7 +41,7 @@ internal data class DraftContent( } @Serializable -internal enum class DraftReferenceType { +public enum class DraftReferenceType { @SerialName("reply") REPLY, @@ -54,7 +53,16 @@ internal enum class DraftReferenceType { } @Serializable -internal enum class DraftTargetStatus { +public enum class DraftVisibility { + Public, + Home, + Followers, + Specified, + Channel, +} + +@Serializable +public enum class DraftTargetStatus { @SerialName("draft") DRAFT, @@ -66,7 +74,7 @@ internal enum class DraftTargetStatus { } @Serializable -internal enum class DraftMediaType { +public enum class DraftMediaType { @SerialName("image") IMAGE, @@ -83,7 +91,7 @@ internal enum class DraftMediaType { ], ) @Serializable -internal data class DbDraftGroup( +public data class DbDraftGroup( @PrimaryKey val group_id: String = Uuid.random().toString(), val content: DraftContent, @@ -108,7 +116,7 @@ internal data class DbDraftGroup( ], ) @Serializable -internal data class DbDraftTarget( +public data class DbDraftTarget( val group_id: String, val account_key: MicroBlogKey, val status: DraftTargetStatus, @@ -136,7 +144,7 @@ internal data class DbDraftTarget( ], ) @Serializable -internal data class DbDraftMedia( +public data class DbDraftMedia( val group_id: String, val cache_path: String, val file_name: String? = null, @@ -148,7 +156,7 @@ internal data class DbDraftMedia( val media_id: String = Uuid.random().toString(), ) -internal data class DbDraftGroupWithRelations( +public data class DbDraftGroupWithRelations( @Embedded val group: DbDraftGroup, @Relation( @@ -165,22 +173,22 @@ internal data class DbDraftGroupWithRelations( val medias: List, ) -internal class DraftConverters { +public class DraftConverters { @TypeConverter - fun fromDraftContent(value: DraftContent): String = value.encodeJson() + public fun fromDraftContent(value: DraftContent): String = value.encodeJson() @TypeConverter - fun toDraftContent(value: String): DraftContent = value.decodeJson() + public fun toDraftContent(value: String): DraftContent = value.decodeJson() @TypeConverter - fun fromDraftTargetStatus(value: DraftTargetStatus): String = value.name + public fun fromDraftTargetStatus(value: DraftTargetStatus): String = value.name @TypeConverter - fun toDraftTargetStatus(value: String): DraftTargetStatus = DraftTargetStatus.valueOf(value) + public fun toDraftTargetStatus(value: String): DraftTargetStatus = DraftTargetStatus.valueOf(value) @TypeConverter - fun fromDraftMediaType(value: DraftMediaType): String = value.name + public fun fromDraftMediaType(value: DraftMediaType): String = value.name @TypeConverter - fun toDraftMediaType(value: String): DraftMediaType = DraftMediaType.valueOf(value) + public fun toDraftMediaType(value: String): DraftMediaType = DraftMediaType.valueOf(value) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbKeywordFilter.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbKeywordFilter.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbKeywordFilter.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbKeywordFilter.kt index 1e92e03b89..3fe6c900d1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbKeywordFilter.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbKeywordFilter.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable @Serializable @Entity -internal data class DbKeywordFilter( +public data class DbKeywordFilter( @PrimaryKey val keyword: String, val for_timeline: Long, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbRssSources.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbRssSources.kt similarity index 82% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbRssSources.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbRssSources.kt index 9b60bcaf8c..4b7943dbad 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbRssSources.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbRssSources.kt @@ -3,6 +3,7 @@ package dev.dimension.flare.data.database.app.model import androidx.room3.ColumnInfo import androidx.room3.Entity import androidx.room3.PrimaryKey +import dev.dimension.flare.model.RssDisplayMode import kotlinx.serialization.Serializable @Serializable @@ -13,16 +14,9 @@ public enum class SubscriptionType { MASTODON_LOCAL, } -@Serializable -public enum class RssDisplayMode { - FULL_CONTENT, - OPEN_IN_BROWSER, - DESCRIPTION_ONLY, -} - @Serializable @Entity -internal data class DbRssSources( +public data class DbRssSources( @PrimaryKey(autoGenerate = true) val id: Int = 0, val url: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbSearchHistory.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbSearchHistory.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbSearchHistory.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbSearchHistory.kt index b239551738..c98373848b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbSearchHistory.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/app/model/DbSearchHistory.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable @Serializable @Entity -internal data class DbSearchHistory( +public data class DbSearchHistory( @PrimaryKey val search: String, val created_at: Long, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/CacaheDatabase.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/CacaheDatabase.kt similarity index 67% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/CacaheDatabase.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/CacaheDatabase.kt index 544ff0957d..0bd991c441 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/CacaheDatabase.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/CacaheDatabase.kt @@ -5,10 +5,8 @@ import androidx.room3.Database import androidx.room3.RoomDatabase import androidx.room3.RoomDatabaseConstructor import androidx.room3.TypeConverters -import androidx.room3.immediateTransaction -import androidx.room3.useWriterConnection -internal const val CACHE_DATABASE_VERSION = 39 +public const val CACHE_DATABASE_VERSION: Int = 39 @Database( entities = [ @@ -43,33 +41,26 @@ internal const val CACHE_DATABASE_VERSION = 39 dev.dimension.flare.data.database.cache.model.TranslationConverters::class, ) @ConstructedBy(CacheDatabaseConstructor::class) -internal abstract class CacheDatabase : RoomDatabase() { - abstract fun emojiDao(): dev.dimension.flare.data.database.cache.dao.EmojiDao +public abstract class CacheDatabase : RoomDatabase() { + public abstract fun emojiDao(): dev.dimension.flare.data.database.cache.dao.EmojiDao - abstract fun statusReferenceDao(): dev.dimension.flare.data.database.cache.dao.StatusReferenceDao + public abstract fun statusReferenceDao(): dev.dimension.flare.data.database.cache.dao.StatusReferenceDao - abstract fun statusDao(): dev.dimension.flare.data.database.cache.dao.StatusDao + public abstract fun statusDao(): dev.dimension.flare.data.database.cache.dao.StatusDao - abstract fun userDao(): dev.dimension.flare.data.database.cache.dao.UserDao + public abstract fun userDao(): dev.dimension.flare.data.database.cache.dao.UserDao - abstract fun pagingTimelineDao(): dev.dimension.flare.data.database.cache.dao.PagingTimelineDao + public abstract fun pagingTimelineDao(): dev.dimension.flare.data.database.cache.dao.PagingTimelineDao - abstract fun messageDao(): dev.dimension.flare.data.database.cache.dao.MessageDao + public abstract fun messageDao(): dev.dimension.flare.data.database.cache.dao.MessageDao - abstract fun listDao(): dev.dimension.flare.data.database.cache.dao.ListDao + public abstract fun listDao(): dev.dimension.flare.data.database.cache.dao.ListDao - abstract fun translationDao(): dev.dimension.flare.data.database.cache.dao.TranslationDao + public abstract fun translationDao(): dev.dimension.flare.data.database.cache.dao.TranslationDao } // The Room compiler generates the `actual` implementations. @Suppress("NO_ACTUAL_FOR_EXPECT") -internal expect object CacheDatabaseConstructor : RoomDatabaseConstructor { +public expect object CacheDatabaseConstructor : RoomDatabaseConstructor { override fun initialize(): CacheDatabase } - -internal suspend fun RoomDatabase.connect(block: suspend () -> R): R = - useWriterConnection { - it.immediateTransaction { - block.invoke() - } - } diff --git a/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/RoomDatabaseConnect.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/RoomDatabaseConnect.kt new file mode 100644 index 0000000000..f61614ddc7 --- /dev/null +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/RoomDatabaseConnect.kt @@ -0,0 +1,12 @@ +package dev.dimension.flare.data.database.cache + +import androidx.room3.RoomDatabase +import androidx.room3.immediateTransaction +import androidx.room3.useWriterConnection + +public suspend fun RoomDatabase.connect(block: suspend () -> R): R = + useWriterConnection { + it.immediateTransaction { + block.invoke() + } + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/EmojiDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/EmojiDao.kt similarity index 64% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/EmojiDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/EmojiDao.kt index 4b0d8bd33e..06a3d142ec 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/EmojiDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/EmojiDao.kt @@ -10,31 +10,31 @@ import dev.dimension.flare.model.DbAccountType import kotlinx.coroutines.flow.Flow @Dao -internal interface EmojiDao { +public interface EmojiDao { @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(emoji: DbEmoji) + public suspend fun insert(emoji: DbEmoji) @Query("SELECT * FROM DbEmoji WHERE host = :host") - fun get(host: String): Flow + public fun get(host: String): Flow @Query("SELECT content FROM DbEmoji WHERE host = :host") - fun getContent(host: String): Flow + public fun getContent(host: String): Flow @Query("DELETE FROM DbEmoji WHERE host = :host") - suspend fun delete(host: String) + public suspend fun delete(host: String) @Query("DELETE FROM DbEmoji") - suspend fun clear() + public suspend fun clear() @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertHistory(emoji: DbEmojiHistory) + public suspend fun insertHistory(emoji: DbEmojiHistory) @Query("SELECT * FROM DbEmojiHistory WHERE accountType = :accountType ORDER BY lastUse DESC") - suspend fun getHistory(accountType: DbAccountType): List + public suspend fun getHistory(accountType: DbAccountType): List @Query("DELETE FROM DbEmojiHistory") - suspend fun clearHistory() + public suspend fun clearHistory() @Query("DELETE FROM DbEmojiHistory WHERE accountType = :accountType") - suspend fun clearHistoryByAccountType(accountType: DbAccountType) + public suspend fun clearHistoryByAccountType(accountType: DbAccountType) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/ListDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/ListDao.kt similarity index 69% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/ListDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/ListDao.kt index 6a1710f6f8..f711156163 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/ListDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/ListDao.kt @@ -20,55 +20,55 @@ import kotlinx.coroutines.flow.Flow @Dao @DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class) -internal interface ListDao { +public interface ListDao { @Transaction @Query( "SELECT * FROM DbListPaging WHERE pagingKey = :pagingKey", ) - fun getPagingSource(pagingKey: String): PagingSource + public fun getPagingSource(pagingKey: String): PagingSource @Transaction @Query("SELECT * FROM DbListPaging WHERE pagingKey = :pagingKey") - fun getListKeysFlow(pagingKey: String): Flow> + public fun getListKeysFlow(pagingKey: String): Flow> @Query("SELECT * FROM DbList WHERE listKey = :listKey AND accountType = :accountType") - fun getList( + public fun getList( listKey: MicroBlogKey, accountType: DbAccountType, ): Flow @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAllPaging(timelines: List) + public suspend fun insertAllPaging(timelines: List) @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAllList(lists: List) + public suspend fun insertAllList(lists: List) @Query("DELETE FROM DbListPaging WHERE pagingKey = :pagingKey") - suspend fun deleteByPagingKey(pagingKey: String) + public suspend fun deleteByPagingKey(pagingKey: String) @Query("DELETE FROM DbList WHERE accountType = :accountType") - suspend fun deleteByAccountType(accountType: String) + public suspend fun deleteByAccountType(accountType: String) @Query("DELETE FROM DbList WHERE listKey = :listKey AND accountType = :accountType") - suspend fun deleteByListKey( + public suspend fun deleteByListKey( listKey: MicroBlogKey, accountType: DbAccountType, ) @Query("DELETE FROM DbListPaging WHERE listKey = :listKey AND accountType = :accountType") - suspend fun deletePagingByListKey( + public suspend fun deletePagingByListKey( listKey: MicroBlogKey, accountType: DbAccountType, ) @Query("DELETE FROM DbList") - suspend fun clearAllLists() + public suspend fun clearAllLists() @Query("DELETE FROM DbListPaging") - suspend fun clearAllListPaging() + public suspend fun clearAllListPaging() @Query("UPDATE DbList SET content = :content WHERE listKey = :listKey AND accountType = :accountType") - suspend fun updateListContent( + public suspend fun updateListContent( listKey: MicroBlogKey, accountType: DbAccountType, content: DbList.ListContent, @@ -76,29 +76,29 @@ internal interface ListDao { @Transaction @Query("SELECT * FROM DbListMember WHERE listKey = :listKey") - fun getListMembers(listKey: MicroBlogKey): PagingSource + public fun getListMembers(listKey: MicroBlogKey): PagingSource @Transaction @Query("SELECT * FROM DbListMember WHERE listKey = :listKey") - fun getListMembersFlow(listKey: MicroBlogKey): Flow> + public fun getListMembersFlow(listKey: MicroBlogKey): Flow> @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAllMember(members: List) + public suspend fun insertAllMember(members: List) @Query("DELETE FROM DbListMember WHERE listKey = :listKey") - suspend fun deleteMembersByListKey(listKey: MicroBlogKey) + public suspend fun deleteMembersByListKey(listKey: MicroBlogKey) @Query("DELETE FROM DbListMember WHERE memberKey = :memberKey AND listKey = :listKey") - suspend fun deleteMemberFromList( + public suspend fun deleteMemberFromList( memberKey: MicroBlogKey, listKey: MicroBlogKey, ) @Transaction @Query("SELECT * FROM DbUser WHERE userKey = :userKey") - fun getUserByKey(userKey: MicroBlogKey): PagingSource + public fun getUserByKey(userKey: MicroBlogKey): PagingSource @Transaction @Query("SELECT * FROM DbUser WHERE userKey = :userKey") - fun getUserByKeyFlow(userKey: MicroBlogKey): Flow + public fun getUserByKeyFlow(userKey: MicroBlogKey): Flow } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/MessageDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/MessageDao.kt similarity index 66% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/MessageDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/MessageDao.kt index 74a97dea2a..9dcaca781b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/MessageDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/MessageDao.kt @@ -16,67 +16,67 @@ import kotlinx.coroutines.flow.Flow @Dao @DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class) -internal interface MessageDao { +public interface MessageDao { @Query("SELECT * FROM DbDirectMessageTimeline WHERE accountType = :accountType ORDER BY sortId DESC") - fun getRoomPagingSource(accountType: DbAccountType): PagingSource + public fun getRoomPagingSource(accountType: DbAccountType): PagingSource @Query("SELECT * FROM DbDirectMessageTimeline WHERE accountType = :accountType ORDER BY sortId DESC") - fun getRoomTimeline(accountType: DbAccountType): Flow> + public fun getRoomTimeline(accountType: DbAccountType): Flow> @Query("SELECT * FROM DbMessageItem WHERE roomKey = :roomKey ORDER BY timestamp DESC") - fun getRoomMessagesPagingSource(roomKey: MicroBlogKey): PagingSource + public fun getRoomMessagesPagingSource(roomKey: MicroBlogKey): PagingSource @Query("SELECT * FROM DbDirectMessageTimeline WHERE roomKey = :roomKey AND accountType = :accountType") - fun getRoomInfo( + public fun getRoomInfo( roomKey: MicroBlogKey, accountType: DbAccountType, ): Flow @Insert(onConflict = androidx.room3.OnConflictStrategy.REPLACE) - suspend fun insert(items: List) + public suspend fun insert(items: List) @Insert(onConflict = androidx.room3.OnConflictStrategy.REPLACE) - suspend fun insertReferences(items: List) + public suspend fun insertReferences(items: List) @Insert(onConflict = androidx.room3.OnConflictStrategy.REPLACE) - suspend fun insertMessages(items: List) + public suspend fun insertMessages(items: List) @Insert(onConflict = androidx.room3.OnConflictStrategy.REPLACE) - suspend fun insertTimeline(items: List) + public suspend fun insertTimeline(items: List) @Query("DELETE FROM DbMessageItem WHERE roomKey = :roomKey") - suspend fun clearRoomMessage(roomKey: MicroBlogKey) + public suspend fun clearRoomMessage(roomKey: MicroBlogKey) @Query("DELETE FROM DbMessageItem WHERE messageKey = :messageKey") - suspend fun deleteMessage(messageKey: MicroBlogKey) + public suspend fun deleteMessage(messageKey: MicroBlogKey) @Query("SELECT * FROM DbMessageItem WHERE messageKey = :messageKey") - suspend fun getMessage(messageKey: MicroBlogKey): DbMessageItem? + public suspend fun getMessage(messageKey: MicroBlogKey): DbMessageItem? @Query("SELECT * FROM DbMessageItem WHERE roomKey = :roomKey AND isLocal = 0 ORDER BY timestamp DESC") - suspend fun getLatestMessage(roomKey: MicroBlogKey): DbMessageItem? + public suspend fun getLatestMessage(roomKey: MicroBlogKey): DbMessageItem? @Query("DELETE FROM DbDirectMessageTimeline WHERE accountType = :accountType") - suspend fun clearMessageTimeline(accountType: DbAccountType) + public suspend fun clearMessageTimeline(accountType: DbAccountType) @Query("UPDATE DbDirectMessageTimeline SET unreadCount = 0 WHERE roomKey = :roomKey AND accountType = :accountType") - suspend fun clearUnreadCount( + public suspend fun clearUnreadCount( roomKey: MicroBlogKey, accountType: DbAccountType, ) @Query("DELETE FROM DbDirectMessageTimeline WHERE roomKey = :roomKey AND accountType = :accountType") - suspend fun deleteRoomTimeline( + public suspend fun deleteRoomTimeline( roomKey: MicroBlogKey, accountType: DbAccountType, ) @Query("DELETE FROM DbMessageRoom WHERE roomKey = :roomKey") - suspend fun deleteRoom(roomKey: MicroBlogKey) + public suspend fun deleteRoom(roomKey: MicroBlogKey) @Query("DELETE FROM DbMessageRoomReference WHERE roomKey = :roomKey") - suspend fun deleteRoomReference(roomKey: MicroBlogKey) + public suspend fun deleteRoomReference(roomKey: MicroBlogKey) @Query("DELETE FROM DbMessageItem WHERE roomKey = :roomKey") - suspend fun deleteRoomMessages(roomKey: MicroBlogKey) + public suspend fun deleteRoomMessages(roomKey: MicroBlogKey) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/PagingTimelineDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/PagingTimelineDao.kt similarity index 81% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/PagingTimelineDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/PagingTimelineDao.kt index cc325b770c..68b52e9a22 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/PagingTimelineDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/PagingTimelineDao.kt @@ -20,7 +20,7 @@ import kotlinx.coroutines.flow.Flow @Dao @DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class) -internal interface PagingTimelineDao { +public interface PagingTimelineDao { @Transaction @Query( "SELECT DbStatus.* FROM DbStatus " + @@ -28,7 +28,7 @@ internal interface PagingTimelineDao { "WHERE DbPagingTimeline.pagingKey = :pagingKey AND DbStatus.accountType = :accountType " + "ORDER BY DbPagingTimeline.sortId", ) - fun getPagingSource( + public fun getPagingSource( pagingKey: String, accountType: DbAccountType, ): PagingSource @@ -40,7 +40,7 @@ internal interface PagingTimelineDao { "WHERE DbPagingTimeline.pagingKey = :pagingKey " + "ORDER BY DbPagingTimeline.sortId", ) - fun getPagingSource(pagingKey: String): PagingSource + public fun getPagingSource(pagingKey: String): PagingSource @Transaction @RewriteQueriesToDropUnusedColumns @@ -48,7 +48,7 @@ internal interface PagingTimelineDao { "SELECT * FROM DbStatus " + "WHERE DbStatus.text like :query", ) - fun searchHistoryPagingSource(query: String): PagingSource + public fun searchHistoryPagingSource(query: String): PagingSource @Transaction @Query( @@ -61,7 +61,7 @@ internal interface PagingTimelineDao { ") " + "LIMIT 1", ) - fun get( + public fun get( pagingKey: String, accountType: DbAccountType, ): Flow @@ -73,16 +73,16 @@ internal interface PagingTimelineDao { "WHERE DbPagingTimeline.pagingKey = :pagingKey " + "ORDER BY DbPagingTimeline.sortId DESC", ) - fun getStatusHistoryPagingSource(pagingKey: String): PagingSource + public fun getStatusHistoryPagingSource(pagingKey: String): PagingSource @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAll(timeline: List) + public suspend fun insertAll(timeline: List) @Query( "SELECT * FROM DbPagingTimeline " + "WHERE pagingKey = :pagingKey AND statusId IN (:statusIds)", ) - suspend fun getByPagingKeyAndStatusIds( + public suspend fun getByPagingKeyAndStatusIds( pagingKey: String, statusIds: List, ): List @@ -91,10 +91,10 @@ internal interface PagingTimelineDao { "SELECT * FROM DbPagingTimeline " + "WHERE pagingKey = :pagingKey ORDER BY sortId", ) - suspend fun getByPagingKey(pagingKey: String): List + public suspend fun getByPagingKey(pagingKey: String): List @Delete - suspend fun delete(timeline: List) + public suspend fun delete(timeline: List) @Query( "DELETE FROM DbPagingTimeline WHERE pagingKey = :pagingKey " + @@ -104,12 +104,12 @@ internal interface PagingTimelineDao { "AND DbStatus.accountType = :accountType" + ")", ) - suspend fun delete( + public suspend fun delete( pagingKey: String, accountType: DbAccountType, ) - suspend fun delete( + public suspend fun delete( pagingKey: String, accountKey: MicroBlogKey, ) { @@ -120,7 +120,7 @@ internal interface PagingTimelineDao { * Should be used to delete a specific paging timeline by its key. */ @Query("DELETE FROM DbPagingTimeline WHERE pagingKey = :pagingKey") - suspend fun delete(pagingKey: String) + public suspend fun delete(pagingKey: String) @Query( "DELETE FROM DbPagingTimeline " + @@ -130,7 +130,7 @@ internal interface PagingTimelineDao { "AND DbStatus.accountType = :accountType" + ")", ) - suspend fun deleteByAccountType(accountType: DbAccountType) + public suspend fun deleteByAccountType(accountType: DbAccountType) @Query( "DELETE FROM DbPagingTimeline " + @@ -141,7 +141,7 @@ internal interface PagingTimelineDao { "AND DbStatus.accountType = :accountType" + ")", ) - suspend fun deleteStatus( + public suspend fun deleteStatus( accountType: DbAccountType, statusId: String, ) @@ -157,12 +157,12 @@ internal interface PagingTimelineDao { ")" + ")", ) - suspend fun existsPaging( + public suspend fun existsPaging( accountType: DbAccountType, paging_key: String, ): Boolean - suspend fun existsPaging( + public suspend fun existsPaging( account_key: MicroBlogKey, paging_key: String, ): Boolean = @@ -172,28 +172,28 @@ internal interface PagingTimelineDao { ) @Query("DELETE FROM DbPagingTimeline") - suspend fun clear() + public suspend fun clear() @Query("SELECT EXISTS(SELECT 1 FROM DbPagingTimeline WHERE pagingKey = :pagingKey)") - suspend fun anyPaging(pagingKey: String): Boolean + public suspend fun anyPaging(pagingKey: String): Boolean @Query("SELECT * FROM DbPagingKey WHERE pagingKey = :pagingKey LIMIT 1") - suspend fun getPagingKey(pagingKey: String): DbPagingKey? + public suspend fun getPagingKey(pagingKey: String): DbPagingKey? @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertPagingKey(pagingKey: DbPagingKey) + public suspend fun insertPagingKey(pagingKey: DbPagingKey) @Query("DELETE FROM DbPagingKey WHERE pagingKey = :pagingKey") - suspend fun deletePagingKey(pagingKey: String) + public suspend fun deletePagingKey(pagingKey: String) @Query("UPDATE DbPagingKey SET nextKey = :nextKey WHERE pagingKey = :pagingKey") - suspend fun updatePagingKeyNextKey( + public suspend fun updatePagingKeyNextKey( pagingKey: String, nextKey: String, ) @Query("UPDATE DbPagingKey SET prevKey = :prevKey WHERE pagingKey = :pagingKey") - suspend fun updatePagingKeyPrevKey( + public suspend fun updatePagingKeyPrevKey( pagingKey: String, prevKey: String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusDao.kt similarity index 81% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusDao.kt index 8e4f61e7d4..bfd6150713 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusDao.kt @@ -13,58 +13,58 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import kotlinx.coroutines.flow.Flow @Dao -internal interface StatusDao { +public interface StatusDao { @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(status: DbStatus) + public suspend fun insert(status: DbStatus) @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAll(statuses: List) + public suspend fun insertAll(statuses: List) @Query("SELECT * FROM DbStatus WHERE statusKey = :statusKey AND accountType = :accountType") - fun get( + public fun get( statusKey: MicroBlogKey, accountType: DbAccountType, ): Flow @Transaction @Query("SELECT * FROM DbStatus WHERE statusKey = :statusKey AND accountType = :accountType") - fun getWithReferences( + public fun getWithReferences( statusKey: MicroBlogKey, accountType: DbAccountType, ): Flow @Transaction @Query("SELECT * FROM DbStatus WHERE statusKey = :statusKey AND accountType = :accountType") - suspend fun getWithReferencesSync( + public suspend fun getWithReferencesSync( statusKey: MicroBlogKey, accountType: DbAccountType, ): DbStatusWithReference? @Query("SELECT * FROM DbStatus WHERE accountType = :accountType AND statusKey IN (:statusKeys)") - suspend fun getByKeys( + public suspend fun getByKeys( statusKeys: List, accountType: DbAccountType, ): List @Query("UPDATE DbStatus SET content = :content WHERE statusKey = :statusKey AND accountType = :accountType") - suspend fun update( + public suspend fun update( statusKey: MicroBlogKey, accountType: DbAccountType, content: UiTimelineV2, ) @Query("DELETE FROM DbStatus WHERE statusKey = :statusKey AND accountType = :accountType") - suspend fun delete( + public suspend fun delete( statusKey: MicroBlogKey, accountType: DbAccountType, ) @Query("DELETE FROM DbStatus WHERE accountType = :accountType") - suspend fun deleteByAccountType(accountType: DbAccountType) + public suspend fun deleteByAccountType(accountType: DbAccountType) @Query("SELECT COUNT(*) FROM DbStatus") - fun count(): Flow + public fun count(): Flow @Query("DELETE FROM DbStatus") - suspend fun clear() + public suspend fun clear() } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusReferenceDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusReferenceDao.kt similarity index 70% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusReferenceDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusReferenceDao.kt index 07d3d04d1a..a80aa32dde 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusReferenceDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusReferenceDao.kt @@ -8,22 +8,22 @@ import dev.dimension.flare.data.database.cache.model.DbStatusReference import dev.dimension.flare.model.ReferenceType @Dao -internal interface StatusReferenceDao { +public interface StatusReferenceDao { @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAll(items: List) + public suspend fun insertAll(items: List) @Query("DELETE FROM status_reference WHERE statusId = :statusId") - suspend fun delete(statusId: String) + public suspend fun delete(statusId: String) @Query("DELETE FROM status_reference WHERE statusId in (:statusIds)") - suspend fun delete(statusIds: List) + public suspend fun delete(statusIds: List) @Query("DELETE FROM status_reference WHERE statusId in (:statusIds) AND referenceType in (:types)") - suspend fun delete( + public suspend fun delete( statusIds: List, types: List, ) @Query("SELECT * FROM status_reference WHERE statusId = :statusId") - suspend fun getByStatusId(statusId: String): List + public suspend fun getByStatusId(statusId: String): List } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDao.kt similarity index 83% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDao.kt index 0dd7f6af56..91552932fb 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDao.kt @@ -5,26 +5,26 @@ import androidx.room3.Insert import androidx.room3.OnConflictStrategy import androidx.room3.Query import dev.dimension.flare.data.database.cache.model.DbTranslation -import dev.dimension.flare.data.database.cache.model.TranslationDisplayMode -import dev.dimension.flare.data.database.cache.model.TranslationEntityType -import dev.dimension.flare.data.database.cache.model.TranslationPayload -import dev.dimension.flare.data.database.cache.model.TranslationStatus +import dev.dimension.flare.data.translation.TranslationDisplayMode +import dev.dimension.flare.data.translation.TranslationEntityType +import dev.dimension.flare.data.translation.TranslationPayload +import dev.dimension.flare.data.translation.TranslationStatus import kotlinx.coroutines.flow.Flow @Dao -internal interface TranslationDao { +public interface TranslationDao { @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(data: DbTranslation) + public suspend fun insert(data: DbTranslation) @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAll(data: List) + public suspend fun insertAll(data: List) @Query( "SELECT * FROM DbTranslation " + "WHERE entityType = :entityType AND entityKey = :entityKey AND targetLanguage = :targetLanguage " + "LIMIT 1", ) - fun find( + public fun find( entityType: TranslationEntityType, entityKey: String, targetLanguage: String, @@ -35,7 +35,7 @@ internal interface TranslationDao { "WHERE entityType = :entityType AND entityKey = :entityKey AND targetLanguage = :targetLanguage " + "LIMIT 1", ) - suspend fun get( + public suspend fun get( entityType: TranslationEntityType, entityKey: String, targetLanguage: String, @@ -45,7 +45,7 @@ internal interface TranslationDao { "SELECT * FROM DbTranslation " + "WHERE entityType = :entityType AND entityKey IN (:entityKeys) AND targetLanguage = :targetLanguage", ) - suspend fun getByEntityKeys( + public suspend fun getByEntityKeys( entityType: TranslationEntityType, entityKeys: List, targetLanguage: String, @@ -62,7 +62,7 @@ internal interface TranslationDao { "updatedAt = :updatedAt " + "WHERE entityType = :entityType AND entityKey = :entityKey AND targetLanguage = :targetLanguage", ) - suspend fun update( + public suspend fun update( entityType: TranslationEntityType, entityKey: String, targetLanguage: String, @@ -81,7 +81,7 @@ internal interface TranslationDao { "updatedAt = :updatedAt " + "WHERE entityType = :entityType AND entityKey = :entityKey AND targetLanguage = :targetLanguage", ) - suspend fun updateDisplayMode( + public suspend fun updateDisplayMode( entityType: TranslationEntityType, entityKey: String, targetLanguage: String, @@ -97,7 +97,7 @@ internal interface TranslationDao { "updatedAt = :updatedAt " + "WHERE (status = :pendingStatus OR status = :translatingStatus) AND updatedAt < :staleBefore", ) - suspend fun markStaleInFlightAsFailed( + public suspend fun markStaleInFlightAsFailed( staleBefore: Long, statusReason: String, updatedAt: Long, @@ -110,7 +110,7 @@ internal interface TranslationDao { "DELETE FROM DbTranslation " + "WHERE status = :pendingStatus OR status = :translatingStatus", ) - suspend fun deleteInFlight( + public suspend fun deleteInFlight( pendingStatus: TranslationStatus = TranslationStatus.Pending, translatingStatus: TranslationStatus = TranslationStatus.Translating, ) @@ -119,15 +119,15 @@ internal interface TranslationDao { "DELETE FROM DbTranslation " + "WHERE entityType = :entityType AND entityKey = :entityKey AND targetLanguage = :targetLanguage", ) - suspend fun delete( + public suspend fun delete( entityType: TranslationEntityType, entityKey: String, targetLanguage: String, ) @Query("DELETE FROM DbTranslation WHERE targetLanguage = :targetLanguage") - suspend fun deleteByLanguage(targetLanguage: String) + public suspend fun deleteByLanguage(targetLanguage: String) @Query("DELETE FROM DbTranslation") - suspend fun clear() + public suspend fun clear() } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/UserDao.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/UserDao.kt similarity index 72% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/UserDao.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/UserDao.kt index abc48191ef..f1b762eb1a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/UserDao.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/UserDao.kt @@ -19,69 +19,69 @@ import kotlinx.coroutines.flow.Flow @Dao @DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class) -internal interface UserDao { +public interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(user: DbUser) + public suspend fun insert(user: DbUser) @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAll(users: List) + public suspend fun insertAll(users: List) @Query("UPDATE DbUser SET content = :content WHERE userKey = :userKey") - suspend fun update( + public suspend fun update( userKey: MicroBlogKey, content: UiProfile, ) @Query("SELECT * FROM DbUser WHERE userKey IN (:userKeys)") - fun findByKeys(userKeys: List): Flow> + public fun findByKeys(userKeys: List): Flow> @Query("SELECT * FROM DbUser WHERE userKey = :userKey") - fun findByKey(userKey: MicroBlogKey): Flow + public fun findByKey(userKey: MicroBlogKey): Flow @Query("SELECT * FROM DbUser WHERE canonicalHandle = :canonicalHandle AND host = :host") - fun findByCanonicalHandleAndHost( + public fun findByCanonicalHandleAndHost( canonicalHandle: String, host: String, ): Flow @Query("SELECT COUNT(*) FROM DbUser") - fun count(): Flow + public fun count(): Flow @Query("DELETE FROM DbUser") - suspend fun clear() + public suspend fun clear() @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertHistory(data: DbUserHistory) + public suspend fun insertHistory(data: DbUserHistory) @Transaction @Query("SELECT * FROM DbUserHistory ORDER BY lastVisit DESC") - fun getUserHistory(): PagingSource + public fun getUserHistory(): PagingSource @Transaction @Query( "SELECT * FROM DbUser " + "WHERE DbUser.name like :query OR DbUser.canonicalHandle like :query", ) - fun searchUser(query: String): PagingSource + public fun searchUser(query: String): PagingSource @Query("DELETE FROM DbUserHistory WHERE accountType = :accountType") - suspend fun deleteHistoryByAccountType(accountType: DbAccountType) + public suspend fun deleteHistoryByAccountType(accountType: DbAccountType) @Query("SELECT * FROM DbUserRelation WHERE accountType = :accountType AND userKey = :userKey") - fun getUserRelation( + public fun getUserRelation( accountType: DbAccountType, userKey: MicroBlogKey, ): Flow @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertUserRelation(relation: DbUserRelation) + public suspend fun insertUserRelation(relation: DbUserRelation) @Query("DELETE FROM DbUserRelation WHERE accountType = :accountType AND userKey = :userKey") - suspend fun deleteUserRelation( + public suspend fun deleteUserRelation( accountType: DbAccountType, userKey: MicroBlogKey, ) @Query("DELETE FROM DbUserRelation") - suspend fun clearUserRelations() + public suspend fun clearUserRelations() } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Microblog.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Microblog.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Microblog.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Microblog.kt index 1fbacc8f04..05d1ef2824 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Microblog.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Microblog.kt @@ -12,7 +12,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -internal suspend fun saveToDatabase( +public suspend fun saveToDatabase( database: CacheDatabase, items: List, ) { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/TimelinePagingMapper.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/TimelinePagingMapper.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/TimelinePagingMapper.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/TimelinePagingMapper.kt index c3687eb7d8..c2fa332156 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/TimelinePagingMapper.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/TimelinePagingMapper.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare.data.datasource.microblog.paging +package dev.dimension.flare.data.database.cache.mapper import dev.dimension.flare.common.SerializableImmutableList import dev.dimension.flare.common.SnowflakeIdGenerator @@ -9,8 +9,7 @@ import dev.dimension.flare.data.database.cache.model.DbStatusReference import dev.dimension.flare.data.database.cache.model.DbStatusReferenceWithStatus import dev.dimension.flare.data.database.cache.model.DbStatusWithReference import dev.dimension.flare.data.database.cache.model.DbStatusWithUser -import dev.dimension.flare.data.database.cache.model.TranslationDisplayOptions -import dev.dimension.flare.data.database.cache.model.applyTranslation +import dev.dimension.flare.data.translation.TranslationDisplayOptions import dev.dimension.flare.model.DbAccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.ReferenceType @@ -20,8 +19,8 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlin.uuid.Uuid -internal object TimelinePagingMapper { - suspend fun toDb( +public object TimelinePagingMapper { + public suspend fun toDb( data: UiTimelineV2, pagingKey: String, sortId: Long? = null, @@ -72,7 +71,7 @@ internal object TimelinePagingMapper { ) } - fun toUi( + public fun toUi( item: DbPagingTimelineWithStatus, pagingKey: String, translationDisplayOptions: TranslationDisplayOptions, @@ -83,7 +82,7 @@ internal object TimelinePagingMapper { translationDisplayOptions = translationDisplayOptions, ) - fun toUi( + public fun toUi( item: DbStatusWithReference, pagingKey: String, translationDisplayOptions: TranslationDisplayOptions, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/TranslationDisplay.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/TranslationDisplay.kt similarity index 88% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/TranslationDisplay.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/TranslationDisplay.kt index e3a1029797..f80f145244 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/TranslationDisplay.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/TranslationDisplay.kt @@ -1,9 +1,14 @@ -package dev.dimension.flare.data.database.cache.model +package dev.dimension.flare.data.database.cache.mapper import dev.dimension.flare.common.Locale -import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.data.database.cache.model.DbTranslation +import dev.dimension.flare.data.database.cache.model.canRetrySkippedManually import dev.dimension.flare.data.datasource.microblog.ActionMenu -import dev.dimension.flare.data.translation.PreTranslationStoreSupport +import dev.dimension.flare.data.translation.TranslationDisplayMode +import dev.dimension.flare.data.translation.TranslationDisplayOptions +import dev.dimension.flare.data.translation.TranslationPayload +import dev.dimension.flare.data.translation.TranslationStatus +import dev.dimension.flare.data.translation.sourceHash import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.ClickEvent import dev.dimension.flare.ui.model.DeeplinkEvent @@ -14,13 +19,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.render.toUiPlainText import kotlinx.collections.immutable.toPersistentList -internal data class TranslationDisplayOptions( - val translationEnabled: Boolean, - val autoDisplayEnabled: Boolean, - val providerCacheKey: String, -) - -internal fun UiTimelineV2.applyTranslation( +public fun UiTimelineV2.applyTranslation( options: TranslationDisplayOptions, translations: List, ): UiTimelineV2 { @@ -75,7 +74,7 @@ internal fun UiTimelineV2.applyTranslation( translation?.status == TranslationStatus.Failed -> TranslationMenuAction.Retry shouldShowTranslated -> TranslationMenuAction.ShowOriginal translation?.status == TranslationStatus.Pending || translation?.status == TranslationStatus.Translating -> null - PreTranslationStoreSupport.canRetrySkippedManually(translation) -> TranslationMenuAction.Translate + translation.canRetrySkippedManually() -> TranslationMenuAction.Translate translation?.status == TranslationStatus.Skipped -> null else -> TranslationMenuAction.Translate } @@ -102,7 +101,7 @@ internal fun UiTimelineV2.applyTranslation( } } -internal fun UiProfile.applyTranslation( +public fun UiProfile.applyTranslation( options: TranslationDisplayOptions, translation: DbTranslation?, ): UiProfile { @@ -122,7 +121,7 @@ internal fun UiProfile.applyTranslation( ) } -internal fun UiTimelineV2.translationPayload(): TranslationPayload? = +public fun UiTimelineV2.translationPayload(): TranslationPayload? = when (this) { is UiTimelineV2.Feed -> { TranslationPayload( @@ -151,18 +150,11 @@ internal fun UiTimelineV2.translationPayload(): TranslationPayload? = } } -internal fun UiProfile.translationPayload(): TranslationPayload = +public fun UiProfile.translationPayload(): TranslationPayload = TranslationPayload( description = description, ) -internal fun TranslationPayload.sourceHash(providerCacheKey: String): String = - buildString { - append(providerCacheKey) - append('\u0000') - append(encodeJson(TranslationPayload.serializer())) - }.stableTranslationHash() - private fun DbTranslation?.toDisplayState(): TranslationDisplayState = when (this?.status) { TranslationStatus.Pending, @@ -262,12 +254,3 @@ private enum class TranslationMenuAction { Translate, ShowOriginal, } - -private fun String.stableTranslationHash(): String { - var hash = -0x340d631b8c4674c3L - encodeToByteArray().forEach { byte -> - hash = hash xor (byte.toLong() and 0xffL) - hash *= 0x100000001b3L - } - return hash.toULong().toString(16) -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/User.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/User.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/User.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/User.kt index 832266126e..cd151b0a91 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/User.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/User.kt @@ -5,7 +5,7 @@ import dev.dimension.flare.data.database.cache.model.DbUser import dev.dimension.flare.ui.model.UiProfile import kotlinx.coroutines.flow.firstOrNull -internal fun UiProfile.toDbUser(host: String = this.host ?: key.host) = +public fun UiProfile.toDbUser(host: String = this.host ?: key.host): DbUser = DbUser( userKey = key, name = name.raw, @@ -14,11 +14,11 @@ internal fun UiProfile.toDbUser(host: String = this.host ?: key.host) = content = this, ) -internal suspend fun CacheDatabase.upsertUser(user: DbUser) { +public suspend fun CacheDatabase.upsertUser(user: DbUser) { upsertUsers(listOf(user)) } -internal suspend fun CacheDatabase.upsertUsers(users: List) { +public suspend fun CacheDatabase.upsertUsers(users: List) { if (users.isEmpty()) { return } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbDirectMessage.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbDirectMessage.kt similarity index 51% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbDirectMessage.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbDirectMessage.kt index f1dc7a282e..9b9ac444cf 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbDirectMessage.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbDirectMessage.kt @@ -21,23 +21,23 @@ import dev.dimension.flare.ui.model.UiDMRoom ), ], ) -internal data class DbDirectMessageTimeline( - val accountType: DbAccountType, - val roomKey: MicroBlogKey, - val sortId: Long, - val unreadCount: Long, +public data class DbDirectMessageTimeline( + public val accountType: DbAccountType, + public val roomKey: MicroBlogKey, + public val sortId: Long, + public val unreadCount: Long, @ColumnInfo(typeAffinity = ColumnInfo.BLOB) - val content: UiDMRoom, + public val content: UiDMRoom, @PrimaryKey - val _id: String = "$accountType-$roomKey", + public val _id: String = "$accountType-$roomKey", ) @Entity -internal data class DbMessageRoom( +public data class DbMessageRoom( @PrimaryKey - val roomKey: MicroBlogKey, - val platformType: PlatformType, - val messageKey: MicroBlogKey?, + public val roomKey: MicroBlogKey, + public val platformType: PlatformType, + public val messageKey: MicroBlogKey?, ) @Entity( @@ -47,11 +47,11 @@ internal data class DbMessageRoom( ), ], ) -internal data class DbMessageRoomReference( - val roomKey: MicroBlogKey, - val userKey: MicroBlogKey, +public data class DbMessageRoomReference( + public val roomKey: MicroBlogKey, + public val userKey: MicroBlogKey, @PrimaryKey - val _id: String = "$roomKey-$userKey", + public val _id: String = "$roomKey-$userKey", ) @Entity( @@ -61,15 +61,15 @@ internal data class DbMessageRoomReference( ), ], ) -internal data class DbMessageItem( +public data class DbMessageItem( @PrimaryKey - val messageKey: MicroBlogKey, - val roomKey: MicroBlogKey, - val userKey: MicroBlogKey, - val timestamp: Long, + public val messageKey: MicroBlogKey, + public val roomKey: MicroBlogKey, + public val userKey: MicroBlogKey, + public val timestamp: Long, @ColumnInfo(typeAffinity = ColumnInfo.BLOB) - val content: UiDMItem, - val showSender: Boolean, - val isLocal: Boolean = false, - val remoteCursor: String? = null, + public val content: UiDMItem, + public val showSender: Boolean, + public val isLocal: Boolean = false, + public val remoteCursor: String? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbEmoji.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbEmoji.kt similarity index 56% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbEmoji.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbEmoji.kt index 36e12084ed..eb00d9a1de 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbEmoji.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbEmoji.kt @@ -9,20 +9,20 @@ import dev.dimension.flare.common.encodeProtobuf import dev.dimension.flare.model.DbAccountType @Entity -internal data class DbEmoji( +public data class DbEmoji( @PrimaryKey @ColumnInfo(name = "host") - val host: String, + public val host: String, @ColumnInfo(name = "content", typeAffinity = ColumnInfo.BLOB) - val content: EmojiContent, + public val content: EmojiContent, ) -internal class EmojiContentConverter { +public class EmojiContentConverter { @TypeConverter - fun fromEmojiContent(emojiContent: EmojiContent): ByteArray = emojiContent.encodeProtobuf() + public fun fromEmojiContent(emojiContent: EmojiContent): ByteArray = emojiContent.encodeProtobuf() @TypeConverter - fun toEmojiContent(data: ByteArray): EmojiContent = + public fun toEmojiContent(data: ByteArray): EmojiContent = if (data.isEmpty()) { EmojiContent() } else { @@ -31,10 +31,10 @@ internal class EmojiContentConverter { } @Entity -internal data class DbEmojiHistory( - val accountType: DbAccountType, - val shortCode: String, - val lastUse: Long, +public data class DbEmojiHistory( + public val accountType: DbAccountType, + public val shortCode: String, + public val lastUse: Long, @PrimaryKey - val _id: String = "$accountType-$shortCode", + public val _id: String = "$accountType-$shortCode", ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbList.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbList.kt similarity index 58% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbList.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbList.kt index e5b298fa75..b2d0e65ba2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbList.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbList.kt @@ -19,26 +19,26 @@ import kotlin.uuid.Uuid @Entity( indices = [Index(value = ["listKey", "accountType"], unique = true)], ) -internal data class DbList( - val listKey: MicroBlogKey, - val accountType: DbAccountType, +public data class DbList( + public val listKey: MicroBlogKey, + public val accountType: DbAccountType, @ColumnInfo(typeAffinity = ColumnInfo.BLOB) - val content: ListContent, + public val content: ListContent, @PrimaryKey - val id: String = "${accountType}_$listKey", + public val id: String = "${accountType}_$listKey", ) { @Serializable - data class ListContent( - val data: UiList, + public data class ListContent( + public val data: UiList, ) } -internal class ListContentConverters { +public class ListContentConverters { @TypeConverter - fun fromMessageContent(content: ListContent): ByteArray = content.encodeProtobuf() + public fun fromMessageContent(content: ListContent): ByteArray = content.encodeProtobuf() @TypeConverter - fun toMessageContent(value: ByteArray): ListContent = value.decodeProtobuf() + public fun toMessageContent(value: ByteArray): ListContent = value.decodeProtobuf() } @Entity( @@ -52,23 +52,23 @@ internal class ListContentConverters { ), ], ) -internal data class DbListPaging( - val accountType: DbAccountType, - val pagingKey: String, - val listKey: MicroBlogKey, +public data class DbListPaging( + public val accountType: DbAccountType, + public val pagingKey: String, + public val listKey: MicroBlogKey, @PrimaryKey - val _id: String = Uuid.random().toString(), + public val _id: String = Uuid.random().toString(), ) -internal data class DbListWithContent( +public data class DbListWithContent( @Embedded - val paging: DbListPaging, + public val paging: DbListPaging, @Relation( parentColumn = "listKey", entityColumn = "listKey", entity = DbList::class, ) - val list: DbList, + public val list: DbList, ) @Entity( @@ -82,42 +82,42 @@ internal data class DbListWithContent( ), ], ) -internal data class DbListMember( - val listKey: MicroBlogKey, - val memberKey: MicroBlogKey, +public data class DbListMember( + public val listKey: MicroBlogKey, + public val memberKey: MicroBlogKey, @PrimaryKey - val id: String = "${listKey}_$memberKey", + public val id: String = "${listKey}_$memberKey", ) -internal data class DbListMemberWithContent( +public data class DbListMemberWithContent( @Embedded - val member: DbListMember, + public val member: DbListMember, @Relation( parentColumn = "memberKey", entityColumn = "userKey", entity = DbUser::class, ) - val user: DbUser, + public val user: DbUser, ) -internal data class DbListMemberWithList( +public data class DbListMemberWithList( @Embedded - val member: DbListMember, + public val member: DbListMember, @Relation( parentColumn = "listKey", entityColumn = "listKey", entity = DbList::class, ) - val list: DbList, + public val list: DbList, ) -internal data class DbUserWithListMembership( +public data class DbUserWithListMembership( @Embedded - val user: DbUser, + public val user: DbUser, @Relation( parentColumn = "userKey", entityColumn = "memberKey", entity = DbListMember::class, ) - val listMemberships: List, + public val listMemberships: List, ) diff --git a/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbPagingTimeline.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbPagingTimeline.kt new file mode 100644 index 0000000000..d52db11376 --- /dev/null +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbPagingTimeline.kt @@ -0,0 +1,96 @@ +package dev.dimension.flare.data.database.cache.model + +import androidx.room3.Embedded +import androidx.room3.Entity +import androidx.room3.Index +import androidx.room3.PrimaryKey +import androidx.room3.Relation +import androidx.room3.TypeConverter +import dev.dimension.flare.model.ReferenceType +import kotlin.time.Instant +import kotlin.uuid.Uuid + +@Entity( + indices = [ + Index( + value = ["statusId", "pagingKey"], + unique = true, + ), + Index( + value = ["pagingKey", "sortId"], + ), + ], +) +public data class DbPagingTimeline( + public val pagingKey: String, + public val statusId: String, + public val sortId: Long, + @PrimaryKey + public val _id: String = Uuid.random().toString(), +) + +@Entity +public data class DbPagingKey( + @PrimaryKey + public val pagingKey: String, + public val nextKey: String? = null, + public val prevKey: String? = null, +) + +public data class DbPagingTimelineWithStatus( + @Embedded + public val timeline: DbPagingTimeline, + @Relation( + parentColumn = "statusId", + entityColumn = "id", + entity = DbStatus::class, + ) + public val status: DbStatusWithReference, +) + +public data class DbStatusWithUser( + @Embedded + public val data: DbStatus, + @Relation( + parentColumn = "id", + entityColumn = "entityKey", + entity = DbTranslation::class, + ) + public val translations: List = emptyList(), +) + +public data class DbStatusReferenceWithStatus( + @Embedded + public val reference: DbStatusReference, + @Relation( + parentColumn = "referenceStatusId", + entityColumn = "id", + entity = DbStatus::class, + ) + public val status: DbStatusWithUser?, +) + +public data class DbStatusWithReference( + @Embedded + public val status: DbStatusWithUser, + @Relation( + parentColumn = "id", + entityColumn = "statusId", + entity = DbStatusReference::class, + ) + public val references: List, +) + +public class StatusConverter { + @TypeConverter + public fun fromReferenceType(value: ReferenceType): String = value.name + + @TypeConverter + public fun toReferenceType(value: String): ReferenceType = ReferenceType.valueOf(value) + + @TypeConverter + public fun fromTimestamp(value: Instant): Long = value.toEpochMilliseconds() + + @TypeConverter + public fun toTimestamp(value: Long): Instant = Instant.fromEpochMilliseconds(value) +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatus.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatus.kt similarity index 63% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatus.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatus.kt index ca23531c40..e7dd91cedf 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatus.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatus.kt @@ -11,17 +11,17 @@ import dev.dimension.flare.ui.model.UiTimelineV2 @Entity( indices = [Index(value = ["statusKey", "accountType"], unique = true)], ) -internal data class DbStatus( - val statusKey: MicroBlogKey, - val accountType: DbAccountType, +public data class DbStatus( + public val statusKey: MicroBlogKey, + public val accountType: DbAccountType, @ColumnInfo(typeAffinity = ColumnInfo.BLOB) - val content: UiTimelineV2, - val text: String?, // For Searching + public val content: UiTimelineV2, + public val text: String?, // For Searching @PrimaryKey - val id: String = createId(accountType = accountType, statusKey = statusKey), + public val id: String = createId(accountType = accountType, statusKey = statusKey), ) { - companion object { - fun createId( + public companion object { + public fun createId( accountType: DbAccountType, statusKey: MicroBlogKey, ): String = "${accountType}_$statusKey" diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatusReference.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatusReference.kt similarity index 76% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatusReference.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatusReference.kt index 5aa7be824f..454c0210f1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatusReference.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbStatusReference.kt @@ -23,13 +23,13 @@ import dev.dimension.flare.model.ReferenceType ), ], ) -internal data class DbStatusReference( +public data class DbStatusReference( /** * Id that being used in the database */ @PrimaryKey - val _id: String, - val referenceType: ReferenceType, - val statusId: String, - val referenceStatusId: String, + public val _id: String, + public val referenceType: ReferenceType, + public val statusId: String, + public val referenceStatusId: String, ) diff --git a/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbTranslation.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbTranslation.kt new file mode 100644 index 0000000000..a35ba5213d --- /dev/null +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbTranslation.kt @@ -0,0 +1,84 @@ +package dev.dimension.flare.data.database.cache.model + +import androidx.room3.ColumnInfo +import androidx.room3.Entity +import androidx.room3.Index +import androidx.room3.PrimaryKey +import androidx.room3.TypeConverter +import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.data.translation.TRANSLATION_SKIPPED_EXCLUDED_LANGUAGE_REASON +import dev.dimension.flare.data.translation.TranslationDisplayMode +import dev.dimension.flare.data.translation.TranslationEntityType +import dev.dimension.flare.data.translation.TranslationPayload +import dev.dimension.flare.data.translation.TranslationStatus +import dev.dimension.flare.model.AccountType +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiProfile + +@Entity( + indices = [ + Index(value = ["entityType", "entityKey", "targetLanguage"], unique = true), + Index(value = ["entityType", "entityKey"]), + Index(value = ["status"]), + Index(value = ["targetLanguage"]), + ], +) +public data class DbTranslation( + public val entityType: TranslationEntityType, + public val entityKey: String, + public val targetLanguage: String, + public val sourceHash: String, + public val status: TranslationStatus, + public val displayMode: TranslationDisplayMode = TranslationDisplayMode.Auto, + @ColumnInfo(typeAffinity = ColumnInfo.TEXT) + public val payload: TranslationPayload? = null, + public val statusReason: String? = null, + public val attemptCount: Int = 0, + public val updatedAt: Long, + @PrimaryKey + public val id: String = "${entityType.name}:$entityKey:$targetLanguage", +) + +public fun DbTranslation?.canRetrySkippedManually(): Boolean = + this?.status == TranslationStatus.Skipped && + statusReason == TRANSLATION_SKIPPED_EXCLUDED_LANGUAGE_REASON + +public class TranslationConverters { + @TypeConverter + public fun fromEntityType(value: TranslationEntityType): String = value.name + + @TypeConverter + public fun toEntityType(value: String): TranslationEntityType = TranslationEntityType.valueOf(value) + + @TypeConverter + public fun fromStatus(value: TranslationStatus): String = value.name + + @TypeConverter + public fun toStatus(value: String): TranslationStatus = TranslationStatus.valueOf(value) + + @TypeConverter + public fun fromDisplayMode(value: TranslationDisplayMode): String = value.name + + @TypeConverter + public fun toDisplayMode(value: String): TranslationDisplayMode = TranslationDisplayMode.valueOf(value) + + @TypeConverter + public fun fromPayload(value: TranslationPayload?): String? = value?.encodeJson(TranslationPayload.serializer()) + + @TypeConverter + public fun toPayload(value: String?): TranslationPayload? = value?.decodeJson(TranslationPayload.serializer()) +} + +public fun DbStatus.translationEntityKey(): String = id + +public fun statusTranslationEntityKey( + accountType: AccountType, + statusKey: MicroBlogKey, +): String = "${accountType}_$statusKey" + +public fun DbUser.translationEntityKey(): String = profileTranslationEntityKey(userKey) + +public fun UiProfile.translationEntityKey(): String = profileTranslationEntityKey(key) + +public fun profileTranslationEntityKey(userKey: MicroBlogKey): String = "profile:$userKey" diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUser.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUser.kt similarity index 67% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUser.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUser.kt index ccd81c8d9b..fc9cda5e4d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUser.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUser.kt @@ -14,14 +14,14 @@ import dev.dimension.flare.ui.model.UiRelation Index(value = ["canonicalHandle", "host"], unique = true), ], ) -internal data class DbUser( +public data class DbUser( @PrimaryKey - val userKey: MicroBlogKey, - val name: String, - val canonicalHandle: String, - val host: String, + public val userKey: MicroBlogKey, + public val name: String, + public val canonicalHandle: String, + public val host: String, @ColumnInfo(typeAffinity = ColumnInfo.BLOB) - val content: UiProfile, + public val content: UiProfile, ) @Entity( @@ -30,9 +30,9 @@ internal data class DbUser( Index(value = ["accountType", "userKey"], unique = true), ], ) -internal data class DbUserRelation( - val accountType: DbAccountType, - val userKey: MicroBlogKey, +public data class DbUserRelation( + public val accountType: DbAccountType, + public val userKey: MicroBlogKey, @ColumnInfo(typeAffinity = ColumnInfo.BLOB) - val relation: UiRelation, + public val relation: UiRelation, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUserHistory.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUserHistory.kt similarity index 63% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUserHistory.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUserHistory.kt index 89941f6805..37bb2c85a0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUserHistory.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbUserHistory.kt @@ -14,17 +14,17 @@ import dev.dimension.flare.model.MicroBlogKey Index(value = ["lastVisit"]), ], ) -internal data class DbUserHistory( - val userKey: MicroBlogKey, - val accountType: DbAccountType, - val lastVisit: Long, +public data class DbUserHistory( + public val userKey: MicroBlogKey, + public val accountType: DbAccountType, + public val lastVisit: Long, @PrimaryKey - val _id: String = "$accountType-$userKey", + public val _id: String = "$accountType-$userKey", ) -internal data class DbUserHistoryWithUser( +public data class DbUserHistoryWithUser( @Embedded - val data: DbUserHistory, + public val data: DbUserHistory, @Relation(parentColumn = "userKey", entityColumn = "userKey") - val user: DbUser, + public val user: DbUser, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/EmojiContent.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/EmojiContent.kt similarity index 69% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/EmojiContent.kt rename to foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/EmojiContent.kt index 2c73e9f3ea..df6d486b6e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/EmojiContent.kt +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/EmojiContent.kt @@ -7,6 +7,6 @@ import kotlinx.collections.immutable.persistentMapOf import kotlinx.serialization.Serializable @Serializable -internal data class EmojiContent( - val data: SerializableImmutableMap> = persistentMapOf(), +public data class EmojiContent( + public val data: SerializableImmutableMap> = persistentMapOf(), ) diff --git a/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationService.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationService.kt new file mode 100644 index 0000000000..6f51762678 --- /dev/null +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationService.kt @@ -0,0 +1,45 @@ +package dev.dimension.flare.data.translation + +import dev.dimension.flare.data.database.cache.model.DbStatus +import dev.dimension.flare.data.database.cache.model.DbUser +import dev.dimension.flare.data.translation.TranslationDisplayMode + +public interface PreTranslationService { + public fun enqueueStatuses( + statuses: List, + allowLongText: Boolean = false, + ) + + public fun enqueueProfile(user: DbUser) + + public fun retryStatus( + accountType: dev.dimension.flare.model.AccountType, + statusKey: dev.dimension.flare.model.MicroBlogKey, + ) + + public fun setStatusDisplayMode( + accountType: dev.dimension.flare.model.AccountType, + statusKey: dev.dimension.flare.model.MicroBlogKey, + mode: TranslationDisplayMode, + ) +} + +public data object NoopPreTranslationService : PreTranslationService { + public override fun enqueueStatuses( + statuses: List, + allowLongText: Boolean, + ): Unit = Unit + + public override fun enqueueProfile(user: DbUser): Unit = Unit + + public override fun retryStatus( + accountType: dev.dimension.flare.model.AccountType, + statusKey: dev.dimension.flare.model.MicroBlogKey, + ): Unit = Unit + + public override fun setStatusDisplayMode( + accountType: dev.dimension.flare.model.AccountType, + statusKey: dev.dimension.flare.model.MicroBlogKey, + mode: TranslationDisplayMode, + ): Unit = Unit +} diff --git a/foundation/database/src/commonMain/kotlin/dev/dimension/flare/di/FoundationDatabaseModule.kt b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/di/FoundationDatabaseModule.kt new file mode 100644 index 0000000000..657973119e --- /dev/null +++ b/foundation/database/src/commonMain/kotlin/dev/dimension/flare/di/FoundationDatabaseModule.kt @@ -0,0 +1,12 @@ +package dev.dimension.flare.di + +import dev.dimension.flare.data.database.provideAppDatabase +import dev.dimension.flare.data.database.provideCacheDatabase +import org.koin.core.module.Module +import org.koin.dsl.module + +public val foundationDatabaseModule: Module = + module { + single { provideAppDatabase(get()) } + single { provideCacheDatabase(get()) } + } diff --git a/shared/src/jvmMain/kotlin/dev/dimension/flare/data/database/DriverFactory.jvm.kt b/foundation/database/src/jvmMain/kotlin/dev/dimension/flare/data/database/DriverFactory.jvm.kt similarity index 82% rename from shared/src/jvmMain/kotlin/dev/dimension/flare/data/database/DriverFactory.jvm.kt rename to foundation/database/src/jvmMain/kotlin/dev/dimension/flare/data/database/DriverFactory.jvm.kt index 705196b1db..3f6180a395 100644 --- a/shared/src/jvmMain/kotlin/dev/dimension/flare/data/database/DriverFactory.jvm.kt +++ b/foundation/database/src/jvmMain/kotlin/dev/dimension/flare/data/database/DriverFactory.jvm.kt @@ -2,11 +2,11 @@ package dev.dimension.flare.data.database import androidx.room3.Room import androidx.room3.RoomDatabase -import dev.dimension.flare.common.FileSystemUtilsExt +import dev.dimension.flare.data.io.FileSystemUtilsExt import java.io.File -internal actual class DriverFactory { - actual inline fun createBuilder( +public actual class DriverFactory { + public actual inline fun createBuilder( name: String, isCache: Boolean, ): RoomDatabase.Builder { @@ -22,7 +22,7 @@ internal actual class DriverFactory { ) } - actual fun deleteDatabase( + public actual fun deleteDatabase( name: String, isCache: Boolean, ) { diff --git a/foundation/database/src/jvmMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.jvm.kt b/foundation/database/src/jvmMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.jvm.kt new file mode 100644 index 0000000000..e12a81bed9 --- /dev/null +++ b/foundation/database/src/jvmMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.jvm.kt @@ -0,0 +1,16 @@ +package dev.dimension.flare.data.database + +import androidx.room3.Room +import androidx.room3.RoomDatabase +import dev.dimension.flare.data.database.app.AppDatabase +import dev.dimension.flare.data.database.cache.CacheDatabase +import kotlin.reflect.KClass + +@PublishedApi +@Suppress("UNCHECKED_CAST") +internal actual fun Room.platformMemoryDatabaseBuilder(databaseClass: KClass): RoomDatabase.Builder = + when (databaseClass) { + AppDatabase::class -> Room.inMemoryDatabaseBuilder() + CacheDatabase::class -> Room.inMemoryDatabaseBuilder() + else -> error("Unsupported test database: ${databaseClass.simpleName}") + } as RoomDatabase.Builder diff --git a/foundation/database/src/nonWebMain/kotlin/dev/dimension/flare/data/database/DatabaseDriverProvider.nonWeb.kt b/foundation/database/src/nonWebMain/kotlin/dev/dimension/flare/data/database/DatabaseDriverProvider.nonWeb.kt new file mode 100644 index 0000000000..4db5486818 --- /dev/null +++ b/foundation/database/src/nonWebMain/kotlin/dev/dimension/flare/data/database/DatabaseDriverProvider.nonWeb.kt @@ -0,0 +1,6 @@ +package dev.dimension.flare.data.database + +import androidx.sqlite.SQLiteDriver +import androidx.sqlite.driver.bundled.BundledSQLiteDriver + +public actual fun createDatabaseDriver(): SQLiteDriver = BundledSQLiteDriver() diff --git a/foundation/database/src/wasmJsMain/kotlin/dev/dimension/flare/data/database/DatabaseDriverProvider.wasmJs.kt b/foundation/database/src/wasmJsMain/kotlin/dev/dimension/flare/data/database/DatabaseDriverProvider.wasmJs.kt new file mode 100644 index 0000000000..03feb37c01 --- /dev/null +++ b/foundation/database/src/wasmJsMain/kotlin/dev/dimension/flare/data/database/DatabaseDriverProvider.wasmJs.kt @@ -0,0 +1,13 @@ +package dev.dimension.flare.data.database + +import androidx.sqlite.SQLiteDriver +import androidx.sqlite.driver.web.WebWorkerSQLiteDriver +import org.w3c.dom.Worker +import kotlin.js.ExperimentalWasmJsInterop +import kotlin.js.js + +public actual fun createDatabaseDriver(): SQLiteDriver = WebWorkerSQLiteDriver(createSQLiteWorker()) + +@OptIn(ExperimentalWasmJsInterop::class) +private fun createSQLiteWorker(): Worker = + js("new Worker(new URL('@androidx/sqlite-web-worker/worker.js', import.meta.url), { type: 'module' })") diff --git a/foundation/database/src/wasmJsMain/kotlin/dev/dimension/flare/data/database/DriverFactory.wasmJs.kt b/foundation/database/src/wasmJsMain/kotlin/dev/dimension/flare/data/database/DriverFactory.wasmJs.kt new file mode 100644 index 0000000000..939f3e2e43 --- /dev/null +++ b/foundation/database/src/wasmJsMain/kotlin/dev/dimension/flare/data/database/DriverFactory.wasmJs.kt @@ -0,0 +1,20 @@ +package dev.dimension.flare.data.database + +import androidx.room3.Room +import androidx.room3.RoomDatabase + +public actual class DriverFactory { + public actual inline fun createBuilder( + name: String, + isCache: Boolean, + ): RoomDatabase.Builder = + Room.databaseBuilder( + name = name, + ) + + public actual fun deleteDatabase( + name: String, + isCache: Boolean, + ) { + } +} diff --git a/foundation/database/src/wasmJsMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.wasmJs.kt b/foundation/database/src/wasmJsMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.wasmJs.kt new file mode 100644 index 0000000000..e12a81bed9 --- /dev/null +++ b/foundation/database/src/wasmJsMain/kotlin/dev/dimension/flare/data/database/MemoryDatabaseBuilder.wasmJs.kt @@ -0,0 +1,16 @@ +package dev.dimension.flare.data.database + +import androidx.room3.Room +import androidx.room3.RoomDatabase +import dev.dimension.flare.data.database.app.AppDatabase +import dev.dimension.flare.data.database.cache.CacheDatabase +import kotlin.reflect.KClass + +@PublishedApi +@Suppress("UNCHECKED_CAST") +internal actual fun Room.platformMemoryDatabaseBuilder(databaseClass: KClass): RoomDatabase.Builder = + when (databaseClass) { + AppDatabase::class -> Room.inMemoryDatabaseBuilder() + CacheDatabase::class -> Room.inMemoryDatabaseBuilder() + else -> error("Unsupported test database: ${databaseClass.simpleName}") + } as RoomDatabase.Builder diff --git a/foundation/datastore/build.gradle.kts b/foundation/datastore/build.gradle.kts new file mode 100644 index 0000000000..12d87a8b54 --- /dev/null +++ b/foundation/datastore/build.gradle.kts @@ -0,0 +1,42 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.foundation.datastore" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + compilerOptions { + allWarningsAsErrors.set(false) + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.foundation.filesystem) + api(libs.datastore.core) + api(libs.datastore.core.okio) + api(libs.okio) + } + } + val androidMain by getting { + dependencies { + implementation(libs.datastore) + } + } + } +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/ProtobufSerializer.kt b/foundation/datastore/src/commonMain/kotlin/dev/dimension/flare/common/ProtobufSerializer.kt similarity index 67% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/ProtobufSerializer.kt rename to foundation/datastore/src/commonMain/kotlin/dev/dimension/flare/common/ProtobufSerializer.kt index 629717019a..d5ea4e11a6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/ProtobufSerializer.kt +++ b/foundation/datastore/src/commonMain/kotlin/dev/dimension/flare/common/ProtobufSerializer.kt @@ -1,22 +1,20 @@ package dev.dimension.flare.common import androidx.datastore.core.okio.OkioSerializer -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import kotlinx.coroutines.withContext import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.serializer -internal inline fun protobufSerializer(defaultValue: T): OkioSerializer = ProtobufSerializer(defaultValue, serializer()) +public inline fun protobufSerializer(defaultValue: T): OkioSerializer = ProtobufSerializer(defaultValue, serializer()) @OptIn(ExperimentalSerializationApi::class) -internal class ProtobufSerializer( +public class ProtobufSerializer( override val defaultValue: T, - val serializer: kotlinx.serialization.KSerializer, + public val serializer: kotlinx.serialization.KSerializer, ) : OkioSerializer { override suspend fun readFrom(source: okio.BufferedSource): T = - withContext(Dispatchers.IO) { + withContext(PlatformDispatchers.IO) { ProtoBuf.decodeFromByteArray(serializer, source.readByteArray()) } diff --git a/foundation/datastore/src/commonMain/kotlin/dev/dimension/flare/data/datastore/DataStoreStorage.kt b/foundation/datastore/src/commonMain/kotlin/dev/dimension/flare/data/datastore/DataStoreStorage.kt new file mode 100644 index 0000000000..66128ff874 --- /dev/null +++ b/foundation/datastore/src/commonMain/kotlin/dev/dimension/flare/data/datastore/DataStoreStorage.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.data.datastore + +import androidx.datastore.core.Storage +import androidx.datastore.core.okio.OkioSerializer +import dev.dimension.flare.data.io.FileStorage + +public expect fun createDataStoreStorage( + name: String, + serializer: OkioSerializer, + fileStorage: FileStorage, +): Storage diff --git a/foundation/datastore/src/nonWebMain/kotlin/dev/dimension/flare/data/datastore/DataStoreStorage.nonWeb.kt b/foundation/datastore/src/nonWebMain/kotlin/dev/dimension/flare/data/datastore/DataStoreStorage.nonWeb.kt new file mode 100644 index 0000000000..e39961fa8d --- /dev/null +++ b/foundation/datastore/src/nonWebMain/kotlin/dev/dimension/flare/data/datastore/DataStoreStorage.nonWeb.kt @@ -0,0 +1,25 @@ +package dev.dimension.flare.data.datastore + +import androidx.datastore.core.Storage +import androidx.datastore.core.okio.OkioSerializer +import androidx.datastore.core.okio.OkioStorage +import dev.dimension.flare.data.io.FileStorage +import dev.dimension.flare.data.io.OkioFileStorage + +public actual fun createDataStoreStorage( + name: String, + serializer: OkioSerializer, + fileStorage: FileStorage, +): Storage { + val okioFileStorage = + requireNotNull(fileStorage as? OkioFileStorage) { + "Non-web DataStore storage requires OkioFileStorage" + } + return OkioStorage( + fileSystem = okioFileStorage.fileSystem, + serializer = serializer, + producePath = { + fileStorage.dataStoreFile(name) + }, + ) +} diff --git a/foundation/datastore/src/wasmJsMain/kotlin/dev/dimension/flare/data/datastore/DataStoreStorage.wasmJs.kt b/foundation/datastore/src/wasmJsMain/kotlin/dev/dimension/flare/data/datastore/DataStoreStorage.wasmJs.kt new file mode 100644 index 0000000000..555f29853e --- /dev/null +++ b/foundation/datastore/src/wasmJsMain/kotlin/dev/dimension/flare/data/datastore/DataStoreStorage.wasmJs.kt @@ -0,0 +1,16 @@ +package dev.dimension.flare.data.datastore + +import androidx.datastore.core.Storage +import androidx.datastore.core.okio.OkioSerializer +import androidx.datastore.core.okio.WebOpfsStorage +import dev.dimension.flare.data.io.FileStorage + +public actual fun createDataStoreStorage( + name: String, + serializer: OkioSerializer, + @Suppress("UNUSED_PARAMETER") fileStorage: FileStorage, +): Storage = + WebOpfsStorage( + serializer = serializer, + name = name, + ) diff --git a/foundation/deeplink/build.gradle.kts b/foundation/deeplink/build.gradle.kts new file mode 100644 index 0000000000..e110ab2bc5 --- /dev/null +++ b/foundation/deeplink/build.gradle.kts @@ -0,0 +1,39 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.foundation.deeplink" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.model) + api(projects.modules.account.model) + api(libs.ktor.client.resources) + api(libs.kotlinx.immutable) + api(libs.kotlinx.serialization.json) + api(libs.kotlinx.serialization.protobuf) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + } +} diff --git a/foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMapping.kt b/foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMapping.kt new file mode 100644 index 0000000000..bafe02b41f --- /dev/null +++ b/foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMapping.kt @@ -0,0 +1,106 @@ +package dev.dimension.flare.common.deeplink + +import dev.dimension.flare.model.AccountType +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.route.DeeplinkRoute +import io.ktor.http.Url +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.persistentMapOf +import kotlinx.serialization.Serializable + +public object DeepLinkMapping { + public sealed interface Type { + @Serializable + public data class Profile( + val handle: String, + ) : Type + + @Serializable + public data class Post( + val handle: String? = null, + val id: String, + ) : Type + + @Serializable + public data class BlueskyPost( + val handle: String, + val id: String, + ) : Type + + @Serializable + public data class PostMedia( + val handle: String, + val id: String, + val index: Int, + ) : Type + } + + public fun matches( + url: String, + mapping: ImmutableMap>>, + ): ImmutableMap { + val request = DeepLinkRequest(Url(url)) + + val resultBuilder = persistentMapOf().builder() + + mapping.forEach { (accountKey, patterns) -> + val matchType = + patterns.firstNotNullOfOrNull { pattern -> + DeepLinkMatcher(request, pattern).match()?.let { match -> + KeyDecoder(match.args).decodeSerializableValue(match.serializer) + } + } + + if (matchType != null) { + resultBuilder[accountKey] = matchType.toDeeplinkRoute(accountKey) + } + } + + return resultBuilder.build() + } +} + +private fun DeepLinkMapping.Type.toDeeplinkRoute(accountKey: MicroBlogKey): DeeplinkRoute = + when (this) { + is DeepLinkMapping.Type.BlueskyPost -> { + DeeplinkRoute.Status.Detail( + accountType = AccountType.Specific(accountKey), + statusKey = + MicroBlogKey( + "at://$handle/app.bsky.feed.post/$id", + accountKey.host, + ), + ) + } + + is DeepLinkMapping.Type.Post -> { + DeeplinkRoute.Status.Detail( + accountType = AccountType.Specific(accountKey), + statusKey = MicroBlogKey(id, accountKey.host), + ) + } + + is DeepLinkMapping.Type.PostMedia -> { + DeeplinkRoute.Media.StatusMedia( + accountType = AccountType.Specific(accountKey), + statusKey = MicroBlogKey(id, accountKey.host), + index = index, + preview = null, + ) + } + + is DeepLinkMapping.Type.Profile -> { + val (userName, host) = + if (handle.contains('@')) { + MicroBlogKey.valueOf(handle) + } else { + MicroBlogKey(handle, accountKey.host) + } + DeeplinkRoute.Profile.UserNameWithHost( + accountType = AccountType.Specific(accountKey), + userName = userName, + host = host, + ) + } + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMatcher.kt b/foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMatcher.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMatcher.kt rename to foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMatcher.kt index bfec80985a..70082fa4fb 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMatcher.kt +++ b/foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMatcher.kt @@ -1,18 +1,18 @@ package dev.dimension.flare.common.deeplink -import dev.dimension.flare.data.repository.DebugRepository import kotlinx.serialization.KSerializer internal class DeepLinkMatcher( - val request: DeepLinkRequest, - val deepLinkPattern: DeepLinkPattern, + private val request: DeepLinkRequest, + private val deepLinkPattern: DeepLinkPattern, + private val errorLogger: (String) -> Unit = {}, ) { /** * Match a [DeepLinkRequest] to a [DeepLinkPattern]. * * Returns a [DeepLinkMatchResult] if this matches the pattern, returns null otherwise */ - fun match(): DeepLinkMatchResult? { + internal fun match(): DeepLinkMatchResult? { if (request.pathSegments.size != deepLinkPattern.pathSegments.size) return null // exact match (url does not contain any arguments) if (request.uri == deepLinkPattern.uriPattern) { @@ -47,7 +47,7 @@ internal class DeepLinkMatcher( try { candidateSegment.typeParser.invoke(valueToParse) } catch (e: IllegalArgumentException) { - DebugRepository.log( + errorLogger( "${TAG_LOG_ERROR}: Failed to parse path value:[$requestedSegment]" + ". ${e.stackTraceToString()}", ) @@ -68,7 +68,7 @@ internal class DeepLinkMatcher( try { queryStringParser.invoke(query.value.first()) } catch (e: IllegalArgumentException) { - DebugRepository.log( + errorLogger( "${TAG_LOG_ERROR}: Failed to parse query name:[$name] value:[${query.value}]." + " ${e.stackTraceToString()}", ) @@ -91,8 +91,8 @@ internal class DeepLinkMatcher( * Includes arguments for all parts of the uri - path, query, etc. * */ internal data class DeepLinkMatchResult( - val serializer: KSerializer, - val args: Map, + internal val serializer: KSerializer, + internal val args: Map, ) private const val TAG_LOG_ERROR = "Nav3RecipesDeepLink" diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkPattern.kt b/foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkPattern.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkPattern.kt rename to foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkPattern.kt index 5accd45936..944463820f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkPattern.kt +++ b/foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkPattern.kt @@ -34,9 +34,9 @@ import kotlinx.serialization.descriptors.SerialKind * @param serializer the serializer of [T] * @param uriPattern the supported deeplink's uri pattern, i.e. "abc.com/home/{pathArg}" */ -internal class DeepLinkPattern( - val serializer: KSerializer, - val uriPattern: Url, +public class DeepLinkPattern( + public val serializer: KSerializer, + public val uriPattern: Url, ) { /** * Help differentiate if a path segment is an argument or a static value @@ -49,7 +49,7 @@ internal class DeepLinkPattern( * order matters here - path segments need to match in value and order when matching * requested deeplink to supported deeplink */ - val pathSegments: List = + internal val pathSegments: List = buildList { uriPattern.rawSegments.forEach { segment -> // first, check if it is a path arg @@ -79,7 +79,7 @@ internal class DeepLinkPattern( * * This will be used later on to parse a provided query value into the correct KType */ - val queryValueParsers: Map = + internal val queryValueParsers: Map = buildMap { uriPattern.parameters.forEach { paramName, _ -> val elementIndex = serializer.descriptor.getElementIndex(paramName) @@ -91,19 +91,19 @@ internal class DeepLinkPattern( /** * Metadata about a supported path segment */ - class PathSegment( - val stringValue: String, - val isParamArg: Boolean, - val typeParser: TypeParser, - val prefix: String = "", - val suffix: String = "", + internal class PathSegment( + internal val stringValue: String, + internal val isParamArg: Boolean, + internal val typeParser: TypeParser, + internal val prefix: String = "", + internal val suffix: String = "", ) } /** * Parses a String into a Serializable Primitive */ -private typealias TypeParser = (String) -> Any +internal typealias TypeParser = (String) -> Any private fun getTypeParser(kind: SerialKind): TypeParser = when (kind) { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkRequest.kt b/foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkRequest.kt similarity index 73% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkRequest.kt rename to foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkRequest.kt index d507d7dc51..dc4569a787 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkRequest.kt +++ b/foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkRequest.kt @@ -9,17 +9,17 @@ import io.ktor.util.toMap * @param uri the target deeplink uri to link to */ internal class DeepLinkRequest( - val uri: Url, + internal val uri: Url, ) { /** * A list of path segments */ - val pathSegments: List = uri.rawSegments + internal val pathSegments: List = uri.rawSegments /** * A map of query name to query value */ - val queries = uri.parameters.toMap() + internal val queries: Map> = uri.parameters.toMap() // TODO add parsing for other Uri components, i.e. fragments, mimeType, action } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/KeyDecoder.kt b/foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/KeyDecoder.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/KeyDecoder.kt rename to foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/KeyDecoder.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/route/DeeplinkRoute.kt b/foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/ui/route/DeeplinkRoute.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/route/DeeplinkRoute.kt rename to foundation/deeplink/src/commonMain/kotlin/dev/dimension/flare/ui/route/DeeplinkRoute.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMatcherTest.kt b/foundation/deeplink/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMatcherTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMatcherTest.kt rename to foundation/deeplink/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMatcherTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkPatternTest.kt b/foundation/deeplink/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkPatternTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkPatternTest.kt rename to foundation/deeplink/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkPatternTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/KeyDecoderTest.kt b/foundation/deeplink/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/KeyDecoderTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/KeyDecoderTest.kt rename to foundation/deeplink/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/KeyDecoderTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/route/DeeplinkRouteTest.kt b/foundation/deeplink/src/commonTest/kotlin/dev/dimension/flare/ui/route/DeeplinkRouteTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/route/DeeplinkRouteTest.kt rename to foundation/deeplink/src/commonTest/kotlin/dev/dimension/flare/ui/route/DeeplinkRouteTest.kt diff --git a/foundation/filesystem/build.gradle.kts b/foundation/filesystem/build.gradle.kts new file mode 100644 index 0000000000..5c06be76c2 --- /dev/null +++ b/foundation/filesystem/build.gradle.kts @@ -0,0 +1,38 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.foundation.filesystem" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + sourceSets { + val commonMain by getting { + dependencies { + api(libs.okio) + } + } + val androidMain by getting { + dependencies { + implementation(libs.datastore) + } + } + val jvmMain by getting { + dependencies { + implementation(libs.commons.lang3) + } + } + } +} diff --git a/shared/src/androidMain/kotlin/dev/dimension/flare/data/io/AndroidPlatformPathProducer.kt b/foundation/filesystem/src/androidMain/kotlin/dev/dimension/flare/data/io/AndroidPlatformPathProducer.kt similarity index 92% rename from shared/src/androidMain/kotlin/dev/dimension/flare/data/io/AndroidPlatformPathProducer.kt rename to foundation/filesystem/src/androidMain/kotlin/dev/dimension/flare/data/io/AndroidPlatformPathProducer.kt index d5905cfd6e..6c8876fccd 100644 --- a/shared/src/androidMain/kotlin/dev/dimension/flare/data/io/AndroidPlatformPathProducer.kt +++ b/foundation/filesystem/src/androidMain/kotlin/dev/dimension/flare/data/io/AndroidPlatformPathProducer.kt @@ -5,7 +5,7 @@ import androidx.datastore.dataStoreFile import okio.Path import okio.Path.Companion.toOkioPath -internal class AndroidPlatformPathProducer( +public class AndroidPlatformPathProducer( private val context: Context, ) : PlatformPathProducer { override fun dataStoreFile(fileName: String): Path = context.dataStoreFile(fileName).toOkioPath() diff --git a/shared/src/androidMain/kotlin/dev/dimension/flare/common/FileItem.android.kt b/foundation/filesystem/src/androidMain/kotlin/dev/dimension/flare/data/io/FileItem.android.kt similarity index 68% rename from shared/src/androidMain/kotlin/dev/dimension/flare/common/FileItem.android.kt rename to foundation/filesystem/src/androidMain/kotlin/dev/dimension/flare/data/io/FileItem.android.kt index 81efa004da..348ea0e4d7 100644 --- a/shared/src/androidMain/kotlin/dev/dimension/flare/common/FileItem.android.kt +++ b/foundation/filesystem/src/androidMain/kotlin/dev/dimension/flare/data/io/FileItem.android.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare.common +package dev.dimension.flare.data.io import android.content.Context import android.net.Uri @@ -6,9 +6,9 @@ import java.io.File public actual class FileItem { private val source: Source - internal actual val name: String? - internal actual val type: FileType - internal actual val mimeType: String? + public actual val name: String? + public actual val type: FileType + public actual val mimeType: String? public constructor( context: Context, @@ -20,7 +20,31 @@ public actual class FileItem { this.source = Source.UriSource(context, uri) } - internal constructor( + public constructor( + name: String?, + type: FileType, + path: String, + mimeType: String? = null, + ) : this( + name = name, + type = type, + source = Source.PathSource(path), + mimeType = mimeType, + ) + + public actual constructor( + name: String?, + type: FileType, + loader: suspend () -> ByteArray, + mimeType: String?, + ) : this( + name = name, + type = type, + source = Source.LoaderSource(loader), + mimeType = mimeType, + ) + + private constructor( name: String?, type: FileType, source: Source, @@ -32,9 +56,9 @@ public actual class FileItem { this.mimeType = mimeType } - internal actual suspend fun readBytes(): ByteArray = source.readBytes() + public actual suspend fun readBytes(): ByteArray = source.readBytes() - internal sealed interface Source { + private sealed interface Source { suspend fun readBytes(): ByteArray data class UriSource( @@ -52,6 +76,12 @@ public actual class FileItem { ) : Source { override suspend fun readBytes(): ByteArray = File(path).readBytes() } + + data class LoaderSource( + private val loader: suspend () -> ByteArray, + ) : Source { + override suspend fun readBytes(): ByteArray = loader() + } } private companion object { diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/data/io/ApplePlatformPathProducer.kt b/foundation/filesystem/src/appleMain/kotlin/dev/dimension/flare/data/io/ApplePlatformPathProducer.kt similarity index 93% rename from shared/src/appleMain/kotlin/dev/dimension/flare/data/io/ApplePlatformPathProducer.kt rename to foundation/filesystem/src/appleMain/kotlin/dev/dimension/flare/data/io/ApplePlatformPathProducer.kt index 1dbb701250..a1e31e786f 100644 --- a/shared/src/appleMain/kotlin/dev/dimension/flare/data/io/ApplePlatformPathProducer.kt +++ b/foundation/filesystem/src/appleMain/kotlin/dev/dimension/flare/data/io/ApplePlatformPathProducer.kt @@ -8,7 +8,7 @@ import platform.Foundation.NSFileManager import platform.Foundation.NSURL import platform.Foundation.NSUserDomainMask -internal class ApplePlatformPathProducer : PlatformPathProducer { +public class ApplePlatformPathProducer : PlatformPathProducer { override fun dataStoreFile(fileName: String): Path = "${fileDirectory()}/$fileName".toPath() override fun draftMediaFile( diff --git a/foundation/filesystem/src/appleMain/kotlin/dev/dimension/flare/data/io/FileItem.apple.kt b/foundation/filesystem/src/appleMain/kotlin/dev/dimension/flare/data/io/FileItem.apple.kt new file mode 100644 index 0000000000..da91a0f355 --- /dev/null +++ b/foundation/filesystem/src/appleMain/kotlin/dev/dimension/flare/data/io/FileItem.apple.kt @@ -0,0 +1,34 @@ +package dev.dimension.flare.data.io + +import okio.FileSystem +import okio.Path.Companion.toPath + +public actual class FileItem internal constructor( + public actual val name: String?, + private val loader: suspend () -> ByteArray, + public actual val type: FileType, + public actual val mimeType: String? = null, +) { + public constructor( + name: String?, + data: ByteArray, + type: FileType, + mimeType: String? = null, + ) : this(name, { data }, type, mimeType) + + public actual constructor( + name: String?, + type: FileType, + loader: suspend () -> ByteArray, + mimeType: String?, + ) : this(name, loader, type, mimeType) + + public constructor( + name: String?, + path: String, + type: FileType, + mimeType: String? = null, + ) : this(name, { FileSystem.SYSTEM.read(path.toPath()) { readByteArray() } }, type, mimeType) + + public actual suspend fun readBytes(): ByteArray = loader() +} diff --git a/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/FileItem.kt b/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/FileItem.kt new file mode 100644 index 0000000000..f5f55c83ff --- /dev/null +++ b/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/FileItem.kt @@ -0,0 +1,16 @@ +package dev.dimension.flare.data.io + +public expect class FileItem { + public constructor( + name: String?, + type: FileType, + loader: suspend () -> ByteArray, + mimeType: String? = null, + ) + + public suspend fun readBytes(): ByteArray + + public val name: String? + public val type: FileType + public val mimeType: String? +} diff --git a/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/FileNameSanitizer.kt b/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/FileNameSanitizer.kt new file mode 100644 index 0000000000..b272b91cc6 --- /dev/null +++ b/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/FileNameSanitizer.kt @@ -0,0 +1,5 @@ +package dev.dimension.flare.data.io + +private val invalidFileNameCharsRegex = Regex("[^A-Za-z0-9._-]") + +public fun String.sanitizeFileName(): String = replace(invalidFileNameCharsRegex, "_") diff --git a/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/FileStorage.kt b/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/FileStorage.kt new file mode 100644 index 0000000000..10a054dcf8 --- /dev/null +++ b/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/FileStorage.kt @@ -0,0 +1,139 @@ +package dev.dimension.flare.data.io + +import okio.FileSystem +import okio.Path +import okio.Path.Companion.toPath + +public interface FileStorage { + public fun dataStoreFile(fileName: String): Path + + public fun draftMediaFile( + groupId: String, + fileName: String, + ): Path + + public fun createDirectories(path: Path) + + public fun write( + path: Path, + bytes: ByteArray, + ) + + public fun read(path: Path): ByteArray + + public fun exists(path: Path): Boolean + + public fun delete(path: Path) + + public fun list(path: Path): List +} + +public class OkioFileStorage( + public val fileSystem: FileSystem, + private val platformPathProducer: PlatformPathProducer, +) : FileStorage { + public constructor( + fileSystem: FileSystem, + root: Path, + ) : this( + fileSystem = fileSystem, + platformPathProducer = RootPlatformPathProducer(root), + ) + + override fun dataStoreFile(fileName: String): Path = platformPathProducer.dataStoreFile(fileName) + + override fun draftMediaFile( + groupId: String, + fileName: String, + ): Path = platformPathProducer.draftMediaFile(groupId, fileName) + + override fun createDirectories(path: Path) { + fileSystem.createDirectories(path) + } + + override fun write( + path: Path, + bytes: ByteArray, + ) { + fileSystem.write(path) { + write(bytes) + } + } + + override fun read(path: Path): ByteArray = + fileSystem.read(path) { + readByteArray() + } + + override fun exists(path: Path): Boolean = fileSystem.exists(path) + + override fun delete(path: Path) { + fileSystem.delete(path) + } + + override fun list(path: Path): List = fileSystem.list(path) +} + +public class InMemoryFileStorage( + private val root: Path = "/flare".toPath(), +) : FileStorage { + private val directories = mutableSetOf() + private val files = mutableMapOf() + + override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) + + override fun draftMediaFile( + groupId: String, + fileName: String, + ): Path = + root + .resolve("draft_media") + .resolve(groupId) + .resolve(fileName) + + override fun createDirectories(path: Path) { + generateSequence(path) { it.parent } + .forEach { directories += it } + } + + override fun write( + path: Path, + bytes: ByteArray, + ) { + path.parent?.let(::createDirectories) + files[path] = bytes.copyOf() + } + + override fun read(path: Path): ByteArray = + checkNotNull(files[path]) { "File not found: $path" } + .copyOf() + + override fun exists(path: Path): Boolean = path in files || path in directories + + override fun delete(path: Path) { + files -= path + directories -= path + } + + override fun list(path: Path): List = + (files.keys.asSequence() + directories.asSequence()) + .filter { it.parent == path } + .distinct() + .toList() + .sorted() +} + +private class RootPlatformPathProducer( + private val root: Path, +) : PlatformPathProducer { + override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) + + override fun draftMediaFile( + groupId: String, + fileName: String, + ): Path = + root + .resolve("draft_media") + .resolve(groupId) + .resolve(fileName) +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/FileType.kt b/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/FileType.kt similarity index 64% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/FileType.kt rename to foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/FileType.kt index a287f43aef..b81dc377e0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/FileType.kt +++ b/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/FileType.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare.common +package dev.dimension.flare.data.io public enum class FileType { Image, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/MimeTypes.kt b/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/MimeTypes.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/MimeTypes.kt rename to foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/MimeTypes.kt index 4a68bffc6a..799af794b0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/MimeTypes.kt +++ b/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/MimeTypes.kt @@ -1,7 +1,7 @@ -package dev.dimension.flare.common +package dev.dimension.flare.data.io -internal object MimeTypes { - fun detectFromBytes(bytes: ByteArray): String? { +public object MimeTypes { + public fun detectFromBytes(bytes: ByteArray): String? { if (bytes.size < 4) return null val b0 = bytes[0].toInt() and 0xFF val b1 = bytes[1].toInt() and 0xFF @@ -30,7 +30,7 @@ internal object MimeTypes { return null } - fun extensionFor(mimeType: String?): String? = + public fun extensionFor(mimeType: String?): String? = when (mimeType?.lowercase()?.substringBefore(';')?.trim()) { "image/jpeg", "image/jpg" -> "jpg" "image/png" -> "png" @@ -48,7 +48,7 @@ internal object MimeTypes { else -> null } - fun hasExtension(name: String): Boolean { + public fun hasExtension(name: String): Boolean { val dot = name.lastIndexOf('.') if (dot <= 0 || dot == name.lastIndex) return false val ext = name.substring(dot + 1) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/io/PlatformPathProducer.kt b/foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/PlatformPathProducer.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/io/PlatformPathProducer.kt rename to foundation/filesystem/src/commonMain/kotlin/dev/dimension/flare/data/io/PlatformPathProducer.kt diff --git a/shared/src/jvmMain/kotlin/dev/dimension/flare/common/FileItem.jvm.kt b/foundation/filesystem/src/jvmMain/kotlin/dev/dimension/flare/data/io/FileItem.jvm.kt similarity index 58% rename from shared/src/jvmMain/kotlin/dev/dimension/flare/common/FileItem.jvm.kt rename to foundation/filesystem/src/jvmMain/kotlin/dev/dimension/flare/data/io/FileItem.jvm.kt index c3bd361e80..90d4e5150d 100644 --- a/shared/src/jvmMain/kotlin/dev/dimension/flare/common/FileItem.jvm.kt +++ b/foundation/filesystem/src/jvmMain/kotlin/dev/dimension/flare/data/io/FileItem.jvm.kt @@ -1,16 +1,30 @@ -package dev.dimension.flare.common +package dev.dimension.flare.data.io import java.io.File import java.net.URLConnection import java.nio.file.Files -public actual class FileItem( - private val file: File, - internal actual val name: String? = file.name, - internal actual val type: FileType = resolveType(file.name), - internal actual val mimeType: String? = resolveMimeType(file), +public actual class FileItem private constructor( + private val loader: suspend () -> ByteArray, + public actual val name: String?, + public actual val type: FileType, + public actual val mimeType: String?, ) { - internal actual suspend fun readBytes(): ByteArray = file.readBytes() + public constructor( + file: File, + name: String? = file.name, + type: FileType = resolveType(file.name), + mimeType: String? = resolveMimeType(file), + ) : this({ file.readBytes() }, name, type, mimeType) + + public actual constructor( + name: String?, + type: FileType, + loader: suspend () -> ByteArray, + mimeType: String?, + ) : this(loader, name, type, mimeType) + + public actual suspend fun readBytes(): ByteArray = loader() private companion object { fun resolveType(fileName: String): FileType { diff --git a/shared/src/jvmMain/kotlin/dev/dimension/flare/common/FileSystemUtilsExt.kt b/foundation/filesystem/src/jvmMain/kotlin/dev/dimension/flare/data/io/FileSystemUtilsExt.kt similarity index 92% rename from shared/src/jvmMain/kotlin/dev/dimension/flare/common/FileSystemUtilsExt.kt rename to foundation/filesystem/src/jvmMain/kotlin/dev/dimension/flare/data/io/FileSystemUtilsExt.kt index 83de44f99b..1d1536ea88 100644 --- a/shared/src/jvmMain/kotlin/dev/dimension/flare/common/FileSystemUtilsExt.kt +++ b/foundation/filesystem/src/jvmMain/kotlin/dev/dimension/flare/data/io/FileSystemUtilsExt.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare.common +package dev.dimension.flare.data.io import org.apache.commons.lang3.SystemUtils import java.io.File diff --git a/shared/src/jvmMain/kotlin/dev/dimension/flare/data/io/JvmPlatformPathProducer.kt b/foundation/filesystem/src/jvmMain/kotlin/dev/dimension/flare/data/io/JvmPlatformPathProducer.kt similarity index 80% rename from shared/src/jvmMain/kotlin/dev/dimension/flare/data/io/JvmPlatformPathProducer.kt rename to foundation/filesystem/src/jvmMain/kotlin/dev/dimension/flare/data/io/JvmPlatformPathProducer.kt index a5d8663b1a..9e10891b38 100644 --- a/shared/src/jvmMain/kotlin/dev/dimension/flare/data/io/JvmPlatformPathProducer.kt +++ b/foundation/filesystem/src/jvmMain/kotlin/dev/dimension/flare/data/io/JvmPlatformPathProducer.kt @@ -1,10 +1,9 @@ package dev.dimension.flare.data.io -import dev.dimension.flare.common.FileSystemUtilsExt import okio.Path import okio.Path.Companion.toOkioPath -internal class JvmPlatformPathProducer : PlatformPathProducer { +public class JvmPlatformPathProducer : PlatformPathProducer { override fun dataStoreFile(fileName: String): Path = FileSystemUtilsExt.flareDirectory().toOkioPath().resolve(fileName) override fun draftMediaFile( diff --git a/foundation/filesystem/src/wasmJsMain/kotlin/dev/dimension/flare/data/io/FileItem.wasmJs.kt b/foundation/filesystem/src/wasmJsMain/kotlin/dev/dimension/flare/data/io/FileItem.wasmJs.kt new file mode 100644 index 0000000000..a3c1a64df0 --- /dev/null +++ b/foundation/filesystem/src/wasmJsMain/kotlin/dev/dimension/flare/data/io/FileItem.wasmJs.kt @@ -0,0 +1,24 @@ +package dev.dimension.flare.data.io + +public actual class FileItem( + private val loader: suspend () -> ByteArray, + public actual val name: String?, + public actual val type: FileType, + public actual val mimeType: String? = null, +) { + public constructor( + name: String?, + data: ByteArray, + type: FileType, + mimeType: String? = null, + ) : this({ data }, name, type, mimeType) + + public actual constructor( + name: String?, + type: FileType, + loader: suspend () -> ByteArray, + mimeType: String?, + ) : this(loader, name, type, mimeType) + + public actual suspend fun readBytes(): ByteArray = loader() +} diff --git a/foundation/network/build.gradle.kts b/foundation/network/build.gradle.kts new file mode 100644 index 0000000000..f8861de9be --- /dev/null +++ b/foundation/network/build.gradle.kts @@ -0,0 +1,67 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ktorfit) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.foundation.network" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.common) + api(libs.kotlinx.coroutines.core) + api(libs.ktor.client.core) + api(libs.ktorfit.lib) + implementation(libs.ktorfit.converters.response) + implementation(libs.ktorfit.converters.flow) + implementation(libs.ktorfit.converters.call) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.ktor.client.logging) + } + } + + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + + val androidJvmMain by getting { + dependencies { + implementation(libs.ktor.client.okhttp) + } + } + + val appleMain by getting { + dependencies { + implementation(libs.ktor.client.darwin) + } + } + + val wasmJsMain by getting { + dependencies { + implementation(libs.ktor.client.js) + } + } + } +} + +ktorfit { + compilerPluginVersion.set("2.3.3") +} diff --git a/shared/src/androidMain/kotlin/dev/dimension/flare/data/network/HttpClientEngine.android.kt b/foundation/network/src/androidJvmMain/kotlin/dev/dimension/flare/data/network/HttpClientEngineProvider.androidJvm.kt similarity index 62% rename from shared/src/androidMain/kotlin/dev/dimension/flare/data/network/HttpClientEngine.android.kt rename to foundation/network/src/androidJvmMain/kotlin/dev/dimension/flare/data/network/HttpClientEngineProvider.androidJvm.kt index bd471e466d..a49370b8c9 100644 --- a/shared/src/androidMain/kotlin/dev/dimension/flare/data/network/HttpClientEngine.android.kt +++ b/foundation/network/src/androidJvmMain/kotlin/dev/dimension/flare/data/network/HttpClientEngineProvider.androidJvm.kt @@ -3,4 +3,4 @@ package dev.dimension.flare.data.network import io.ktor.client.engine.HttpClientEngine import io.ktor.client.engine.okhttp.OkHttp -public actual val httpClientEngine: HttpClientEngine = OkHttp.create {} +public actual fun createHttpClientEngine(): HttpClientEngine = OkHttp.create {} diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/data/network/HttpClientEngine.apple.kt b/foundation/network/src/appleMain/kotlin/dev/dimension/flare/data/network/HttpClientEngineProvider.apple.kt similarity index 71% rename from shared/src/appleMain/kotlin/dev/dimension/flare/data/network/HttpClientEngine.apple.kt rename to foundation/network/src/appleMain/kotlin/dev/dimension/flare/data/network/HttpClientEngineProvider.apple.kt index d3ce43e830..c34b1e5baf 100644 --- a/shared/src/appleMain/kotlin/dev/dimension/flare/data/network/HttpClientEngine.apple.kt +++ b/foundation/network/src/appleMain/kotlin/dev/dimension/flare/data/network/HttpClientEngineProvider.apple.kt @@ -3,6 +3,6 @@ package dev.dimension.flare.data.network import io.ktor.client.engine.HttpClientEngine import io.ktor.client.engine.darwin.Darwin -internal actual val httpClientEngine: HttpClientEngine = +public actual fun createHttpClientEngine(): HttpClientEngine = Darwin.create { } diff --git a/foundation/network/src/commonMain/kotlin/dev/dimension/flare/data/network/HttpClientEngineProvider.kt b/foundation/network/src/commonMain/kotlin/dev/dimension/flare/data/network/HttpClientEngineProvider.kt new file mode 100644 index 0000000000..992a68bcd5 --- /dev/null +++ b/foundation/network/src/commonMain/kotlin/dev/dimension/flare/data/network/HttpClientEngineProvider.kt @@ -0,0 +1,5 @@ +package dev.dimension.flare.data.network + +import io.ktor.client.engine.HttpClientEngine + +public expect fun createHttpClientEngine(): HttpClientEngine diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/Ktorfit.kt b/foundation/network/src/commonMain/kotlin/dev/dimension/flare/data/network/Ktorfit.kt similarity index 63% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/Ktorfit.kt rename to foundation/network/src/commonMain/kotlin/dev/dimension/flare/data/network/Ktorfit.kt index fd7ee6e6c1..da9fdb5138 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/Ktorfit.kt +++ b/foundation/network/src/commonMain/kotlin/dev/dimension/flare/data/network/Ktorfit.kt @@ -1,44 +1,47 @@ package dev.dimension.flare.data.network +import de.jensklingenberg.ktorfit.Ktorfit import de.jensklingenberg.ktorfit.converter.CallConverterFactory +import de.jensklingenberg.ktorfit.converter.Converter import de.jensklingenberg.ktorfit.converter.FlowConverterFactory import de.jensklingenberg.ktorfit.converter.ResponseConverterFactory import dev.dimension.flare.common.BuildConfig +import dev.dimension.flare.common.DebugRepository import dev.dimension.flare.common.JSON -import dev.dimension.flare.data.network.mastodon.api.model.MastodonPagingConverterFactory -import dev.dimension.flare.data.repository.DebugRepository import io.ktor.client.HttpClient import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.HttpClientEngine import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logger import io.ktor.client.plugins.logging.Logging import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json import kotlin.experimental.ExperimentalObjCRefinement import kotlin.native.HiddenFromObjC -internal fun ktorfit( +public fun ktorfit( baseUrl: String, json: Json = JSON, + converterFactories: List = emptyList(), config: HttpClientConfig<*>.() -> Unit = {}, -) = de.jensklingenberg.ktorfit.ktorfit { - baseUrl(baseUrl) - httpClient( - ktorClient { - install(ContentNegotiation) { - json(json) - } - config.invoke(this) - }, - ) - converterFactories( - FlowConverterFactory(), - CallConverterFactory(), - ResponseConverterFactory(), - MastodonPagingConverterFactory(), - ) -} +): Ktorfit = + de.jensklingenberg.ktorfit.ktorfit { + baseUrl(baseUrl) + httpClient( + ktorClient { + install(ContentNegotiation) { + json(json) + } + config.invoke(this) + }, + ) + converterFactories( + FlowConverterFactory(), + CallConverterFactory(), + ResponseConverterFactory(), + *converterFactories.toTypedArray(), + ) + } @OptIn(ExperimentalObjCRefinement::class) @HiddenFromObjC @@ -49,7 +52,7 @@ public fun ktorClient( } }, ): HttpClient = - HttpClient(httpClientEngine) { + HttpClient(createHttpClientEngine()) { config.invoke(this) install(Logging) { logger = FlareLogger @@ -62,9 +65,7 @@ public fun ktorClient( } } -internal expect val httpClientEngine: HttpClientEngine - -internal data object FlareLogger : io.ktor.client.plugins.logging.Logger { +public data object FlareLogger : Logger { override fun log(message: String) { if (BuildConfig.debug) { println(message) diff --git a/foundation/network/src/wasmJsMain/kotlin/dev/dimension/flare/data/network/HttpClientEngineProvider.wasmJs.kt b/foundation/network/src/wasmJsMain/kotlin/dev/dimension/flare/data/network/HttpClientEngineProvider.wasmJs.kt new file mode 100644 index 0000000000..5bd4aff424 --- /dev/null +++ b/foundation/network/src/wasmJsMain/kotlin/dev/dimension/flare/data/network/HttpClientEngineProvider.wasmJs.kt @@ -0,0 +1,6 @@ +package dev.dimension.flare.data.network + +import io.ktor.client.engine.HttpClientEngine +import io.ktor.client.engine.js.Js + +public actual fun createHttpClientEngine(): HttpClientEngine = Js.create() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d9cb5aac18..85f7550342 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,7 @@ paging = "3.5.0" kotlinx-datetime = "0.8.0" kotlinx-collections-immutable = "0.4.0" kotlinx-serialization = "1.11.0" +kotlinx-browser = "0.5.0" coil3 = "3.4.0" ktorfit = "2.7.4-SNAPSHOT" ktor = "3.4.3" @@ -50,12 +51,13 @@ koin = "4.2.1" composeIcons = "1.3.0" media3 = "1.10.1" desugar_jdk_libs = "2.1.5" +datastore = "1.3.0-alpha09" firebase-bom = "34.13.0" google-services = "4.4.4" firebase-crashlytics = "3.0.7" materialKolor = "4.1.1" room = "3.0.0-alpha04" -sqlite = "2.6.2" +sqlite = "2.7.0-alpha04" compose-multiplatform = "1.11.0" navigation3 = "1.1.1" zoomable = "0.19.0" @@ -113,14 +115,19 @@ paging-common = { group = "androidx.paging", name = "paging-common", version.ref paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "paging" } paging-testing = { group = "androidx.paging", name = "paging-testing", version.ref = "paging" } composeIcons-fontAwesome = { module = "moe.tlaster.compose.icons:font-awesome", version.ref = "composeIcons" } -datastore = { group = "androidx.datastore", name = "datastore", version = "1.2.1" } +datastore = { group = "androidx.datastore", name = "datastore", version.ref = "datastore" } +datastore-core = { group = "androidx.datastore", name = "datastore-core", version.ref = "datastore" } +datastore-core-okio = { group = "androidx.datastore", name = "datastore-core-okio", version.ref = "datastore" } desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" } androidx-window = { group = "androidx.window", name = "window-core", version = "1.5.1" } androidx-splash = { group = "androidx.core", name = "core-splashscreen", version = "1.2.0" } room-runtime = { group = "androidx.room3", name = "room3-runtime", version.ref = "room" } room-paging = { group = "androidx.room3", name = "room3-paging", version.ref = "room" } room-compiler = { group = "androidx.room3", name = "room3-compiler", version.ref = "room" } +sqlite = { group = "androidx.sqlite", name = "sqlite", version.ref = "sqlite" } +sqlite-async = { group = "androidx.sqlite", name = "sqlite-async", version.ref = "sqlite" } sqlite-bundled = { group = "androidx.sqlite", name = "sqlite-bundled", version.ref = "sqlite" } +sqlite-web = { group = "androidx.sqlite", name = "sqlite-web", version.ref = "sqlite" } nestedScrollView = { group = "com.github.Tlaster", name = "NestedScrollView", version = "1.0.3" } webkit = { group = "androidx.webkit", name = "webkit", version = "1.16.0" } androidx-browser = { group = "androidx.browser", name = "browser", version = "1.10.0" } @@ -135,6 +142,7 @@ kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinx-collections-immutable" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization" } kotlinx-serialization-protobuf = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" } +kotlinx-browser = { group = "org.jetbrains.kotlinx", name = "kotlinx-browser", version.ref = "kotlinx-browser" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } @@ -164,10 +172,12 @@ ktorfit-converters-response = { group = "de.jensklingenberg.ktorfit", name = "kt ktorfit-converters-call = { group = "de.jensklingenberg.ktorfit", name = "ktorfit-converters-call", version.ref = "ktorfit" } ktorfit-converters-flow = { group = "de.jensklingenberg.ktorfit", name = "ktorfit-converters-flow", version.ref = "ktorfit" } +ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } ktor-serialization-kotlinx-xml = { group = "io.ktor", name = "ktor-serialization-kotlinx-xml", version.ref = "ktor" } ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" } +ktor-client-js = { group = "io.ktor", name = "ktor-client-js", version.ref = "ktor" } ktor-client-websockets = { group = "io.ktor", name = "ktor-client-websockets", version.ref = "ktor" } ktor-client-darwin = { group = "io.ktor", name = "ktor-client-darwin", version.ref = "ktor" } ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" } diff --git a/karma.config.d/cross-origin-isolation.js b/karma.config.d/cross-origin-isolation.js new file mode 100644 index 0000000000..2d2b1ceb6b --- /dev/null +++ b/karma.config.d/cross-origin-isolation.js @@ -0,0 +1,14 @@ +config.set({ + customHeaders: [ + { + match: '.*', + name: 'Cross-Origin-Opener-Policy', + value: 'same-origin', + }, + { + match: '.*', + name: 'Cross-Origin-Embedder-Policy', + value: 'require-corp', + }, + ], +}); diff --git a/modules/account/api/build.gradle.kts b/modules/account/api/build.gradle.kts new file mode 100644 index 0000000000..f533934999 --- /dev/null +++ b/modules/account/api/build.gradle.kts @@ -0,0 +1,66 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.modules.account.api" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_account_api_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_account_api", + ) + } + } + } + } + } + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.core.model) + api(projects.modules.account.model) + api(dependencies.platform(libs.compose.bom)) + api(libs.compose.runtime) + api(libs.kotlinx.coroutines.core) + api(libs.kotlinx.serialization.json) + api(libs.bluesky.oauth) + } + } + } +} diff --git a/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/data/account/AccountLookup.kt b/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/data/account/AccountLookup.kt new file mode 100644 index 0000000000..4f67e0f285 --- /dev/null +++ b/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/data/account/AccountLookup.kt @@ -0,0 +1,8 @@ +package dev.dimension.flare.data.account + +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiAccount + +public interface AccountLookup { + public suspend fun find(accountKey: MicroBlogKey): UiAccount? +} diff --git a/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/data/account/AccountProfileProvider.kt b/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/data/account/AccountProfileProvider.kt new file mode 100644 index 0000000000..7326b88a12 --- /dev/null +++ b/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/data/account/AccountProfileProvider.kt @@ -0,0 +1,13 @@ +package dev.dimension.flare.data.account + +import dev.dimension.flare.ui.model.UiAccount +import kotlinx.coroutines.flow.Flow + +public interface AccountProfileProvider { + public val accounts: Flow> + + public data class AccountProfile( + val account: UiAccount, + val avatar: String?, + ) +} diff --git a/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/data/account/CredentialProvider.kt b/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/data/account/CredentialProvider.kt new file mode 100644 index 0000000000..11cc480c8b --- /dev/null +++ b/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/data/account/CredentialProvider.kt @@ -0,0 +1,14 @@ +package dev.dimension.flare.data.account + +import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiAccount +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +public interface CredentialProvider { + public fun credentialJsonFlow(accountKey: MicroBlogKey): Flow +} + +public inline fun CredentialProvider.credentialFlow(accountKey: MicroBlogKey): Flow = + credentialJsonFlow(accountKey).map { it.decodeJson() } diff --git a/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/model/AccountExceptions.kt b/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/model/AccountExceptions.kt new file mode 100644 index 0000000000..beb002b42a --- /dev/null +++ b/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/model/AccountExceptions.kt @@ -0,0 +1,13 @@ +package dev.dimension.flare.model + +public data object NoActiveAccountException : Exception("No active account.") + +public data class LoginExpiredException( + val accountKey: MicroBlogKey, + val platformType: PlatformType, +) : Exception("Login expired.") + +public data class RequireReLoginException( + val accountKey: MicroBlogKey, + val platformType: PlatformType, +) : Exception("Login required.") diff --git a/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiAccount.kt b/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiAccount.kt new file mode 100644 index 0000000000..e24ea5c9d6 --- /dev/null +++ b/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiAccount.kt @@ -0,0 +1,227 @@ +package dev.dimension.flare.ui.model + +import androidx.compose.runtime.Immutable +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.PlatformType +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import sh.christian.ozone.oauth.OAuthToken + +@Immutable +@Serializable +public sealed interface NostrSignerCredential { + public val stableId: String + + @Immutable + @Serializable + @SerialName("NostrSignerLocalKey") + public data class LocalKey( + public val nsec: String, + ) : NostrSignerCredential { + public override val stableId: String + get() = "local:$nsec" + } + + @Immutable + @Serializable + @SerialName("NostrSignerBunker") + public data class Bunker( + public val uri: String, + public val userPubkeyHex: String? = null, + public val signerRelay: String? = null, + public val secret: String? = null, + ) : NostrSignerCredential { + public override val stableId: String + get() = "bunker:$uri" + } + + @Immutable + @Serializable + @SerialName("NostrSignerAmber") + public data class Amber( + public val userPubkeyHex: String, + public val packageName: String? = null, + public val approvedSignerPubkey: String? = null, + ) : NostrSignerCredential { + public override val stableId: String + get() = "amber:$userPubkeyHex:${packageName.orEmpty()}:${approvedSignerPubkey.orEmpty()}" + } +} + +@Immutable +public sealed class UiAccount { + public abstract val accountKey: MicroBlogKey + public abstract val platformType: PlatformType + + @Immutable + @Serializable + public sealed interface Credential + + @Immutable + public data class Nostr( + public override val accountKey: MicroBlogKey, + ) : UiAccount() { + public override val platformType: PlatformType + get() = PlatformType.Nostr + + @Immutable + @Serializable + @SerialName("NostrCredential") + public data class Credential( + public val pubkeyHex: String = "", + public val relays: List = emptyList(), + public val mediaServerUrl: String = "https://blossom.nostr.build/", + public val signer: NostrSignerCredential? = null, + @SerialName("nsec") + internal val legacyNsec: String? = null, + ) : UiAccount.Credential + } + + @Immutable + public data class Mastodon( + public override val accountKey: MicroBlogKey, + public val forkType: Credential.ForkType = Credential.ForkType.Mastodon, + public val instance: String, + public val nodeType: String? = null, + ) : UiAccount() { + public override val platformType: PlatformType + get() = PlatformType.Mastodon + + @Immutable + @Serializable + @SerialName("MastodonCredential") + public data class Credential( + public val instance: String, + public val accessToken: String, + public val forkType: ForkType = ForkType.Mastodon, + // to support more forks in the future + public val nodeType: String? = null, + ) : UiAccount.Credential { + public enum class ForkType { + Mastodon, + Pleroma, + } + } + } + + @Immutable + public data class Misskey( + public override val accountKey: MicroBlogKey, + public val host: String, + // to support more forks in the future + public val nodeType: String? = null, + ) : UiAccount() { + public override val platformType: PlatformType + get() = PlatformType.Misskey + + @Immutable + @Serializable + @SerialName("MisskeyCredential") + public data class Credential( + public val host: String, + public val accessToken: String, + public val nodeType: String? = null, + ) : UiAccount.Credential + } + + @Immutable + public data class Bluesky( + public override val accountKey: MicroBlogKey, + ) : UiAccount() { + public override val platformType: PlatformType + get() = PlatformType.Bluesky + + @Serializable + public sealed interface Credential : UiAccount.Credential { + public val baseUrl: String + public val accessToken: String + public val refreshToken: String + + @Immutable + @Serializable + @SerialName("BlueskyCredential") + public data class BlueskyCredential( + public override val baseUrl: String, + public override val accessToken: String, + public override val refreshToken: String, + ) : Bluesky.Credential + + @Immutable + @Serializable + @SerialName("BlueskyOAuthCredential") + public data class OAuthCredential( + public override val baseUrl: String, + public val oAuthToken: OAuthToken, + ) : Bluesky.Credential { + public override val accessToken: String + get() = oAuthToken.accessToken + + public override val refreshToken: String + get() = oAuthToken.refreshToken + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is OAuthCredential) return false + + if (baseUrl != other.baseUrl) return false + if (oAuthToken.accessToken != other.oAuthToken.accessToken) return false + if (oAuthToken.refreshToken != other.oAuthToken.refreshToken) return false + if (oAuthToken.nonce != other.oAuthToken.nonce) return false + if (oAuthToken.expiresIn != other.oAuthToken.expiresIn) return false + + return true + } + + override fun hashCode(): Int { + var result = baseUrl.hashCode() + result = 31 * result + oAuthToken.hashCode() + return result + } + } + } + } + + @Immutable + public data class XQT( + public override val accountKey: MicroBlogKey, + ) : UiAccount() { + public override val platformType: PlatformType + get() = PlatformType.xQt + + @Immutable + @Serializable + @SerialName("XQTCredential") + public data class Credential( + public val chocolate: String, + ) : UiAccount.Credential + } + + @Immutable + public data class VVo( + public override val accountKey: MicroBlogKey, + ) : UiAccount() { + public override val platformType: PlatformType + get() = PlatformType.VVo + + @Immutable + @Serializable + @SerialName("VVoCredential") + public data class Credential( + public val chocolate: String, + ) : UiAccount.Credential + } +} + +public val UiAccount.Nostr.Credential.effectiveSigner: NostrSignerCredential? + get() = signer ?: legacyNsec?.let(NostrSignerCredential::LocalKey) + +public fun UiAccount.Nostr.Credential.effectivePubkeyHex(accountKey: MicroBlogKey): String = pubkeyHex.ifBlank { accountKey.id } + +public fun UiAccount.Nostr.Credential.normalized(accountKey: MicroBlogKey): UiAccount.Nostr.Credential = + copy( + pubkeyHex = effectivePubkeyHex(accountKey), + signer = effectiveSigner, + ) + +public fun UiAccount.Nostr.Credential.signerStableId(accountKey: MicroBlogKey): String = + effectiveSigner?.stableId ?: "readonly:${effectivePubkeyHex(accountKey)}" diff --git a/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiApplication.kt b/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiApplication.kt new file mode 100644 index 0000000000..35c360a375 --- /dev/null +++ b/modules/account/api/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiApplication.kt @@ -0,0 +1,61 @@ +package dev.dimension.flare.ui.model + +import androidx.compose.runtime.Immutable +import dev.dimension.flare.model.vvoHost +import dev.dimension.flare.model.xqtHost +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Immutable +public sealed interface UiApplication { + public val host: String + + @Immutable + public data class Nostr( + override val host: String, + ) : UiApplication + + @Immutable + public data class Mastodon( + override val host: String, + val application: MastodonApplicationCredential, + ) : UiApplication + + @Immutable + public data class Misskey( + override val host: String, + val session: String, + ) : UiApplication + + @Immutable + public data class Bluesky( + override val host: String, + ) : UiApplication + + @Immutable + public data object XQT : UiApplication { + override val host: String = xqtHost + } + + @Immutable + public data object VVo : UiApplication { + override val host: String = vvoHost + val loginUrl: String = "https://$host/login?backURL=https://$host/" + } +} + +@Immutable +@Serializable +public data class MastodonApplicationCredential( + val id: String? = null, + val name: String? = null, + val website: String? = null, + @SerialName("redirect_uri") + val redirectURI: String, + @SerialName("client_id") + val clientID: String, + @SerialName("client_secret") + val clientSecret: String, + @SerialName("vapid_key") + val vapidKey: String? = null, +) diff --git a/modules/account/data/build.gradle.kts b/modules/account/data/build.gradle.kts new file mode 100644 index 0000000000..50d130cc8d --- /dev/null +++ b/modules/account/data/build.gradle.kts @@ -0,0 +1,74 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.modules.account.data" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_account_data_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_account_data", + ) + } + } + } + } + } + } + + compilerOptions { + allWarningsAsErrors.set(false) + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.core.model) + api(projects.foundation.database) + api(projects.modules.account.api) + api(projects.modules.account.model) + api(projects.modules.settings.data) + api(projects.social.api) + api(projects.ui.model) + api(libs.kotlinx.coroutines.core) + implementation(projects.social.microblog) + api(dependencies.platform(libs.koin.bom)) + api(libs.koin.core) + } + } + } +} diff --git a/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/data/account/AccountProfileProviderImpl.kt b/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/data/account/AccountProfileProviderImpl.kt new file mode 100644 index 0000000000..8aa896f563 --- /dev/null +++ b/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/data/account/AccountProfileProviderImpl.kt @@ -0,0 +1,50 @@ +package dev.dimension.flare.data.account + +import dev.dimension.flare.common.CacheState +import dev.dimension.flare.common.combineLatestFlowLists +import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource +import dev.dimension.flare.data.datasource.microblog.datasource.UserDataSource +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map + +public class AccountProfileProviderImpl( + private val accountRepository: AccountRepository, +) : AccountProfileProvider { + override val accounts: Flow> = + accountRepository + .allAccounts + .map { accounts -> + accounts.map { account -> + flow { + val service = accountRepository.getOrCreateDataSource(account) + if (service is UserDataSource && service is AuthenticatedMicroblogDataSource) { + emitAll( + service + .userHandler + .userById(account.accountKey.id) + .data + .map { profile -> + AccountProfileProvider.AccountProfile( + account = account, + avatar = + when (profile) { + is CacheState.Success -> profile.data.avatar + is CacheState.Empty -> null + }, + ) + }, + ) + } else { + emit( + AccountProfileProvider.AccountProfile( + account = account, + avatar = null, + ), + ) + } + } + } + }.combineLatestFlowLists() +} diff --git a/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/data/account/AccountRepository.kt b/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/data/account/AccountRepository.kt new file mode 100644 index 0000000000..f550c2de1e --- /dev/null +++ b/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/data/account/AccountRepository.kt @@ -0,0 +1,207 @@ +package dev.dimension.flare.data.account + +import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.data.database.app.AppDatabase +import dev.dimension.flare.data.database.app.model.DbAccount +import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.database.cache.connect +import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource +import dev.dimension.flare.data.datastore.AppDataStore +import dev.dimension.flare.model.AccountType +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.NoActiveAccountException +import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.model.SocialPlatformRegistry +import dev.dimension.flare.ui.model.UiAccount +import dev.dimension.flare.ui.model.UiState +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlin.time.Clock + +public class AccountRepository( + @PublishedApi internal val appDatabase: AppDatabase, + private val coroutineScope: CoroutineScope, + private val appDataStore: AppDataStore, + private val cacheDatabase: CacheDatabase, + private val platformRegistry: SocialPlatformRegistry, +) : CredentialProvider, + AccountLookup { + public val activeAccount: Flow> by lazy { + appDatabase + .accountDao() + .activeAccount() + .distinctUntilChangedBy { + it?.account_key + }.map { + it?.toUi() + }.map { + if (it == null) { + UiState.Error(NoActiveAccountException) + } else { + UiState.Success(it) + } + } + } + public val allAccounts: Flow> by lazy { + appDatabase.accountDao().sortedAccounts().map { + it.map { it.toUi() }.toImmutableList() + } + } + private val dataSourceCacheMutex = Mutex() + private val dataSourceCache by lazy { + mutableMapOf() + } + + private val addAccountFlow by lazy { + MutableStateFlow(null) + } + public val onAdded: Flow by lazy { + addAccountFlow + .mapNotNull { it } + .distinctUntilChangedBy { it.accountKey } + } + private val removeAccountFlow by lazy { + MutableStateFlow(null) + } + public val onRemoved: Flow by lazy { + removeAccountFlow + .mapNotNull { it } + .distinctUntilChangedBy { it } + } + + public fun addAccount( + account: UiAccount, + credential: UiAccount.Credential, + ): Job = + coroutineScope.launch { + val existingAccount = appDatabase.accountDao().getAccount(account.accountKey) + val dbAccount = + existingAccount?.copy( + credential_json = credential.encodeJson(), + last_active = Clock.System.now().toEpochMilliseconds(), + ) ?: DbAccount( + account_key = account.accountKey, + platform_type = account.platformType, + credential_json = credential.encodeJson(), + last_active = Clock.System.now().toEpochMilliseconds(), + sort_id = appDatabase.accountDao().getMaxSortId()?.plus(1) ?: 0L, + ) + appDatabase.accountDao().insert(dbAccount) + addAccountFlow.value = account + } + + public fun updateCredential( + accountKey: MicroBlogKey, + credential: UiAccount.Credential, + ): Job = + coroutineScope.launch { + appDatabase.accountDao().setCredential( + accountKey, + credential.encodeJson(), + ) + } + + public fun updateAccountOrder(accounts: List): Job = + coroutineScope.launch { + appDatabase.connect { + accounts.forEachIndexed { index, accountKey -> + appDatabase.accountDao().setSortId( + accountKey, + index.toLong(), + ) + } + } + } + + public fun setActiveAccount(accountKey: MicroBlogKey): Job = + coroutineScope.launch { + appDatabase.accountDao().setLastActive( + accountKey, + Clock.System.now().toEpochMilliseconds(), + ) + } + + public fun delete(accountKey: MicroBlogKey): Job = + coroutineScope.launch { + appDataStore.composeConfigData.updateData { + it.copy( + lastAccounts = it.lastAccounts.filterNot { key -> key == accountKey }, + ) + } + removeAccountFlow.value = accountKey + dataSourceCacheMutex.withLock { + val datasource = dataSourceCache.remove(accountKey) + if (datasource is AutoCloseable) { + datasource.close() + } + } + cacheDatabase.pagingTimelineDao().deleteByAccountType( + AccountType.Specific(accountKey), + ) + cacheDatabase.statusDao().deleteByAccountType( + AccountType.Specific(accountKey), + ) + cacheDatabase.userDao().deleteHistoryByAccountType( + AccountType.Specific(accountKey), + ) + cacheDatabase.emojiDao().clearHistoryByAccountType( + AccountType.Specific(accountKey), + ) + cacheDatabase.messageDao().clearMessageTimeline( + AccountType.Specific(accountKey), + ) + appDatabase.accountDao().delete(accountKey) + } + + public fun getFlow(accountKey: MicroBlogKey): Flow> = + appDatabase.accountDao().get(accountKey).map { + if (it == null) { + UiState.Error(NoActiveAccountException) + } else { + UiState.Success(it.toUi()) + } + } + + override suspend fun find(accountKey: MicroBlogKey): UiAccount? = + appDatabase + .accountDao() + .get(accountKey) + .firstOrNull() + ?.toUi() + + override fun credentialJsonFlow(accountKey: MicroBlogKey): Flow = + appDatabase + .accountDao() + .get(accountKey) + .mapNotNull { it?.credential_json } + + public suspend fun getOrCreateDataSource(account: UiAccount): MicroblogDataSource = + dataSourceCacheMutex.withLock { + dataSourceCache.getOrPut(account.accountKey) { + platformRegistry.createDataSource(account) + } + } + + public fun guestDataSource( + type: PlatformType, + host: String, + locale: String, + ): MicroblogDataSource = + platformRegistry.guestDataSource( + type = type, + host = host, + locale = locale, + ) +} diff --git a/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/data/account/ApplicationRepository.kt b/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/data/account/ApplicationRepository.kt new file mode 100644 index 0000000000..ebf5622972 --- /dev/null +++ b/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/data/account/ApplicationRepository.kt @@ -0,0 +1,90 @@ +package dev.dimension.flare.data.account + +import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.data.database.app.AppDatabase +import dev.dimension.flare.data.database.app.model.DbApplication +import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.ui.model.MastodonApplicationCredential +import dev.dimension.flare.ui.model.UiApplication +import kotlinx.coroutines.flow.firstOrNull + +public class ApplicationRepository( + private val database: AppDatabase, +) { + public suspend fun findByHost(host: String): UiApplication? = + database + .applicationDao() + .get(host) + .firstOrNull() + ?.toUi() + + public suspend fun addApplication( + host: String, + credentialJson: String, + platformType: PlatformType, + ) { + database.applicationDao().insert( + DbApplication( + host = host, + credential_json = credentialJson, + platform_type = platformType, + ), + ) + } + + public suspend fun setPendingOAuth( + host: String, + pendingOAuth: Boolean, + ) { + database.applicationDao().updatePending(host, if (pendingOAuth) 1L else 0L) + } + + public suspend fun getPendingOAuth(): UiApplication? = + database + .applicationDao() + .getPending() + .firstOrNull() + ?.firstOrNull() + ?.toUi() + + public suspend fun clearPendingOAuth() { + database.applicationDao().clearPending() + } + + private fun DbApplication.toUi(): UiApplication = + when (platform_type) { + PlatformType.Nostr -> { + UiApplication.Nostr( + host = host, + ) + } + + PlatformType.Mastodon -> { + UiApplication.Mastodon( + host = host, + application = credential_json.decodeJson(), + ) + } + + PlatformType.Misskey -> { + UiApplication.Misskey( + host = host, + session = credential_json, + ) + } + + PlatformType.Bluesky -> { + UiApplication.Bluesky( + host = host, + ) + } + + PlatformType.xQt -> { + UiApplication.XQT + } + + PlatformType.VVo -> { + UiApplication.VVo + } + } +} diff --git a/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/data/account/DbAccountMapper.kt b/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/data/account/DbAccountMapper.kt new file mode 100644 index 0000000000..d600ee3233 --- /dev/null +++ b/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/data/account/DbAccountMapper.kt @@ -0,0 +1,52 @@ +package dev.dimension.flare.data.account + +import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.data.database.app.model.DbAccount +import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.ui.model.UiAccount + +internal fun DbAccount.toUi(): UiAccount = + when (platform_type) { + PlatformType.Nostr -> { + UiAccount.Nostr( + accountKey = account_key, + ) + } + + PlatformType.Mastodon -> { + val credential = credential_json.decodeJson() + UiAccount.Mastodon( + accountKey = account_key, + forkType = credential.forkType, + instance = credential.instance, + nodeType = credential.nodeType, + ) + } + + PlatformType.Misskey -> { + val credential = credential_json.decodeJson() + UiAccount.Misskey( + accountKey = account_key, + host = credential.host, + nodeType = credential.nodeType, + ) + } + + PlatformType.Bluesky -> { + UiAccount.Bluesky( + accountKey = account_key, + ) + } + + PlatformType.xQt -> { + UiAccount.XQT( + accountKey = account_key, + ) + } + + PlatformType.VVo -> { + UiAccount.VVo( + accountKey = account_key, + ) + } + } diff --git a/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/di/AccountDataModule.kt b/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/di/AccountDataModule.kt new file mode 100644 index 0000000000..84f096d2a1 --- /dev/null +++ b/modules/account/data/src/commonMain/kotlin/dev/dimension/flare/di/AccountDataModule.kt @@ -0,0 +1,20 @@ +package dev.dimension.flare.di + +import dev.dimension.flare.data.account.AccountLookup +import dev.dimension.flare.data.account.AccountProfileProvider +import dev.dimension.flare.data.account.AccountProfileProviderImpl +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.account.ApplicationRepository +import dev.dimension.flare.data.account.CredentialProvider +import org.koin.core.module.Module +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +public val accountDataModule: Module = + module { + singleOf(::AccountRepository) + single { AccountProfileProviderImpl(get()) } + single { get() } + single { get() } + singleOf(::ApplicationRepository) + } diff --git a/modules/account/model/build.gradle.kts b/modules/account/model/build.gradle.kts new file mode 100644 index 0000000000..6fdb7ee6e8 --- /dev/null +++ b/modules/account/model/build.gradle.kts @@ -0,0 +1,62 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.modules.account.model" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_account_model_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_account_model", + ) + } + } + } + } + } + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.model) + api(dependencies.platform(libs.compose.bom)) + api(libs.compose.runtime) + api(libs.kotlinx.serialization.json) + } + } + } +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/model/AccountType.kt b/modules/account/model/src/commonMain/kotlin/dev/dimension/flare/model/AccountType.kt similarity index 85% rename from shared/src/commonMain/kotlin/dev/dimension/flare/model/AccountType.kt rename to modules/account/model/src/commonMain/kotlin/dev/dimension/flare/model/AccountType.kt index fcf0adb54f..d174a88ed1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/model/AccountType.kt +++ b/modules/account/model/src/commonMain/kotlin/dev/dimension/flare/model/AccountType.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable @Immutable @Serializable -internal sealed interface DbAccountType +public sealed interface DbAccountType @Immutable @Serializable @@ -35,14 +35,14 @@ public sealed class AccountType { } } -internal fun MicroBlogKey?.toAccountType(): AccountType = +public fun MicroBlogKey?.toAccountType(): AccountType = if (this == null) { AccountType.Guest } else { AccountType.Specific(this) } -internal fun MicroBlogKey?.toAccountType(guestHost: String): AccountType = +public fun MicroBlogKey?.toAccountType(guestHost: String): AccountType = if (this == null) { AccountType.GuestHost(guestHost) } else { diff --git a/modules/ai/data/build.gradle.kts b/modules/ai/data/build.gradle.kts new file mode 100644 index 0000000000..0db2f3c874 --- /dev/null +++ b/modules/ai/data/build.gradle.kts @@ -0,0 +1,93 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +val hasGoogleServices = rootProject.file("app/google-services.json").exists() + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.modules.ai.data" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_ai_data_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_ai_data", + ) + } + } + } + } + } + } + + sourceSets { + val commonMain by getting { + dependencies { + implementation(projects.core.common) + api(projects.modules.settings.data) + implementation(projects.foundation.network) + implementation(libs.ktor.client.logging) + implementation(libs.openai.client) + api(dependencies.platform(libs.koin.bom)) + api(libs.koin.core) + } + } + val androidMain by getting { + if (hasGoogleServices) { + kotlin.srcDir("src/play/kotlin") + } else { + kotlin.srcDir("src/foss/kotlin") + } + dependencies { + // START Non-FOSS component + if (hasGoogleServices) { + implementation(libs.kotlinx.coroutines.play.services) + implementation("com.google.mlkit:genai-prompt:1.0.0-beta2") + implementation("com.google.mlkit:genai-summarization:1.0.0-beta1") + } + // END Non-FOSS component + } + } + val jvmMain by getting { + dependencies { + implementation(libs.jna) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + } +} diff --git a/modules/ai/data/src/androidMain/kotlin/dev/dimension/flare/di/AiPlatformModule.android.kt b/modules/ai/data/src/androidMain/kotlin/dev/dimension/flare/di/AiPlatformModule.android.kt new file mode 100644 index 0000000000..121148737e --- /dev/null +++ b/modules/ai/data/src/androidMain/kotlin/dev/dimension/flare/di/AiPlatformModule.android.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.di + +import dev.dimension.flare.common.AndroidOnDeviceAI +import dev.dimension.flare.data.ai.OnDeviceAI +import org.koin.core.module.Module +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind + +internal actual fun Module.registerPlatformAi() { + singleOf(::AndroidOnDeviceAI) bind OnDeviceAI::class +} diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/common/AppleOnDeviceAI.kt b/modules/ai/data/src/appleMain/kotlin/dev/dimension/flare/common/AppleOnDeviceAI.kt similarity index 95% rename from shared/src/appleMain/kotlin/dev/dimension/flare/common/AppleOnDeviceAI.kt rename to modules/ai/data/src/appleMain/kotlin/dev/dimension/flare/common/AppleOnDeviceAI.kt index 07a50ccee7..30e53fc054 100644 --- a/shared/src/appleMain/kotlin/dev/dimension/flare/common/AppleOnDeviceAI.kt +++ b/modules/ai/data/src/appleMain/kotlin/dev/dimension/flare/common/AppleOnDeviceAI.kt @@ -1,5 +1,7 @@ package dev.dimension.flare.common +import dev.dimension.flare.data.ai.OnDeviceAI + internal class AppleOnDeviceAI( private val delegate: SwiftOnDeviceAI, ) : OnDeviceAI { diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/common/SwiftOnDeviceAI.kt b/modules/ai/data/src/appleMain/kotlin/dev/dimension/flare/common/SwiftOnDeviceAI.kt similarity index 100% rename from shared/src/appleMain/kotlin/dev/dimension/flare/common/SwiftOnDeviceAI.kt rename to modules/ai/data/src/appleMain/kotlin/dev/dimension/flare/common/SwiftOnDeviceAI.kt diff --git a/modules/ai/data/src/appleMain/kotlin/dev/dimension/flare/di/AiPlatformModule.apple.kt b/modules/ai/data/src/appleMain/kotlin/dev/dimension/flare/di/AiPlatformModule.apple.kt new file mode 100644 index 0000000000..6f97543eec --- /dev/null +++ b/modules/ai/data/src/appleMain/kotlin/dev/dimension/flare/di/AiPlatformModule.apple.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.di + +import dev.dimension.flare.common.AppleOnDeviceAI +import dev.dimension.flare.data.ai.OnDeviceAI +import org.koin.core.module.Module +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind + +internal actual fun Module.registerPlatformAi() { + singleOf(::AppleOnDeviceAI) bind OnDeviceAI::class +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/ai/AiCompletionService.kt b/modules/ai/data/src/commonMain/kotlin/dev/dimension/flare/data/ai/AiCompletionService.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/ai/AiCompletionService.kt rename to modules/ai/data/src/commonMain/kotlin/dev/dimension/flare/data/ai/AiCompletionService.kt index 519a755ad4..12c89a6533 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/ai/AiCompletionService.kt +++ b/modules/ai/data/src/commonMain/kotlin/dev/dimension/flare/data/ai/AiCompletionService.kt @@ -1,13 +1,12 @@ -package dev.dimension.flare.data.network.ai +package dev.dimension.flare.data.ai -import dev.dimension.flare.common.OnDeviceAI import dev.dimension.flare.data.datastore.model.AppSettings -internal class AiCompletionService( +public class AiCompletionService( private val openAIService: OpenAIService, private val onDeviceAI: OnDeviceAI, ) { - suspend fun translate( + public suspend fun translate( config: AppSettings.AiConfig, source: String, targetLanguage: String, @@ -17,7 +16,7 @@ internal class AiCompletionService( onDeviceAI.translate(source, targetLanguage, prompt) } - suspend fun tldr( + public suspend fun tldr( config: AppSettings.AiConfig, source: String, targetLanguage: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/OnDeviceAI.kt b/modules/ai/data/src/commonMain/kotlin/dev/dimension/flare/data/ai/OnDeviceAI.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/OnDeviceAI.kt rename to modules/ai/data/src/commonMain/kotlin/dev/dimension/flare/data/ai/OnDeviceAI.kt index 0b49a75996..e09c99979e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/OnDeviceAI.kt +++ b/modules/ai/data/src/commonMain/kotlin/dev/dimension/flare/data/ai/OnDeviceAI.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare.common +package dev.dimension.flare.data.ai public interface OnDeviceAI { public suspend fun isAvailable(): Boolean diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/ai/OpenAIService.kt b/modules/ai/data/src/commonMain/kotlin/dev/dimension/flare/data/ai/OpenAIService.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/ai/OpenAIService.kt rename to modules/ai/data/src/commonMain/kotlin/dev/dimension/flare/data/ai/OpenAIService.kt index b1f0c35cf0..eb47a911b7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/ai/OpenAIService.kt +++ b/modules/ai/data/src/commonMain/kotlin/dev/dimension/flare/data/ai/OpenAIService.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare.data.network.ai +package dev.dimension.flare.data.ai import com.aallam.openai.api.chat.ChatCompletion import com.aallam.openai.api.http.Timeout @@ -9,7 +9,7 @@ import dev.dimension.flare.common.BuildConfig import dev.dimension.flare.common.JSON import dev.dimension.flare.data.datastore.model.AppSettings import dev.dimension.flare.data.network.FlareLogger -import dev.dimension.flare.data.network.httpClientEngine +import dev.dimension.flare.data.network.createHttpClientEngine import dev.dimension.flare.data.network.ktorClient import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.logging.LogLevel @@ -26,8 +26,8 @@ import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.put import kotlin.time.Duration.Companion.minutes -internal class OpenAIService { - suspend fun models( +public class OpenAIService { + public suspend fun models( serverUrl: String, apiKey: String, ): List = @@ -38,7 +38,7 @@ internal class OpenAIService { .map { it.id.id } .sorted() - suspend fun chatCompletion( + public suspend fun chatCompletion( config: AppSettings.AiConfig.Type.OpenAI, prompt: String, ): String { @@ -92,7 +92,7 @@ internal class OpenAIService { ?.let { put("reasoning_effort", it) } } - suspend fun chatCompletionOrNull( + public suspend fun chatCompletionOrNull( config: AppSettings.AiConfig.Type.OpenAI, prompt: String, ): String? = @@ -113,7 +113,7 @@ internal class OpenAIService { OpenAIConfig( host = OpenAIHost(baseUrl = serverUrl), token = apiKey, - engine = httpClientEngine, + engine = createHttpClientEngine(), timeout = Timeout( request = 1.minutes, diff --git a/modules/ai/data/src/commonMain/kotlin/dev/dimension/flare/di/AiDataModule.kt b/modules/ai/data/src/commonMain/kotlin/dev/dimension/flare/di/AiDataModule.kt new file mode 100644 index 0000000000..1a61f384f9 --- /dev/null +++ b/modules/ai/data/src/commonMain/kotlin/dev/dimension/flare/di/AiDataModule.kt @@ -0,0 +1,16 @@ +package dev.dimension.flare.di + +import dev.dimension.flare.data.ai.AiCompletionService +import dev.dimension.flare.data.ai.OpenAIService +import org.koin.core.module.Module +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +public val aiDataModule: Module = + module { + singleOf(::OpenAIService) + singleOf(::AiCompletionService) + registerPlatformAi() + } + +internal expect fun Module.registerPlatformAi() diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/network/ai/OpenAIServiceTest.kt b/modules/ai/data/src/commonTest/kotlin/dev/dimension/flare/data/ai/OpenAIServiceTest.kt similarity index 98% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/network/ai/OpenAIServiceTest.kt rename to modules/ai/data/src/commonTest/kotlin/dev/dimension/flare/data/ai/OpenAIServiceTest.kt index 19ae5792d3..8ad7bc1f1d 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/network/ai/OpenAIServiceTest.kt +++ b/modules/ai/data/src/commonTest/kotlin/dev/dimension/flare/data/ai/OpenAIServiceTest.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare.data.network.ai +package dev.dimension.flare.data.ai import dev.dimension.flare.data.datastore.model.AppSettings import kotlinx.serialization.json.boolean diff --git a/app/src/foss/java/dev/dimension/flare/common/FossOnDeviceAI.kt b/modules/ai/data/src/foss/kotlin/dev/dimension/flare/common/AndroidOnDeviceAI.kt similarity index 79% rename from app/src/foss/java/dev/dimension/flare/common/FossOnDeviceAI.kt rename to modules/ai/data/src/foss/kotlin/dev/dimension/flare/common/AndroidOnDeviceAI.kt index 335ce721ae..43657c06d5 100644 --- a/app/src/foss/java/dev/dimension/flare/common/FossOnDeviceAI.kt +++ b/modules/ai/data/src/foss/kotlin/dev/dimension/flare/common/AndroidOnDeviceAI.kt @@ -1,8 +1,10 @@ package dev.dimension.flare.common import android.content.Context +import dev.dimension.flare.data.ai.OnDeviceAI -internal class FossOnDeviceAI( +internal class AndroidOnDeviceAI( + @Suppress("UNUSED_PARAMETER") private val context: Context, ) : OnDeviceAI { override suspend fun isAvailable(): Boolean = false diff --git a/shared/src/jvmMain/kotlin/dev/dimension/flare/common/JvmOnDeviceAI.kt b/modules/ai/data/src/jvmMain/kotlin/dev/dimension/flare/common/JvmOnDeviceAI.kt similarity index 95% rename from shared/src/jvmMain/kotlin/dev/dimension/flare/common/JvmOnDeviceAI.kt rename to modules/ai/data/src/jvmMain/kotlin/dev/dimension/flare/common/JvmOnDeviceAI.kt index 07b109cdb0..532f0cebd8 100644 --- a/shared/src/jvmMain/kotlin/dev/dimension/flare/common/JvmOnDeviceAI.kt +++ b/modules/ai/data/src/jvmMain/kotlin/dev/dimension/flare/common/JvmOnDeviceAI.kt @@ -6,12 +6,12 @@ import com.sun.jna.Platform import com.sun.jna.Pointer import com.sun.jna.ptr.IntByReference import com.sun.jna.ptr.PointerByReference -import kotlinx.coroutines.Dispatchers +import dev.dimension.flare.data.ai.OnDeviceAI import kotlinx.coroutines.withContext internal class JvmOnDeviceAI : OnDeviceAI { override suspend fun isAvailable(): Boolean = - withContext(Dispatchers.IO) { + withContext(PlatformDispatchers.IO) { FoundationModelsBridge.isAvailable() } @@ -20,7 +20,7 @@ internal class JvmOnDeviceAI : OnDeviceAI { targetLanguage: String, prompt: String, ): String? = - withContext(Dispatchers.IO) { + withContext(PlatformDispatchers.IO) { FoundationModelsBridge.generate(prompt) } @@ -29,7 +29,7 @@ internal class JvmOnDeviceAI : OnDeviceAI { targetLanguage: String, prompt: String, ): String? = - withContext(Dispatchers.IO) { + withContext(PlatformDispatchers.IO) { FoundationModelsBridge.generate(prompt) } } diff --git a/modules/ai/data/src/jvmMain/kotlin/dev/dimension/flare/di/AiPlatformModule.jvm.kt b/modules/ai/data/src/jvmMain/kotlin/dev/dimension/flare/di/AiPlatformModule.jvm.kt new file mode 100644 index 0000000000..ab83aaf87d --- /dev/null +++ b/modules/ai/data/src/jvmMain/kotlin/dev/dimension/flare/di/AiPlatformModule.jvm.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.di + +import dev.dimension.flare.common.JvmOnDeviceAI +import dev.dimension.flare.data.ai.OnDeviceAI +import org.koin.core.module.Module +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind + +internal actual fun Module.registerPlatformAi() { + singleOf(::JvmOnDeviceAI) bind OnDeviceAI::class +} diff --git a/app/src/play/java/dev/dimension/flare/common/AndroidOnDeviceAI.kt b/modules/ai/data/src/play/kotlin/dev/dimension/flare/common/AndroidOnDeviceAI.kt similarity index 99% rename from app/src/play/java/dev/dimension/flare/common/AndroidOnDeviceAI.kt rename to modules/ai/data/src/play/kotlin/dev/dimension/flare/common/AndroidOnDeviceAI.kt index 3d12f58018..11e848e927 100644 --- a/app/src/play/java/dev/dimension/flare/common/AndroidOnDeviceAI.kt +++ b/modules/ai/data/src/play/kotlin/dev/dimension/flare/common/AndroidOnDeviceAI.kt @@ -7,6 +7,7 @@ import com.google.mlkit.genai.prompt.Generation import com.google.mlkit.genai.summarization.Summarization import com.google.mlkit.genai.summarization.SummarizationRequest import com.google.mlkit.genai.summarization.SummarizerOptions +import dev.dimension.flare.data.ai.OnDeviceAI import kotlinx.coroutines.flow.collect import kotlinx.coroutines.suspendCancellableCoroutine import java.util.concurrent.Executor diff --git a/modules/ai/data/src/wasmJsMain/kotlin/dev/dimension/flare/common/WebOnDeviceAI.kt b/modules/ai/data/src/wasmJsMain/kotlin/dev/dimension/flare/common/WebOnDeviceAI.kt new file mode 100644 index 0000000000..e953a6905d --- /dev/null +++ b/modules/ai/data/src/wasmJsMain/kotlin/dev/dimension/flare/common/WebOnDeviceAI.kt @@ -0,0 +1,19 @@ +package dev.dimension.flare.common + +import dev.dimension.flare.data.ai.OnDeviceAI + +internal data object WebOnDeviceAI : OnDeviceAI { + override suspend fun isAvailable(): Boolean = false + + override suspend fun translate( + source: String, + targetLanguage: String, + prompt: String, + ): String? = null + + override suspend fun tldr( + source: String, + targetLanguage: String, + prompt: String, + ): String? = null +} diff --git a/modules/ai/data/src/wasmJsMain/kotlin/dev/dimension/flare/di/AiPlatformModule.wasmJs.kt b/modules/ai/data/src/wasmJsMain/kotlin/dev/dimension/flare/di/AiPlatformModule.wasmJs.kt new file mode 100644 index 0000000000..ae303f7f24 --- /dev/null +++ b/modules/ai/data/src/wasmJsMain/kotlin/dev/dimension/flare/di/AiPlatformModule.wasmJs.kt @@ -0,0 +1,9 @@ +package dev.dimension.flare.di + +import dev.dimension.flare.common.WebOnDeviceAI +import dev.dimension.flare.data.ai.OnDeviceAI +import org.koin.core.module.Module + +internal actual fun Module.registerPlatformAi() { + single { WebOnDeviceAI } +} diff --git a/modules/draft/data/build.gradle.kts b/modules/draft/data/build.gradle.kts new file mode 100644 index 0000000000..0f0f73f184 --- /dev/null +++ b/modules/draft/data/build.gradle.kts @@ -0,0 +1,84 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.modules.draft.data" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_draft_data_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_draft_data", + ) + } + } + } + } + } + } + + compilerOptions { + allWarningsAsErrors.set(false) + } + + sourceSets { + all { + languageSettings { + optIn("kotlin.uuid.ExperimentalUuidApi") + } + } + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.core.model) + api(projects.foundation.database) + api(projects.foundation.filesystem) + api(projects.modules.account.api) + api(projects.modules.draft.model) + api(projects.social.microblog) + api(libs.kotlinx.coroutines.core) + api(libs.okio) + api(dependencies.platform(libs.koin.bom)) + api(libs.koin.core) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) + } + } + } +} diff --git a/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/DraftContentMapper.kt b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/DraftContentMapper.kt new file mode 100644 index 0000000000..179ffd6474 --- /dev/null +++ b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/DraftContentMapper.kt @@ -0,0 +1,61 @@ +package dev.dimension.flare.data.draft + +import dev.dimension.flare.data.datasource.microblog.ComposeData +import dev.dimension.flare.model.draft.DraftContent +import dev.dimension.flare.model.draft.DraftReference +import dev.dimension.flare.model.draft.DraftReferenceType +import dev.dimension.flare.model.draft.DraftVisibility +import dev.dimension.flare.ui.model.UiTimelineV2 +import dev.dimension.flare.ui.presenter.compose.ComposeStatus + +public fun DraftContent.toComposeData(medias: List): ComposeData = + ComposeData( + content = text, + visibility = visibility.toUiVisibility(), + language = language, + medias = medias, + sensitive = sensitive, + spoilerText = spoilerText, + poll = + poll?.let { + ComposeData.Poll( + options = it.options, + expiredAfter = it.expiredAfter, + multiple = it.multiple, + ) + }, + localOnly = localOnly, + referenceStatus = + reference?.let { reference -> + ComposeData.ReferenceStatus( + composeStatus = reference.toComposeStatus(), + ) + }, + ) + +private fun DraftVisibility.toUiVisibility(): UiTimelineV2.Post.Visibility = + when (this) { + DraftVisibility.Public -> UiTimelineV2.Post.Visibility.Public + DraftVisibility.Home -> UiTimelineV2.Post.Visibility.Home + DraftVisibility.Followers -> UiTimelineV2.Post.Visibility.Followers + DraftVisibility.Specified -> UiTimelineV2.Post.Visibility.Specified + DraftVisibility.Channel -> UiTimelineV2.Post.Visibility.Channel + } + +private fun DraftReference.toComposeStatus(): ComposeStatus = + when (type) { + DraftReferenceType.QUOTE -> { + ComposeStatus.Quote(statusKey) + } + + DraftReferenceType.REPLY -> { + ComposeStatus.Reply(statusKey) + } + + DraftReferenceType.VVO_COMMENT -> { + ComposeStatus.VVOComment( + statusKey = statusKey, + rootId = requireNotNull(rootId), + ) + } + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/DraftMediaStore.kt b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/DraftMediaStore.kt similarity index 53% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/DraftMediaStore.kt rename to modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/DraftMediaStore.kt index 5838174354..754dc65219 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/DraftMediaStore.kt +++ b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/DraftMediaStore.kt @@ -1,20 +1,20 @@ -package dev.dimension.flare.data.repository +package dev.dimension.flare.data.draft -import dev.dimension.flare.common.FileItem -import dev.dimension.flare.common.FileType -import dev.dimension.flare.data.database.app.model.DraftMediaType +import dev.dimension.flare.data.io.FileItem +import dev.dimension.flare.data.io.FileType +import dev.dimension.flare.data.io.sanitizeFileName +import dev.dimension.flare.data.database.app.model.DraftMediaType as DbDraftMediaType import dev.dimension.flare.data.datasource.microblog.ComposeData -import dev.dimension.flare.data.io.PlatformPathProducer -import okio.FileSystem +import dev.dimension.flare.data.io.FileStorage +import dev.dimension.flare.model.draft.DraftMedia +import dev.dimension.flare.model.draft.DraftMediaType import okio.Path.Companion.toPath -import okio.SYSTEM import kotlin.uuid.Uuid -internal class DraftMediaStore( - private val platformPathProducer: PlatformPathProducer, - private val fileSystem: FileSystem = FileSystem.SYSTEM, +public class DraftMediaStore( + private val fileStorage: FileStorage, ) { - suspend fun persist( + public suspend fun persist( groupId: String, medias: List, ): List { @@ -25,15 +25,13 @@ internal class DraftMediaStore( ?.sanitizeFileName() .orEmpty() .ifBlank { "${Uuid.random()}.bin" } - val path = platformPathProducer.draftMediaFile(groupId, "${index}_$fileName") - fileSystem.createDirectories(checkNotNull(path.parent)) - fileSystem.write(path) { - write(media.file.readBytes()) - } + val path = fileStorage.draftMediaFile(groupId, "${index}_$fileName") + fileStorage.createDirectories(checkNotNull(path.parent)) + fileStorage.write(path, media.file.readBytes()) SaveDraftMedia( cachePath = path.toString(), fileName = media.file.name, - mediaType = media.file.type.toDraftMediaType(), + mediaType = media.file.type.toDbDraftMediaType(), altText = media.altText, sortOrder = index, ) @@ -45,24 +43,27 @@ internal class DraftMediaStore( return persisted } - suspend fun restore(medias: List): List = + public suspend fun restore(medias: List): List = medias.map { media -> + val path = media.cachePath.toPath() ComposeData.Media( file = - draftFileItem( - path = media.cachePath, - name = media.fileName, - type = media.mediaType.toFileType(), + FileItem( + media.fileName ?: path.name, + media.mediaType.toFileType(), + { + fileStorage.read(path) + }, ), altText = media.altText, ) } - fun delete(medias: List) { + public fun delete(medias: List) { medias.forEach { media -> val path = media.cachePath.toPath() - if (fileSystem.exists(path)) { - fileSystem.delete(path) + if (fileStorage.exists(path)) { + fileStorage.delete(path) } cleanupEmptyGroupDirectory(media.groupId) } @@ -73,18 +74,18 @@ internal class DraftMediaStore( keepPaths: Set, ) { val groupDirectory = - platformPathProducer + fileStorage .draftMediaFile(groupId, "__placeholder__") .parent ?: return - if (!fileSystem.exists(groupDirectory)) { + if (!fileStorage.exists(groupDirectory)) { return } - fileSystem + fileStorage .list(groupDirectory) .filterNot { keepPaths.contains(it) } .forEach { stalePath -> - if (fileSystem.exists(stalePath)) { - fileSystem.delete(stalePath) + if (fileStorage.exists(stalePath)) { + fileStorage.delete(stalePath) } } cleanupEmptyGroupDirectory(groupId) @@ -92,29 +93,23 @@ internal class DraftMediaStore( private fun cleanupEmptyGroupDirectory(groupId: String) { val groupDirectory = - platformPathProducer + fileStorage .draftMediaFile(groupId, "__placeholder__") .parent ?: return - if (!fileSystem.exists(groupDirectory)) { + if (!fileStorage.exists(groupDirectory)) { return } - if (fileSystem.list(groupDirectory).isEmpty()) { - fileSystem.delete(groupDirectory) + if (fileStorage.list(groupDirectory).isEmpty()) { + fileStorage.delete(groupDirectory) } } } -internal expect fun draftFileItem( - path: String, - name: String?, - type: FileType, -): FileItem - -private fun FileType.toDraftMediaType(): DraftMediaType = +private fun FileType.toDbDraftMediaType(): DbDraftMediaType = when (this) { - FileType.Image -> DraftMediaType.IMAGE - FileType.Video -> DraftMediaType.VIDEO - FileType.Other -> DraftMediaType.OTHER + FileType.Image -> DbDraftMediaType.IMAGE + FileType.Video -> DbDraftMediaType.VIDEO + FileType.Other -> DbDraftMediaType.OTHER } private fun DraftMediaType.toFileType(): FileType = @@ -123,5 +118,3 @@ private fun DraftMediaType.toFileType(): FileType = DraftMediaType.VIDEO -> FileType.Video DraftMediaType.OTHER -> FileType.Other } - -private fun String.sanitizeFileName(): String = replace(Regex("[^A-Za-z0-9._-]"), "_") diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/DraftRepository.kt b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/DraftRepository.kt similarity index 60% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/DraftRepository.kt rename to modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/DraftRepository.kt index 28f8cc5f6c..e9630ca0cf 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/DraftRepository.kt +++ b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/DraftRepository.kt @@ -1,45 +1,54 @@ -package dev.dimension.flare.data.repository +package dev.dimension.flare.data.draft import dev.dimension.flare.data.database.app.AppDatabase import dev.dimension.flare.data.database.app.model.DbDraftGroup import dev.dimension.flare.data.database.app.model.DbDraftGroupWithRelations import dev.dimension.flare.data.database.app.model.DbDraftMedia import dev.dimension.flare.data.database.app.model.DbDraftTarget -import dev.dimension.flare.data.database.app.model.DraftContent -import dev.dimension.flare.data.database.app.model.DraftMediaType -import dev.dimension.flare.data.database.app.model.DraftTargetStatus +import dev.dimension.flare.data.database.app.model.DraftContent as DbDraftContent +import dev.dimension.flare.data.database.app.model.DraftMediaType as DbDraftMediaType +import dev.dimension.flare.data.database.app.model.DraftReferenceType as DbDraftReferenceType +import dev.dimension.flare.data.database.app.model.DraftTargetStatus as DbDraftTargetStatus +import dev.dimension.flare.data.database.app.model.DraftVisibility as DbDraftVisibility import dev.dimension.flare.data.database.cache.connect -import dev.dimension.flare.data.datasource.microblog.ComposeData import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.UiAccount +import dev.dimension.flare.model.draft.DraftContent +import dev.dimension.flare.model.draft.DraftGroup +import dev.dimension.flare.model.draft.DraftMedia +import dev.dimension.flare.model.draft.DraftMediaType +import dev.dimension.flare.model.draft.DraftPoll +import dev.dimension.flare.model.draft.DraftReference +import dev.dimension.flare.model.draft.DraftReferenceType +import dev.dimension.flare.model.draft.DraftTarget +import dev.dimension.flare.model.draft.DraftTargetStatus +import dev.dimension.flare.model.draft.DraftVisibility import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlin.time.Clock -import kotlin.uuid.Uuid -internal class DraftRepository( +public class DraftRepository( private val database: AppDatabase, private val draftMediaStore: DraftMediaStore, ) { - val visibleDrafts: Flow> = + public val visibleDrafts: Flow> = database .draftDao() .visibleDraftGroups() .map { drafts -> drafts.map { it.toModel() } } - val sendingDrafts: Flow> = + public val sendingDrafts: Flow> = database .draftDao() .sendingDraftGroups() .map { drafts -> drafts.map { it.toModel() } } - fun draft(groupId: String): Flow = + public fun draft(groupId: String): Flow = database .draftDao() .draftGroup(groupId) .map { it?.toModel() } - suspend fun saveDraft(input: SaveDraftInput): String { + public suspend fun saveDraft(input: SaveDraftInput): String { val now = Clock.System.now().toEpochMilliseconds() val groupId = input.groupId val createdAt = input.createdAt ?: database.draftDao().getGroup(groupId)?.created_at ?: now @@ -90,10 +99,10 @@ internal class DraftRepository( return groupId } - suspend fun updateTargetStatus( + public suspend fun updateTargetStatus( groupId: String, accountKey: MicroBlogKey, - status: DraftTargetStatus, + status: DbDraftTargetStatus, errorMessage: String? = null, attemptCount: Int = 0, lastAttemptAt: Long? = null, @@ -112,7 +121,7 @@ internal class DraftRepository( } } - suspend fun deleteTarget( + public suspend fun deleteTarget( groupId: String, accountKey: MicroBlogKey, ) { @@ -131,7 +140,7 @@ internal class DraftRepository( } } - suspend fun deleteGroup(groupId: String) { + public suspend fun deleteGroup(groupId: String) { val medias = database .draftDao() @@ -143,37 +152,37 @@ internal class DraftRepository( draftMediaStore.delete(medias) } - suspend fun markSendingAsDraftIfExpired( + public suspend fun markSendingAsDraftIfExpired( expiredBefore: Long, errorMessage: String? = null, ) { val now = Clock.System.now().toEpochMilliseconds() database.connect { database.draftDao().resetSendingTargets( - fromStatus = DraftTargetStatus.SENDING, - toStatus = DraftTargetStatus.DRAFT, + fromStatus = DbDraftTargetStatus.SENDING, + toStatus = DbDraftTargetStatus.DRAFT, expiredBefore = expiredBefore, errorMessage = errorMessage, updatedAt = now, ) database.draftDao().touchExpiredSendingGroups( - fromStatus = DraftTargetStatus.SENDING, + fromStatus = DbDraftTargetStatus.SENDING, expiredBefore = expiredBefore, updatedAt = now, ) } } - suspend fun markSendingAsFailed(errorMessage: String? = null) { + public suspend fun markSendingAsFailed(errorMessage: String? = null) { val now = Clock.System.now().toEpochMilliseconds() database.connect { database.draftDao().touchGroupsByTargetStatus( - status = DraftTargetStatus.SENDING, + status = DbDraftTargetStatus.SENDING, updatedAt = now, ) database.draftDao().updateTargetsByStatus( - fromStatus = DraftTargetStatus.SENDING, - toStatus = DraftTargetStatus.FAILED, + fromStatus = DbDraftTargetStatus.SENDING, + toStatus = DbDraftTargetStatus.FAILED, errorMessage = errorMessage, updatedAt = now, ) @@ -186,80 +195,36 @@ internal class DraftRepository( ) = "${groupId}_$accountKey" } -internal data class SaveDraftInput( - val groupId: String, - val content: DraftContent, - val targets: List, - val medias: List, - val createdAt: Long? = null, +public data class SaveDraftInput( + public val groupId: String, + public val content: DbDraftContent, + public val targets: List, + public val medias: List, + public val createdAt: Long? = null, ) -internal data class SaveDraftTarget( - val accountKey: MicroBlogKey, - val status: DraftTargetStatus = DraftTargetStatus.DRAFT, - val errorMessage: String? = null, - val attemptCount: Int = 0, - val lastAttemptAt: Long? = null, - val createdAt: Long? = null, +public data class SaveDraftTarget( + public val accountKey: MicroBlogKey, + public val status: DbDraftTargetStatus = DbDraftTargetStatus.DRAFT, + public val errorMessage: String? = null, + public val attemptCount: Int = 0, + public val lastAttemptAt: Long? = null, + public val createdAt: Long? = null, ) -internal data class SaveDraftMedia( - val cachePath: String, - val fileName: String? = null, - val mediaType: DraftMediaType, - val altText: String? = null, - val sortOrder: Int? = null, - val createdAt: Long? = null, +public data class SaveDraftMedia( + public val cachePath: String, + public val fileName: String? = null, + public val mediaType: DbDraftMediaType, + public val altText: String? = null, + public val sortOrder: Int? = null, + public val createdAt: Long? = null, ) -internal data class DraftGroup( - val groupId: String, - val content: DraftContent, - val createdAt: Long, - val updatedAt: Long, - val targets: List, - val medias: List, -) - -internal data class DraftTarget( - val groupId: String, - val accountKey: MicroBlogKey, - val status: DraftTargetStatus, - val errorMessage: String?, - val attemptCount: Int, - val lastAttemptAt: Long?, - val createdAt: Long, - val updatedAt: Long, -) - -internal data class DraftMedia( - val mediaId: String, - val groupId: String, - val cachePath: String, - val fileName: String?, - val mediaType: DraftMediaType, - val altText: String?, - val sortOrder: Int, - val createdAt: Long, -) - -internal data class ComposeDraftBundle( - val accounts: List, - val template: ComposeData, - val groupId: String = newDraftGroupId(), -) - -internal fun ComposeData.toComposeDraftBundle( - accounts: List, - groupId: String = newDraftGroupId(), -): ComposeDraftBundle = ComposeDraftBundle(accounts = accounts, template = this, groupId = groupId) - -internal fun newDraftGroupId(): String = Uuid.random().toString() - private fun DbDraftGroupWithRelations.toModel(): DraftGroup = DraftGroup( groupId = group.group_id, - content = group.content, + content = group.content.toModel(), createdAt = group.created_at, updatedAt = group.updated_at, targets = @@ -269,7 +234,7 @@ private fun DbDraftGroupWithRelations.toModel(): DraftGroup = DraftTarget( groupId = it.group_id, accountKey = it.account_key, - status = it.status, + status = it.status.toModel(), errorMessage = it.error_message, attemptCount = it.attempt_count, lastAttemptAt = it.last_attempt_at, @@ -286,10 +251,66 @@ private fun DbDraftGroupWithRelations.toModel(): DraftGroup = groupId = it.group_id, cachePath = it.cache_path, fileName = it.file_name, - mediaType = it.media_type, + mediaType = it.media_type.toModel(), altText = it.alt_text, sortOrder = it.sort_order, createdAt = it.created_at, ) }, ) + +private fun DbDraftContent.toModel(): DraftContent = + DraftContent( + text = text, + visibility = visibility.toModel(), + language = language, + sensitive = sensitive, + spoilerText = spoilerText, + localOnly = localOnly, + poll = + poll?.let { + DraftPoll( + options = it.options, + expiredAfter = it.expiredAfter, + multiple = it.multiple, + ) + }, + reference = + reference?.let { + DraftReference( + type = it.type.toModel(), + statusKey = it.statusKey, + rootId = it.rootId, + ) + }, + ) + +private fun DbDraftVisibility.toModel(): DraftVisibility = + when (this) { + DbDraftVisibility.Public -> DraftVisibility.Public + DbDraftVisibility.Home -> DraftVisibility.Home + DbDraftVisibility.Followers -> DraftVisibility.Followers + DbDraftVisibility.Specified -> DraftVisibility.Specified + DbDraftVisibility.Channel -> DraftVisibility.Channel + } + +private fun DbDraftReferenceType.toModel(): DraftReferenceType = + when (this) { + DbDraftReferenceType.REPLY -> DraftReferenceType.REPLY + DbDraftReferenceType.QUOTE -> DraftReferenceType.QUOTE + DbDraftReferenceType.VVO_COMMENT -> DraftReferenceType.VVO_COMMENT + } + +private fun DbDraftTargetStatus.toModel(): DraftTargetStatus = + when (this) { + DbDraftTargetStatus.DRAFT -> DraftTargetStatus.DRAFT + DbDraftTargetStatus.SENDING -> DraftTargetStatus.SENDING + DbDraftTargetStatus.FAILED -> DraftTargetStatus.FAILED + } + +private fun DbDraftMediaType.toModel(): DraftMediaType = + when (this) { + DbDraftMediaType.IMAGE -> DraftMediaType.IMAGE + DbDraftMediaType.VIDEO -> DraftMediaType.VIDEO + DbDraftMediaType.OTHER -> DraftMediaType.OTHER + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/DraftSendingRecoveryCoordinator.kt b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/DraftSendingRecoveryCoordinator.kt similarity index 75% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/DraftSendingRecoveryCoordinator.kt rename to modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/DraftSendingRecoveryCoordinator.kt index a543194af5..4490b57ae1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/DraftSendingRecoveryCoordinator.kt +++ b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/DraftSendingRecoveryCoordinator.kt @@ -1,9 +1,9 @@ -package dev.dimension.flare.data.repository +package dev.dimension.flare.data.draft import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -internal class DraftSendingRecoveryCoordinator( +public class DraftSendingRecoveryCoordinator( private val draftRepository: DraftRepository, coroutineScope: CoroutineScope, ) { diff --git a/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/RestoreDraftUseCase.kt b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/RestoreDraftUseCase.kt new file mode 100644 index 0000000000..101d2b9b13 --- /dev/null +++ b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/RestoreDraftUseCase.kt @@ -0,0 +1,59 @@ +package dev.dimension.flare.data.draft + +import dev.dimension.flare.data.account.AccountLookup +import dev.dimension.flare.model.draft.DraftGroup +import dev.dimension.flare.model.draft.DraftMediaType +import dev.dimension.flare.model.draft.DraftTargetStatus +import dev.dimension.flare.ui.model.UiDraft +import dev.dimension.flare.ui.model.UiDraftAccount +import dev.dimension.flare.ui.model.UiDraftMedia +import dev.dimension.flare.ui.model.UiDraftMediaType +import dev.dimension.flare.ui.model.UiDraftStatus +import dev.dimension.flare.ui.render.toUi +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.firstOrNull +import kotlin.time.Instant + +public class RestoreDraftUseCase( + private val draftRepository: DraftRepository, + private val accountLookup: AccountLookup, +) { + public suspend operator fun invoke(groupId: String): UiDraft? { + val draft = draftRepository.draft(groupId).firstOrNull() ?: return null + val accounts = + draft.targets.mapNotNull { target -> + accountLookup.find(target.accountKey)?.let { + UiDraftAccount(account = it) + } + } + return UiDraft( + groupId = draft.groupId, + status = draft.toUiDraftStatus(), + updatedAt = Instant.fromEpochMilliseconds(draft.updatedAt).toUi(), + accounts = accounts.toImmutableList(), + data = draft.content.toComposeData(medias = emptyList()), + medias = + draft.medias + .map { media -> + UiDraftMedia( + cachePath = media.cachePath, + fileName = media.fileName, + type = + when (media.mediaType) { + DraftMediaType.IMAGE -> UiDraftMediaType.IMAGE + DraftMediaType.VIDEO -> UiDraftMediaType.VIDEO + DraftMediaType.OTHER -> UiDraftMediaType.OTHER + }, + altText = media.altText, + ) + }.toImmutableList(), + ) + } +} + +public fun DraftGroup.toUiDraftStatus(): UiDraftStatus = + when { + targets.any { it.status == DraftTargetStatus.SENDING } -> UiDraftStatus.SENDING + targets.any { it.status == DraftTargetStatus.FAILED } -> UiDraftStatus.FAILED + else -> UiDraftStatus.DRAFT + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/SaveDraftUseCase.kt b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/SaveDraftUseCase.kt similarity index 64% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/SaveDraftUseCase.kt rename to modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/SaveDraftUseCase.kt index 373e0ca931..f999bf5bba 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/SaveDraftUseCase.kt +++ b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/SaveDraftUseCase.kt @@ -1,18 +1,18 @@ -package dev.dimension.flare.ui.presenter.compose +package dev.dimension.flare.data.draft import dev.dimension.flare.data.database.app.model.DraftContent import dev.dimension.flare.data.database.app.model.DraftReferenceType -import dev.dimension.flare.data.repository.ComposeDraftBundle -import dev.dimension.flare.data.repository.DraftMediaStore -import dev.dimension.flare.data.repository.DraftRepository -import dev.dimension.flare.data.repository.SaveDraftInput -import dev.dimension.flare.data.repository.SaveDraftTarget +import dev.dimension.flare.data.database.app.model.DraftVisibility +import dev.dimension.flare.data.datasource.microblog.ComposeData +import dev.dimension.flare.model.draft.ComposeDraftBundle +import dev.dimension.flare.ui.model.UiTimelineV2 +import dev.dimension.flare.ui.presenter.compose.ComposeStatus -internal class SaveDraftUseCase( +public class SaveDraftUseCase( private val draftRepository: DraftRepository, private val draftMediaStore: DraftMediaStore, ) { - suspend operator fun invoke(bundle: ComposeDraftBundle): String { + public suspend operator fun invoke(bundle: ComposeDraftBundle): String { val persistedMedia = draftMediaStore.persist(groupId = bundle.groupId, medias = bundle.template.medias) return draftRepository.saveDraft( SaveDraftInput( @@ -30,10 +30,10 @@ internal class SaveDraftUseCase( } } -internal fun dev.dimension.flare.data.datasource.microblog.ComposeData.toDraftContent(): DraftContent = +public fun ComposeData.toDraftContent(): DraftContent = DraftContent( text = content, - visibility = visibility, + visibility = visibility.toDraftVisibility(), language = language, sensitive = sensitive, spoilerText = spoilerText, @@ -60,3 +60,12 @@ internal fun dev.dimension.flare.data.datasource.microblog.ComposeData.toDraftCo ) }, ) + +private fun UiTimelineV2.Post.Visibility.toDraftVisibility(): DraftVisibility = + when (this) { + UiTimelineV2.Post.Visibility.Public -> DraftVisibility.Public + UiTimelineV2.Post.Visibility.Home -> DraftVisibility.Home + UiTimelineV2.Post.Visibility.Followers -> DraftVisibility.Followers + UiTimelineV2.Post.Visibility.Specified -> DraftVisibility.Specified + UiTimelineV2.Post.Visibility.Channel -> DraftVisibility.Channel + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/SendDraftUseCase.kt b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/SendDraftUseCase.kt similarity index 80% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/SendDraftUseCase.kt rename to modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/SendDraftUseCase.kt index cbf117de45..d7acb5e362 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/SendDraftUseCase.kt +++ b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/data/draft/SendDraftUseCase.kt @@ -1,40 +1,21 @@ -package dev.dimension.flare.ui.presenter.compose +package dev.dimension.flare.data.draft -import dev.dimension.flare.data.database.app.model.DraftTargetStatus -import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource +import dev.dimension.flare.data.database.app.model.DraftTargetStatus as DbDraftTargetStatus import dev.dimension.flare.data.datasource.microblog.ComposeData -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.ComposeDraftBundle -import dev.dimension.flare.data.repository.DraftMediaStore -import dev.dimension.flare.data.repository.DraftRepository -import dev.dimension.flare.data.repository.SaveDraftInput -import dev.dimension.flare.data.repository.SaveDraftTarget import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.draft.ComposeDraftBundle +import dev.dimension.flare.model.draft.DraftTargetStatus import dev.dimension.flare.ui.model.UiAccount import kotlinx.coroutines.flow.firstOrNull import kotlin.time.Clock -internal class SendDraftUseCase( +public class SendDraftUseCase( private val draftRepository: DraftRepository, private val draftMediaStore: DraftMediaStore, private val findAccount: suspend (MicroBlogKey) -> UiAccount?, private val composeDraft: suspend (UiAccount, ComposeData, () -> Unit) -> Unit, ) { - constructor( - draftRepository: DraftRepository, - accountRepository: AccountRepository, - draftMediaStore: DraftMediaStore, - ) : this( - draftRepository = draftRepository, - draftMediaStore = draftMediaStore, - findAccount = { accountRepository.find(it) }, - composeDraft = { account, data, progress -> - (accountRepository.getOrCreateDataSource(account) as? AuthenticatedMicroblogDataSource) - ?.compose(data = data, progress = progress) - }, - ) - - suspend operator fun invoke( + public suspend operator fun invoke( bundle: ComposeDraftBundle, progress: suspend (ComposeProgressState) -> Unit, ) { @@ -49,7 +30,7 @@ internal class SendDraftUseCase( bundle.accounts.map { SaveDraftTarget( accountKey = it.accountKey, - status = DraftTargetStatus.SENDING, + status = DbDraftTargetStatus.SENDING, attemptCount = 1, lastAttemptAt = Clock.System.now().toEpochMilliseconds(), ) @@ -64,7 +45,7 @@ internal class SendDraftUseCase( ) } - suspend operator fun invoke( + public suspend operator fun invoke( groupId: String, progress: suspend (ComposeProgressState) -> Unit, ) { @@ -100,7 +81,7 @@ internal class SendDraftUseCase( draftRepository.updateTargetStatus( groupId = groupId, accountKey = target.account.accountKey, - status = DraftTargetStatus.SENDING, + status = DbDraftTargetStatus.SENDING, attemptCount = 1, lastAttemptAt = Clock.System.now().toEpochMilliseconds(), ) @@ -124,7 +105,7 @@ internal class SendDraftUseCase( draftRepository.updateTargetStatus( groupId = groupId, accountKey = target.account.accountKey, - status = DraftTargetStatus.FAILED, + status = DbDraftTargetStatus.FAILED, errorMessage = throwable.message, attemptCount = 1, lastAttemptAt = Clock.System.now().toEpochMilliseconds(), @@ -178,8 +159,21 @@ private data class ComposeTargetData( val data: ComposeData, ) -internal class ComposeDraftFailedException( - val failures: List, +public sealed interface ComposeProgressState { + public data object Success : ComposeProgressState + + public data class Progress( + public val current: Int, + public val max: Int, + ) : ComposeProgressState + + public data class Error( + public val throwable: Throwable, + ) : ComposeProgressState +} + +public class ComposeDraftFailedException( + public val failures: List, ) : Exception( failures.firstOrNull()?.message ?: "Compose failed.", ) diff --git a/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/di/DraftDataModule.kt b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/di/DraftDataModule.kt new file mode 100644 index 0000000000..9ebd835911 --- /dev/null +++ b/modules/draft/data/src/commonMain/kotlin/dev/dimension/flare/di/DraftDataModule.kt @@ -0,0 +1,26 @@ +package dev.dimension.flare.di + +import dev.dimension.flare.data.draft.DraftMediaStore +import dev.dimension.flare.data.draft.DraftRepository +import dev.dimension.flare.data.draft.DraftSendingRecoveryCoordinator +import dev.dimension.flare.data.draft.RestoreDraftUseCase +import dev.dimension.flare.data.draft.SaveDraftUseCase +import org.koin.core.module.Module +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +public val draftDataModule: Module = + module { + single { + DraftMediaStore(fileStorage = get()) + } + single { + DraftRepository( + database = get(), + draftMediaStore = get(), + ) + } + single(createdAtStart = true) { DraftSendingRecoveryCoordinator(get(), get()) } + singleOf(::SaveDraftUseCase) + singleOf(::RestoreDraftUseCase) + } diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/repository/DraftMediaStoreTest.kt b/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/DraftMediaStoreTest.kt similarity index 70% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/repository/DraftMediaStoreTest.kt rename to modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/DraftMediaStoreTest.kt index 492fac2467..896d8da747 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/repository/DraftMediaStoreTest.kt +++ b/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/DraftMediaStoreTest.kt @@ -1,18 +1,16 @@ -package dev.dimension.flare.data.repository +package dev.dimension.flare.data.draft -import dev.dimension.flare.common.FileType -import dev.dimension.flare.createTestFileItem -import dev.dimension.flare.createTestRootPath -import dev.dimension.flare.data.database.app.model.DraftMediaType +import dev.dimension.flare.data.io.FileItem +import dev.dimension.flare.data.io.FileType +import dev.dimension.flare.data.database.app.model.DraftMediaType as DbDraftMediaType import dev.dimension.flare.data.datasource.microblog.ComposeData -import dev.dimension.flare.data.io.PlatformPathProducer -import dev.dimension.flare.deleteTestRootPath +import dev.dimension.flare.data.io.FakeFileStorage +import dev.dimension.flare.model.draft.DraftMedia +import dev.dimension.flare.model.draft.DraftMediaType import kotlinx.coroutines.test.runTest -import okio.FileSystem import okio.Path import okio.Path.Companion.toPath -import okio.SYSTEM -import kotlin.test.AfterTest +import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals @@ -22,27 +20,18 @@ import kotlin.test.assertNull import kotlin.test.assertTrue class DraftMediaStoreTest { - private val root = createTestRootPath() - private val fileSystem = FileSystem.SYSTEM - private val pathProducer = - object : PlatformPathProducer { - override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) - - override fun draftMediaFile( - groupId: String, - fileName: String, - ): Path = root.resolve("draft_media").resolve(groupId).resolve(fileName) - } + private val root = "/draft-media-store-test".toPath() + private lateinit var fileStorage: FakeFileStorage - @AfterTest - fun tearDown() { - deleteTestRootPath(root) + @BeforeTest + fun setup() { + fileStorage = FakeFileStorage(root) } @Test fun persistRestoreDeleteFlow() = runTest { - val store = DraftMediaStore(pathProducer, fileSystem) + val store = DraftMediaStore(fileStorage) val medias = listOf( media(name = "a.png", bytes = byteArrayOf(1, 2, 3), type = FileType.Image, altText = "a"), @@ -53,7 +42,7 @@ class DraftMediaStoreTest { assertEquals(2, persisted.size) persisted.forEach { - assertTrue(fileSystem.exists(it.cachePath.toPath())) + assertTrue(fileStorage.exists(it.cachePath.toPath())) } val restored = store.restore(persisted.mapIndexed { index, media -> media.toDraftMedia("group-1", index) }) @@ -66,15 +55,15 @@ class DraftMediaStoreTest { store.delete(persisted.mapIndexed { index, media -> media.toDraftMedia("group-1", index) }) persisted.forEach { - assertFalse(fileSystem.exists(it.cachePath.toPath())) + assertFalse(fileStorage.exists(it.cachePath.toPath())) } - assertFalse(fileSystem.exists(root.resolve("draft_media").resolve("group-1"))) + assertFalse(fileStorage.exists(root.resolve("draft_media").resolve("group-1"))) } @Test fun persistRestorePersistDoesNotCreateNewFiles() = runTest { - val store = DraftMediaStore(pathProducer, fileSystem) + val store = DraftMediaStore(fileStorage) val firstPersist = store.persist( "group-2", @@ -90,19 +79,19 @@ class DraftMediaStoreTest { assertEquals(firstPersist.map { it.cachePath }, secondPersist.map { it.cachePath }) assertEquals( 2, - fileSystem + fileStorage .list(root.resolve("draft_media").resolve("group-2")) .size, ) secondPersist.forEach { - assertTrue(fileSystem.exists(it.cachePath.toPath())) + assertTrue(fileStorage.exists(it.cachePath.toPath())) } } @Test fun persistRestorePersistWithSameFileNameOverwritesContent() = runTest { - val store = DraftMediaStore(pathProducer, fileSystem) + val store = DraftMediaStore(fileStorage) val firstPersist = store.persist( "group-same-name", @@ -114,7 +103,7 @@ class DraftMediaStoreTest { val updatedMedia = listOf( restored.single().copy( - file = createTestFileItem(root = root, name = "a.png", bytes = byteArrayOf(9, 8, 7), type = FileType.Image), + file = fileItem(name = "a.png", bytes = byteArrayOf(9, 8, 7), type = FileType.Image), ), ) @@ -123,14 +112,14 @@ class DraftMediaStoreTest { assertEquals(firstPersist.single().cachePath, secondPersist.single().cachePath) assertContentEquals( byteArrayOf(9, 8, 7), - fileSystem.read(secondPersist.single().cachePath.toPath()) { readByteArray() }, + fileStorage.read(secondPersist.single().cachePath.toPath()), ) } @Test fun persistRestoreModifyPersistDeletesRemovedAndAddsNewFiles() = runTest { - val store = DraftMediaStore(pathProducer, fileSystem) + val store = DraftMediaStore(fileStorage) val firstPersist = store.persist( "group-3", @@ -154,18 +143,18 @@ class DraftMediaStoreTest { assertTrue(firstPersist[0].cachePath in newPaths) assertTrue(secondPersist.any { it.fileName == "c.png" }) - assertFalse(fileSystem.exists(removedPath.toPath())) + assertFalse(fileStorage.exists(removedPath.toPath())) assertEquals(2, newPaths.size) assertTrue(newPaths.any { it !in originalPaths }) secondPersist.forEach { - assertTrue(fileSystem.exists(it.cachePath.toPath())) + assertTrue(fileStorage.exists(it.cachePath.toPath())) } } @Test fun persistHandlesNullAndBlankFileNames() = runTest { - val store = DraftMediaStore(pathProducer, fileSystem) + val store = DraftMediaStore(fileStorage) val persisted = store.persist( "group-4", @@ -179,7 +168,7 @@ class DraftMediaStoreTest { assertNull(persisted[0].fileName) assertEquals("", persisted[1].fileName) persisted.forEach { - assertTrue(fileSystem.exists(it.cachePath.toPath())) + assertTrue(fileStorage.exists(it.cachePath.toPath())) assertTrue( it.cachePath .toPath() @@ -189,10 +178,35 @@ class DraftMediaStoreTest { } } + @Test + fun restoreFallsBackToCacheFileNameWhenOriginalFileNameIsMissing() = + runTest { + val store = DraftMediaStore(fileStorage) + val firstPersist = + store + .persist( + "group-missing-file-name", + listOf( + media(name = null, bytes = byteArrayOf(7), type = FileType.Image, altText = null), + ), + ).single() + + val restored = store.restore(listOf(firstPersist.toDraftMedia("group-missing-file-name", 0))).single() + val secondPersist = store.persist("group-missing-file-name", listOf(restored)).single() + val fallbackFileName = firstPersist.cachePath.toPath().name + + assertEquals(fallbackFileName, restored.file.name) + assertEquals(fallbackFileName, secondPersist.fileName) + assertContentEquals( + byteArrayOf(7), + fileStorage.read(secondPersist.cachePath.toPath()), + ) + } + @Test fun persistSanitizesIllegalCharactersInFileName() = runTest { - val store = DraftMediaStore(pathProducer, fileSystem) + val store = DraftMediaStore(fileStorage) val persisted = store.persist( "group-5", @@ -202,7 +216,7 @@ class DraftMediaStoreTest { ) assertEquals(1, persisted.size) - assertTrue(fileSystem.exists(persisted.single().cachePath.toPath())) + assertTrue(fileStorage.exists(persisted.single().cachePath.toPath())) assertEquals( "0_a__b__c___.png", persisted @@ -216,7 +230,7 @@ class DraftMediaStoreTest { @Test fun persistSanitizesWhitespaceAndUnicodeFileName() = runTest { - val store = DraftMediaStore(pathProducer, fileSystem) + val store = DraftMediaStore(fileStorage) val persisted = store.persist( @@ -234,13 +248,13 @@ class DraftMediaStoreTest { .toPath() .name, ) - assertTrue(fileSystem.exists(persisted.single().cachePath.toPath())) + assertTrue(fileStorage.exists(persisted.single().cachePath.toPath())) } @Test fun persistEmptyListClearsDraftGroupFiles() = runTest { - val store = DraftMediaStore(pathProducer, fileSystem) + val store = DraftMediaStore(fileStorage) val firstPersist = store.persist( "group-6", @@ -254,26 +268,21 @@ class DraftMediaStoreTest { assertTrue(secondPersist.isEmpty()) firstPersist.forEach { - assertFalse(fileSystem.exists(it.cachePath.toPath())) + assertFalse(fileStorage.exists(it.cachePath.toPath())) } val groupDir = root.resolve("draft_media").resolve("group-6") - assertTrue(!fileSystem.exists(groupDir) || fileSystem.list(groupDir).isEmpty()) + assertTrue(!fileStorage.exists(groupDir) || fileStorage.list(groupDir).isEmpty()) } @Test fun deleteIgnoresMissingFilesAndRepeatedDeletes() = runTest { - val store = DraftMediaStore(pathProducer, fileSystem) + val store = DraftMediaStore(fileStorage) val missing = DraftMedia( mediaId = "missing", groupId = "group-7", - cachePath = - root - .resolve("draft_media") - .resolve("group-7") - .resolve("missing.png") - .toString(), + cachePath = fileStorage.draftMediaFile("group-7", "missing.png").toString(), fileName = "missing.png", mediaType = DraftMediaType.IMAGE, altText = null, @@ -293,24 +302,19 @@ class DraftMediaStoreTest { store.delete(listOf(missing, persisted)) store.delete(listOf(missing, persisted)) - assertFalse(fileSystem.exists(persisted.cachePath.toPath())) - assertFalse(fileSystem.exists(missing.cachePath.toPath())) + assertFalse(fileStorage.exists(persisted.cachePath.toPath())) + assertFalse(fileStorage.exists(missing.cachePath.toPath())) } @Test fun restoreFailsWhenCachedFileIsMissing() = runTest { - val store = DraftMediaStore(pathProducer, fileSystem) + val store = DraftMediaStore(fileStorage) val missingMedia = DraftMedia( mediaId = "missing-restore", groupId = "group-restore-fail", - cachePath = - root - .resolve("draft_media") - .resolve("group-restore-fail") - .resolve("missing.png") - .toString(), + cachePath = fileStorage.draftMediaFile("group-restore-fail", "missing.png").toString(), fileName = "missing.png", mediaType = DraftMediaType.IMAGE, altText = null, @@ -330,22 +334,13 @@ class DraftMediaStoreTest { @Test fun persistFailsWhenDraftDirectoryCannotBeCreated() = runTest { - val blockedParent = root.resolve("blocked") - fileSystem.write(blockedParent) { - writeUtf8("not a directory") - } val blockedStore = DraftMediaStore( - platformPathProducer = - object : PlatformPathProducer { - override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) - - override fun draftMediaFile( - groupId: String, - fileName: String, - ): Path = blockedParent.resolve(groupId).resolve(fileName) + FakeFileStorage( + onCreateDirectories = { path: Path -> + error("Cannot create directory: $path") }, - fileSystem = fileSystem, + ), ) assertFailsWith { @@ -365,10 +360,21 @@ class DraftMediaStoreTest { altText: String?, ): ComposeData.Media = ComposeData.Media( - file = createTestFileItem(root = root, name = name, bytes = bytes, type = type), + file = fileItem(name = name, bytes = bytes, type = type), altText = altText, ) + private fun fileItem( + name: String?, + bytes: ByteArray, + type: FileType, + ): FileItem = + FileItem( + name, + type, + { bytes }, + ) + private fun SaveDraftMedia.toDraftMedia( groupId: String, index: Int, @@ -377,9 +383,16 @@ class DraftMediaStoreTest { groupId = groupId, cachePath = cachePath, fileName = fileName, - mediaType = mediaType, + mediaType = mediaType.toModel(), altText = altText, sortOrder = sortOrder ?: index, createdAt = createdAt ?: 0L, ) + + private fun DbDraftMediaType.toModel(): DraftMediaType = + when (this) { + DbDraftMediaType.IMAGE -> DraftMediaType.IMAGE + DbDraftMediaType.VIDEO -> DraftMediaType.VIDEO + DbDraftMediaType.OTHER -> DraftMediaType.OTHER + } } diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/repository/DraftRepositoryTest.kt b/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/DraftRepositoryTest.kt similarity index 84% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/repository/DraftRepositoryTest.kt rename to modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/DraftRepositoryTest.kt index 059b710491..c7e0effe6e 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/repository/DraftRepositoryTest.kt +++ b/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/DraftRepositoryTest.kt @@ -1,29 +1,23 @@ -package dev.dimension.flare.data.repository +package dev.dimension.flare.data.draft import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import dev.dimension.flare.RobolectricTest -import dev.dimension.flare.common.FileType -import dev.dimension.flare.createTestFileItem -import dev.dimension.flare.createTestRootPath +import dev.dimension.flare.data.io.FileItem +import dev.dimension.flare.data.io.FileType import dev.dimension.flare.data.database.app.AppDatabase import dev.dimension.flare.data.database.app.model.DraftContent import dev.dimension.flare.data.database.app.model.DraftMediaType import dev.dimension.flare.data.database.app.model.DraftReferenceType import dev.dimension.flare.data.database.app.model.DraftTargetStatus +import dev.dimension.flare.data.database.app.model.DraftVisibility +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.data.datasource.microblog.ComposeData -import dev.dimension.flare.data.io.PlatformPathProducer -import dev.dimension.flare.deleteTestRootPath -import dev.dimension.flare.memoryDatabaseBuilder +import dev.dimension.flare.data.io.FakeFileStorage import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.UiTimelineV2 +import dev.dimension.flare.model.draft.DraftTargetStatus as ModelDraftTargetStatus import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest -import okio.FileSystem -import okio.Path import okio.Path.Companion.toPath -import okio.SYSTEM import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -32,39 +26,27 @@ import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertNull -class DraftRepositoryTest : RobolectricTest() { - private val root = createTestRootPath() - private val fileSystem = FileSystem.SYSTEM - private val pathProducer = - object : PlatformPathProducer { - override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) - - override fun draftMediaFile( - groupId: String, - fileName: String, - ): Path = root.resolve("draft_media").resolve(groupId).resolve(fileName) - } - +class DraftRepositoryTest { + private lateinit var fileStorage: FakeFileStorage private lateinit var db: AppDatabase private lateinit var repository: DraftRepository private lateinit var mediaStore: DraftMediaStore @BeforeTest fun setup() { + fileStorage = FakeFileStorage() db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() - mediaStore = DraftMediaStore(pathProducer, fileSystem) + mediaStore = DraftMediaStore(fileStorage) repository = DraftRepository(db, mediaStore) } @AfterTest fun tearDown() { db.close() - deleteTestRootPath(root) } @Test @@ -80,7 +62,7 @@ class DraftRepositoryTest : RobolectricTest() { content = DraftContent( text = "hello draft", - visibility = UiTimelineV2.Post.Visibility.Public, + visibility = DraftVisibility.Public, language = listOf("zh", "en"), sensitive = true, spoilerText = "cw", @@ -197,7 +179,7 @@ class DraftRepositoryTest : RobolectricTest() { medias = listOf( ComposeData.Media( - file = createTestFileItem(root = root, name = "a.png", bytes = byteArrayOf(1, 2, 3), type = FileType.Image), + file = fileItem(name = "a.png", bytes = byteArrayOf(1, 2, 3), type = FileType.Image), altText = "cover", ), ), @@ -215,7 +197,7 @@ class DraftRepositoryTest : RobolectricTest() { repository.deleteGroup("group-media-delete") assertNull(repository.draft("group-media-delete").first()) - assertFalse(fileSystem.exists(persistedMedia.single().cachePath.toPath())) + assertFalse(fileStorage.exists(persistedMedia.single().cachePath.toPath())) } @Test @@ -247,7 +229,7 @@ class DraftRepositoryTest : RobolectricTest() { val draft = repository.draft(groupId).first() assertNotNull(draft) - assertEquals(DraftTargetStatus.FAILED, draft.targets.single().status) + assertEquals(ModelDraftTargetStatus.FAILED, draft.targets.single().status) assertEquals("interrupted", draft.targets.single().errorMessage) assertEquals( groupId, @@ -261,8 +243,19 @@ class DraftRepositoryTest : RobolectricTest() { private fun sampleContent(text: String) = DraftContent( text = text, - visibility = UiTimelineV2.Post.Visibility.Public, + visibility = DraftVisibility.Public, language = listOf("en"), sensitive = false, ) + + private fun fileItem( + name: String?, + bytes: ByteArray, + type: FileType, + ): FileItem = + FileItem( + name, + type, + { bytes }, + ) } diff --git a/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/RestoreDraftUseCaseTest.kt b/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/RestoreDraftUseCaseTest.kt new file mode 100644 index 0000000000..4f4ac2e74e --- /dev/null +++ b/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/RestoreDraftUseCaseTest.kt @@ -0,0 +1,122 @@ +package dev.dimension.flare.data.draft + +import androidx.room3.Room +import dev.dimension.flare.data.account.AccountLookup +import dev.dimension.flare.data.database.app.AppDatabase +import dev.dimension.flare.data.database.app.model.DraftContent +import dev.dimension.flare.data.database.app.model.DraftMediaType +import dev.dimension.flare.data.database.app.model.DraftTargetStatus +import dev.dimension.flare.data.database.app.model.DraftVisibility +import dev.dimension.flare.data.database.memoryDatabaseBuilder +import dev.dimension.flare.data.io.FakeFileStorage +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiAccount +import dev.dimension.flare.ui.model.UiDraftMediaType +import dev.dimension.flare.ui.model.UiDraftStatus +import dev.dimension.flare.ui.model.UiTimelineV2 +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.runTest +import okio.Path.Companion.toPath +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class RestoreDraftUseCaseTest { + private val root = "/restore-draft-use-case-test".toPath() + private lateinit var db: AppDatabase + private lateinit var repository: DraftRepository + private lateinit var useCase: RestoreDraftUseCase + private lateinit var accountLookup: FakeAccountLookup + + @BeforeTest + fun setup() { + db = + Room + .memoryDatabaseBuilder() + .setQueryCoroutineContext(Dispatchers.Unconfined) + .build() + repository = DraftRepository(db, DraftMediaStore(FakeFileStorage(root))) + accountLookup = FakeAccountLookup() + useCase = RestoreDraftUseCase(repository, accountLookup) + } + + @AfterTest + fun tearDown() { + db.close() + } + + @Test + fun restoreDraftMapsDraftDataAndResolvedAccounts() = + runTest { + val account = mastodonAccount("alice", "mastodon.social") + accountLookup.accounts = mapOf(account.accountKey to account) + repository.saveDraft( + SaveDraftInput( + groupId = "restore-1", + content = + DraftContent( + text = "restored text", + visibility = DraftVisibility.Followers, + language = listOf("en"), + sensitive = true, + spoilerText = "cw", + ), + targets = + listOf( + SaveDraftTarget( + accountKey = account.accountKey, + status = DraftTargetStatus.FAILED, + ), + ), + medias = + listOf( + SaveDraftMedia( + cachePath = "/drafts/restore-1/a.png", + fileName = "a.png", + mediaType = DraftMediaType.IMAGE, + altText = "cover", + ), + ), + ), + ) + + val draft = assertNotNull(useCase("restore-1")) + + assertEquals("restore-1", draft.groupId) + assertEquals(UiDraftStatus.FAILED, draft.status) + assertEquals(listOf(account), draft.accounts.map { it.account }) + assertEquals("restored text", draft.data.content) + assertEquals(UiTimelineV2.Post.Visibility.Followers, draft.data.visibility) + assertEquals(listOf("en"), draft.data.language) + assertEquals(true, draft.data.sensitive) + assertEquals("cw", draft.data.spoilerText) + assertEquals(1, draft.medias.size) + assertEquals("a.png", draft.medias.single().fileName) + assertEquals(UiDraftMediaType.IMAGE, draft.medias.single().type) + assertEquals("cover", draft.medias.single().altText) + } + + @Test + fun restoreDraftReturnsNullWhenMissing() = + runTest { + assertNull(useCase("missing")) + } + + private class FakeAccountLookup : AccountLookup { + var accounts: Map = emptyMap() + + override suspend fun find(accountKey: MicroBlogKey): UiAccount? = accounts[accountKey] + } + + private fun mastodonAccount( + id: String, + host: String, + ): UiAccount = + UiAccount.Mastodon( + accountKey = MicroBlogKey(id, host), + instance = host, + ) +} diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/compose/SaveDraftUseCaseTest.kt b/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/SaveDraftUseCaseTest.kt similarity index 88% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/compose/SaveDraftUseCaseTest.kt rename to modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/SaveDraftUseCaseTest.kt index a9d70ae0d6..bd8937e492 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/compose/SaveDraftUseCaseTest.kt +++ b/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/SaveDraftUseCaseTest.kt @@ -1,32 +1,25 @@ -package dev.dimension.flare.ui.presenter.compose +package dev.dimension.flare.data.draft import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import dev.dimension.flare.RobolectricTest -import dev.dimension.flare.common.FileType -import dev.dimension.flare.createTestFileItem -import dev.dimension.flare.createTestRootPath +import dev.dimension.flare.data.io.FileItem +import dev.dimension.flare.data.io.FileType import dev.dimension.flare.data.database.app.AppDatabase -import dev.dimension.flare.data.database.app.model.DraftMediaType -import dev.dimension.flare.data.database.app.model.DraftReferenceType +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.data.datasource.microblog.ComposeData -import dev.dimension.flare.data.io.PlatformPathProducer -import dev.dimension.flare.data.repository.ComposeDraftBundle -import dev.dimension.flare.data.repository.DraftMediaStore -import dev.dimension.flare.data.repository.DraftRepository -import dev.dimension.flare.deleteTestRootPath -import dev.dimension.flare.memoryDatabaseBuilder +import dev.dimension.flare.data.io.FakeFileStorage import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.draft.ComposeDraftBundle +import dev.dimension.flare.model.draft.DraftMediaType +import dev.dimension.flare.model.draft.DraftReferenceType +import dev.dimension.flare.model.draft.DraftVisibility import dev.dimension.flare.ui.model.UiAccount import dev.dimension.flare.ui.model.UiTimelineV2 +import dev.dimension.flare.ui.presenter.compose.ComposeStatus import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest -import okio.FileSystem -import okio.Path import okio.Path.Companion.toPath -import okio.SYSTEM import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -39,18 +32,9 @@ import kotlin.test.assertNull import kotlin.test.assertTrue import kotlin.uuid.Uuid -class SaveDraftUseCaseTest : RobolectricTest() { - private val root = createTestRootPath() - private val fileSystem = FileSystem.SYSTEM - private val pathProducer = - object : PlatformPathProducer { - override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) - - override fun draftMediaFile( - groupId: String, - fileName: String, - ): Path = root.resolve("draft_media").resolve(groupId).resolve(fileName) - } +class SaveDraftUseCaseTest { + private val root = "/save-draft-use-case-test".toPath() + private lateinit var fileStorage: FakeFileStorage private lateinit var db: AppDatabase private lateinit var repository: DraftRepository @@ -59,13 +43,13 @@ class SaveDraftUseCaseTest : RobolectricTest() { @BeforeTest fun setup() { + fileStorage = FakeFileStorage(root) db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() - mediaStore = DraftMediaStore(pathProducer, fileSystem) + mediaStore = DraftMediaStore(fileStorage) repository = DraftRepository(db, mediaStore) useCase = SaveDraftUseCase(repository, mediaStore) } @@ -73,7 +57,6 @@ class SaveDraftUseCaseTest : RobolectricTest() { @AfterTest fun tearDown() { db.close() - deleteTestRootPath(root) } @Test @@ -99,7 +82,7 @@ class SaveDraftUseCaseTest : RobolectricTest() { media( name = "b.mov", bytes = secondBytes, - type = dev.dimension.flare.common.FileType.Video, + type = dev.dimension.flare.data.io.FileType.Video, altText = "clip", ), ), @@ -126,7 +109,7 @@ class SaveDraftUseCaseTest : RobolectricTest() { assertNotNull(draft) assertEquals("hello draft", draft.content.text) - assertEquals(UiTimelineV2.Post.Visibility.Followers, draft.content.visibility) + assertEquals(DraftVisibility.Followers, draft.content.visibility) assertEquals(listOf("zh", "en"), draft.content.language) assertTrue(draft.content.sensitive) assertEquals("cw", draft.content.spoilerText) @@ -147,10 +130,10 @@ class SaveDraftUseCaseTest : RobolectricTest() { assertEquals(listOf("cover", "clip"), draft.medias.map { it.altText }) draft.medias.forEach { media -> - assertTrue(fileSystem.exists(media.cachePath.toPath())) + assertTrue(fileStorage.exists(media.cachePath.toPath())) } - assertContentEquals(firstBytes, fileSystem.read(draft.medias[0].cachePath.toPath()) { readByteArray() }) - assertContentEquals(secondBytes, fileSystem.read(draft.medias[1].cachePath.toPath()) { readByteArray() }) + assertContentEquals(firstBytes, fileStorage.read(draft.medias[0].cachePath.toPath())) + assertContentEquals(secondBytes, fileStorage.read(draft.medias[1].cachePath.toPath())) } @Test @@ -305,7 +288,7 @@ class SaveDraftUseCaseTest : RobolectricTest() { assertNotNull(draft) assertEquals("", draft.content.text) - assertEquals(UiTimelineV2.Post.Visibility.Public, draft.content.visibility) + assertEquals(DraftVisibility.Public, draft.content.visibility) assertEquals(listOf("en"), draft.content.language) assertEquals(false, draft.content.sensitive) assertNull(draft.content.spoilerText) @@ -390,10 +373,10 @@ class SaveDraftUseCaseTest : RobolectricTest() { assertEquals("second", updatedDraft.content.text) assertEquals(1, updatedDraft.medias.size) assertEquals(initialPath, updatedDraft.medias.single().cachePath) - assertTrue(fileSystem.exists(initialPath.toPath())) + assertTrue(fileStorage.exists(initialPath.toPath())) assertEquals( 1, - fileSystem.list(root.resolve("draft_media").resolve(initialGroupId)).size, + fileStorage.list(root.resolve("draft_media").resolve(initialGroupId)).size, ) assertNotEquals("", updatedDraft.medias.single().cachePath) } @@ -437,8 +420,8 @@ class SaveDraftUseCaseTest : RobolectricTest() { val updatedDraft = assertNotNull(repository.draft(groupId).first()) assertEquals(1, updatedDraft.medias.size) - assertFalse(fileSystem.exists(removedPath.toPath())) - assertEquals(1, fileSystem.list(root.resolve("draft_media").resolve(groupId)).size) + assertFalse(fileStorage.exists(removedPath.toPath())) + assertEquals(1, fileStorage.list(root.resolve("draft_media").resolve(groupId)).size) } @Test @@ -541,7 +524,18 @@ class SaveDraftUseCaseTest : RobolectricTest() { altText: String?, ): ComposeData.Media = ComposeData.Media( - file = createTestFileItem(root = root, name = name, bytes = bytes, type = type), + file = fileItem(name = name, bytes = bytes, type = type), altText = altText, ) + + private fun fileItem( + name: String?, + bytes: ByteArray, + type: FileType, + ): FileItem = + FileItem( + name, + type, + { bytes }, + ) } diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/compose/SendDraftUseCaseTest.kt b/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/SendDraftUseCaseTest.kt similarity index 91% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/compose/SendDraftUseCaseTest.kt rename to modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/SendDraftUseCaseTest.kt index ad18502d6d..00ac35496f 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/compose/SendDraftUseCaseTest.kt +++ b/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/draft/SendDraftUseCaseTest.kt @@ -1,37 +1,29 @@ -package dev.dimension.flare.ui.presenter.compose +package dev.dimension.flare.data.draft import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import dev.dimension.flare.RobolectricTest -import dev.dimension.flare.common.FileType -import dev.dimension.flare.createTestFileItem -import dev.dimension.flare.createTestRootPath +import dev.dimension.flare.data.io.FileItem +import dev.dimension.flare.data.io.FileType import dev.dimension.flare.data.database.app.AppDatabase import dev.dimension.flare.data.database.app.model.DraftContent import dev.dimension.flare.data.database.app.model.DraftMediaType import dev.dimension.flare.data.database.app.model.DraftReferenceType import dev.dimension.flare.data.database.app.model.DraftTargetStatus +import dev.dimension.flare.data.database.app.model.DraftVisibility +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.data.datasource.microblog.ComposeData -import dev.dimension.flare.data.io.PlatformPathProducer -import dev.dimension.flare.data.repository.ComposeDraftBundle -import dev.dimension.flare.data.repository.DraftMediaStore -import dev.dimension.flare.data.repository.DraftRepository -import dev.dimension.flare.data.repository.SaveDraftInput -import dev.dimension.flare.data.repository.SaveDraftMedia -import dev.dimension.flare.data.repository.SaveDraftTarget -import dev.dimension.flare.deleteTestRootPath -import dev.dimension.flare.memoryDatabaseBuilder +import dev.dimension.flare.data.io.FakeFileStorage import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.draft.ComposeDraftBundle +import dev.dimension.flare.model.draft.DraftTargetStatus as ModelDraftTargetStatus import dev.dimension.flare.ui.model.UiAccount import dev.dimension.flare.ui.model.UiTimelineV2 +import dev.dimension.flare.ui.presenter.compose.ComposeStatus import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest -import okio.FileSystem -import okio.Path -import okio.SYSTEM +import okio.Path.Companion.toPath import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -44,18 +36,9 @@ import kotlin.test.assertNull import kotlin.test.assertTrue @OptIn(ExperimentalCoroutinesApi::class) -class SendDraftUseCaseTest : RobolectricTest() { - private val root = createTestRootPath() - private val fileSystem = FileSystem.SYSTEM - private val pathProducer = - object : PlatformPathProducer { - override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) - - override fun draftMediaFile( - groupId: String, - fileName: String, - ): Path = root.resolve("draft_media").resolve(groupId).resolve(fileName) - } +class SendDraftUseCaseTest { + private val root = "/send-draft-use-case-test".toPath() + private lateinit var fileStorage: FakeFileStorage private lateinit var db: AppDatabase private lateinit var repository: DraftRepository @@ -63,20 +46,19 @@ class SendDraftUseCaseTest : RobolectricTest() { @BeforeTest fun setup() { + fileStorage = FakeFileStorage(root) db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() - mediaStore = DraftMediaStore(pathProducer, fileSystem) + mediaStore = DraftMediaStore(fileStorage) repository = DraftRepository(db, mediaStore) } @AfterTest fun tearDown() { db.close() - deleteTestRootPath(root) } @Test @@ -167,7 +149,7 @@ class SendDraftUseCaseTest : RobolectricTest() { val draft = assertNotNull(repository.draft("send-partial-failure").first()) assertEquals(1, draft.targets.size) assertEquals(accountB.accountKey, draft.targets.single().accountKey) - assertEquals(DraftTargetStatus.FAILED, draft.targets.single().status) + assertEquals(ModelDraftTargetStatus.FAILED, draft.targets.single().status) assertEquals("account-b failed", draft.targets.single().errorMessage) assertEquals(listOf(accountA.accountKey, accountB.accountKey), sent.map { it.account.accountKey }) val error = assertIs(progresses.last()) @@ -198,7 +180,7 @@ class SendDraftUseCaseTest : RobolectricTest() { val draft = assertNotNull(repository.draft("send-all-failed").first()) assertEquals(2, draft.targets.size) - assertTrue(draft.targets.all { it.status == DraftTargetStatus.FAILED }) + assertTrue(draft.targets.all { it.status == ModelDraftTargetStatus.FAILED }) assertEquals(setOf("all failed"), draft.targets.mapNotNull { it.errorMessage }.toSet()) } @@ -224,7 +206,7 @@ class SendDraftUseCaseTest : RobolectricTest() { val draft = assertNotNull(repository.draft("send-failure").first()) val target = draft.targets.single() - assertEquals(DraftTargetStatus.FAILED, target.status) + assertEquals(ModelDraftTargetStatus.FAILED, target.status) assertEquals("boom", target.errorMessage) assertEquals(ComposeProgressState.Progress(0, 1), progresses.first()) val error = assertIs(progresses.last()) @@ -363,7 +345,7 @@ class SendDraftUseCaseTest : RobolectricTest() { advanceUntilIdle() val draft = assertNotNull(repository.draft("resend-missing-account").first()) - assertEquals(DraftTargetStatus.FAILED, draft.targets.single().status) + assertEquals(ModelDraftTargetStatus.FAILED, draft.targets.single().status) assertEquals( listOf( ComposeProgressState.Progress(0, 0), @@ -401,7 +383,7 @@ class SendDraftUseCaseTest : RobolectricTest() { progresses, ) assertEquals( - DraftTargetStatus.SENDING, + ModelDraftTargetStatus.SENDING, repository .draft("resend-all-sending") .first() @@ -430,7 +412,7 @@ class SendDraftUseCaseTest : RobolectricTest() { content = DraftContent( text = "restore me", - visibility = UiTimelineV2.Post.Visibility.Followers, + visibility = DraftVisibility.Followers, language = listOf("zh"), sensitive = true, spoilerText = "cw", @@ -521,7 +503,7 @@ class SendDraftUseCaseTest : RobolectricTest() { val remainingDraft = assertNotNull(repository.draft("resend-group").first()) assertEquals(1, remainingDraft.targets.size) assertEquals(sendingAccount.accountKey, remainingDraft.targets.single().accountKey) - assertEquals(DraftTargetStatus.SENDING, remainingDraft.targets.single().status) + assertEquals(ModelDraftTargetStatus.SENDING, remainingDraft.targets.single().status) } @Test @@ -678,7 +660,7 @@ class SendDraftUseCaseTest : RobolectricTest() { advanceUntilIdle() val draft = assertNotNull(repository.draft("resend-restore-fail").first()) - assertEquals(DraftTargetStatus.FAILED, draft.targets.single().status) + assertEquals(ModelDraftTargetStatus.FAILED, draft.targets.single().status) assertTrue( draft.targets .single() @@ -690,22 +672,13 @@ class SendDraftUseCaseTest : RobolectricTest() { @Test fun sendBundlePersistFailurePropagatesException() = runTest { - val blockedParent = root.resolve("blocked-send") - fileSystem.write(blockedParent) { - writeUtf8("not a directory") - } val blockedStore = DraftMediaStore( - platformPathProducer = - object : PlatformPathProducer { - override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) - - override fun draftMediaFile( - groupId: String, - fileName: String, - ): Path = blockedParent.resolve(groupId).resolve(fileName) + FakeFileStorage( + onCreateDirectories = { path -> + error("Cannot create directory: $path") }, - fileSystem = fileSystem, + ), ) val account = mastodonAccount("alice", "mastodon.social") val useCase = @@ -798,7 +771,7 @@ class SendDraftUseCaseTest : RobolectricTest() { private fun sampleContent(text: String) = DraftContent( text = text, - visibility = UiTimelineV2.Post.Visibility.Public, + visibility = DraftVisibility.Public, language = listOf("en"), sensitive = false, spoilerText = null, @@ -823,10 +796,21 @@ class SendDraftUseCaseTest : RobolectricTest() { altText: String?, ): ComposeData.Media = ComposeData.Media( - file = createTestFileItem(root = root, name = name, bytes = bytes, type = type), + file = fileItem(name = name, bytes = bytes, type = type), altText = altText, ) + private fun fileItem( + name: String?, + bytes: ByteArray, + type: FileType, + ): FileItem = + FileItem( + name, + type, + { bytes }, + ) + private data class SentCompose( val account: UiAccount, val data: ComposeData, diff --git a/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/io/FakeFileStorage.kt b/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/io/FakeFileStorage.kt new file mode 100644 index 0000000000..7ddf37d56f --- /dev/null +++ b/modules/draft/data/src/commonTest/kotlin/dev/dimension/flare/data/io/FakeFileStorage.kt @@ -0,0 +1,62 @@ +package dev.dimension.flare.data.io + +import okio.Path +import okio.Path.Companion.toPath + +internal class FakeFileStorage( + private val root: Path = "/flare-test".toPath(), + private val onCreateDirectories: (Path) -> Unit = {}, + private val onWrite: (Path, ByteArray) -> Unit = { _, _ -> }, + private val onRead: (Path) -> Unit = {}, + private val onDelete: (Path) -> Unit = {}, +) : FileStorage { + private val directories = mutableSetOf() + private val files = mutableMapOf() + + override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) + + override fun draftMediaFile( + groupId: String, + fileName: String, + ): Path = + root + .resolve("draft_media") + .resolve(groupId) + .resolve(fileName) + + override fun createDirectories(path: Path) { + onCreateDirectories(path) + generateSequence(path) { it.parent } + .forEach { directories += it } + } + + override fun write( + path: Path, + bytes: ByteArray, + ) { + onWrite(path, bytes) + path.parent?.let(::createDirectories) + files[path] = bytes.copyOf() + } + + override fun read(path: Path): ByteArray { + onRead(path) + return checkNotNull(files[path]) { "File not found: $path" } + .copyOf() + } + + override fun exists(path: Path): Boolean = path in files || path in directories + + override fun delete(path: Path) { + onDelete(path) + files -= path + directories -= path + } + + override fun list(path: Path): List = + (files.keys.asSequence() + directories.asSequence()) + .filter { it.parent == path } + .distinct() + .toList() + .sorted() +} diff --git a/modules/draft/model/build.gradle.kts b/modules/draft/model/build.gradle.kts new file mode 100644 index 0000000000..9695f22ad1 --- /dev/null +++ b/modules/draft/model/build.gradle.kts @@ -0,0 +1,70 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.modules.draft.model" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_draft_model_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_draft_model", + ) + } + } + } + } + } + } + + sourceSets { + all { + languageSettings { + optIn("kotlin.uuid.ExperimentalUuidApi") + } + } + val commonMain by getting { + dependencies { + api(projects.core.model) + api(projects.modules.account.api) + api(projects.social.microblog) + api(projects.ui.model) + api(dependencies.platform(libs.compose.bom)) + api(libs.compose.runtime) + api(libs.kotlinx.immutable) + } + } + } +} diff --git a/modules/draft/model/src/commonMain/kotlin/dev/dimension/flare/model/draft/DraftModels.kt b/modules/draft/model/src/commonMain/kotlin/dev/dimension/flare/model/draft/DraftModels.kt new file mode 100644 index 0000000000..e8df1ac48a --- /dev/null +++ b/modules/draft/model/src/commonMain/kotlin/dev/dimension/flare/model/draft/DraftModels.kt @@ -0,0 +1,99 @@ +package dev.dimension.flare.model.draft + +import dev.dimension.flare.data.datasource.microblog.ComposeData +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiAccount +import kotlin.uuid.Uuid + +public data class DraftGroup( + public val groupId: String, + public val content: DraftContent, + public val createdAt: Long, + public val updatedAt: Long, + public val targets: List, + public val medias: List, +) + +public data class DraftContent( + public val text: String, + public val visibility: DraftVisibility, + public val language: List, + public val sensitive: Boolean, + public val spoilerText: String? = null, + public val localOnly: Boolean = false, + public val poll: DraftPoll? = null, + public val reference: DraftReference? = null, +) + +public data class DraftPoll( + public val options: List, + public val expiredAfter: Long, + public val multiple: Boolean, +) + +public data class DraftReference( + public val type: DraftReferenceType, + public val statusKey: MicroBlogKey, + public val rootId: String? = null, +) + +public enum class DraftReferenceType { + REPLY, + QUOTE, + VVO_COMMENT, +} + +public enum class DraftVisibility { + Public, + Home, + Followers, + Specified, + Channel, +} + +public data class DraftTarget( + public val groupId: String, + public val accountKey: MicroBlogKey, + public val status: DraftTargetStatus, + public val errorMessage: String?, + public val attemptCount: Int, + public val lastAttemptAt: Long?, + public val createdAt: Long, + public val updatedAt: Long, +) + +public enum class DraftTargetStatus { + DRAFT, + SENDING, + FAILED, +} + +public data class DraftMedia( + public val mediaId: String, + public val groupId: String, + public val cachePath: String, + public val fileName: String?, + public val mediaType: DraftMediaType, + public val altText: String?, + public val sortOrder: Int, + public val createdAt: Long, +) + +public enum class DraftMediaType { + IMAGE, + VIDEO, + OTHER, +} + +public data class ComposeDraftBundle( + public val accounts: List, + public val template: ComposeData, + public val groupId: String = newDraftGroupId(), +) + +public fun ComposeData.toComposeDraftBundle( + accounts: List, + groupId: String = newDraftGroupId(), +): ComposeDraftBundle = ComposeDraftBundle(accounts = accounts, template = this, groupId = groupId) + +public fun newDraftGroupId(): String = Uuid.random().toString() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiDraft.kt b/modules/draft/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiDraft.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiDraft.kt rename to modules/draft/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiDraft.kt diff --git a/modules/draft/presentation/build.gradle.kts b/modules/draft/presentation/build.gradle.kts new file mode 100644 index 0000000000..c39bcc51f0 --- /dev/null +++ b/modules/draft/presentation/build.gradle.kts @@ -0,0 +1,71 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose.compiler) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.modules.draft.presentation" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_draft_presentation_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_draft_presentation", + ) + } + } + } + } + } + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.core.model) + api(projects.modules.account.api) + api(projects.modules.draft.data) + api(projects.modules.draft.model) + api(projects.social.microblog) + api(projects.ui.model) + api(projects.ui.presenterRuntime) + api(dependencies.platform(libs.compose.bom)) + api(libs.compose.runtime) + api(libs.kotlinx.immutable) + implementation(dependencies.platform(libs.koin.bom)) + implementation(libs.koin.core) + } + } + } +} diff --git a/modules/draft/presentation/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiDraftMapper.kt b/modules/draft/presentation/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiDraftMapper.kt new file mode 100644 index 0000000000..67999c40ef --- /dev/null +++ b/modules/draft/presentation/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiDraftMapper.kt @@ -0,0 +1,42 @@ +package dev.dimension.flare.ui.model + +import dev.dimension.flare.data.draft.toComposeData +import dev.dimension.flare.data.draft.toUiDraftStatus +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.draft.DraftGroup +import dev.dimension.flare.model.draft.DraftMedia +import dev.dimension.flare.model.draft.DraftMediaType +import dev.dimension.flare.ui.render.toUi +import kotlin.time.Instant +import kotlinx.collections.immutable.toImmutableList + +internal fun DraftGroup.toUiDraft(accountProvider: (MicroBlogKey) -> UiDraftAccount?): UiDraft? { + val accounts = + targets + .mapNotNull { target -> accountProvider(target.accountKey) } + .toImmutableList() + if (accounts.isEmpty()) { + return null + } + return UiDraft( + groupId = groupId, + status = toUiDraftStatus(), + updatedAt = Instant.fromEpochMilliseconds(updatedAt).toUi(), + accounts = accounts, + data = content.toComposeData(medias = emptyList()), + medias = medias.map { it.toUiDraftMedia() }.toImmutableList(), + ) +} + +private fun DraftMedia.toUiDraftMedia(): UiDraftMedia = + UiDraftMedia( + cachePath = cachePath, + fileName = fileName, + type = + when (mediaType) { + DraftMediaType.IMAGE -> UiDraftMediaType.IMAGE + DraftMediaType.VIDEO -> UiDraftMediaType.VIDEO + DraftMediaType.OTHER -> UiDraftMediaType.OTHER + }, + altText = altText, + ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/DraftBoxPresenter.kt b/modules/draft/presentation/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/DraftBoxPresenter.kt similarity index 50% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/DraftBoxPresenter.kt rename to modules/draft/presentation/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/DraftBoxPresenter.kt index 8734f01d41..dee32f45d3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/DraftBoxPresenter.kt +++ b/modules/draft/presentation/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/DraftBoxPresenter.kt @@ -6,24 +6,23 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import dev.dimension.flare.common.InAppNotification import dev.dimension.flare.common.Message -import dev.dimension.flare.data.database.app.model.DraftMediaType -import dev.dimension.flare.data.repository.DraftRepository +import dev.dimension.flare.data.account.AccountProfileProvider +import dev.dimension.flare.data.draft.ComposeProgressState +import dev.dimension.flare.data.draft.DraftRepository +import dev.dimension.flare.data.draft.SendDraftUseCase +import dev.dimension.flare.data.draft.toUiDraftStatus +import dev.dimension.flare.model.draft.DraftGroup import dev.dimension.flare.ui.model.UiDraft import dev.dimension.flare.ui.model.UiDraftAccount -import dev.dimension.flare.ui.model.UiDraftMedia -import dev.dimension.flare.ui.model.UiDraftMediaType import dev.dimension.flare.ui.model.UiDraftStatus -import dev.dimension.flare.ui.model.takeSuccess +import dev.dimension.flare.ui.model.toUiDraft import dev.dimension.flare.ui.presenter.PresenterBase -import dev.dimension.flare.ui.presenter.settings.AccountsPresenter -import dev.dimension.flare.ui.render.toUi import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import kotlin.time.Instant public class DraftBoxPresenter : PresenterBase(), @@ -31,74 +30,36 @@ public class DraftBoxPresenter : private val draftRepository: DraftRepository by inject() private val sendDraftUseCase: SendDraftUseCase by inject() private val inAppNotification: InAppNotification by inject() + private val accountProfileProvider: AccountProfileProvider by inject() private val coroutineScope: CoroutineScope by inject() @Composable override fun body(): DraftBoxState { val visibleDrafts by draftRepository.visibleDrafts.collectAsState(emptyList()) val sendingDrafts by draftRepository.sendingDrafts.collectAsState(emptyList()) - val accountsState = remember { AccountsPresenter() }.body() - val avatarMap = - remember( - accountsState.accounts, - ) { - accountsState.accounts - .takeSuccess() - ?.toImmutableList() - ?.associate { (account, profileState) -> - account.accountKey to profileState.takeSuccess()?.avatar - }.orEmpty() - } + val accountProfiles by accountProfileProvider.accounts.collectAsState(emptyList()) val items = - remember(visibleDrafts, sendingDrafts, avatarMap, accountsState.accounts) { + remember(visibleDrafts, sendingDrafts, accountProfiles) { val accountMap = - accountsState.accounts - .takeSuccess() - ?.toImmutableList() - ?.associate { it.account.accountKey to it.account } - .orEmpty() + accountProfiles.associate { it.account.accountKey to it.account } + val avatarMap = + accountProfiles.associate { it.account.accountKey to it.avatar } (visibleDrafts + sendingDrafts) .associateBy { it.groupId } .values .sortedWith( - compareBy({ it.toUiDraftStatus().sortOrder }) + compareBy({ it.toUiDraftStatus().sortOrder }) .thenByDescending { it.updatedAt }, ).mapNotNull { draft -> - val accounts = - draft.targets.mapNotNull { target -> - accountMap[target.accountKey]?.let { account -> - UiDraftAccount( - account = account, - avatar = avatarMap[target.accountKey], - ) - } + draft.toUiDraft { accountKey -> + accountMap[accountKey]?.let { account -> + UiDraftAccount( + account = account, + avatar = avatarMap[accountKey], + ) } - if (accounts.isEmpty()) { - return@mapNotNull null } - UiDraft( - groupId = draft.groupId, - status = draft.toUiDraftStatus(), - updatedAt = Instant.fromEpochMilliseconds(draft.updatedAt).toUi(), - accounts = accounts.toImmutableList(), - data = draft.content.toComposeData(medias = emptyList()), - medias = - draft.medias - .map { media -> - UiDraftMedia( - cachePath = media.cachePath, - fileName = media.fileName, - type = - when (media.mediaType) { - DraftMediaType.IMAGE -> UiDraftMediaType.IMAGE - DraftMediaType.VIDEO -> UiDraftMediaType.VIDEO - DraftMediaType.OTHER -> UiDraftMediaType.OTHER - }, - altText = media.altText, - ) - }.toImmutableList(), - ) }.toImmutableList() } @@ -165,10 +126,3 @@ private val UiDraftStatus.sortOrder: Int UiDraftStatus.FAILED -> 1 UiDraftStatus.DRAFT -> 2 } - -private fun dev.dimension.flare.data.repository.DraftGroup.toUiDraftStatus(): UiDraftStatus = - when { - targets.any { it.status == dev.dimension.flare.data.database.app.model.DraftTargetStatus.SENDING } -> UiDraftStatus.SENDING - targets.any { it.status == dev.dimension.flare.data.database.app.model.DraftTargetStatus.FAILED } -> UiDraftStatus.FAILED - else -> UiDraftStatus.DRAFT - } diff --git a/modules/local/data/build.gradle.kts b/modules/local/data/build.gradle.kts new file mode 100644 index 0000000000..f10af449f2 --- /dev/null +++ b/modules/local/data/build.gradle.kts @@ -0,0 +1,64 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.modules.local.data" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_local_data_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_local_data", + ) + } + } + } + } + } + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.foundation.database) + api(projects.modules.local.model) + api(libs.kotlinx.coroutines.core) + api(libs.kotlinx.immutable) + api(dependencies.platform(libs.koin.bom)) + api(libs.koin.core) + } + } + } +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/LocalFilterRepository.kt b/modules/local/data/src/commonMain/kotlin/dev/dimension/flare/data/local/LocalFilterRepository.kt similarity index 56% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/LocalFilterRepository.kt rename to modules/local/data/src/commonMain/kotlin/dev/dimension/flare/data/local/LocalFilterRepository.kt index 9843c0d0f5..7242f68e82 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/LocalFilterRepository.kt +++ b/modules/local/data/src/commonMain/kotlin/dev/dimension/flare/data/local/LocalFilterRepository.kt @@ -1,20 +1,23 @@ -package dev.dimension.flare.data.repository +package dev.dimension.flare.data.local import dev.dimension.flare.data.database.app.AppDatabase import dev.dimension.flare.data.database.app.model.DbKeywordFilter import dev.dimension.flare.ui.model.UiKeywordFilter +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlin.time.Clock import kotlin.time.Instant -internal class LocalFilterRepository( +public class LocalFilterRepository( private val database: AppDatabase, private val coroutineScope: CoroutineScope, ) { - fun getAllFlow() = + public fun getAllFlow(): Flow> = database .keywordFilterDao() .selectAll() @@ -25,71 +28,74 @@ internal class LocalFilterRepository( }.toImmutableList() } - fun getFlow( + public fun getFlow( forTimeline: Boolean = false, forNotification: Boolean = false, forSearch: Boolean = false, - ) = database - .keywordFilterDao() - .selectNotExpiredFor( - currentTime = Clock.System.now().toEpochMilliseconds(), - forTimeline = if (forTimeline) 1L else 0L, - forNotification = if (forNotification) 1L else 0L, - forSearch = if (forSearch) 1L else 0L, - ).map { - it.map(DbKeywordFilter::toKeywordFilterPattern) - } + ): Flow> = + database + .keywordFilterDao() + .selectNotExpiredFor( + currentTime = Clock.System.now().toEpochMilliseconds(), + forTimeline = if (forTimeline) 1L else 0L, + forNotification = if (forNotification) 1L else 0L, + forSearch = if (forSearch) 1L else 0L, + ).map { + it.map(DbKeywordFilter::toKeywordFilterPattern) + } - fun add( + public fun add( keyword: String, forTimeline: Boolean, forNotification: Boolean, forSearch: Boolean, expiredAt: Instant?, isRegex: Boolean, - ) = coroutineScope.launch { - database.keywordFilterDao().insert( - DbKeywordFilter( - keyword = keyword, - for_timeline = if (forTimeline) 1L else 0L, - for_notification = if (forNotification) 1L else 0L, - for_search = if (forSearch) 1L else 0L, - expired_at = expiredAt?.toEpochMilliseconds() ?: 0L, - is_regex = if (isRegex) 1L else 0L, - ), - ) - } + ): Job = + coroutineScope.launch { + database.keywordFilterDao().insert( + DbKeywordFilter( + keyword = keyword, + for_timeline = if (forTimeline) 1L else 0L, + for_notification = if (forNotification) 1L else 0L, + for_search = if (forSearch) 1L else 0L, + expired_at = expiredAt?.toEpochMilliseconds() ?: 0L, + is_regex = if (isRegex) 1L else 0L, + ), + ) + } - fun update( + public fun update( keyword: String, forTimeline: Boolean, forNotification: Boolean, forSearch: Boolean, expiredAt: Instant?, isRegex: Boolean, - ) = coroutineScope.launch { - database.keywordFilterDao().update( - forTimeline = if (forTimeline) 1L else 0L, - forNotification = if (forNotification) 1L else 0L, - forSearch = if (forSearch) 1L else 0L, - expiredAt = expiredAt?.toEpochMilliseconds() ?: 0L, - isRegex = if (isRegex) 1L else 0L, - keyword = keyword, - ) - } + ): Job = + coroutineScope.launch { + database.keywordFilterDao().update( + forTimeline = if (forTimeline) 1L else 0L, + forNotification = if (forNotification) 1L else 0L, + forSearch = if (forSearch) 1L else 0L, + expiredAt = expiredAt?.toEpochMilliseconds() ?: 0L, + isRegex = if (isRegex) 1L else 0L, + keyword = keyword, + ) + } - fun delete(filter: String) = + public fun delete(filter: String): Job = coroutineScope.launch { database.keywordFilterDao().deleteByKeyword(filter) } - fun clear() = + public fun clear(): Job = coroutineScope.launch { database.keywordFilterDao().deleteAll() } } -internal data class KeywordFilterPattern( +public data class KeywordFilterPattern( val keyword: String, val isRegex: Boolean, val regex: Regex? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/SearchHistoryRepository.kt b/modules/local/data/src/commonMain/kotlin/dev/dimension/flare/data/local/SearchHistoryRepository.kt similarity index 76% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/SearchHistoryRepository.kt rename to modules/local/data/src/commonMain/kotlin/dev/dimension/flare/data/local/SearchHistoryRepository.kt index 026f30c16b..65edf871ae 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/SearchHistoryRepository.kt +++ b/modules/local/data/src/commonMain/kotlin/dev/dimension/flare/data/local/SearchHistoryRepository.kt @@ -1,21 +1,24 @@ -package dev.dimension.flare.data.repository +package dev.dimension.flare.data.local +import dev.dimension.flare.common.ImmutableListWrapper import dev.dimension.flare.common.toImmutableListWrapper import dev.dimension.flare.data.database.app.AppDatabase import dev.dimension.flare.data.database.app.model.DbSearchHistory import dev.dimension.flare.ui.model.UiSearchHistory import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlin.time.Clock import kotlin.time.Instant -internal class SearchHistoryRepository( +public class SearchHistoryRepository( private val database: AppDatabase, private val coroutineScope: CoroutineScope, ) { - val allSearchHistory = + public val allSearchHistory: Flow> = database .searchHistoryDao() .select() @@ -30,7 +33,7 @@ internal class SearchHistoryRepository( .toImmutableListWrapper() } - fun addSearchHistory(keyword: String) = + public fun addSearchHistory(keyword: String): Job = coroutineScope.launch { database.searchHistoryDao().insert( DbSearchHistory( @@ -40,12 +43,12 @@ internal class SearchHistoryRepository( ) } - fun deleteSearchHistory(keyword: String) = + public fun deleteSearchHistory(keyword: String): Job = coroutineScope.launch { database.searchHistoryDao().delete(keyword) } - fun deleteAllSearchHistory() = + public fun deleteAllSearchHistory(): Job = coroutineScope.launch { database.searchHistoryDao().deleteAll() } diff --git a/modules/local/data/src/commonMain/kotlin/dev/dimension/flare/di/LocalDataModule.kt b/modules/local/data/src/commonMain/kotlin/dev/dimension/flare/di/LocalDataModule.kt new file mode 100644 index 0000000000..02b2c3350a --- /dev/null +++ b/modules/local/data/src/commonMain/kotlin/dev/dimension/flare/di/LocalDataModule.kt @@ -0,0 +1,13 @@ +package dev.dimension.flare.di + +import dev.dimension.flare.data.local.LocalFilterRepository +import dev.dimension.flare.data.local.SearchHistoryRepository +import org.koin.core.module.Module +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +public val localDataModule: Module = + module { + singleOf(::LocalFilterRepository) + singleOf(::SearchHistoryRepository) + } diff --git a/modules/local/model/build.gradle.kts b/modules/local/model/build.gradle.kts new file mode 100644 index 0000000000..91ea3b0bdf --- /dev/null +++ b/modules/local/model/build.gradle.kts @@ -0,0 +1,59 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.modules.local.model" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_local_model_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_local_model", + ) + } + } + } + } + } + } + + sourceSets { + val commonMain by getting { + dependencies { + api(dependencies.platform(libs.compose.bom)) + api(libs.compose.runtime) + } + } + } +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiKeywordFilter.kt b/modules/local/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiKeywordFilter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiKeywordFilter.kt rename to modules/local/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiKeywordFilter.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiSearchHistory.kt b/modules/local/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiSearchHistory.kt similarity index 75% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiSearchHistory.kt rename to modules/local/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiSearchHistory.kt index 3bec533002..47bf2ddd1e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiSearchHistory.kt +++ b/modules/local/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiSearchHistory.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.Immutable import kotlin.time.Instant @Immutable -public data class UiSearchHistory internal constructor( +public data class UiSearchHistory( val keyword: String, val createdAt: Instant, ) diff --git a/modules/settings/data/build.gradle.kts b/modules/settings/data/build.gradle.kts new file mode 100644 index 0000000000..d7d6339099 --- /dev/null +++ b/modules/settings/data/build.gradle.kts @@ -0,0 +1,78 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.modules.settings.data" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_settings_data_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_settings_data", + ) + } + } + } + } + } + } + + compilerOptions { + allWarningsAsErrors.set(false) + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.core.model) + api(projects.foundation.database) + api(projects.foundation.datastore) + implementation(projects.modules.account.model) + api(projects.modules.translation.api) + api(libs.datastore.core) + implementation(libs.kotlinx.serialization.json) + api(dependencies.platform(libs.koin.bom)) + api(libs.koin.core) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) + } + } + } +} diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/AppDataStore.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/AppDataStore.kt new file mode 100644 index 0000000000..b9df083c07 --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/AppDataStore.kt @@ -0,0 +1,256 @@ +package dev.dimension.flare.data.datastore + +import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import dev.dimension.flare.common.JSON +import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.common.protobufSerializer +import dev.dimension.flare.data.datastore.model.AppSettings +import dev.dimension.flare.data.datastore.model.ComposeConfigData +import dev.dimension.flare.data.datastore.model.FlareConfig +import dev.dimension.flare.data.io.FileStorage +import dev.dimension.flare.data.model.SettingsExport +import dev.dimension.flare.data.model.SettingsImportData +import dev.dimension.flare.data.model.appearance.AppearanceBag +import dev.dimension.flare.data.model.appearance.AppearanceKey +import dev.dimension.flare.data.model.appearance.AppearancePatch +import dev.dimension.flare.data.model.appearance.GlobalAppearance +import dev.dimension.flare.data.model.appearance.TimelineAppearance +import dev.dimension.flare.data.model.appearance.migrateAppearanceV1ToV2 +import dev.dimension.flare.data.model.appearance.toBag +import dev.dimension.flare.data.model.appearance.toGlobalAppearance +import dev.dimension.flare.data.model.appearance.toPatch +import dev.dimension.flare.data.model.appearance.toTimelineAppearance +import dev.dimension.flare.data.model.decodeLegacyAppearanceSettingsAndTabsExport +import dev.dimension.flare.data.model.decodeLegacyAppearanceSettingsExport +import dev.dimension.flare.data.model.decodeLegacySettingsExport +import dev.dimension.flare.data.model.tab.TabSettingsV2 +import dev.dimension.flare.data.model.tab.migrateTabSettingsV1ToV2 +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.serialization.json.jsonObject + +public class AppDataStore( + private val fileStorage: FileStorage, +) { + private val appearanceMigrationMutex = Mutex() + private var appearanceMigrationCompleted = false + private val tabSettingsMigrationMutex = Mutex() + private var tabSettingsMigrationCompleted = false + + public val flareDataStore: DataStore by lazy { + createDataStore( + name = "flare_config.pb", + defaultValue = FlareConfig(), + ) + } + + public val composeConfigData: DataStore by lazy { + createDataStore( + name = "compose_config.pb", + defaultValue = ComposeConfigData(), + ) + } + + private val appSettingsStore: DataStore by lazy { + createDataStore( + name = "app_settings.pb", + defaultValue = AppSettings(version = ""), + ) + } + + private val appearanceBagStore: DataStore by lazy { + createDataStore( + name = "appearance_bag.pb", + defaultValue = AppearanceBag(), + ) + } + + private val tabSettingsV2Store: DataStore by lazy { + createDataStore( + name = "tab_settings_v2.pb", + defaultValue = TabSettingsV2(), + ) + } + + public val appearanceBag: Flow by lazy { + flow { + ensureAppearanceMigrated() + emitAll( + appearanceBagStore.data + .distinctUntilChanged(), + ) + } + } + + public val appearancePatch: Flow by lazy { + appearanceBag + .map { it.toPatch() } + .distinctUntilChanged() + } + + public val globalAppearance: Flow by lazy { + appearancePatch + .map { it.toGlobalAppearance() } + .distinctUntilChanged() + } + + public val timelineAppearance: Flow by lazy { + appearancePatch + .map { it.toTimelineAppearance() } + .distinctUntilChanged() + } + + public val appSettings: Flow by lazy { + appSettingsStore.data + } + + public val tabSettingsV2: Flow by lazy { + flow { + ensureTabSettingsMigrated() + emitAll(tabSettingsV2Store.data) + } + } + + public suspend fun ensureAppearanceMigrated() { + if (appearanceMigrationCompleted) return + appearanceMigrationMutex.withLock { + if (appearanceMigrationCompleted) return + migrateAppearanceV1ToV2( + fileStorage = fileStorage, + legacyAppearanceSettingsPath = + fileStorage.dataStoreFile(LEGACY_APPEARANCE_SETTINGS_FILE_NAME), + bagStore = appearanceBagStore, + ) + appearanceMigrationCompleted = true + } + } + + public suspend fun updateAppearanceBag(block: AppearanceBag.() -> AppearanceBag) { + ensureAppearanceMigrated() + appearanceBagStore.updateData(block) + } + + public suspend fun replaceAppearanceBag(bag: AppearanceBag) { + updateAppearanceBag { bag } + } + + public suspend fun updateAppearance( + key: AppearanceKey, + value: T, + ) { + updateAppearance { set(key, value) } + } + + public suspend fun updateAppearance(block: AppearancePatch.() -> AppearancePatch) { + updateAppearanceBag { + toPatch().block().toBag() + } + } + + public suspend fun clearAppearance(key: AppearanceKey<*>) { + updateAppearance { clear(key) } + } + + public suspend fun replaceAppearance(patch: AppearancePatch) { + replaceAppearanceBag(patch.toBag()) + } + + public suspend fun replaceAppearance(bag: AppearanceBag) { + replaceAppearanceBag(bag) + } + + public suspend fun ensureTabSettingsMigrated() { + if (tabSettingsMigrationCompleted) return + tabSettingsMigrationMutex.withLock { + if (tabSettingsMigrationCompleted) return + migrateTabSettingsV1ToV2( + fileStorage = fileStorage, + legacyTabSettingsPath = fileStorage.dataStoreFile(LEGACY_TAB_SETTINGS_FILE_NAME), + tabSettingsV2Store = tabSettingsV2Store, + ) + tabSettingsMigrationCompleted = true + } + } + + public suspend fun updateTabSettingsV2(block: TabSettingsV2.() -> TabSettingsV2) { + ensureTabSettingsMigrated() + tabSettingsV2Store.updateData(block) + } + + public suspend fun updateAppSettings(block: AppSettings.() -> AppSettings) { + appSettingsStore.updateData(block) + } + + public suspend fun importSettings(jsonContent: String) { + val imported = decodeSettingsImport(jsonContent) + replaceAppearanceBag(imported.appearanceBag) + updateAppSettings { imported.appSettings } + updateTabSettingsV2 { imported.tabSettingsV2 } + } + + public suspend fun exportSettings(): String { + ensureAppearanceMigrated() + ensureTabSettingsMigrated() + val export = + SettingsExport( + appearanceBag = appearanceBagStore.data.first(), + appSettings = appSettingsStore.data.first(), + tabSettingsV2 = tabSettingsV2Store.data.first(), + ) + return export.encodeJson(SettingsExport.serializer()) + } + + private fun decodeSettingsImport(jsonContent: String): SettingsImportData { + val root = JSON.parseToJsonElement(jsonContent).jsonObject + return when { + "tabSettingsV2" in root && "appearanceBag" in root -> { + val export = jsonContent.decodeJson(SettingsExport.serializer()) + SettingsImportData( + appearanceBag = export.appearanceBag, + appSettings = export.appSettings, + tabSettingsV2 = export.tabSettingsV2, + ) + } + + "tabSettingsV2" in root && "appearanceSettings" in root -> { + decodeLegacyAppearanceSettingsExport(jsonContent) + } + + "tabSettings" in root && "appearanceBag" in root -> { + decodeLegacySettingsExport(jsonContent) + } + + "tabSettings" in root && "appearanceSettings" in root -> { + decodeLegacyAppearanceSettingsAndTabsExport(jsonContent) + } + + else -> { + error("Unsupported settings export format") + } + } + } + + private inline fun createDataStore( + name: String, + defaultValue: T, + ): DataStore = + DataStoreFactory.create( + storage = + createDataStoreStorage( + name = name, + serializer = protobufSerializer(defaultValue), + fileStorage = fileStorage, + ), + ) +} + +private const val LEGACY_APPEARANCE_SETTINGS_FILE_NAME = "appearance_settings.pb" +private const val LEGACY_TAB_SETTINGS_FILE_NAME = "tab_settings.pb" diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/AiPromptDefaults.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/AiPromptDefaults.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/AiPromptDefaults.kt rename to modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/AiPromptDefaults.kt index b9614429c7..a51a85f99c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/AiPromptDefaults.kt +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/AiPromptDefaults.kt @@ -1,7 +1,7 @@ package dev.dimension.flare.data.datastore.model -internal object AiPromptDefaults { - const val TRANSLATE_PROMPT: String = +public object AiPromptDefaults { + public const val TRANSLATE_PROMPT: String = "You are a translation engine. Output only the translated template.\n" + "The target language is {target_language}.\n" + "The input is a plain-text translation template extracted from a social post.\n" + @@ -31,7 +31,7 @@ internal object AiPromptDefaults { "Translate the following template to {target_language}:\n" + "{source_text}" - const val TLDR_PROMPT: String = + public const val TLDR_PROMPT: String = "Summarize the following text in {target_language}\n" + "Respond in raw text, limit the response to 200 characters.\n" + "Text: \"{source_text}\"" diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/AppSettings.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/AppSettings.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/AppSettings.kt rename to modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/AppSettings.kt diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/ComposeConfigData.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/ComposeConfigData.kt new file mode 100644 index 0000000000..637d730819 --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/ComposeConfigData.kt @@ -0,0 +1,19 @@ +package dev.dimension.flare.data.datastore.model + +import dev.dimension.flare.model.MicroBlogKey +import kotlinx.serialization.Serializable + +@Serializable +public data class ComposeConfigData( + val visibility: ComposeVisibility = ComposeVisibility.Public, + val lastAccounts: List = emptyList(), +) + +@Serializable +public enum class ComposeVisibility { + Public, + Home, + Followers, + Specified, + Channel, +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/FlareConfig.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/FlareConfig.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/FlareConfig.kt rename to modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/FlareConfig.kt index f21d10939c..9a6325026c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/FlareConfig.kt +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/FlareConfig.kt @@ -5,6 +5,6 @@ import kotlinx.serialization.Serializable private const val DEFAULT_SERVER_URL = "https://api.flareapp.moe" @Serializable -internal data class FlareConfig( +public data class FlareConfig( val serverUrl: String = DEFAULT_SERVER_URL, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationProviderCacheKey.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/TranslationProviderCacheKey.kt similarity index 61% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationProviderCacheKey.kt rename to modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/TranslationProviderCacheKey.kt index 224b38488a..07c9a179be 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationProviderCacheKey.kt +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/TranslationProviderCacheKey.kt @@ -1,10 +1,8 @@ -package dev.dimension.flare.data.translation +package dev.dimension.flare.data.datastore.model -import dev.dimension.flare.data.datastore.model.AppSettings +public fun AppSettings.translationProviderCacheKey(): String = translateConfig.provider.cacheKey() -internal fun AppSettings.translationProviderCacheKey(): String = translateConfig.provider.cacheKey() - -internal fun AppSettings.TranslateConfig.Provider.cacheKey(): String = +public fun AppSettings.TranslateConfig.Provider.cacheKey(): String = when (this) { AppSettings.TranslateConfig.Provider.AI -> "ai" AppSettings.TranslateConfig.Provider.GoogleWeb -> "google-web" diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/AppearanceEnums.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/AppearanceEnums.kt new file mode 100644 index 0000000000..bce2abfb8a --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/AppearanceEnums.kt @@ -0,0 +1,51 @@ +package dev.dimension.flare.data.model + +import kotlinx.serialization.Serializable + +@Serializable +public enum class PostActionStyle { + Hidden, + LeftAligned, + RightAligned, + Stretch, +} + +@Serializable +public enum class BottomBarStyle { + Floating, + Classic, +} + +@Serializable +public enum class BottomBarBehavior { + AlwaysShow, + HideOnScroll, + MinimizeOnScroll, +} + +@Serializable +public enum class Theme { + LIGHT, + DARK, + SYSTEM, +} + +@Serializable +public enum class AvatarShape { + CIRCLE, + SQUARE, +} + +@Serializable +public enum class VideoAutoplay { + ALWAYS, + WIFI, + NEVER, +} + +@Serializable +public enum class TimelineDisplayMode { + Card, + Plain, + Gallery, +} diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/LegacySettingsFile.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/LegacySettingsFile.kt new file mode 100644 index 0000000000..2c65279219 --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/LegacySettingsFile.kt @@ -0,0 +1,39 @@ +package dev.dimension.flare.data.model + +import dev.dimension.flare.common.PlatformDispatchers +import dev.dimension.flare.data.io.FileStorage +import kotlinx.coroutines.withContext +import okio.Path + +internal suspend fun legacySettingsFileExists( + fileStorage: FileStorage, + path: Path, +): Boolean = + withContext(PlatformDispatchers.IO) { + runCatching { + fileStorage.exists(path) + }.getOrDefault(false) + } + +internal suspend fun readLegacySettingsFile( + fileStorage: FileStorage, + path: Path, +): ByteArray? = + withContext(PlatformDispatchers.IO) { + runCatching { + fileStorage.read(path) + }.getOrNull() + } + +internal suspend fun deleteLegacySettingsFile( + fileStorage: FileStorage, + path: Path, +) { + withContext(PlatformDispatchers.IO) { + runCatching { + if (fileStorage.exists(path)) { + fileStorage.delete(path) + } + } + } +} diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/LegacySettingsImport.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/LegacySettingsImport.kt new file mode 100644 index 0000000000..adef07d1fc --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/LegacySettingsImport.kt @@ -0,0 +1,64 @@ +package dev.dimension.flare.data.model + +import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.data.datastore.model.AppSettings +import dev.dimension.flare.data.model.appearance.AppearanceBag +import dev.dimension.flare.data.model.appearance.LegacyAppearanceSettings +import dev.dimension.flare.data.model.appearance.toBag +import dev.dimension.flare.data.model.tab.TabSettingsV2 +import dev.dimension.flare.data.model.tab.toTabSettingsV2 +import kotlinx.serialization.Serializable + +internal data class SettingsImportData( + val appearanceBag: AppearanceBag, + val appSettings: AppSettings, + val tabSettingsV2: TabSettingsV2, +) + +internal fun decodeLegacyAppearanceSettingsExport(jsonContent: String): SettingsImportData { + val export = jsonContent.decodeJson(LegacyAppearanceSettingsExport.serializer()) + return SettingsImportData( + appearanceBag = export.appearanceSettings.toBag(), + appSettings = export.appSettings, + tabSettingsV2 = export.tabSettingsV2, + ) +} + +internal fun decodeLegacySettingsExport(jsonContent: String): SettingsImportData { + val export = jsonContent.decodeJson(LegacySettingsExport.serializer()) + return SettingsImportData( + appearanceBag = export.appearanceBag, + appSettings = export.appSettings, + tabSettingsV2 = export.tabSettings.toTabSettingsV2(), + ) +} + +internal fun decodeLegacyAppearanceSettingsAndTabsExport(jsonContent: String): SettingsImportData { + val export = jsonContent.decodeJson(LegacyAppearanceSettingsAndTabsExport.serializer()) + return SettingsImportData( + appearanceBag = export.appearanceSettings.toBag(), + appSettings = export.appSettings, + tabSettingsV2 = export.tabSettings.toTabSettingsV2(), + ) +} + +@Serializable +internal data class LegacyAppearanceSettingsExport( + val appearanceSettings: LegacyAppearanceSettings, + val appSettings: AppSettings, + val tabSettingsV2: TabSettingsV2, +) + +@Serializable +internal data class LegacySettingsExport( + val appearanceBag: AppearanceBag, + val appSettings: AppSettings, + val tabSettings: TabSettings, +) + +@Serializable +internal data class LegacyAppearanceSettingsAndTabsExport( + val appearanceSettings: LegacyAppearanceSettings, + val appSettings: AppSettings, + val tabSettings: TabSettings, +) diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/SettingsExport.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/SettingsExport.kt new file mode 100644 index 0000000000..ec56189dbf --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/SettingsExport.kt @@ -0,0 +1,13 @@ +package dev.dimension.flare.data.model + +import dev.dimension.flare.data.datastore.model.AppSettings +import dev.dimension.flare.data.model.appearance.AppearanceBag +import dev.dimension.flare.data.model.tab.TabSettingsV2 +import kotlinx.serialization.Serializable + +@Serializable +public data class SettingsExport( + val appearanceBag: AppearanceBag, + val appSettings: AppSettings, + val tabSettingsV2: TabSettingsV2, +) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/TabSettings.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/TabSettings.kt similarity index 68% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/model/TabSettings.kt rename to modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/TabSettings.kt index 1d990ae6b2..d3d0f7d309 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/TabSettings.kt +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/TabSettings.kt @@ -1,41 +1,11 @@ package dev.dimension.flare.data.model -import androidx.compose.runtime.Immutable -import dev.dimension.flare.data.database.app.model.SubscriptionType import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.UiList -import dev.dimension.flare.ui.model.UiRssSource -import dev.dimension.flare.ui.presenter.home.DiscoverStatusTimelinePresenter -import dev.dimension.flare.ui.presenter.home.HomeTimelinePresenter -import dev.dimension.flare.ui.presenter.home.MixedTimelinePresenter -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import dev.dimension.flare.ui.presenter.home.bluesky.BlueskyBookmarkTimelinePresenter -import dev.dimension.flare.ui.presenter.home.bluesky.BlueskyFeedTimelinePresenter -import dev.dimension.flare.ui.presenter.home.mastodon.MastodonBookmarkTimelinePresenter -import dev.dimension.flare.ui.presenter.home.mastodon.MastodonFavouriteTimelinePresenter -import dev.dimension.flare.ui.presenter.home.mastodon.MastodonLocalTimelinePresenter -import dev.dimension.flare.ui.presenter.home.mastodon.MastodonPublicTimelinePresenter -import dev.dimension.flare.ui.presenter.home.misskey.MissKeyLocalTimelinePresenter -import dev.dimension.flare.ui.presenter.home.misskey.MissKeyPublicTimelinePresenter -import dev.dimension.flare.ui.presenter.home.misskey.MisskeyFavouriteTimelinePresenter -import dev.dimension.flare.ui.presenter.home.misskey.MisskeyHybridTimelinePresenter -import dev.dimension.flare.ui.presenter.home.rss.AllRssTimelinePresenter -import dev.dimension.flare.ui.presenter.home.rss.RssTimelinePresenter -import dev.dimension.flare.ui.presenter.home.rss.SubscriptionTimelinePresenter -import dev.dimension.flare.ui.presenter.home.vvo.VVOFavouriteTimelinePresenter -import dev.dimension.flare.ui.presenter.home.vvo.VVOLikeTimelinePresenter -import dev.dimension.flare.ui.presenter.home.xqt.XQTBookmarkTimelinePresenter -import dev.dimension.flare.ui.presenter.home.xqt.XQTDeviceFollowTimelinePresenter -import dev.dimension.flare.ui.presenter.home.xqt.XQTFeaturedTimelinePresenter -import dev.dimension.flare.ui.presenter.list.AntennasTimelinePresenter -import dev.dimension.flare.ui.presenter.list.ChannelTimelinePresenter -import dev.dimension.flare.ui.presenter.list.ListTimelinePresenter import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.serialization.Serializable -@Immutable @Serializable internal data class TabSettings( val secondaryItems: List? = null, @@ -43,7 +13,6 @@ internal data class TabSettings( val mainTabs: List = listOf(), ) -@Immutable @Serializable internal sealed class TabItem { // for iOS @@ -60,7 +29,6 @@ internal interface WithAccountTabItem { val account: AccountType } -@Immutable @Serializable internal sealed class AccountTabItem : TabItem(), @@ -68,22 +36,27 @@ internal sealed class AccountTabItem : abstract override val account: AccountType } -@Immutable @Serializable internal data class TabMetaData( val title: TitleType, val icon: IconType, ) +@Serializable +internal enum class LegacySubscriptionType { + RSS, + MASTODON_TRENDS, + MASTODON_PUBLIC, + MASTODON_LOCAL, +} + @Serializable internal sealed class TitleType { - @Immutable @Serializable internal data class Text( val content: String, ) : TitleType() - @Immutable @Serializable internal data class Localized( val key: LocalizedKey, @@ -128,7 +101,6 @@ internal data object AllNotificationTabItem : TabItem() { } // keep this here for compatibility -@Immutable @Serializable internal data class NotificationTabItem( override val account: AccountType, @@ -139,11 +111,8 @@ internal data class NotificationTabItem( override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } -@Immutable @Serializable internal sealed class TimelineTabItem : TabItem() { - internal abstract fun createPresenter(): TimelinePresenter - internal companion object { internal val default: ImmutableList = persistentListOf( @@ -151,7 +120,6 @@ internal sealed class TimelineTabItem : TabItem() { AllNotificationTabItem, DiscoverTabItem, ) - internal val guest: ImmutableList = persistentListOf( HomeTabItem, @@ -160,7 +128,6 @@ internal sealed class TimelineTabItem : TabItem() { } } -@Immutable @Serializable internal data object HomeTabItem : TabItem() { override val metaData: TabMetaData = @@ -173,7 +140,6 @@ internal data object HomeTabItem : TabItem() { override fun update(metaData: TabMetaData): TabItem = this } -@Immutable @Serializable internal data class HomeTimelineTabItem( override val metaData: TabMetaData, @@ -182,10 +148,7 @@ internal data class HomeTimelineTabItem( WithAccountTabItem { override val key: String = "home_$account" - override fun createPresenter(): TimelinePresenter = HomeTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) - internal constructor(accountType: AccountType) : this( account = accountType, @@ -195,7 +158,6 @@ internal data class HomeTimelineTabItem( icon = IconType.Material(dev.dimension.flare.ui.model.UiIcon.Home), ), ) - internal constructor( accountKey: MicroBlogKey, title: String, @@ -211,7 +173,6 @@ internal data class HomeTimelineTabItem( ) } -@Immutable @Serializable internal data class MixedTimelineTabItem( val subTimelineTabItem: List, @@ -221,8 +182,6 @@ internal data class MixedTimelineTabItem( icon = IconType.Material(dev.dimension.flare.ui.model.UiIcon.Rss), ), ) : TimelineTabItem() { - override fun createPresenter(): TimelinePresenter = MixedTimelinePresenter(subTimelineTabItem.map { it.createPresenter() }) - override val key: String get() = buildString { @@ -236,7 +195,6 @@ internal data class MixedTimelineTabItem( override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } -@Immutable @Serializable internal data class ListTimelineTabItem( override val account: AccountType, @@ -244,28 +202,11 @@ internal data class ListTimelineTabItem( override val metaData: TabMetaData, ) : TimelineTabItem(), WithAccountTabItem { - internal constructor(accountKey: MicroBlogKey, data: UiList) : this( - listId = data.id, - account = AccountType.Specific(accountKey), - metaData = - TabMetaData( - title = TitleType.Text(data.title), - icon = - IconType.Mixed( - icon = dev.dimension.flare.ui.model.UiIcon.List, - accountKey = accountKey, - ), - ), - ) - override val key: String = "list_${account}_$listId" - override fun createPresenter(): TimelinePresenter = ListTimelinePresenter(account, listId) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } -@Immutable @Serializable internal data class AllListTabItem( override val account: AccountType, @@ -277,7 +218,6 @@ internal data class AllListTabItem( } internal object Mastodon { - @Immutable @Serializable internal data class LocalTimelineTabItem( override val account: AccountType, @@ -286,12 +226,9 @@ internal object Mastodon { WithAccountTabItem { override val key: String = "local_$account" - override fun createPresenter(): TimelinePresenter = MastodonLocalTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class PublicTimelineTabItem( override val account: AccountType, @@ -300,12 +237,9 @@ internal object Mastodon { WithAccountTabItem { override val key: String = "internal_$account" - override fun createPresenter(): TimelinePresenter = MastodonPublicTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class BookmarkTimelineTabItem( override val account: AccountType, @@ -314,12 +248,9 @@ internal object Mastodon { WithAccountTabItem { override val key: String = "bookmark_$account" - override fun createPresenter(): TimelinePresenter = MastodonBookmarkTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class FavouriteTimelineTabItem( override val account: AccountType, @@ -328,14 +259,11 @@ internal object Mastodon { WithAccountTabItem { override val key: String = "favourite_$account" - override fun createPresenter(): TimelinePresenter = MastodonFavouriteTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } } internal object Misskey { - @Immutable @Serializable internal data class LocalTimelineTabItem( override val account: AccountType, @@ -344,12 +272,9 @@ internal object Misskey { WithAccountTabItem { override val key: String = "local_$account" - override fun createPresenter(): TimelinePresenter = MissKeyLocalTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class GlobalTimelineTabItem( override val account: AccountType, @@ -358,12 +283,9 @@ internal object Misskey { WithAccountTabItem { override val key: String = "global_$account" - override fun createPresenter(): TimelinePresenter = MissKeyPublicTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class HybridTimelineTabItem( override val account: AccountType, @@ -372,12 +294,9 @@ internal object Misskey { WithAccountTabItem { override val key: String = "hybrid_$account" - override fun createPresenter(): TimelinePresenter = MisskeyHybridTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class FavouriteTimelineTabItem( override val account: AccountType, @@ -386,12 +305,9 @@ internal object Misskey { WithAccountTabItem { override val key: String = "favourite_$account" - override fun createPresenter(): TimelinePresenter = MisskeyFavouriteTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class AntennasListTabItem( override val account: AccountType, @@ -402,7 +318,6 @@ internal object Misskey { override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class AntennasTimelineTabItem( val antennasId: String, @@ -410,28 +325,11 @@ internal object Misskey { override val metaData: TabMetaData, ) : TimelineTabItem(), WithAccountTabItem { - internal constructor(accountKey: MicroBlogKey, data: UiList) : this( - antennasId = data.id, - account = AccountType.Specific(accountKey), - metaData = - TabMetaData( - title = TitleType.Text(data.title), - icon = - IconType.Mixed( - icon = dev.dimension.flare.ui.model.UiIcon.Rss, - accountKey = accountKey, - ), - ), - ) - override val key: String = "antennas_${account}_$antennasId" - override fun createPresenter(): TimelinePresenter = AntennasTimelinePresenter(account, antennasId) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class ChannelTimelineTabItem( val channelId: String, @@ -439,28 +337,11 @@ internal object Misskey { override val metaData: TabMetaData, ) : TimelineTabItem(), WithAccountTabItem { - internal constructor(accountKey: MicroBlogKey, data: UiList) : this( - channelId = data.id, - account = AccountType.Specific(accountKey), - metaData = - TabMetaData( - title = TitleType.Text(data.title), - icon = - IconType.Mixed( - icon = dev.dimension.flare.ui.model.UiIcon.List, - accountKey = accountKey, - ), - ), - ) - override val key: String = "channel_${account}_$channelId" - override fun createPresenter(): TimelinePresenter = ChannelTimelinePresenter(account, channelId) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class ChannelListTabItem( override val account: AccountType, @@ -473,7 +354,6 @@ internal object Misskey { } internal object XQT { - @Immutable @Serializable internal data class FeaturedTimelineTabItem( override val account: AccountType, @@ -482,12 +362,9 @@ internal object XQT { WithAccountTabItem { override val key: String = "featured_$account" - override fun createPresenter(): TimelinePresenter = XQTFeaturedTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class BookmarkTimelineTabItem( override val account: AccountType, @@ -496,12 +373,9 @@ internal object XQT { WithAccountTabItem { override val key: String = "bookmark_$account" - override fun createPresenter(): TimelinePresenter = XQTBookmarkTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class DeviceFollowTimelineTabItem( override val account: AccountType, @@ -514,14 +388,11 @@ internal object XQT { WithAccountTabItem { override val key: String = "device_follow_$account" - override fun createPresenter(): TimelinePresenter = XQTDeviceFollowTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } } internal object Bluesky { - @Immutable @Serializable internal data class FeedsTabItem( override val account: AccountType, @@ -532,7 +403,6 @@ internal object Bluesky { override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class FeedTabItem( override val account: AccountType, @@ -540,28 +410,11 @@ internal object Bluesky { override val metaData: TabMetaData, ) : TimelineTabItem(), WithAccountTabItem { - internal constructor(accountKey: MicroBlogKey, data: UiList) : this( - uri = data.id, - account = AccountType.Specific(accountKey), - metaData = - TabMetaData( - title = TitleType.Text(data.title), - icon = - IconType.Mixed( - icon = dev.dimension.flare.ui.model.UiIcon.Feeds, - accountKey = accountKey, - ), - ), - ) - - override fun createPresenter(): TimelinePresenter = BlueskyFeedTimelinePresenter(account, uri) - override val key: String = "feed_${account}_$uri" override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class BookmarkTimelineTabItem( override val account: AccountType, @@ -570,10 +423,7 @@ internal object Bluesky { WithAccountTabItem { override val key: String = "bookmark_$account" - override fun createPresenter(): TimelinePresenter = BlueskyBookmarkTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) - internal constructor(accountType: AccountType) : this( account = accountType, @@ -587,7 +437,6 @@ internal object Bluesky { } internal object VVo { - @Immutable @Serializable internal data class FeaturedTimelineTabItem( override val account: AccountType, @@ -596,12 +445,9 @@ internal object VVo { WithAccountTabItem { override val key: String = "featured_$account" - override fun createPresenter(): TimelinePresenter = DiscoverStatusTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class FavoriteTimelineTabItem( override val account: AccountType, @@ -610,12 +456,9 @@ internal object VVo { WithAccountTabItem { override val key: String = "favorite_$account" - override fun createPresenter(): TimelinePresenter = VVOFavouriteTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } - @Immutable @Serializable internal data class LikedTimelineTabItem( override val account: AccountType, @@ -624,13 +467,10 @@ internal object VVo { WithAccountTabItem { override val key: String = "liked_$account" - override fun createPresenter(): TimelinePresenter = VVOLikeTimelinePresenter(account) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } } -@Immutable @Serializable internal data class RssTimelineTabItem( val feedUrl: String, @@ -639,25 +479,9 @@ internal data class RssTimelineTabItem( ) : TimelineTabItem() { override val key: String = "rss_$feedUrl" - override fun createPresenter(): TimelinePresenter = RssTimelinePresenter(feedUrl) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) - - internal constructor(data: UiRssSource) : this( - data.url, - favIcon = data.favIcon, - metaData = - TabMetaData( - title = TitleType.Text(data.title ?: data.url), - icon = - data.favIcon?.let { - IconType.Url(it) - } ?: IconType.Material(dev.dimension.flare.ui.model.UiIcon.Rss), - ), - ) } -@Immutable @Serializable internal data class AllRssTimelineTabItem( override val metaData: TabMetaData = @@ -669,43 +493,20 @@ internal data class AllRssTimelineTabItem( override val key: String = "all_rss" override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) - - override fun createPresenter(): TimelinePresenter = AllRssTimelinePresenter() } -@Immutable @Serializable internal data class SubscriptionTimelineTabItem( val subscriptionUrl: String, - val subscriptionType: SubscriptionType, + val subscriptionType: LegacySubscriptionType, override val metaData: TabMetaData, val favIcon: String? = null, ) : TimelineTabItem() { override val key: String = "subscription_${subscriptionType.name}_$subscriptionUrl" - override fun createPresenter(): TimelinePresenter = SubscriptionTimelinePresenter(subscriptionType, subscriptionUrl) - override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) - - internal constructor(data: UiRssSource) : this( - subscriptionUrl = data.url, - subscriptionType = data.type, - favIcon = data.favIcon, - metaData = - TabMetaData( - title = TitleType.Text(data.title ?: data.url), - icon = - data.favIcon?.let { - IconType.Url(it) - } ?: when (data.type) { - SubscriptionType.RSS -> IconType.Material(dev.dimension.flare.ui.model.UiIcon.Rss) - else -> IconType.FavIcon(data.host) - }, - ), - ) } -@Immutable @Serializable internal data class ProfileTabItem( override val account: AccountType, @@ -730,7 +531,6 @@ internal data class ProfileTabItem( override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } -@Immutable @Serializable internal data object DiscoverTabItem : TabItem() { override val metaData: TabMetaData @@ -758,7 +558,6 @@ internal data object SettingsTabItem : TabItem() { override fun update(metaData: TabMetaData): TabItem = this } -@Immutable @Serializable internal data class DirectMessageTabItem( override val account: AccountType, @@ -769,7 +568,6 @@ internal data class DirectMessageTabItem( override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData) } -@Immutable @Serializable internal data object RssTabItem : TabItem() { override val key: String = "rss" diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceBag.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceBag.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceBag.kt rename to modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceBag.kt index d973b05f0b..c26339b710 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceBag.kt +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceBag.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.protobuf.ProtoNumber @OptIn(ExperimentalSerializationApi::class) @Serializable -internal data class AppearanceBag( +public data class AppearanceBag( @ProtoNumber(1) val schemaVersion: Int = 2, @ProtoNumber(2) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceKey.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceKey.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceKey.kt rename to modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceKey.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceKeys.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceKeys.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceKeys.kt rename to modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceKeys.kt diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceMigration.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceMigration.kt new file mode 100644 index 0000000000..6a9c160fa9 --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceMigration.kt @@ -0,0 +1,181 @@ +package dev.dimension.flare.data.model.appearance + +import androidx.datastore.core.DataStore +import dev.dimension.flare.data.io.FileStorage +import dev.dimension.flare.data.model.deleteLegacySettingsFile +import dev.dimension.flare.data.model.legacySettingsFileExists +import dev.dimension.flare.data.model.readLegacySettingsFile +import kotlinx.coroutines.flow.first +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.encodeToHexString +import kotlinx.serialization.protobuf.ProtoBuf +import okio.Path + +public suspend fun migrateAppearanceV1ToV2( + fileStorage: FileStorage, + legacyAppearanceSettingsPath: Path, + bagStore: DataStore, +) { + if (!legacyAppearanceSettingsExists(fileStorage, legacyAppearanceSettingsPath)) return + if (bagStore.data + .first() + .entries + .isNotEmpty() + ) { + deleteLegacyAppearanceSettings(fileStorage, legacyAppearanceSettingsPath) + return + } + + val v1 = readLegacyAppearanceSettings(fileStorage, legacyAppearanceSettingsPath) + if (v1 != null) { + bagStore.updateData { v1.toBag() } + } + deleteLegacyAppearanceSettings(fileStorage, legacyAppearanceSettingsPath) +} + +internal suspend fun legacyAppearanceSettingsExists( + fileStorage: FileStorage, + path: Path, +): Boolean = legacySettingsFileExists(fileStorage, path) + +@OptIn(ExperimentalSerializationApi::class) +internal suspend fun readLegacyAppearanceSettings( + fileStorage: FileStorage, + path: Path, +): LegacyAppearanceSettings? = + readLegacySettingsFile(fileStorage, path) + ?.let { bytes -> + runCatching { + ProtoBuf.decodeFromByteArray(bytes) + }.getOrNull() + } + +internal suspend fun deleteLegacyAppearanceSettings( + fileStorage: FileStorage, + path: Path, +) { + deleteLegacySettingsFile(fileStorage, path) +} + +@Serializable +internal data class LegacyAppearanceSettings( + val theme: LegacyTheme = LegacyTheme.SYSTEM, + val dynamicTheme: Boolean = true, + val colorSeed: ULong = 0x02EBD2u, + val avatarShape: LegacyAvatarShape = LegacyAvatarShape.CIRCLE, + @Deprecated( + "Use postActionStyle instead", + ReplaceWith("postActionStyle"), + DeprecationLevel.ERROR, + ) + val showActions: Boolean = true, + val pureColorMode: Boolean = true, + val showNumbers: Boolean = true, + val showLinkPreview: Boolean = true, + val showMedia: Boolean = true, + val showSensitiveContent: Boolean = false, + val videoAutoplay: LegacyVideoAutoplay = LegacyVideoAutoplay.WIFI, + val expandMediaSize: Boolean = true, + val compatLinkPreview: Boolean = false, + val fontSizeDiff: Float = 0f, + val lineHeightDiff: Float = 0f, + val showComposeInHomeTimeline: Boolean = true, + val bottomBarStyle: LegacyBottomBarStyle = LegacyBottomBarStyle.Classic, + val bottomBarBehavior: LegacyBottomBarBehavior = LegacyBottomBarBehavior.MinimizeOnScroll, + val inAppBrowser: Boolean = true, + val fullWidthPost: Boolean = false, + val postActionStyle: LegacyPostActionStyle = LegacyPostActionStyle.LeftAligned, + val absoluteTimestamp: Boolean = false, + val showPlatformLogo: Boolean = true, + val timelineDisplayMode: LegacyTimelineDisplayMode = LegacyTimelineDisplayMode.Card, +) + +@Serializable +internal enum class LegacyPostActionStyle { + Hidden, + LeftAligned, + RightAligned, + Stretch, +} + +@Serializable +internal enum class LegacyBottomBarStyle { + Floating, + Classic, +} + +@Serializable +internal enum class LegacyBottomBarBehavior { + AlwaysShow, + HideOnScroll, + MinimizeOnScroll, +} + +@Serializable +internal enum class LegacyTheme { + LIGHT, + DARK, + SYSTEM, +} + +@Serializable +internal enum class LegacyAvatarShape { + CIRCLE, + SQUARE, +} + +@Serializable +internal enum class LegacyVideoAutoplay { + ALWAYS, + WIFI, + NEVER, +} + +@Serializable +internal enum class LegacyTimelineDisplayMode { + Card, + Plain, + Gallery, +} + +@OptIn(ExperimentalSerializationApi::class) +internal fun LegacyAppearanceSettings.toBag(): AppearanceBag = + AppearanceBag( + entries = + mapOf( + entry("app.theme", LegacyTheme.serializer(), theme), + entry("app.dynamic_theme", Boolean.serializer(), dynamicTheme), + entry("app.color_seed", ULong.serializer(), colorSeed), + entry("timeline.avatar_shape", LegacyAvatarShape.serializer(), avatarShape), + entry("app.pure_color_mode", Boolean.serializer(), pureColorMode), + entry("timeline.show_numbers", Boolean.serializer(), showNumbers), + entry("timeline.show_link_preview", Boolean.serializer(), showLinkPreview), + entry("timeline.show_media", Boolean.serializer(), showMedia), + entry("timeline.show_sensitive_content", Boolean.serializer(), showSensitiveContent), + entry("timeline.video_autoplay", LegacyVideoAutoplay.serializer(), videoAutoplay), + entry("timeline.expand_media_size", Boolean.serializer(), expandMediaSize), + entry("timeline.compat_link_preview", Boolean.serializer(), compatLinkPreview), + entry("app.font_size_diff", Float.serializer(), fontSizeDiff), + entry("app.line_height_diff", Float.serializer(), lineHeightDiff), + entry("app.show_compose_in_home_timeline", Boolean.serializer(), showComposeInHomeTimeline), + entry("app.bottom_bar_style", LegacyBottomBarStyle.serializer(), bottomBarStyle), + entry("app.bottom_bar_behavior", LegacyBottomBarBehavior.serializer(), bottomBarBehavior), + entry("app.in_app_browser", Boolean.serializer(), inAppBrowser), + entry("timeline.full_width_post", Boolean.serializer(), fullWidthPost), + entry("timeline.post_action_style", LegacyPostActionStyle.serializer(), postActionStyle), + entry("timeline.absolute_timestamp", Boolean.serializer(), absoluteTimestamp), + entry("timeline.show_platform_logo", Boolean.serializer(), showPlatformLogo), + entry("timeline.display_mode", LegacyTimelineDisplayMode.serializer(), timelineDisplayMode), + ), + ) + +@OptIn(ExperimentalSerializationApi::class) +private fun entry( + id: String, + serializer: KSerializer, + value: T, +): Pair = id to ProtoBuf.encodeToHexString(serializer, value) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceModels.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceModels.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceModels.kt rename to modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceModels.kt index 1b30bcf974..d7db6800e8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceModels.kt +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceModels.kt @@ -1,6 +1,5 @@ package dev.dimension.flare.data.model.appearance -import androidx.compose.runtime.Immutable import dev.dimension.flare.data.model.AvatarShape import dev.dimension.flare.data.model.BottomBarBehavior import dev.dimension.flare.data.model.BottomBarStyle @@ -9,7 +8,6 @@ import dev.dimension.flare.data.model.Theme import dev.dimension.flare.data.model.TimelineDisplayMode import dev.dimension.flare.data.model.VideoAutoplay -@Immutable public data class GlobalAppearance( val theme: Theme = AppearanceKeys.Theme.default, val dynamicTheme: Boolean = AppearanceKeys.DynamicTheme.default, @@ -29,7 +27,6 @@ public data class GlobalAppearance( } } -@Immutable public data class TimelineAppearance( val avatarShape: AvatarShape = AppearanceKeys.AvatarShape.default, val showMedia: Boolean = AppearanceKeys.ShowMedia.default, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatch.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatch.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatch.kt rename to modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatch.kt index fc85b9e269..c3427ea9d1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatch.kt +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatch.kt @@ -1,8 +1,5 @@ package dev.dimension.flare.data.model.appearance -import androidx.compose.runtime.Immutable - -@Immutable public data class AppearancePatch internal constructor( private val values: Map, ) { diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceSynthesizer.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceSynthesizer.kt new file mode 100644 index 0000000000..0a001332f9 --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceSynthesizer.kt @@ -0,0 +1,34 @@ +package dev.dimension.flare.data.model.appearance + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.decodeFromHexString +import kotlinx.serialization.encodeToHexString +import kotlinx.serialization.protobuf.ProtoBuf + +@OptIn(ExperimentalSerializationApi::class) +public fun AppearanceBag.toPatch(): AppearancePatch { + var patch = AppearancePatch.EMPTY + for ((id, bytes) in entries) { + val key = AppearanceKeys[id] ?: continue + val value = + runCatching { + ProtoBuf.decodeFromHexString(key.serializer, bytes) + }.getOrNull() ?: continue + @Suppress("UNCHECKED_CAST") + patch = patch.set(key as AppearanceKey, value) + } + return patch +} + +@OptIn(ExperimentalSerializationApi::class) +public fun AppearancePatch.toBag(): AppearanceBag = + AppearanceBag( + entries = + explicitEntries + .mapNotNull { (id, value) -> + val key = AppearanceKeys[id] ?: return@mapNotNull null + @Suppress("UNCHECKED_CAST") + id to ProtoBuf.encodeToHexString(key.serializer as KSerializer, value) + }.toMap(), + ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TabSettingsMigration.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TabSettingsMigration.kt similarity index 50% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TabSettingsMigration.kt rename to modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TabSettingsMigration.kt index 502ed4d922..ca171fb436 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TabSettingsMigration.kt +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TabSettingsMigration.kt @@ -1,10 +1,11 @@ package dev.dimension.flare.data.model.tab import androidx.datastore.core.DataStore -import dev.dimension.flare.data.io.PlatformPathProducer +import dev.dimension.flare.data.io.FileStorage import dev.dimension.flare.data.model.AllRssTimelineTabItem import dev.dimension.flare.data.model.Bluesky import dev.dimension.flare.data.model.HomeTimelineTabItem +import dev.dimension.flare.data.model.LegacySubscriptionType import dev.dimension.flare.data.model.ListTimelineTabItem import dev.dimension.flare.data.model.Mastodon import dev.dimension.flare.data.model.Misskey @@ -17,68 +18,78 @@ import dev.dimension.flare.data.model.TimelineTabItem import dev.dimension.flare.data.model.TitleType import dev.dimension.flare.data.model.VVo import dev.dimension.flare.data.model.XQT -import dev.dimension.flare.data.platform.BlueskyPlatformSpec -import dev.dimension.flare.data.platform.CommonTimelineSpecs -import dev.dimension.flare.data.platform.MastodonPlatformSpec -import dev.dimension.flare.data.platform.MisskeyPlatformSpec -import dev.dimension.flare.data.platform.RssTimelineSpecs -import dev.dimension.flare.data.platform.VvoPlatformSpec -import dev.dimension.flare.data.platform.XqtPlatformSpec +import dev.dimension.flare.data.model.deleteLegacySettingsFile +import dev.dimension.flare.data.model.legacySettingsFileExists +import dev.dimension.flare.data.model.readLegacySettingsFile import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.model.UiStrings import dev.dimension.flare.ui.model.UiText import dev.dimension.flare.ui.model.asText -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import kotlinx.coroutines.flow.first -import kotlinx.coroutines.withContext import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.encodeToHexString import kotlinx.serialization.protobuf.ProtoBuf -import okio.FileSystem -import okio.SYSTEM +import okio.Path @OptIn(ExperimentalSerializationApi::class) -internal suspend fun migrateTabSettingsV1ToV2( - pathProducer: PlatformPathProducer, +public suspend fun migrateTabSettingsV1ToV2( + fileStorage: FileStorage, + legacyTabSettingsPath: Path, tabSettingsV2Store: DataStore, ) { - withContext(Dispatchers.IO) { - val v1Path = pathProducer.dataStoreFile("tab_settings.pb") - val fs = FileSystem.SYSTEM - if (!fs.exists(v1Path)) return@withContext - - if (tabSettingsV2Store.data - .first() - .homeSlots - .isNotEmpty() - ) { - runCatching { fs.delete(v1Path) } - return@withContext - } - - val v1 = - runCatching { - fs.read(v1Path) { - ProtoBuf.decodeFromByteArray(readByteArray()) - } - }.getOrNull() + if (!legacyTabSettingsExists(fileStorage, legacyTabSettingsPath)) return + if (tabSettingsV2Store.data + .first() + .homeSlots + .isNotEmpty() + ) { + deleteLegacyTabSettings(fileStorage, legacyTabSettingsPath) + return + } - if (v1 != null) { - val migratedSlots = v1.toTabSettingsV2().homeSlots - if (migratedSlots.isNotEmpty()) { - tabSettingsV2Store.updateData { current -> - if (current.homeSlots.isEmpty()) { - current.copy(homeSlots = migratedSlots) - } else { - current - } + val v1 = readLegacyTabSettings(fileStorage, legacyTabSettingsPath) + if (v1 != null) { + val migratedSlots = v1.toTabSettingsV2().homeSlots + if (migratedSlots.isNotEmpty()) { + tabSettingsV2Store.updateData { current -> + if (current.homeSlots.isEmpty()) { + current.copy(homeSlots = migratedSlots) + } else { + current } } } - runCatching { fs.delete(v1Path) } } + deleteLegacyTabSettings(fileStorage, legacyTabSettingsPath) +} + +internal suspend fun legacyTabSettingsExists( + fileStorage: FileStorage, + path: Path, +): Boolean = legacySettingsFileExists(fileStorage, path) + +@OptIn(ExperimentalSerializationApi::class) +internal suspend fun readLegacyTabSettings( + fileStorage: FileStorage, + path: Path, +): TabSettings? = + readLegacySettingsFile(fileStorage, path) + ?.let { bytes -> + runCatching { + ProtoBuf.decodeFromByteArray(bytes) + }.getOrNull() + } + +internal suspend fun deleteLegacyTabSettings( + fileStorage: FileStorage, + path: Path, +) { + deleteLegacySettingsFile(fileStorage, path) } internal fun TabSettings.toTabSettingsV2(): TabSettingsV2 = @@ -111,9 +122,9 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = when (this) { is HomeTimelineTabItem -> { account.specificAccountKey()?.let { accountKey -> - CommonTimelineSpecs.home + LegacyTimelineSpecs.home .target( - data = TimelineSpec.AccountBasedData(accountKey), + data = LegacyAccountBasedData(accountKey), title = metaData.title.toUiText(UiStrings.Home.asText()), icon = metaData.icon, ).toSlot(presentation = metaData.toPresentation()) @@ -122,9 +133,9 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is ListTimelineTabItem -> { account.specificAccountKey()?.let { accountKey -> - CommonTimelineSpecs.list + LegacyTimelineSpecs.list .target( - data = TimelineSpec.AccountResourceData(accountKey, listId), + data = LegacyAccountResourceData(accountKey, listId), title = metaData.title.toUiText(UiStrings.List.asText()), icon = metaData.icon, ).toSlot(presentation = metaData.toPresentation()) @@ -133,7 +144,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is Mastodon.LocalTimelineTabItem -> { account.toAccountBasedSlot( - spec = MastodonPlatformSpec.localTimelineSpec, + spec = LegacyTimelineSpecs.mastodonLocal, metaData = metaData, fallbackTitle = UiStrings.MastodonLocal, ) @@ -141,7 +152,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is Mastodon.PublicTimelineTabItem -> { account.toAccountBasedSlot( - spec = MastodonPlatformSpec.publicTimelineSpec, + spec = LegacyTimelineSpecs.mastodonPublic, metaData = metaData, fallbackTitle = UiStrings.MastodonPublic, ) @@ -149,7 +160,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is Mastodon.BookmarkTimelineTabItem -> { account.toAccountBasedSlot( - spec = MastodonPlatformSpec.bookmarkTimelineSpec, + spec = LegacyTimelineSpecs.mastodonBookmark, metaData = metaData, fallbackTitle = UiStrings.Bookmark, ) @@ -157,7 +168,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is Mastodon.FavouriteTimelineTabItem -> { account.toAccountBasedSlot( - spec = MastodonPlatformSpec.favouriteTimelineSpec, + spec = LegacyTimelineSpecs.mastodonFavourite, metaData = metaData, fallbackTitle = UiStrings.Favourite, ) @@ -165,7 +176,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is Misskey.LocalTimelineTabItem -> { account.toAccountBasedSlot( - spec = MisskeyPlatformSpec.localTimelineSpec, + spec = LegacyTimelineSpecs.misskeyLocal, metaData = metaData, fallbackTitle = UiStrings.MastodonLocal, ) @@ -173,7 +184,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is Misskey.GlobalTimelineTabItem -> { account.toAccountBasedSlot( - spec = MisskeyPlatformSpec.globalTimelineSpec, + spec = LegacyTimelineSpecs.misskeyGlobal, metaData = metaData, fallbackTitle = UiStrings.MastodonPublic, ) @@ -181,7 +192,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is Misskey.HybridTimelineTabItem -> { account.toAccountBasedSlot( - spec = MisskeyPlatformSpec.hybridTimelineSpec, + spec = LegacyTimelineSpecs.misskeyHybrid, metaData = metaData, fallbackTitle = UiStrings.Social, ) @@ -189,7 +200,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is Misskey.FavouriteTimelineTabItem -> { account.toAccountBasedSlot( - spec = MisskeyPlatformSpec.favouriteTimelineSpec, + spec = LegacyTimelineSpecs.misskeyFavourite, metaData = metaData, fallbackTitle = UiStrings.Favourite, ) @@ -197,7 +208,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is Misskey.AntennasTimelineTabItem -> { account.toAccountResourceSlot( - spec = MisskeyPlatformSpec.antennaTimelineSpec, + spec = LegacyTimelineSpecs.misskeyAntenna, resourceId = antennasId, metaData = metaData, fallbackTitle = UiStrings.Antenna, @@ -206,7 +217,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is Misskey.ChannelTimelineTabItem -> { account.toAccountResourceSlot( - spec = MisskeyPlatformSpec.channelTimelineSpec, + spec = LegacyTimelineSpecs.misskeyChannel, resourceId = channelId, metaData = metaData, fallbackTitle = UiStrings.Channel, @@ -215,7 +226,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is XQT.FeaturedTimelineTabItem -> { account.toAccountBasedSlot( - spec = XqtPlatformSpec.featuredTimelineSpec, + spec = LegacyTimelineSpecs.xqtFeatured, metaData = metaData, fallbackTitle = UiStrings.Featured, ) @@ -223,7 +234,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is XQT.BookmarkTimelineTabItem -> { account.toAccountBasedSlot( - spec = XqtPlatformSpec.bookmarkTimelineSpec, + spec = LegacyTimelineSpecs.xqtBookmark, metaData = metaData, fallbackTitle = UiStrings.Bookmark, ) @@ -231,7 +242,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is XQT.DeviceFollowTimelineTabItem -> { account.toAccountBasedSlot( - spec = XqtPlatformSpec.deviceFollowTimelineSpec, + spec = LegacyTimelineSpecs.xqtDeviceFollow, metaData = metaData, fallbackTitle = UiStrings.Posts, ) @@ -239,7 +250,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is Bluesky.FeedTabItem -> { account.toAccountResourceSlot( - spec = BlueskyPlatformSpec.feedTimelineSpec, + spec = LegacyTimelineSpecs.blueskyFeed, resourceId = uri, metaData = metaData, fallbackTitle = UiStrings.Feeds, @@ -248,7 +259,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is Bluesky.BookmarkTimelineTabItem -> { account.toAccountBasedSlot( - spec = BlueskyPlatformSpec.bookmarkTimelineSpec, + spec = LegacyTimelineSpecs.blueskyBookmark, metaData = metaData, fallbackTitle = UiStrings.Bookmark, ) @@ -256,7 +267,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is VVo.FeaturedTimelineTabItem -> { account.toAccountBasedSlot( - spec = CommonTimelineSpecs.discover, + spec = LegacyTimelineSpecs.discover, metaData = metaData, fallbackTitle = UiStrings.Featured, ) @@ -264,7 +275,7 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is VVo.FavoriteTimelineTabItem -> { account.toAccountBasedSlot( - spec = VvoPlatformSpec.favoriteTimelineSpec, + spec = LegacyTimelineSpecs.vvoFavorite, metaData = metaData, fallbackTitle = UiStrings.Bookmark, ) @@ -272,35 +283,35 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = is VVo.LikedTimelineTabItem -> { account.toAccountBasedSlot( - spec = VvoPlatformSpec.likedTimelineSpec, + spec = LegacyTimelineSpecs.vvoLiked, metaData = metaData, fallbackTitle = UiStrings.Liked, ) } is RssTimelineTabItem -> { - RssTimelineSpecs.rss + LegacyTimelineSpecs.rss .target( - data = RssTimelineSpecs.RssData(feedUrl), + data = LegacyRssData(feedUrl), title = metaData.title.toUiText(UiStrings.Rss.asText()), icon = metaData.icon, ).toSlot(presentation = metaData.toPresentation()) } is AllRssTimelineTabItem -> { - RssTimelineSpecs.allRss + LegacyTimelineSpecs.allRss .target( - data = RssTimelineSpecs.AllRssData, + data = LegacyAllRssData, title = metaData.title.toUiText(UiStrings.AllRssFeeds.asText()), icon = metaData.icon, ).toSlot(presentation = metaData.toPresentation()) } is SubscriptionTimelineTabItem -> { - RssTimelineSpecs.subscription + LegacyTimelineSpecs.subscription .target( data = - RssTimelineSpecs.SubscriptionData( + LegacySubscriptionData( subscriptionUrl = subscriptionUrl, subscriptionType = subscriptionType, ), @@ -329,21 +340,21 @@ internal fun TimelineTabItem.toTimelineSlotOrNull(): TimelineSlot? = } private fun AccountType.toAccountBasedSlot( - spec: TimelineSpec, + spec: LegacyTimelineSpec, metaData: TabMetaData, fallbackTitle: UiStrings, ): TimelineSlot? = specificAccountKey()?.let { accountKey -> spec .target( - data = TimelineSpec.AccountBasedData(accountKey), + data = LegacyAccountBasedData(accountKey), title = metaData.title.toUiText(fallbackTitle.asText()), icon = metaData.icon, ).toSlot(presentation = metaData.toPresentation()) } private fun AccountType.toAccountResourceSlot( - spec: TimelineSpec, + spec: LegacyTimelineSpec, resourceId: String, metaData: TabMetaData, fallbackTitle: UiStrings, @@ -351,7 +362,7 @@ private fun AccountType.toAccountResourceSlot( specificAccountKey()?.let { accountKey -> spec .target( - data = TimelineSpec.AccountResourceData(accountKey, resourceId), + data = LegacyAccountResourceData(accountKey, resourceId), title = metaData.title.toUiText(fallbackTitle.asText()), icon = metaData.icon, ).toSlot(presentation = metaData.toPresentation()) @@ -370,3 +381,166 @@ private fun TitleType.toUiText(fallback: UiText): UiText = is TitleType.Text -> UiText.Raw(content) is TitleType.Localized -> runCatching { UiStrings.valueOf(key.name).asText() }.getOrDefault(fallback) } + +private data class LegacyTimelineSpec( + val id: String, + val title: UiStrings, + val icon: dev.dimension.flare.data.model.IconType, + val serializer: KSerializer, + val targetId: (data: T) -> String, +) { + @OptIn(ExperimentalSerializationApi::class) + fun target( + data: T, + title: UiText = this.title.asText(), + icon: dev.dimension.flare.data.model.IconType = this.icon, + ): TimelineSourceRef = + TimelineSourceRef( + id = "$id:${targetId(data)}", + specId = id, + title = title, + icon = icon, + data = ProtoBuf.encodeToHexString(serializer, data), + ) +} + +@Serializable +private data class LegacyAccountBasedData( + val accountKey: MicroBlogKey, +) + +@Serializable +private data class LegacyAccountResourceData( + val accountKey: MicroBlogKey, + val resourceId: String, +) + +@Serializable +private data class LegacyRssData( + val feedUrl: String, +) + +@Serializable +private data object LegacyAllRssData + +@Serializable +private data class LegacySubscriptionData( + val subscriptionUrl: String, + val subscriptionType: LegacySubscriptionType, +) + +private object LegacyTimelineSpecs { + val home = + LegacyTimelineSpec( + id = "common.home", + title = UiStrings.Home, + icon = + dev.dimension.flare.data.model.IconType + .Material(UiIcon.Home), + serializer = LegacyAccountBasedData.serializer(), + targetId = { it.accountKey.toString() }, + ) + + val discover = + LegacyTimelineSpec( + id = "common.discover", + title = UiStrings.Discover, + icon = + dev.dimension.flare.data.model.IconType + .Material(UiIcon.Search), + serializer = LegacyAccountBasedData.serializer(), + targetId = { it.accountKey.toString() }, + ) + + val list = + LegacyTimelineSpec( + id = "common.list", + title = UiStrings.List, + icon = + dev.dimension.flare.data.model.IconType + .Material(UiIcon.List), + serializer = LegacyAccountResourceData.serializer(), + targetId = { "${it.accountKey}:${it.resourceId}" }, + ) + + val mastodonLocal = accountBased("mastodon.local", UiStrings.MastodonLocal, UiIcon.Local) + val mastodonPublic = accountBased("mastodon.public", UiStrings.MastodonPublic, UiIcon.World) + val mastodonBookmark = accountBased("mastodon.bookmark", UiStrings.Bookmark, UiIcon.Bookmark) + val mastodonFavourite = accountBased("mastodon.favourite", UiStrings.Favourite, UiIcon.Favourite) + val misskeyFavourite = accountBased("misskey.favourite", UiStrings.Favourite, UiIcon.Favourite) + val misskeyHybrid = accountBased("misskey.hybrid", UiStrings.Social, UiIcon.Featured) + val misskeyLocal = accountBased("misskey.local", UiStrings.MastodonLocal, UiIcon.Local) + val misskeyGlobal = accountBased("misskey.global", UiStrings.MastodonPublic, UiIcon.World) + val misskeyAntenna = accountResource("misskey.antenna", UiStrings.Antenna, UiIcon.Rss) + val misskeyChannel = accountResource("misskey.channel", UiStrings.Channel, UiIcon.Channel) + val xqtFeatured = accountBased("xqt.featured", UiStrings.Featured, UiIcon.Featured) + val xqtBookmark = accountBased("xqt.bookmark", UiStrings.Bookmark, UiIcon.Bookmark) + val xqtDeviceFollow = accountBased("xqt.device_follow", UiStrings.Posts, UiIcon.List) + val blueskyBookmark = accountBased("bluesky.bookmark", UiStrings.Bookmark, UiIcon.Bookmark) + val blueskyFeed = accountResource("bluesky.feed", UiStrings.Feeds, UiIcon.Feeds) + val vvoFavorite = accountBased("vvo.favorite", UiStrings.Bookmark, UiIcon.Bookmark) + val vvoLiked = accountBased("vvo.liked", UiStrings.Liked, UiIcon.Heart) + + val rss = + LegacyTimelineSpec( + id = "rss.feed", + title = UiStrings.Rss, + icon = + dev.dimension.flare.data.model.IconType + .Material(UiIcon.Rss), + serializer = LegacyRssData.serializer(), + targetId = { it.feedUrl }, + ) + + val allRss = + LegacyTimelineSpec( + id = "rss.all", + title = UiStrings.AllRssFeeds, + icon = + dev.dimension.flare.data.model.IconType + .Material(UiIcon.Rss), + serializer = LegacyAllRssData.serializer(), + targetId = { "all" }, + ) + + val subscription = + LegacyTimelineSpec( + id = "rss.subscription", + title = UiStrings.Rss, + icon = + dev.dimension.flare.data.model.IconType + .Material(UiIcon.Rss), + serializer = LegacySubscriptionData.serializer(), + targetId = { "${it.subscriptionType.name}:${it.subscriptionUrl}" }, + ) + + private fun accountBased( + id: String, + title: UiStrings, + icon: UiIcon, + ): LegacyTimelineSpec = + LegacyTimelineSpec( + id = id, + title = title, + icon = + dev.dimension.flare.data.model.IconType + .Material(icon), + serializer = LegacyAccountBasedData.serializer(), + targetId = { it.accountKey.toString() }, + ) + + private fun accountResource( + id: String, + title: UiStrings, + icon: UiIcon, + ): LegacyTimelineSpec = + LegacyTimelineSpec( + id = id, + title = title, + icon = + dev.dimension.flare.data.model.IconType + .Material(icon), + serializer = LegacyAccountResourceData.serializer(), + targetId = { "${it.accountKey}:${it.resourceId}" }, + ) +} diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelineFilterConfig.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelineFilterConfig.kt new file mode 100644 index 0000000000..5a666f80d4 --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelineFilterConfig.kt @@ -0,0 +1,25 @@ +package dev.dimension.flare.data.model.tab + +import kotlinx.serialization.Serializable + +@Serializable +public data class TimelineFilterConfig( + val excludedKinds: List = emptyList(), + val excludedContents: List = emptyList(), +) + +@Serializable +public enum class TimelinePostKind { + Original, + Reply, + Repost, + Quote, +} + +@Serializable +public enum class TimelinePostContent { + Text, + Image, + Video, + Other, +} diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelinePresentationAppearance.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelinePresentationAppearance.kt new file mode 100644 index 0000000000..dda6317996 --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelinePresentationAppearance.kt @@ -0,0 +1,24 @@ +package dev.dimension.flare.data.model.tab + +import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.data.model.appearance.AppearancePatch +import dev.dimension.flare.data.model.appearance.toBag +import dev.dimension.flare.data.model.appearance.toPatch + +public val TimelinePresentation.appearance: AppearancePatch? + get() = appearanceOverride?.toPatch() + +public fun TimelinePresentation.withOverrides( + titleOverride: String?, + iconOverride: IconType?, + appearancePatch: AppearancePatch?, + enabled: Boolean, + filterConfig: TimelineFilterConfig, +): TimelinePresentation = + TimelinePresentation( + titleOverride = titleOverride, + iconOverride = iconOverride, + appearanceOverride = appearancePatch?.takeUnless { it == AppearancePatch.EMPTY }?.toBag(), + enabled = enabled, + filterConfig = filterConfig, + ) diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelineSettings.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelineSettings.kt new file mode 100644 index 0000000000..76d1e4eb20 --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelineSettings.kt @@ -0,0 +1,126 @@ +package dev.dimension.flare.data.model.tab + +import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.data.model.appearance.AppearanceBag +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.UiText +import dev.dimension.flare.ui.model.asText +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +public data class TabSettingsV2( + public val homeSlots: List = emptyList(), +) + +public const val SYSTEM_HOME_MIXED_TIMELINE_ID: String = "mixed_timeline_system_home" + +@Serializable +public data class TimelineSlot( + public val id: String, + public val content: TimelineSlotContent, + public val presentation: TimelinePresentation = TimelinePresentation(), +) { + public val title: UiText = + presentation.titleOverride?.let { UiText.Raw(it) } ?: when (content) { + is TimelineSlotContent.Source -> content.source.title + is TimelineSlotContent.Group -> UiStrings.MixedTimeline.asText() + } + + public val icon: IconType = + presentation.iconOverride ?: when (content) { + is TimelineSlotContent.Source -> content.source.icon + is TimelineSlotContent.Group -> IconType.Material(UiIcon.Rss) + } +} + +@Serializable +public data class TimelinePresentation( + public val titleOverride: String? = null, + public val iconOverride: IconType? = null, + public val appearanceOverride: AppearanceBag? = null, + public val enabled: Boolean = true, + public val filterConfig: TimelineFilterConfig = TimelineFilterConfig(), +) + +@Serializable +public sealed interface TimelineSlotContent { + @Serializable + @SerialName("source") + public data class Source( + @SerialName("target") + public val source: TimelineSourceRef, + ) : TimelineSlotContent + + @Serializable + @SerialName("group") + public data class Group( + public val children: List = emptyList(), + public val source: GroupSource = GroupSource.Manual, + public val mergePolicy: TimelineMergePolicy = TimelineMergePolicy.Time, + ) : TimelineSlotContent +} + +@Serializable +public enum class GroupSource { + Manual, + SystemHome, +} + +@Serializable +public enum class TimelineMergePolicy { + Time, + TimePerPage, + Staggered, +} + +@Serializable +public data class TimelineSourceRef( + public val id: String, + public val specId: String, + public val title: UiText, + public val icon: IconType, + public val data: String, +) + +public fun TimelineSourceRef.toSlot( + slotId: String = id, + presentation: TimelinePresentation = TimelinePresentation(), +): TimelineSlot = + TimelineSlot( + id = slotId, + content = TimelineSlotContent.Source(this), + presentation = presentation, + ) + +public fun List.normalizeSystemHomeMixedTimeline(enabled: Boolean): List { + val deduplicatedSlots = distinctBy { it.id } + val existingSystemHomeGroup = deduplicatedSlots.firstOrNull { it.isSystemHomeMixedTimeline() } + val slotsWithoutSystemHomeGroup = deduplicatedSlots.filterNot { it.isSystemHomeMixedTimeline() } + if (!enabled || slotsWithoutSystemHomeGroup.size < 2) { + return slotsWithoutSystemHomeGroup + } + + val systemHomeGroup = + TimelineSlot( + id = SYSTEM_HOME_MIXED_TIMELINE_ID, + content = + TimelineSlotContent.Group( + children = slotsWithoutSystemHomeGroup, + source = GroupSource.SystemHome, + mergePolicy = + (existingSystemHomeGroup?.content as? TimelineSlotContent.Group)?.mergePolicy + ?: TimelineMergePolicy.TimePerPage, + ), + presentation = existingSystemHomeGroup?.presentation ?: TimelinePresentation(), + ) + val targetIndex = deduplicatedSlots.indexOfFirst { it.isSystemHomeMixedTimeline() }.takeIf { it >= 0 } ?: 0 + return slotsWithoutSystemHomeGroup + .toMutableList() + .apply { + add(minOf(targetIndex, size), systemHomeGroup) + } +} + +private fun TimelineSlot.isSystemHomeMixedTimeline(): Boolean = (content as? TimelineSlotContent.Group)?.source == GroupSource.SystemHome diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/settings/TranslationSettingsProviderImpl.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/settings/TranslationSettingsProviderImpl.kt new file mode 100644 index 0000000000..0f575a7115 --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/settings/TranslationSettingsProviderImpl.kt @@ -0,0 +1,14 @@ +package dev.dimension.flare.data.settings + +import dev.dimension.flare.data.datastore.AppDataStore +import dev.dimension.flare.data.settings.TranslationSettingsSupport +import dev.dimension.flare.data.translation.TranslationDisplayOptions +import dev.dimension.flare.data.translation.TranslationSettingsProvider +import kotlinx.coroutines.flow.Flow + +public class TranslationSettingsProviderImpl( + private val appDataStore: AppDataStore, +) : TranslationSettingsProvider { + override val displayOptionsFlow: Flow + get() = TranslationSettingsSupport.displayOptionsFlow(appDataStore) +} diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/settings/TranslationSettingsSupport.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/settings/TranslationSettingsSupport.kt new file mode 100644 index 0000000000..1b63bba247 --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/data/settings/TranslationSettingsSupport.kt @@ -0,0 +1,23 @@ +package dev.dimension.flare.data.settings + +import dev.dimension.flare.data.datastore.AppDataStore +import dev.dimension.flare.data.datastore.model.AppSettings +import dev.dimension.flare.data.datastore.model.translationProviderCacheKey +import dev.dimension.flare.data.translation.TranslationDisplayOptions +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map + +public object TranslationSettingsSupport { + public fun displayOptionsFlow(appDataStore: AppDataStore): Flow = + appDataStore.appSettings + .map(::displayOptions) + .distinctUntilChanged() + + public fun displayOptions(settings: AppSettings): TranslationDisplayOptions = + TranslationDisplayOptions( + translationEnabled = true, + autoDisplayEnabled = settings.translateConfig.preTranslate, + providerCacheKey = settings.translationProviderCacheKey(), + ) +} diff --git a/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/di/SettingsDataModule.kt b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/di/SettingsDataModule.kt new file mode 100644 index 0000000000..e6706ac51f --- /dev/null +++ b/modules/settings/data/src/commonMain/kotlin/dev/dimension/flare/di/SettingsDataModule.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.di + +import dev.dimension.flare.data.settings.TranslationSettingsProviderImpl +import dev.dimension.flare.data.translation.TranslationSettingsProvider +import org.koin.core.module.Module +import org.koin.dsl.module + +public val settingsDataModule: Module = + module { + single { TranslationSettingsProviderImpl(get()) } + } diff --git a/modules/settings/data/src/commonTest/kotlin/dev/dimension/flare/data/model/LegacySettingsImportTest.kt b/modules/settings/data/src/commonTest/kotlin/dev/dimension/flare/data/model/LegacySettingsImportTest.kt new file mode 100644 index 0000000000..3f036a4d45 --- /dev/null +++ b/modules/settings/data/src/commonTest/kotlin/dev/dimension/flare/data/model/LegacySettingsImportTest.kt @@ -0,0 +1,129 @@ +package dev.dimension.flare.data.model + +import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.data.datastore.model.AppSettings +import dev.dimension.flare.data.model.appearance.AppearanceBag +import dev.dimension.flare.data.model.appearance.LegacyAppearanceSettings +import dev.dimension.flare.data.model.appearance.LegacyTheme +import dev.dimension.flare.data.model.tab.SYSTEM_HOME_MIXED_TIMELINE_ID +import dev.dimension.flare.data.model.tab.TabSettingsV2 +import dev.dimension.flare.data.model.tab.TimelineSlot +import dev.dimension.flare.data.model.tab.TimelineSlotContent +import dev.dimension.flare.model.AccountType +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiIcon +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class LegacySettingsImportTest { + private val accountKey = MicroBlogKey(id = "alice", host = "example.com") + private val account = AccountType.Specific(accountKey) + + @Test + fun legacyAppearanceSettingsExportDecodesToAppearanceBag() { + val slot = groupSlot("manual") + val export = + LegacyAppearanceSettingsExport( + appearanceSettings = + LegacyAppearanceSettings( + theme = LegacyTheme.DARK, + showNumbers = false, + ), + appSettings = AppSettings(version = "legacy-appearance"), + tabSettingsV2 = TabSettingsV2(homeSlots = listOf(slot)), + ) + + val decoded = decodeLegacyAppearanceSettingsExport(export.encodeJson(LegacyAppearanceSettingsExport.serializer())) + + assertEquals("legacy-appearance", decoded.appSettings.version) + assertEquals(listOf(slot.id), decoded.tabSettingsV2.homeSlots.map { it.id }) + assertTrue(decoded.appearanceBag.entries.containsKey("app.theme")) + assertTrue(decoded.appearanceBag.entries.containsKey("timeline.show_numbers")) + } + + @Test + fun legacySettingsExportDecodesTabsToV2() { + val export = + LegacySettingsExport( + appearanceBag = AppearanceBag(), + appSettings = AppSettings(version = "v1"), + tabSettings = + TabSettings( + enableMixedTimeline = true, + mainTabs = + listOf( + HomeTimelineTabItem(account), + Mastodon.LocalTimelineTabItem( + account = account, + metaData = localMetaData(), + ), + RssTimelineTabItem( + feedUrl = "https://example.com/rss.xml", + metaData = rssMetaData(), + ), + ), + ), + ) + + val decoded = decodeLegacySettingsExport(export.encodeJson(LegacySettingsExport.serializer())) + + assertEquals("v1", decoded.appSettings.version) + assertEquals( + listOf( + SYSTEM_HOME_MIXED_TIMELINE_ID, + "common.home:$accountKey", + "mastodon.local:$accountKey", + "rss.feed:https://example.com/rss.xml", + ), + decoded.tabSettingsV2.homeSlots.map { it.id }, + ) + } + + @Test + fun legacyAppearanceSettingsAndTabsExportDecodesAppearanceAndTabs() { + val export = + LegacyAppearanceSettingsAndTabsExport( + appearanceSettings = LegacyAppearanceSettings(theme = LegacyTheme.DARK), + appSettings = AppSettings(version = "v1-with-appearance"), + tabSettings = + TabSettings( + enableMixedTimeline = false, + mainTabs = + listOf( + Mastodon.LocalTimelineTabItem( + account = account, + metaData = localMetaData(), + ), + ), + ), + ) + + val decoded = + decodeLegacyAppearanceSettingsAndTabsExport( + export.encodeJson(LegacyAppearanceSettingsAndTabsExport.serializer()), + ) + + assertEquals("v1-with-appearance", decoded.appSettings.version) + assertEquals(listOf("mastodon.local:$accountKey"), decoded.tabSettingsV2.homeSlots.map { it.id }) + assertTrue(decoded.appearanceBag.entries.containsKey("app.theme")) + } + + private fun groupSlot(id: String) = + TimelineSlot( + id = id, + content = TimelineSlotContent.Group(), + ) + + private fun localMetaData() = + TabMetaData( + title = TitleType.Localized(TitleType.Localized.LocalizedKey.MastodonLocal), + icon = IconType.Material(UiIcon.Local), + ) + + private fun rssMetaData() = + TabMetaData( + title = TitleType.Text("RSS"), + icon = IconType.Material(UiIcon.Rss), + ) +} diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatchTest.kt b/modules/settings/data/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatchTest.kt similarity index 53% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatchTest.kt rename to modules/settings/data/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatchTest.kt index e5293876af..75ff66e90b 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatchTest.kt +++ b/modules/settings/data/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/AppearancePatchTest.kt @@ -1,29 +1,15 @@ package dev.dimension.flare.data.model.appearance -import dev.dimension.flare.data.model.AppearanceSettings import dev.dimension.flare.data.model.AvatarShape -import dev.dimension.flare.data.model.BottomBarBehavior -import dev.dimension.flare.data.model.BottomBarStyle -import dev.dimension.flare.data.model.IconType import dev.dimension.flare.data.model.PostActionStyle import dev.dimension.flare.data.model.Theme import dev.dimension.flare.data.model.TimelineDisplayMode import dev.dimension.flare.data.model.VideoAutoplay -import dev.dimension.flare.data.model.tab.SourceTimelineTabItemV2 -import dev.dimension.flare.data.model.tab.resolveTimelineAppearance -import dev.dimension.flare.ui.model.UiIcon -import dev.dimension.flare.ui.model.UiText -import kotlinx.serialization.ExperimentalSerializationApi import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse class AppearancePatchTest { - @Test - fun emptyPatchSynthesizesDefaultAppearanceSettings() { - assertEquals(AppearanceSettings.Default, AppearancePatch.EMPTY.toAppearanceSettings()) - } - @Test fun getSetAndClearUseDefaults() { val patch = @@ -114,88 +100,6 @@ class AppearancePatchTest { ) } - @Test - fun timelineTabItemV2ResolvesTimelineAppearanceFromItemPatch() { - val item = - SourceTimelineTabItemV2.runtime( - id = "test", - title = UiText.Raw("Test"), - icon = IconType.Material(UiIcon.List), - appearancePatch = - AppearancePatch.EMPTY - .set(AppearanceKeys.ShowNumbers, false) - .set(AppearanceKeys.AbsoluteTimestamp, true), - createPresenter = { error("unused in test") }, - ) - val base = - TimelineAppearance( - showNumbers = true, - absoluteTimestamp = false, - aiConfig = TimelineAppearance.AiConfig(translation = true), - lineLimit = 7, - ) - - assertEquals( - TimelineAppearance( - showNumbers = false, - absoluteTimestamp = true, - aiConfig = TimelineAppearance.AiConfig(translation = true), - lineLimit = 7, - ), - item.resolveTimelineAppearance(base), - ) - } - - @OptIn(ExperimentalSerializationApi::class) - @Test - fun activeAppearanceSettingsFieldsAreCoveredByCatalog() { - val deprecatedFields = setOf("showActions") - val bagOnlyFields = setOf(AppearanceKeys.ShowBottomBarLabels, AppearanceKeys.DeckMode) - val activeFields = - AppearanceSettings - .serializer() - .descriptor - .let { descriptor -> - (0 until descriptor.elementsCount) - .map { descriptor.getElementName(it) } - .filterNot { it in deprecatedFields } - } - - assertEquals(activeFields.size + bagOnlyFields.size, AppearanceKeys.all.size) - } - - @Test - fun appearanceSettingsRoundTripPreservesActiveFields() { - val settings = - AppearanceSettings( - theme = Theme.DARK, - dynamicTheme = false, - colorSeed = 123456u, - avatarShape = AvatarShape.SQUARE, - pureColorMode = false, - showNumbers = false, - showLinkPreview = false, - showMedia = false, - showSensitiveContent = true, - videoAutoplay = VideoAutoplay.NEVER, - expandMediaSize = false, - compatLinkPreview = true, - fontSizeDiff = 2f, - lineHeightDiff = 4f, - showComposeInHomeTimeline = false, - bottomBarStyle = BottomBarStyle.Classic, - bottomBarBehavior = BottomBarBehavior.AlwaysShow, - inAppBrowser = false, - fullWidthPost = true, - postActionStyle = PostActionStyle.Hidden, - absoluteTimestamp = true, - showPlatformLogo = false, - timelineDisplayMode = TimelineDisplayMode.Gallery, - ) - - assertEquals(settings, settings.toPatch().toAppearanceSettings()) - } - @Test fun bagRoundTripPreservesExplicitValues() { val patch = diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelineFilterConfigSerializationTest.kt b/modules/settings/data/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelineFilterConfigSerializationTest.kt similarity index 94% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelineFilterConfigSerializationTest.kt rename to modules/settings/data/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelineFilterConfigSerializationTest.kt index d0fdfcf76b..703c44208b 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelineFilterConfigSerializationTest.kt +++ b/modules/settings/data/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelineFilterConfigSerializationTest.kt @@ -3,6 +3,7 @@ package dev.dimension.flare.data.model.tab import dev.dimension.flare.data.model.IconType import dev.dimension.flare.data.model.appearance.AppearanceBag import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiText import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoBuf @@ -21,9 +22,7 @@ class TimelineFilterConfigSerializationTest { TimelineSourceRef( id = "source", specId = "spec", - title = - dev.dimension.flare.ui.model.UiText - .Raw("Timeline"), + title = UiText.Raw("Timeline"), icon = IconType.Material(UiIcon.Rss), data = "encoded", ), diff --git a/modules/settings/data/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelineSlotNormalizationTest.kt b/modules/settings/data/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelineSlotNormalizationTest.kt new file mode 100644 index 0000000000..d2126b4fd5 --- /dev/null +++ b/modules/settings/data/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelineSlotNormalizationTest.kt @@ -0,0 +1,94 @@ +package dev.dimension.flare.data.model.tab + +import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.asText +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertIs +import kotlin.test.assertNotNull + +class TimelineSlotNormalizationTest { + @Test + fun normalizeSystemHomeMixedTimelineAddsSystemMixedWhenSecondDefaultTabIsAdded() { + val firstSlot = sourceSlot("common.home:1872639344760254464@x.com") + val secondSlot = sourceSlot("common.home:1711111111111111111@mastodon.social") + + val normalized = + listOf( + firstSlot, + secondSlot, + ).normalizeSystemHomeMixedTimeline(enabled = true) + + assertEquals( + listOf( + SYSTEM_HOME_MIXED_TIMELINE_ID, + firstSlot.id, + secondSlot.id, + ), + normalized.map { it.id }, + ) + } + + @Test + fun normalizeSystemHomeMixedTimelineDoesNotReAddSystemMixedAfterItWasRemoved() { + val firstSlot = sourceSlot("common.home:1872639344760254464@x.com") + val secondSlot = sourceSlot("common.home:1711111111111111111@mastodon.social") + + val normalized = + listOf( + firstSlot, + secondSlot, + ).normalizeSystemHomeMixedTimeline(enabled = true) + .filterNot { it.id == SYSTEM_HOME_MIXED_TIMELINE_ID } + .normalizeSystemHomeMixedTimeline(enabled = false) + + assertEquals( + listOf( + firstSlot.id, + secondSlot.id, + ), + normalized.map { it.id }, + ) + } + + @Test + fun normalizeSystemHomeMixedTimelinePreservesDisabledChildrenInManualGroup() { + val enabledChild = sourceSlot("common.home:1872639344760254464@x.com") + val disabledChild = + sourceSlot("common.home:1711111111111111111@mastodon.social") + .copy(presentation = TimelinePresentation(enabled = false)) + val manualGroup = + TimelineSlot( + id = "manual_group", + content = + TimelineSlotContent.Group( + children = listOf(enabledChild, disabledChild), + source = GroupSource.Manual, + ), + ) + + val normalized = + listOf( + enabledChild, + sourceSlot("common.home:1999999999999999999@mastodon.cloud"), + manualGroup, + ).normalizeSystemHomeMixedTimeline(enabled = true) + + val preservedGroup = assertNotNull(normalized.firstOrNull { it.id == manualGroup.id }) + val groupContent = assertIs(preservedGroup.content) + assertEquals(listOf(enabledChild.id, disabledChild.id), groupContent.children.map { it.id }) + assertFalse(groupContent.children[1].presentation.enabled) + } + + private fun sourceSlot(id: String): TimelineSlot = + TimelineSourceRef( + id = id, + specId = "common.home", + title = UiStrings.Home.asText(), + icon = IconType.Material(UiIcon.Home), + data = id, + ).toSlot() +} diff --git a/modules/settings/data/src/nonWebTest/kotlin/dev/dimension/flare/data/datastore/AppDataStoreTest.kt b/modules/settings/data/src/nonWebTest/kotlin/dev/dimension/flare/data/datastore/AppDataStoreTest.kt new file mode 100644 index 0000000000..34c3de3950 --- /dev/null +++ b/modules/settings/data/src/nonWebTest/kotlin/dev/dimension/flare/data/datastore/AppDataStoreTest.kt @@ -0,0 +1,107 @@ +package dev.dimension.flare.data.datastore + +import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.data.datastore.model.AppSettings +import dev.dimension.flare.data.io.OkioFileStorage +import dev.dimension.flare.data.model.SettingsExport +import dev.dimension.flare.data.model.appearance.AppearanceBag +import dev.dimension.flare.data.model.tab.TabSettingsV2 +import dev.dimension.flare.data.model.tab.TimelineSlot +import dev.dimension.flare.data.model.tab.TimelineSlotContent +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import okio.FileSystem +import okio.Path.Companion.toPath +import okio.SYSTEM +import kotlin.random.Random +import kotlin.test.Test +import kotlin.test.assertEquals + +class AppDataStoreTest { + @Test + fun exposesSettingsReadWriteInterface() = + runTest { + withStore { store -> + store.updateAppSettings { + copy(version = "settings-store") + } + store.replaceAppearanceBag(AppearanceBag(entries = mapOf("app.theme" to "dark"))) + store.updateTabSettingsV2 { + TabSettingsV2(homeSlots = listOf(manualGroupSlot())) + } + + assertEquals(AppSettings(version = "settings-store"), store.appSettings.first()) + assertEquals(mapOf("app.theme" to "dark"), store.appearanceBag.first().entries) + assertEquals( + listOf("manual-group"), + store.tabSettingsV2 + .first() + .homeSlots + .map { it.id }, + ) + } + } + + @Test + fun importsSettingsExport() = + runTest { + withStore { store -> + val export = + SettingsExport( + appearanceBag = AppearanceBag(entries = mapOf("app.theme" to "light")), + appSettings = AppSettings(version = "imported"), + tabSettingsV2 = TabSettingsV2(homeSlots = listOf(manualGroupSlot())), + ).encodeJson(SettingsExport.serializer()) + + store.importSettings(export) + + assertEquals(mapOf("app.theme" to "light"), store.appearanceBag.first().entries) + assertEquals(AppSettings(version = "imported"), store.appSettings.first()) + assertEquals( + listOf("manual-group"), + store.tabSettingsV2 + .first() + .homeSlots + .map { it.id }, + ) + } + } + + @Test + fun exportsSettings() = + runTest { + withStore { store -> + store.replaceAppearanceBag(AppearanceBag(entries = mapOf("app.theme" to "dark"))) + store.updateAppSettings { + copy(version = "exported") + } + store.updateTabSettingsV2 { + TabSettingsV2(homeSlots = listOf(manualGroupSlot())) + } + + val export = store.exportSettings().decodeJson(SettingsExport.serializer()) + + assertEquals(mapOf("app.theme" to "dark"), export.appearanceBag.entries) + assertEquals(AppSettings(version = "exported"), export.appSettings) + assertEquals(listOf("manual-group"), export.tabSettingsV2.homeSlots.map { it.id }) + } + } + + private suspend fun withStore(block: suspend (AppDataStore) -> Unit) { + val root = "/tmp/flare-settings-store-${Random.nextLong()}".toPath() + val fs = FileSystem.SYSTEM + fs.createDirectories(root) + try { + block(AppDataStore(OkioFileStorage(fs, root))) + } finally { + fs.deleteRecursively(root) + } + } + + private fun manualGroupSlot(): TimelineSlot = + TimelineSlot( + id = "manual-group", + content = TimelineSlotContent.Group(), + ) +} diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/AppearanceMigrationTest.kt b/modules/settings/data/src/nonWebTest/kotlin/dev/dimension/flare/data/model/appearance/AppearanceMigrationTest.kt similarity index 56% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/AppearanceMigrationTest.kt rename to modules/settings/data/src/nonWebTest/kotlin/dev/dimension/flare/data/model/appearance/AppearanceMigrationTest.kt index 5684c314b5..65cced6f71 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/AppearanceMigrationTest.kt +++ b/modules/settings/data/src/nonWebTest/kotlin/dev/dimension/flare/data/model/appearance/AppearanceMigrationTest.kt @@ -3,18 +3,13 @@ package dev.dimension.flare.data.model.appearance import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.okio.OkioStorage import dev.dimension.flare.common.protobufSerializer -import dev.dimension.flare.data.io.PlatformPathProducer -import dev.dimension.flare.data.model.AppearanceSettings -import dev.dimension.flare.data.model.PostActionStyle -import dev.dimension.flare.data.model.Theme -import dev.dimension.flare.data.model.VideoAutoplay +import dev.dimension.flare.data.io.OkioFileStorage import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.encodeToByteArray import kotlinx.serialization.protobuf.ProtoBuf import okio.FileSystem -import okio.Path import okio.Path.Companion.toPath import okio.SYSTEM import kotlin.random.Random @@ -30,22 +25,14 @@ class AppearanceMigrationTest { val root = "/tmp/flare-appearance-${Random.nextLong()}".toPath() val fs = FileSystem.SYSTEM fs.createDirectories(root) - val pathProducer = - object : PlatformPathProducer { - override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) - - override fun draftMediaFile( - groupId: String, - fileName: String, - ): Path = root.resolve(groupId).resolve(fileName) - } - val oldPath = pathProducer.dataStoreFile("appearance_settings.pb") + val fileStorage = OkioFileStorage(fs, root) + val oldPath = fileStorage.dataStoreFile("appearance_settings.pb") val imported = - AppearanceSettings( - theme = Theme.DARK, + LegacyAppearanceSettings( + theme = LegacyTheme.DARK, showMedia = false, - videoAutoplay = VideoAutoplay.ALWAYS, - postActionStyle = PostActionStyle.Stretch, + videoAutoplay = LegacyVideoAutoplay.ALWAYS, + postActionStyle = LegacyPostActionStyle.Stretch, ) fs.write(oldPath) { write(ProtoBuf.encodeToByteArray(imported)) @@ -56,18 +43,20 @@ class AppearanceMigrationTest { OkioStorage( fileSystem = fs, serializer = protobufSerializer(AppearanceBag()), - producePath = { pathProducer.dataStoreFile("appearance_bag.pb") }, + producePath = { fileStorage.dataStoreFile("appearance_bag.pb") }, ), ) - migrateAppearanceV1ToV2(pathProducer, store) + migrateAppearanceV1ToV2( + fileStorage = fileStorage, + legacyAppearanceSettingsPath = oldPath, + bagStore = store, + ) assertEquals( - imported, + imported.toBag(), store.data - .first() - .toPatch() - .toAppearanceSettings(), + .first(), ) assertFalse(fs.exists(oldPath)) fs.deleteRecursively(root) diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TabSettingsMigrationTest.kt b/modules/settings/data/src/nonWebTest/kotlin/dev/dimension/flare/data/model/tab/TabSettingsMigrationTest.kt similarity index 91% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TabSettingsMigrationTest.kt rename to modules/settings/data/src/nonWebTest/kotlin/dev/dimension/flare/data/model/tab/TabSettingsMigrationTest.kt index 368a40376f..96415b0f44 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TabSettingsMigrationTest.kt +++ b/modules/settings/data/src/nonWebTest/kotlin/dev/dimension/flare/data/model/tab/TabSettingsMigrationTest.kt @@ -3,11 +3,11 @@ package dev.dimension.flare.data.model.tab import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.okio.OkioStorage import dev.dimension.flare.common.protobufSerializer -import dev.dimension.flare.data.database.app.model.SubscriptionType -import dev.dimension.flare.data.io.PlatformPathProducer +import dev.dimension.flare.data.io.OkioFileStorage import dev.dimension.flare.data.model.AllRssTimelineTabItem import dev.dimension.flare.data.model.Bluesky import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.data.model.LegacySubscriptionType import dev.dimension.flare.data.model.ListTimelineTabItem import dev.dimension.flare.data.model.Mastodon import dev.dimension.flare.data.model.MixedTimelineTabItem @@ -16,7 +16,6 @@ import dev.dimension.flare.data.model.SubscriptionTimelineTabItem import dev.dimension.flare.data.model.TabMetaData import dev.dimension.flare.data.model.TabSettings import dev.dimension.flare.data.model.TitleType -import dev.dimension.flare.data.platform.RssTimelineSpecs import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiIcon @@ -26,7 +25,6 @@ import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.encodeToByteArray import kotlinx.serialization.protobuf.ProtoBuf import okio.FileSystem -import okio.Path import okio.Path.Companion.toPath import okio.SYSTEM import kotlin.random.Random @@ -167,7 +165,7 @@ class TabSettingsMigrationTest { AllRssTimelineTabItem(), SubscriptionTimelineTabItem( subscriptionUrl = "https://mastodon.example/public", - subscriptionType = SubscriptionType.MASTODON_PUBLIC, + subscriptionType = LegacySubscriptionType.MASTODON_PUBLIC, favIcon = null, metaData = TabMetaData( @@ -179,9 +177,9 @@ class TabSettingsMigrationTest { assertEquals( listOf( - RssTimelineSpecs.rss.id, - RssTimelineSpecs.allRss.id, - RssTimelineSpecs.subscription.id, + "rss.feed", + "rss.all", + "rss.subscription", ), slots.map { assertIs(it.content).source.specId }, ) @@ -218,16 +216,8 @@ class TabSettingsMigrationTest { val root = "/tmp/flare-tab-settings-${Random.nextLong()}".toPath() val fs = FileSystem.SYSTEM fs.createDirectories(root) - val pathProducer = - object : PlatformPathProducer { - override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) - - override fun draftMediaFile( - groupId: String, - fileName: String, - ): Path = root.resolve(groupId).resolve(fileName) - } - val oldPath = pathProducer.dataStoreFile("tab_settings.pb") + val fileStorage = OkioFileStorage(fs, root) + val oldPath = fileStorage.dataStoreFile("tab_settings.pb") fs.write(oldPath) { write( ProtoBuf.encodeToByteArray( @@ -261,11 +251,15 @@ class TabSettingsMigrationTest { OkioStorage( fileSystem = fs, serializer = protobufSerializer(TabSettingsV2()), - producePath = { pathProducer.dataStoreFile("tab_settings_v2.pb") }, + producePath = { fileStorage.dataStoreFile("tab_settings_v2.pb") }, ), ) - migrateTabSettingsV1ToV2(pathProducer, store) + migrateTabSettingsV1ToV2( + fileStorage = fileStorage, + legacyTabSettingsPath = oldPath, + tabSettingsV2Store = store, + ) val settings = store.data.first() assertEquals( diff --git a/modules/translation/api/build.gradle.kts b/modules/translation/api/build.gradle.kts new file mode 100644 index 0000000000..65f2b62057 --- /dev/null +++ b/modules/translation/api/build.gradle.kts @@ -0,0 +1,59 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.modules.translation.api" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_translation_api_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_translation_api", + ) + } + } + } + } + } + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.modules.translation.model) + api(libs.kotlinx.coroutines.core) + } + } + } +} diff --git a/modules/translation/api/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationSettingsProvider.kt b/modules/translation/api/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationSettingsProvider.kt new file mode 100644 index 0000000000..bdc19ee979 --- /dev/null +++ b/modules/translation/api/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationSettingsProvider.kt @@ -0,0 +1,7 @@ +package dev.dimension.flare.data.translation + +import kotlinx.coroutines.flow.Flow + +public interface TranslationSettingsProvider { + public val displayOptionsFlow: Flow +} diff --git a/modules/translation/data/build.gradle.kts b/modules/translation/data/build.gradle.kts new file mode 100644 index 0000000000..5a80602410 --- /dev/null +++ b/modules/translation/data/build.gradle.kts @@ -0,0 +1,79 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.modules.translation.data" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_translation_data_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_translation_data", + ) + } + } + } + } + } + } + + compilerOptions { + allWarningsAsErrors.set(false) + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.core.model) + api(projects.foundation.database) + api(projects.modules.ai.data) + implementation(projects.modules.account.model) + api(projects.modules.settings.data) + api(projects.social.model) + api(projects.ui.richtext) + implementation(projects.foundation.network) + api(dependencies.platform(libs.koin.bom)) + api(libs.koin.core) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) + } + } + } +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/AiPlaceholderTranslationSupport.kt b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/AiPlaceholderTranslationSupport.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/AiPlaceholderTranslationSupport.kt rename to modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/AiPlaceholderTranslationSupport.kt index d0e07151d2..ad6da6d807 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/AiPlaceholderTranslationSupport.kt +++ b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/AiPlaceholderTranslationSupport.kt @@ -5,17 +5,17 @@ import dev.dimension.flare.ui.render.TranslationDocument import dev.dimension.flare.ui.render.TranslationToken import dev.dimension.flare.ui.render.TranslationTokenKind -internal object AiPlaceholderTranslationSupport { +public object AiPlaceholderTranslationSupport { private val markerPattern = Regex("""\{\{([TL])(\d+)\}\}""") private val blockPattern = Regex("""(?ms)^<<>>\n(.*?)\n<<>>(?:\n|$)""") private val itemPattern = Regex("""(?ms)^<<>>(?:\n(.*?))?(?=^<<>>\n(.*?)(?=^<<, - allowLongText: Boolean = false, - ) - - fun enqueueProfile(user: DbUser) - - fun retryStatus( - accountType: dev.dimension.flare.model.AccountType, - statusKey: dev.dimension.flare.model.MicroBlogKey, - ) - - fun setStatusDisplayMode( - accountType: dev.dimension.flare.model.AccountType, - statusKey: dev.dimension.flare.model.MicroBlogKey, - mode: TranslationDisplayMode, - ) -} - -internal data object NoopPreTranslationService : PreTranslationService { - override fun enqueueStatuses( - statuses: List, - allowLongText: Boolean, - ) = Unit - - override fun enqueueProfile(user: DbUser) = Unit - - override fun retryStatus( - accountType: dev.dimension.flare.model.AccountType, - statusKey: dev.dimension.flare.model.MicroBlogKey, - ) = Unit - - override fun setStatusDisplayMode( - accountType: dev.dimension.flare.model.AccountType, - statusKey: dev.dimension.flare.model.MicroBlogKey, - mode: TranslationDisplayMode, - ) = Unit -} - -internal class OnlinePreTranslationService( +public class OnlinePreTranslationService( private val database: CacheDatabase, private val appDataStore: AppDataStore, private val aiCompletionService: AiCompletionService, @@ -108,7 +64,7 @@ internal class OnlinePreTranslationService( cleanupStaleInFlightTranslations() } coroutineScope.launch { - appDataStore.appSettingsStore.data + appDataStore.appSettings .map { it.translationProviderCacheKey() } .distinctUntilChanged() .collect { providerCacheKey -> @@ -117,7 +73,7 @@ internal class OnlinePreTranslationService( } } - override fun enqueueStatuses( + public override fun enqueueStatuses( statuses: List, allowLongText: Boolean, ) { @@ -136,7 +92,7 @@ internal class OnlinePreTranslationService( } } - override fun enqueueProfile(user: DbUser) { + public override fun enqueueProfile(user: DbUser) { launchPreparedCandidates(requirePreTranslation = true) { settings -> listOfNotNull( prepareProfileCandidate( @@ -149,7 +105,7 @@ internal class OnlinePreTranslationService( } } - override fun retryStatus( + public override fun retryStatus( accountType: dev.dimension.flare.model.AccountType, statusKey: dev.dimension.flare.model.MicroBlogKey, ) { @@ -168,7 +124,7 @@ internal class OnlinePreTranslationService( } } - override fun setStatusDisplayMode( + public override fun setStatusDisplayMode( accountType: dev.dimension.flare.model.AccountType, statusKey: dev.dimension.flare.model.MicroBlogKey, mode: TranslationDisplayMode, @@ -236,7 +192,7 @@ internal class OnlinePreTranslationService( return it } val latestProviderCacheKey = - appDataStore.appSettingsStore.data + appDataStore.appSettings .first() .translationProviderCacheKey() if (latestProviderCacheKey != settings.providerCacheKey) { @@ -291,7 +247,7 @@ internal class OnlinePreTranslationService( } private suspend fun activeTranslationSettings(requirePreTranslation: Boolean): ActivePreTranslationSettings? { - val appSettings = appDataStore.appSettingsStore.data.first() + val appSettings = appDataStore.appSettings.first() val targetLanguage = currentTargetLanguage() val canTranslate = if (requirePreTranslation) { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationContentRules.kt b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationContentRules.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationContentRules.kt rename to modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationContentRules.kt index 2ad76c991a..a5f237ead1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationContentRules.kt +++ b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationContentRules.kt @@ -1,17 +1,16 @@ package dev.dimension.flare.data.translation -import dev.dimension.flare.data.database.cache.model.TranslationPayload import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.render.RenderContent import dev.dimension.flare.ui.render.RenderRun import dev.dimension.flare.ui.render.RenderTextStyle import dev.dimension.flare.ui.render.UiRichText -internal object PreTranslationContentRules { +public object PreTranslationContentRules { private val protectedTranslationPattern = Regex("""https?://\S+|@[A-Za-z0-9._-]+(?:@[A-Za-z0-9.-]+)?|#[\p{L}\p{N}_]+""") - fun sourceLanguages(timeline: UiTimelineV2): List = + public fun sourceLanguages(timeline: UiTimelineV2): List = when (timeline) { is UiTimelineV2.Feed -> timeline.sourceLanguages is UiTimelineV2.Post -> timeline.sourceLanguages @@ -20,7 +19,7 @@ internal object PreTranslationContentRules { is UiTimelineV2.UserList -> emptyList() } - fun shouldSkipForMatchingSourceLanguage( + public fun shouldSkipForMatchingSourceLanguage( sourceLanguages: List, targetLanguage: String, ): Boolean { @@ -31,7 +30,7 @@ internal object PreTranslationContentRules { .any { it == canonicalTargetLanguage } } - fun shouldSkipForExcludedSourceLanguage( + public fun shouldSkipForExcludedSourceLanguage( sourceLanguages: List, excludedLanguages: List, ): Boolean { @@ -45,18 +44,18 @@ internal object PreTranslationContentRules { .any { it in canonicalExcludedLanguages } } - fun canonicalExcludedLanguages(languages: List): Set = + public fun canonicalExcludedLanguages(languages: List): Set = languages .asSequence() .mapNotNull(::canonicalTranslationLanguage) .toSet() - fun isNonTranslatableOnly(payload: TranslationPayload): Boolean { + public fun isNonTranslatableOnly(payload: TranslationPayload): Boolean { val fields = listOfNotNull(payload.content, payload.contentWarning, payload.title, payload.description) return fields.isNotEmpty() && fields.all(::isNonTranslatableOnly) } - internal fun canonicalTranslationLanguage(language: String): String? { + public fun canonicalTranslationLanguage(language: String): String? { val normalized = language.trim().lowercase().replace('_', '-') if (normalized.isBlank()) { return null diff --git a/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationModels.kt b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationModels.kt new file mode 100644 index 0000000000..61c9ebdb9c --- /dev/null +++ b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationModels.kt @@ -0,0 +1,52 @@ +package dev.dimension.flare.data.translation + +import dev.dimension.flare.data.datastore.model.AppSettings +import dev.dimension.flare.ui.render.TranslationDocument +import kotlinx.serialization.Serializable + +@Serializable +public data class PreTranslationBatchDocument( + public val version: Int = 1, + public val targetLanguage: String = "", + public val items: List, +) + +@Serializable +public data class PreTranslationBatchItem( + public val entityKey: String, + public val status: PreTranslationBatchItemStatus = PreTranslationBatchItemStatus.Completed, + public val payload: PreTranslationBatchPayload? = null, + public val reason: String? = null, +) + +@Serializable +public enum class PreTranslationBatchItemStatus { + Completed, + Skipped, +} + +@Serializable +public data class PreTranslationBatchPayload( + public val content: TranslationDocument? = null, + public val contentWarning: TranslationDocument? = null, + public val title: TranslationDocument? = null, + public val description: TranslationDocument? = null, +) + +public data class ActivePreTranslationSettings( + public val targetLanguage: String, + public val autoTranslateExcludedLanguages: List, + public val appSettings: AppSettings, + public val providerCacheKey: String, +) + +public data class PreparedTranslationCandidate( + public val entityType: TranslationEntityType, + public val entityKey: String, + public val targetLanguage: String, + public val sourceHash: String, + public val sourcePayload: TranslationPayload, + public val sourceDocument: PreTranslationBatchPayload, + public val attemptCount: Int, + public val displayMode: TranslationDisplayMode, +) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationPayloadSupport.kt b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationPayloadSupport.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationPayloadSupport.kt rename to modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationPayloadSupport.kt index b3723697a1..49f0e3110b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationPayloadSupport.kt +++ b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationPayloadSupport.kt @@ -1,14 +1,13 @@ package dev.dimension.flare.data.translation import dev.dimension.flare.common.encodeJson -import dev.dimension.flare.data.database.cache.model.TranslationPayload import dev.dimension.flare.ui.render.TranslationDocument import dev.dimension.flare.ui.render.UiRichText import dev.dimension.flare.ui.render.applyTranslationDocument import dev.dimension.flare.ui.render.toTranslationDocument -internal object PreTranslationPayloadSupport { - fun toBatchPayload( +public object PreTranslationPayloadSupport { + public fun toBatchPayload( payload: TranslationPayload, targetLanguage: String, ): PreTranslationBatchPayload = @@ -19,7 +18,7 @@ internal object PreTranslationPayloadSupport { description = toTranslationDocumentOrNull(payload.description, targetLanguage), ) - fun applyBatchPayload( + public fun applyBatchPayload( sourcePayload: TranslationPayload, sourceDocument: PreTranslationBatchPayload, translatedDocument: PreTranslationBatchPayload, @@ -36,10 +35,10 @@ internal object PreTranslationPayloadSupport { description = applyTranslatedField(sourcePayload.description, sourceDocument.description, translatedDocument.description), ) - fun estimatedTokens(payload: PreTranslationBatchPayload): Int = + public fun estimatedTokens(payload: PreTranslationBatchPayload): Int = payload.encodeJson(PreTranslationBatchPayload.serializer()).length / 4 + 1 - fun isEmpty(payload: PreTranslationBatchPayload): Boolean = + public fun isEmpty(payload: PreTranslationBatchPayload): Boolean = payload.content == null && payload.contentWarning == null && payload.title == null && diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationStoreSupport.kt b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationStoreSupport.kt similarity index 74% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationStoreSupport.kt rename to modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationStoreSupport.kt index 5347906e7b..679c33977d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationStoreSupport.kt +++ b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationStoreSupport.kt @@ -2,29 +2,26 @@ package dev.dimension.flare.data.translation import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.model.DbTranslation -import dev.dimension.flare.data.database.cache.model.TranslationDisplayMode -import dev.dimension.flare.data.database.cache.model.TranslationEntityType -import dev.dimension.flare.data.database.cache.model.TranslationPayload -import dev.dimension.flare.data.database.cache.model.TranslationStatus +import dev.dimension.flare.data.database.cache.model.canRetrySkippedManually import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.minutes -internal object PreTranslationStoreSupport { - const val DEFAULT_PRE_TRANSLATION_MAX_ITEMS: Int = 8 - const val DEFAULT_PRE_TRANSLATION_MAX_INPUT_TOKENS: Int = 6000 - const val PRE_TRANSLATION_BATCH_MAX_ATTEMPTS: Int = 2 - const val FAILED_STALE_IN_FLIGHT_REASON: String = "stale_in_flight" - const val SKIPPED_AI_SAME_LANGUAGE_REASON: String = "ai_same_language" - const val SKIPPED_EMPTY_REASON: String = "empty" - const val SKIPPED_EXCLUDED_LANGUAGE_REASON: String = "source_language_excluded" - const val SKIPPED_NON_TRANSLATABLE_ONLY_REASON: String = "non_translatable_only" - const val SKIPPED_SAME_LANGUAGE_REASON: String = "source_language_matches_target" - const val SKIPPED_UNCHANGED_REASON: String = "unchanged" - val PRE_TRANSLATION_BATCH_RETRY_DELAY: Duration = 500.milliseconds - val STALE_TRANSLATION_TIMEOUT: Duration = 10.minutes +public object PreTranslationStoreSupport { + public const val DEFAULT_PRE_TRANSLATION_MAX_ITEMS: Int = 8 + public const val DEFAULT_PRE_TRANSLATION_MAX_INPUT_TOKENS: Int = 6000 + public const val PRE_TRANSLATION_BATCH_MAX_ATTEMPTS: Int = 2 + public const val FAILED_STALE_IN_FLIGHT_REASON: String = "stale_in_flight" + public const val SKIPPED_AI_SAME_LANGUAGE_REASON: String = "ai_same_language" + public const val SKIPPED_EMPTY_REASON: String = "empty" + public const val SKIPPED_EXCLUDED_LANGUAGE_REASON: String = TRANSLATION_SKIPPED_EXCLUDED_LANGUAGE_REASON + public const val SKIPPED_NON_TRANSLATABLE_ONLY_REASON: String = "non_translatable_only" + public const val SKIPPED_SAME_LANGUAGE_REASON: String = "source_language_matches_target" + public const val SKIPPED_UNCHANGED_REASON: String = "unchanged" + public val PRE_TRANSLATION_BATCH_RETRY_DELAY: Duration = 500.milliseconds + public val STALE_TRANSLATION_TIMEOUT: Duration = 10.minutes - fun toDbTranslation( + public fun toDbTranslation( candidate: PreparedTranslationCandidate, status: TranslationStatus, updatedAt: Long, @@ -44,13 +41,13 @@ internal object PreTranslationStoreSupport { updatedAt = updatedAt, ) - fun toBatchItem(candidate: PreparedTranslationCandidate): PreTranslationBatchItem = + public fun toBatchItem(candidate: PreparedTranslationCandidate): PreTranslationBatchItem = PreTranslationBatchItem( entityKey = candidate.entityKey, payload = candidate.sourceDocument, ) - fun chunkCandidatesForBatching(candidates: List): List> { + public fun chunkCandidatesForBatching(candidates: List): List> { val result = mutableListOf>() val current = mutableListOf() var currentTokenEstimate = 0 @@ -74,7 +71,7 @@ internal object PreTranslationStoreSupport { return result } - suspend fun persistSkippedTranslationIfNeeded( + public suspend fun persistSkippedTranslationIfNeeded( database: CacheDatabase, entityType: TranslationEntityType, entityKey: String, @@ -104,7 +101,7 @@ internal object PreTranslationStoreSupport { ) } - fun shouldTranslate( + public fun shouldTranslate( existing: DbTranslation?, sourceHash: String, ): Boolean { @@ -133,9 +130,7 @@ internal object PreTranslationStoreSupport { } } - fun canRetrySkippedManually(existing: DbTranslation?): Boolean = - existing?.status == TranslationStatus.Skipped && - existing.statusReason == SKIPPED_EXCLUDED_LANGUAGE_REASON + public fun canRetrySkippedManually(existing: DbTranslation?): Boolean = existing.canRetrySkippedManually() private fun matchesSkipped( existing: DbTranslation?, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationProvider.kt b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationProvider.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationProvider.kt rename to modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationProvider.kt index 52ccb8902a..80b8cbf06f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationProvider.kt +++ b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationProvider.kt @@ -2,8 +2,8 @@ package dev.dimension.flare.data.translation import dev.dimension.flare.common.decodeJson import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.data.ai.AiCompletionService import dev.dimension.flare.data.datastore.model.AppSettings -import dev.dimension.flare.data.network.ai.AiCompletionService import dev.dimension.flare.data.network.ktorClient import dev.dimension.flare.ui.render.TranslationDocument import dev.dimension.flare.ui.render.TranslationTokenKind @@ -31,8 +31,8 @@ import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonPrimitive -internal object TranslationProvider { - suspend fun translateDocumentJson( +public object TranslationProvider { + public suspend fun translateDocumentJson( settings: AppSettings, aiCompletionService: AiCompletionService, sourceTemplate: String, @@ -50,7 +50,7 @@ internal object TranslationProvider { prompt = prompt, ) - suspend fun translateBatchDocumentJson( + public suspend fun translateBatchDocumentJson( settings: AppSettings, aiCompletionService: AiCompletionService, sourceJson: String, diff --git a/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationSupport.kt b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationSupport.kt new file mode 100644 index 0000000000..901473f52a --- /dev/null +++ b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationSupport.kt @@ -0,0 +1,30 @@ +package dev.dimension.flare.data.translation + +import dev.dimension.flare.data.datastore.model.AiPromptDefaults +import dev.dimension.flare.data.datastore.model.AppSettings + +public object TranslationPromptFormatter { + public fun buildTranslatePrompt( + settings: AppSettings, + targetLanguage: String, + sourceTemplate: String, + ): String = + settings.aiConfig.translatePrompt + .ifBlank { + AiPromptDefaults.TRANSLATE_PROMPT + }.replace("{target_language}", targetLanguage) + .replace("{source_text}", sourceTemplate) +} + +public object TranslationResponseSanitizer { + public fun clean(content: String): String = + content + .removePrefix("```json") + .removePrefix("```html") + .removePrefix("```xml") + .removePrefix("```markup") + .removePrefix("```text") + .removePrefix("```") + .removeSuffix("```") + .trim() +} diff --git a/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/di/TranslationDataModule.kt b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/di/TranslationDataModule.kt new file mode 100644 index 0000000000..bd9c56e3ae --- /dev/null +++ b/modules/translation/data/src/commonMain/kotlin/dev/dimension/flare/di/TranslationDataModule.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.di + +import dev.dimension.flare.data.translation.OnlinePreTranslationService +import dev.dimension.flare.data.translation.PreTranslationService +import org.koin.core.module.Module +import org.koin.dsl.module + +public val translationDataModule: Module = + module { + single { OnlinePreTranslationService(get(), get(), get(), get()) } + } diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/AiPlaceholderTranslationSupportTest.kt b/modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/AiPlaceholderTranslationSupportTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/AiPlaceholderTranslationSupportTest.kt rename to modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/AiPlaceholderTranslationSupportTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/DeepLTargetLanguageTest.kt b/modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/DeepLTargetLanguageTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/DeepLTargetLanguageTest.kt rename to modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/DeepLTargetLanguageTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/GoogleWebTranslationDocumentSupportTest.kt b/modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/GoogleWebTranslationDocumentSupportTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/GoogleWebTranslationDocumentSupportTest.kt rename to modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/GoogleWebTranslationDocumentSupportTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/GoogleWebTranslationWhitespaceSupportTest.kt b/modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/GoogleWebTranslationWhitespaceSupportTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/GoogleWebTranslationWhitespaceSupportTest.kt rename to modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/GoogleWebTranslationWhitespaceSupportTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/PreTranslationContentRulesTest.kt b/modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/PreTranslationContentRulesTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/PreTranslationContentRulesTest.kt rename to modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/PreTranslationContentRulesTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationPromptFormatterTest.kt b/modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationPromptFormatterTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationPromptFormatterTest.kt rename to modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationPromptFormatterTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationProviderAiPlaceholderTest.kt b/modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationProviderAiPlaceholderTest.kt similarity index 96% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationProviderAiPlaceholderTest.kt rename to modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationProviderAiPlaceholderTest.kt index d16130d3a7..88c0ea840d 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationProviderAiPlaceholderTest.kt +++ b/modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationProviderAiPlaceholderTest.kt @@ -1,12 +1,12 @@ package dev.dimension.flare.data.translation -import dev.dimension.flare.common.OnDeviceAI import dev.dimension.flare.common.decodeJson import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.data.ai.AiCompletionService +import dev.dimension.flare.data.ai.OnDeviceAI +import dev.dimension.flare.data.ai.OpenAIService import dev.dimension.flare.data.datastore.model.AiPromptDefaults import dev.dimension.flare.data.datastore.model.AppSettings -import dev.dimension.flare.data.network.ai.AiCompletionService -import dev.dimension.flare.data.network.ai.OpenAIService import dev.dimension.flare.ui.render.TranslationBlock import dev.dimension.flare.ui.render.TranslationDocument import dev.dimension.flare.ui.render.TranslationToken diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationProviderCacheKeyTest.kt b/modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationProviderCacheKeyTest.kt similarity index 96% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationProviderCacheKeyTest.kt rename to modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationProviderCacheKeyTest.kt index 0074134bea..ba6252be8b 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationProviderCacheKeyTest.kt +++ b/modules/translation/data/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationProviderCacheKeyTest.kt @@ -1,6 +1,7 @@ package dev.dimension.flare.data.translation import dev.dimension.flare.data.datastore.model.AppSettings +import dev.dimension.flare.data.datastore.model.cacheKey import kotlin.test.Test import kotlin.test.assertEquals diff --git a/modules/translation/model/build.gradle.kts b/modules/translation/model/build.gradle.kts new file mode 100644 index 0000000000..7540910113 --- /dev/null +++ b/modules/translation/model/build.gradle.kts @@ -0,0 +1,61 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.modules.translation.model" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_translation_model_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_translation_model", + ) + } + } + } + } + } + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.ui.richtext) + api(libs.kotlinx.serialization.json) + } + } + } +} diff --git a/modules/translation/model/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationDisplayOptions.kt b/modules/translation/model/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationDisplayOptions.kt new file mode 100644 index 0000000000..7c50b30f21 --- /dev/null +++ b/modules/translation/model/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationDisplayOptions.kt @@ -0,0 +1,7 @@ +package dev.dimension.flare.data.translation + +public data class TranslationDisplayOptions( + val translationEnabled: Boolean, + val autoDisplayEnabled: Boolean, + val providerCacheKey: String, +) diff --git a/modules/translation/model/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationModels.kt b/modules/translation/model/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationModels.kt new file mode 100644 index 0000000000..cd4b46f6a6 --- /dev/null +++ b/modules/translation/model/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationModels.kt @@ -0,0 +1,53 @@ +package dev.dimension.flare.data.translation + +import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.ui.render.UiRichText +import kotlinx.serialization.Serializable + +@Serializable +public enum class TranslationEntityType { + Status, + Profile, +} + +@Serializable +public enum class TranslationStatus { + Pending, + Translating, + Completed, + Failed, + Skipped, +} + +@Serializable +public enum class TranslationDisplayMode { + Auto, + Original, + Translated, +} + +public const val TRANSLATION_SKIPPED_EXCLUDED_LANGUAGE_REASON: String = "source_language_excluded" + +@Serializable +public data class TranslationPayload( + public val content: UiRichText? = null, + public val contentWarning: UiRichText? = null, + public val title: UiRichText? = null, + public val description: UiRichText? = null, +) + +public fun TranslationPayload.sourceHash(providerCacheKey: String): String = + buildString { + append(providerCacheKey) + append('\u0000') + append(encodeJson(TranslationPayload.serializer())) + }.stableTranslationHash() + +private fun String.stableTranslationHash(): String { + var hash = -0x340d631b8c4674c3L + encodeToByteArray().forEach { byte -> + hash = hash xor (byte.toLong() and 0xffL) + hash *= 0x100000001b3L + } + return hash.toULong().toString(16) +} diff --git a/shared/build.gradle.kts b/presentation/features/build.gradle.kts similarity index 65% rename from shared/build.gradle.kts rename to presentation/features/build.gradle.kts index 34c47497ff..c3987cb4f0 100644 --- a/shared/build.gradle.kts +++ b/presentation/features/build.gradle.kts @@ -14,11 +14,12 @@ plugins { kotlin { flare { - namespace = "dev.dimension.flare.shared" + namespace = "dev.dimension.flare.presentation.features" platforms( FlarePlatform.ANDROID, FlarePlatform.JVM, FlarePlatform.IOS, + FlarePlatform.WEB, ) ksp( libs.ktorfit.ksp, @@ -32,6 +33,10 @@ kotlin { } } + compilerOptions { + allWarningsAsErrors.set(false) + } + sourceSets { all { languageSettings { @@ -40,9 +45,39 @@ kotlin { } val commonMain by getting { dependencies { + api(projects.core.common) + api(projects.core.humanizer) + api(projects.core.model) + api(projects.foundation.deeplink) + api(projects.modules.account.api) + api(projects.modules.draft.presentation) + api(projects.modules.local.model) + api(projects.social.model) + api(projects.ui.model) + api(projects.ui.richtext) + implementation(projects.modules.ai.data) + implementation(projects.modules.account.data) + implementation(projects.foundation.database) + api(projects.modules.settings.data) + api(projects.modules.draft.data) + implementation(projects.modules.local.data) + implementation(projects.modules.translation.data) + implementation(projects.foundation.network) + api(projects.social.api) + implementation(projects.social.bluesky) + implementation(projects.social.mastodon) + implementation(projects.social.misskey) + api(projects.social.microblog) + implementation(projects.social.nodeinfo) + implementation(projects.social.rss) + api(projects.social.rss.model) + implementation(projects.social.vvo) + implementation(projects.social.xqt) + api(projects.ui.presenterRuntime) implementation(dependencies.platform(libs.compose.bom)) implementation(libs.compose.runtime) implementation(libs.bundles.kotlinx) + implementation(libs.kotlinx.serialization.json) implementation(dependencies.platform(libs.koin.bom)) implementation(libs.koin.core) api(libs.paging.common) @@ -59,25 +94,26 @@ kotlin { api(libs.bluesky.oauth) implementation(libs.room.runtime) implementation(libs.room.paging) - implementation(libs.sqlite.bundled) - implementation(libs.datastore) implementation(libs.kotlinx.serialization.protobuf) implementation(libs.xmlUtil) implementation(libs.ktor.client.resources) implementation(libs.cryptography.provider.optimal) - implementation(libs.openai.client) - implementation(libs.nostr.sdk.kmp) - implementation(libs.readability) } } val commonTest by getting { dependencies { implementation(kotlin("test")) + implementation(projects.foundation.filesystem) implementation(libs.kotlinx.coroutines.test) implementation(libs.paging.testing) implementation(libs.ktor.client.mock) } } + val nonWebMain by getting { + dependencies { + implementation(projects.social.nostr) + } + } val androidJvmMain by getting { dependencies { implementation(libs.ktor.client.okhttp) @@ -101,8 +137,6 @@ kotlin { val jvmMain by getting { dependencies { implementation(libs.commons.lang3) - implementation(libs.prettytime) - implementation(libs.jna) } } val appleMain by getting { @@ -120,18 +154,3 @@ room3 { ktorfit { compilerPluginVersion.set("2.3.3") } - -afterEvaluate { -// val kspCommonMainKotlinMetadata by tasks - val runKtlintFormatOverCommonMainSourceSet by tasks - val runKtlintCheckOverCommonMainSourceSet by tasks - runKtlintFormatOverCommonMainSourceSet.dependsOn("kspCommonMainKotlinMetadata") - runKtlintCheckOverCommonMainSourceSet.dependsOn("kspCommonMainKotlinMetadata") - tasks { - configureEach { - if (this.name != "kspCommonMainKotlinMetadata" && this.name.startsWith("ksp")) { - this.dependsOn("kspCommonMainKotlinMetadata") - } - } - } -} diff --git a/presentation/features/src/androidHostTest/kotlin/dev/dimension/flare/DatabaseHelper.android.kt b/presentation/features/src/androidHostTest/kotlin/dev/dimension/flare/DatabaseHelper.android.kt new file mode 100644 index 0000000000..591b4fb38f --- /dev/null +++ b/presentation/features/src/androidHostTest/kotlin/dev/dimension/flare/DatabaseHelper.android.kt @@ -0,0 +1,9 @@ +package dev.dimension.flare + +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import kotlin.test.Ignore + +@RunWith(RobolectricTestRunner::class) +@Ignore +actual open class RobolectricTest actual constructor() diff --git a/shared/src/androidHostTest/kotlin/dev/dimension/flare/shared/image/AndroidImageCompressorTest.kt b/presentation/features/src/androidHostTest/kotlin/dev/dimension/flare/media/AndroidImageCompressorTest.kt similarity index 98% rename from shared/src/androidHostTest/kotlin/dev/dimension/flare/shared/image/AndroidImageCompressorTest.kt rename to presentation/features/src/androidHostTest/kotlin/dev/dimension/flare/media/AndroidImageCompressorTest.kt index 3ce1a2c5cc..5d9e13aeb2 100644 --- a/shared/src/androidHostTest/kotlin/dev/dimension/flare/shared/image/AndroidImageCompressorTest.kt +++ b/presentation/features/src/androidHostTest/kotlin/dev/dimension/flare/media/AndroidImageCompressorTest.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare.shared.image +package dev.dimension.flare.media import android.graphics.Bitmap import android.graphics.BitmapFactory diff --git a/shared/src/androidMain/AndroidManifest.xml b/presentation/features/src/androidMain/AndroidManifest.xml similarity index 100% rename from shared/src/androidMain/AndroidManifest.xml rename to presentation/features/src/androidMain/AndroidManifest.xml diff --git a/shared/src/androidMain/kotlin/dev/dimension/flare/data/network/nostr/AmberSignerBridge.android.kt b/presentation/features/src/androidMain/kotlin/dev/dimension/flare/data/network/nostr/AmberSignerBridge.android.kt similarity index 100% rename from shared/src/androidMain/kotlin/dev/dimension/flare/data/network/nostr/AmberSignerBridge.android.kt rename to presentation/features/src/androidMain/kotlin/dev/dimension/flare/data/network/nostr/AmberSignerBridge.android.kt diff --git a/shared/src/androidMain/kotlin/dev/dimension/flare/di/PlatformModule.android.kt b/presentation/features/src/androidMain/kotlin/dev/dimension/flare/di/PlatformModule.android.kt similarity index 65% rename from shared/src/androidMain/kotlin/dev/dimension/flare/di/PlatformModule.android.kt rename to presentation/features/src/androidMain/kotlin/dev/dimension/flare/di/PlatformModule.android.kt index 95e2ac3e9b..9ca1b2575b 100644 --- a/shared/src/androidMain/kotlin/dev/dimension/flare/di/PlatformModule.android.kt +++ b/presentation/features/src/androidMain/kotlin/dev/dimension/flare/di/PlatformModule.android.kt @@ -1,16 +1,20 @@ package dev.dimension.flare.di import dev.dimension.flare.data.database.DriverFactory +import dev.dimension.flare.data.datasource.nostr.DatabaseNostrCache +import dev.dimension.flare.data.datasource.nostr.NostrCache import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.io.AndroidPlatformPathProducer -import dev.dimension.flare.data.io.PlatformPathProducer +import dev.dimension.flare.data.io.FileStorage +import dev.dimension.flare.data.io.OkioFileStorage import dev.dimension.flare.data.network.nostr.AmberIntentLauncherRegistry import dev.dimension.flare.data.network.nostr.AmberSignerBridge import dev.dimension.flare.data.network.nostr.AndroidAmberSignerBridge -import dev.dimension.flare.shared.image.AndroidImageCompressor -import dev.dimension.flare.shared.image.ImageCompressor +import dev.dimension.flare.media.AndroidImageCompressor +import dev.dimension.flare.media.ImageCompressor import dev.dimension.flare.ui.humanizer.AndroidFormatter import dev.dimension.flare.ui.humanizer.PlatformFormatter +import okio.FileSystem import org.koin.android.ext.koin.androidContext import org.koin.core.module.Module import org.koin.core.module.dsl.singleOf @@ -19,11 +23,12 @@ import org.koin.dsl.module internal actual val platformModule: Module = module { - singleOf(::AppDataStore) + single { AppDataStore(get()) } singleOf(::DriverFactory) singleOf(::AmberIntentLauncherRegistry) single { AndroidAmberSignerBridge(androidContext(), get()) } - singleOf(::AndroidPlatformPathProducer) bind PlatformPathProducer::class + single { OkioFileStorage(FileSystem.SYSTEM, AndroidPlatformPathProducer(androidContext())) } singleOf(::AndroidFormatter) bind PlatformFormatter::class singleOf(::AndroidImageCompressor) bind ImageCompressor::class + single { DatabaseNostrCache(get()) } } diff --git a/shared/src/androidMain/kotlin/dev/dimension/flare/shared/image/AndroidImageCompressor.kt b/presentation/features/src/androidMain/kotlin/dev/dimension/flare/media/AndroidImageCompressor.kt similarity index 99% rename from shared/src/androidMain/kotlin/dev/dimension/flare/shared/image/AndroidImageCompressor.kt rename to presentation/features/src/androidMain/kotlin/dev/dimension/flare/media/AndroidImageCompressor.kt index eb15247c6e..59bfe4671d 100644 --- a/shared/src/androidMain/kotlin/dev/dimension/flare/shared/image/AndroidImageCompressor.kt +++ b/presentation/features/src/androidMain/kotlin/dev/dimension/flare/media/AndroidImageCompressor.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare.shared.image +package dev.dimension.flare.media import android.graphics.Bitmap import android.graphics.BitmapFactory diff --git a/shared/src/androidMain/kotlin/dev/dimension/flare/ui/common/AmberSignerLauncherBinding.kt b/presentation/features/src/androidMain/kotlin/dev/dimension/flare/ui/common/AmberSignerLauncherBinding.kt similarity index 100% rename from shared/src/androidMain/kotlin/dev/dimension/flare/ui/common/AmberSignerLauncherBinding.kt rename to presentation/features/src/androidMain/kotlin/dev/dimension/flare/ui/common/AmberSignerLauncherBinding.kt diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/data/model/appearance/TimelinePresentationAppearancePatchHelper.kt b/presentation/features/src/appleMain/kotlin/dev/dimension/flare/data/model/appearance/TimelinePresentationAppearancePatchHelper.kt similarity index 100% rename from shared/src/appleMain/kotlin/dev/dimension/flare/data/model/appearance/TimelinePresentationAppearancePatchHelper.kt rename to presentation/features/src/appleMain/kotlin/dev/dimension/flare/data/model/appearance/TimelinePresentationAppearancePatchHelper.kt diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/data/model/tab/TimelineTabItemV2Helpers.kt b/presentation/features/src/appleMain/kotlin/dev/dimension/flare/data/model/tab/TimelineTabItemV2Helpers.kt similarity index 100% rename from shared/src/appleMain/kotlin/dev/dimension/flare/data/model/tab/TimelineTabItemV2Helpers.kt rename to presentation/features/src/appleMain/kotlin/dev/dimension/flare/data/model/tab/TimelineTabItemV2Helpers.kt diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/data/network/nostr/AmberSignerBridge.apple.kt b/presentation/features/src/appleMain/kotlin/dev/dimension/flare/data/network/nostr/AmberSignerBridge.apple.kt similarity index 100% rename from shared/src/appleMain/kotlin/dev/dimension/flare/data/network/nostr/AmberSignerBridge.apple.kt rename to presentation/features/src/appleMain/kotlin/dev/dimension/flare/data/network/nostr/AmberSignerBridge.apple.kt diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/di/PlatformModule.apple.kt b/presentation/features/src/appleMain/kotlin/dev/dimension/flare/di/PlatformModule.apple.kt similarity index 66% rename from shared/src/appleMain/kotlin/dev/dimension/flare/di/PlatformModule.apple.kt rename to presentation/features/src/appleMain/kotlin/dev/dimension/flare/di/PlatformModule.apple.kt index abbbcd1dbf..129414fe05 100644 --- a/shared/src/appleMain/kotlin/dev/dimension/flare/di/PlatformModule.apple.kt +++ b/presentation/features/src/appleMain/kotlin/dev/dimension/flare/di/PlatformModule.apple.kt @@ -1,19 +1,21 @@ package dev.dimension.flare.di -import dev.dimension.flare.common.AppleOnDeviceAI -import dev.dimension.flare.common.OnDeviceAI import dev.dimension.flare.data.database.DriverFactory +import dev.dimension.flare.data.datasource.nostr.DatabaseNostrCache +import dev.dimension.flare.data.datasource.nostr.NostrCache import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.io.ApplePlatformPathProducer -import dev.dimension.flare.data.io.PlatformPathProducer +import dev.dimension.flare.data.io.FileStorage +import dev.dimension.flare.data.io.OkioFileStorage import dev.dimension.flare.data.network.nostr.AmberSignerBridge import dev.dimension.flare.data.network.nostr.AppleAmberSignerBridge -import dev.dimension.flare.shared.image.ImageCompressor -import dev.dimension.flare.shared.image.IosImageCompressor +import dev.dimension.flare.media.ImageCompressor +import dev.dimension.flare.media.IosImageCompressor import dev.dimension.flare.ui.humanizer.AppleFormatter import dev.dimension.flare.ui.humanizer.PlatformFormatter import dev.dimension.flare.ui.render.ApplePlatformTextRenderer import dev.dimension.flare.ui.render.PlatformTextRendering +import okio.FileSystem import org.koin.core.module.Module import org.koin.core.module.dsl.singleOf import org.koin.dsl.bind @@ -21,12 +23,12 @@ import org.koin.dsl.module internal actual val platformModule: Module = module { - singleOf(::AppDataStore) + single { AppDataStore(get()) } singleOf(::DriverFactory) - singleOf(::ApplePlatformPathProducer) bind PlatformPathProducer::class + single { OkioFileStorage(FileSystem.SYSTEM, ApplePlatformPathProducer()) } singleOf(::AppleFormatter) bind PlatformFormatter::class singleOf(::ApplePlatformTextRenderer) bind PlatformTextRendering::class singleOf(::IosImageCompressor) bind ImageCompressor::class - singleOf(::AppleOnDeviceAI) bind OnDeviceAI::class singleOf(::AppleAmberSignerBridge) bind AmberSignerBridge::class + single { DatabaseNostrCache(get()) } } diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/shared/image/IosImageCompressor.kt b/presentation/features/src/appleMain/kotlin/dev/dimension/flare/media/IosImageCompressor.kt similarity index 98% rename from shared/src/appleMain/kotlin/dev/dimension/flare/shared/image/IosImageCompressor.kt rename to presentation/features/src/appleMain/kotlin/dev/dimension/flare/media/IosImageCompressor.kt index 578ef04bdd..23c29bb16c 100644 --- a/shared/src/appleMain/kotlin/dev/dimension/flare/shared/image/IosImageCompressor.kt +++ b/presentation/features/src/appleMain/kotlin/dev/dimension/flare/media/IosImageCompressor.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare.shared.image +package dev.dimension.flare.media import dev.whyoleg.cryptography.CryptographyProviderApi import dev.whyoleg.cryptography.providers.base.toByteArray diff --git a/presentation/features/src/appleTest/kotlin/dev/dimension/flare/RobolectricTest.apple.kt b/presentation/features/src/appleTest/kotlin/dev/dimension/flare/RobolectricTest.apple.kt new file mode 100644 index 0000000000..5328330f7b --- /dev/null +++ b/presentation/features/src/appleTest/kotlin/dev/dimension/flare/RobolectricTest.apple.kt @@ -0,0 +1,3 @@ +package dev.dimension.flare + +actual open class RobolectricTest actual constructor() diff --git a/shared/src/appleTest/kotlin/dev/dimension/flare/TestFileHelper.apple.kt b/presentation/features/src/appleTest/kotlin/dev/dimension/flare/TestFileHelper.apple.kt similarity index 91% rename from shared/src/appleTest/kotlin/dev/dimension/flare/TestFileHelper.apple.kt rename to presentation/features/src/appleTest/kotlin/dev/dimension/flare/TestFileHelper.apple.kt index 1d0bcabdec..5044ec5b47 100644 --- a/shared/src/appleTest/kotlin/dev/dimension/flare/TestFileHelper.apple.kt +++ b/presentation/features/src/appleTest/kotlin/dev/dimension/flare/TestFileHelper.apple.kt @@ -1,7 +1,7 @@ package dev.dimension.flare -import dev.dimension.flare.common.FileItem -import dev.dimension.flare.common.FileType +import dev.dimension.flare.data.io.FileItem +import dev.dimension.flare.data.io.FileType import okio.FileSystem import okio.Path import okio.Path.Companion.toPath diff --git a/shared/src/appleTest/kotlin/dev/dimension/flare/common/SerializationFormatBenchmarkAppleTest.kt b/presentation/features/src/appleTest/kotlin/dev/dimension/flare/common/SerializationFormatBenchmarkAppleTest.kt similarity index 99% rename from shared/src/appleTest/kotlin/dev/dimension/flare/common/SerializationFormatBenchmarkAppleTest.kt rename to presentation/features/src/appleTest/kotlin/dev/dimension/flare/common/SerializationFormatBenchmarkAppleTest.kt index 5b39bf0636..4cf220459d 100644 --- a/shared/src/appleTest/kotlin/dev/dimension/flare/common/SerializationFormatBenchmarkAppleTest.kt +++ b/presentation/features/src/appleTest/kotlin/dev/dimension/flare/common/SerializationFormatBenchmarkAppleTest.kt @@ -1,8 +1,8 @@ package dev.dimension.flare.common import com.fleeksoft.ksoup.nodes.Element +import dev.dimension.flare.data.database.cache.mapper.TimelinePagingMapper import dev.dimension.flare.data.datasource.microblog.ActionMenu -import dev.dimension.flare.data.datasource.microblog.paging.TimelinePagingMapper import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType @@ -412,7 +412,7 @@ class SerializationFormatBenchmarkAppleTest { multiple = true, ownVotes = persistentListOf(1), voteEvent = - dev.dimension.flare.data.datasource.microblog.PostEvent.Mastodon.Vote( + dev.dimension.flare.ui.model.PostEvent.Mastodon.Vote( id = "vote-${statusKey.id}", accountKey = accountKey, postKey = statusKey, diff --git a/shared/src/appleTest/kotlin/dev/dimension/flare/shared/image/IosImageCompressorTest.kt b/presentation/features/src/appleTest/kotlin/dev/dimension/flare/media/IosImageCompressorTest.kt similarity index 99% rename from shared/src/appleTest/kotlin/dev/dimension/flare/shared/image/IosImageCompressorTest.kt rename to presentation/features/src/appleTest/kotlin/dev/dimension/flare/media/IosImageCompressorTest.kt index f9de41b7af..8d50b525fe 100644 --- a/shared/src/appleTest/kotlin/dev/dimension/flare/shared/image/IosImageCompressorTest.kt +++ b/presentation/features/src/appleTest/kotlin/dev/dimension/flare/media/IosImageCompressorTest.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare.shared.image +package dev.dimension.flare.media import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.addressOf import kotlinx.cinterop.memScoped diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/Cacheable.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/common/Cacheable.kt similarity index 54% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/Cacheable.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/common/Cacheable.kt index a200e8e956..be3dc44c6f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/Cacheable.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/common/Cacheable.kt @@ -11,109 +11,8 @@ import androidx.paging.LoadStates import androidx.paging.PagingData import dev.dimension.flare.ui.model.UiState import kotlinx.collections.immutable.ImmutableList -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.transform -import kotlinx.coroutines.withContext - -internal class Cacheable( - fetchSource: suspend () -> Unit, - cacheSource: () -> Flow, -) : CacheData( - fetchSource = fetchSource, - cacheSource = cacheSource, - ) - -@Suppress("UNCHECKED_CAST") -internal class MemCacheable( - private val key: String, - fetchSource: suspend () -> T, -) : CacheData( - fetchSource = { - update(key, fetchSource.invoke()) - }, - cacheSource = { - subscribe(key) - }, - ) { - companion object { - private val caches = mutableMapOf>() - - fun update( - key: String, - value: T, - ) { - caches[key]?.value = value - } - - fun updateWith( - key: String, - update: (T) -> T, - ) { - if (caches.containsKey(key)) { - caches[key]?.value = update(caches[key]?.value as T) - } - } - - fun subscribe(key: String): Flow = - caches - .getOrPut(key) { - MutableStateFlow(null) - }.filterNotNull() as Flow - } -} - -internal sealed class CacheData( - private val fetchSource: suspend () -> Unit, - private val cacheSource: () -> Flow, -) { - private val refreshFlow = MutableStateFlow(0) - private val cacheFlow by lazy { - cacheSource.invoke() - } - val refreshState: Flow = - refreshFlow - .transform { - emit(LoadState.Loading) - emit( - try { - withContext(Dispatchers.IO) { - fetchSource.invoke() - } - LoadState.NotLoading(true) - } catch (e: Exception) { - LoadState.Error(e) - }, - ) - }.catch { emit(LoadState.Error(it)) } - - val data: Flow> = - cacheFlow - .map> { - CacheState.Success(it) - }.onStart { - emit(CacheState.Empty()) - } - - fun refresh() { - refreshFlow.value++ - } -} - -internal sealed class CacheState { - class Empty : CacheState() - - data class Success( - val data: T, - ) : CacheState() -} @Composable internal fun CacheData.collectAsState(): CacheableState { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/LazyPagingItemsExt.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/common/LazyPagingItemsExt.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/LazyPagingItemsExt.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/common/LazyPagingItemsExt.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/PagingState.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/common/PagingState.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/common/PagingState.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/common/PagingState.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/DataExport.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/DataExport.kt similarity index 86% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/model/DataExport.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/DataExport.kt index a151b78412..479426f88e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/DataExport.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/DataExport.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement @Serializable -public data class DataExport( +internal data class DataExport( val appDatabase: JsonElement, val settings: JsonElement, ) diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/Timeline.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/Timeline.kt new file mode 100644 index 0000000000..c0d0176896 --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/Timeline.kt @@ -0,0 +1,236 @@ +package dev.dimension.flare.data.model.tab + +import androidx.compose.runtime.Immutable +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineRef +import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.data.model.appearance.AppearancePatch +import dev.dimension.flare.data.model.appearance.TimelineAppearance +import dev.dimension.flare.data.model.appearance.toBag +import dev.dimension.flare.data.model.appearance.withPatch +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.UiText +import dev.dimension.flare.ui.model.asText +import dev.dimension.flare.ui.model.asType +import dev.dimension.flare.ui.presenter.home.TimelinePresenter +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec as SocialTimelineSpec + +@Immutable +public sealed interface TimelineTabItemV2 { + public val id: String + public val title: UiText + public val icon: IconType + public val appearancePatch: AppearancePatch? + public val enabled: Boolean + public val filterConfig: TimelineFilterConfig + + // for iOS and Compose call sites + public val key: String get() = id + + public fun withPresentationOverrides( + title: String, + icon: IconType, + appearancePatch: AppearancePatch? = this.appearancePatch, + enabled: Boolean = this.enabled, + filterConfig: TimelineFilterConfig = this.filterConfig, + ): TimelineTabItemV2 +} + +public fun TimelineTabItemV2.resolveTimelineAppearance(base: TimelineAppearance): TimelineAppearance = base.withPatch(appearancePatch) + +@Immutable +public class SourceTimelineTabItemV2 private constructor( + override val id: String, + public val source: TimelineSourceRef?, + public val ref: TimelineRef?, + internal val presentation: TimelinePresentation?, + override val title: UiText, + override val icon: IconType, + override val appearancePatch: AppearancePatch?, + override val enabled: Boolean, + internal val runtimePresenterFactory: (() -> TimelinePresenter)?, +) : TimelineTabItemV2 { + override val filterConfig: TimelineFilterConfig + get() = presentation?.filterConfig ?: TimelineFilterConfig() + + override fun withPresentationOverrides( + title: String, + icon: IconType, + appearancePatch: AppearancePatch?, + enabled: Boolean, + filterConfig: TimelineFilterConfig, + ): TimelineTabItemV2 { + val updatedPresentation = + presentation?.withOverrides( + titleOverride = title, + iconOverride = icon, + appearancePatch = appearancePatch, + enabled = enabled, + filterConfig = filterConfig, + ) ?: TimelinePresentation( + titleOverride = title, + iconOverride = icon, + appearanceOverride = appearancePatch?.takeUnless { it == AppearancePatch.EMPTY }?.toBag(), + enabled = enabled, + filterConfig = filterConfig, + ) + return SourceTimelineTabItemV2( + id = id, + source = source, + ref = ref, + presentation = updatedPresentation, + title = UiText.Raw(title), + icon = icon, + appearancePatch = updatedPresentation.appearance, + enabled = updatedPresentation.enabled, + runtimePresenterFactory = runtimePresenterFactory, + ) + } + + public companion object { + public fun runtime( + id: String, + title: UiText, + icon: IconType, + appearancePatch: AppearancePatch? = null, + enabled: Boolean = true, + createPresenter: () -> TimelinePresenter, + ): SourceTimelineTabItemV2 = + SourceTimelineTabItemV2( + id = id, + source = null, + ref = null, + presentation = null, + title = title, + icon = icon, + appearancePatch = appearancePatch, + enabled = enabled, + runtimePresenterFactory = createPresenter, + ) + + internal fun fromSlot( + slot: TimelineSlot, + source: TimelineSourceRef, + ref: TimelineRef? = null, + ): SourceTimelineTabItemV2 = + SourceTimelineTabItemV2( + id = slot.id, + source = source, + ref = ref, + presentation = slot.presentation, + title = slot.title, + icon = slot.icon, + appearancePatch = slot.presentation.appearance, + enabled = slot.presentation.enabled, + runtimePresenterFactory = null, + ) + + internal fun fromSource( + source: TimelineSourceRef, + ref: TimelineRef? = null, + ): SourceTimelineTabItemV2 = + SourceTimelineTabItemV2( + id = source.id, + source = source, + ref = ref, + presentation = null, + title = source.title, + icon = source.icon, + appearancePatch = null, + enabled = true, + runtimePresenterFactory = null, + ) + } +} + +@Immutable +public class GroupTimelineTabItemV2 internal constructor( + override val id: String, + public val children: List, + public val mergePolicy: TimelineMergePolicy, + internal val source: GroupSource, + internal val presentation: TimelinePresentation, + override val title: UiText, + override val icon: IconType, + override val appearancePatch: AppearancePatch?, + override val enabled: Boolean, +) : TimelineTabItemV2 { + override val filterConfig: TimelineFilterConfig + get() = presentation.filterConfig + + override fun withPresentationOverrides( + title: String, + icon: IconType, + appearancePatch: AppearancePatch?, + enabled: Boolean, + filterConfig: TimelineFilterConfig, + ): TimelineTabItemV2 { + val updatedPresentation = + presentation.withOverrides( + titleOverride = title, + iconOverride = icon, + appearancePatch = appearancePatch, + enabled = enabled, + filterConfig = filterConfig, + ) + return GroupTimelineTabItemV2( + id = id, + children = children, + mergePolicy = mergePolicy, + source = source, + presentation = updatedPresentation, + title = UiText.Raw(title), + icon = icon, + appearancePatch = updatedPresentation.appearance, + enabled = updatedPresentation.enabled, + ) + } +} + +public val TimelineTabItemV2.isSystemHomeMixedTimeline: Boolean + get() = this is GroupTimelineTabItemV2 && source == GroupSource.SystemHome + +internal fun TimelineTabItemV2.findById(id: String): TimelineTabItemV2? = + when (this) { + is SourceTimelineTabItemV2 -> takeIf { this.id == id } + is GroupTimelineTabItemV2 -> takeIf { this.id == id } ?: children.firstNotNullOfOrNull { it.findById(id) } + } + +internal fun List.findById(id: String): TimelineTabItemV2? = firstNotNullOfOrNull { it.findById(id) } + +public fun List.withSystemHomeMixedTimelineEnabled( + enabled: Boolean, + mergePolicy: TimelineMergePolicy? = null, + filterConfig: TimelineFilterConfig? = null, +): List { + val existingGroup = filterIsInstance().firstOrNull { it.source == GroupSource.SystemHome } + val tabsWithoutSystemGroup = filterNot { it.isSystemHomeMixedTimeline } + if (!enabled || tabsWithoutSystemGroup.size < 2) { + return tabsWithoutSystemGroup + } + + val systemGroup = + GroupTimelineTabItemV2( + id = SYSTEM_HOME_MIXED_TIMELINE_ID, + children = tabsWithoutSystemGroup, + mergePolicy = mergePolicy ?: existingGroup?.mergePolicy ?: TimelineMergePolicy.TimePerPage, + source = GroupSource.SystemHome, + presentation = + (existingGroup?.presentation ?: TimelinePresentation()).copy( + filterConfig = filterConfig ?: existingGroup?.filterConfig ?: TimelineFilterConfig(), + ), + title = existingGroup?.title ?: UiStrings.MixedTimeline.asText(), + icon = existingGroup?.icon ?: UiIcon.Rss.asType(), + appearancePatch = existingGroup?.appearancePatch, + enabled = existingGroup?.enabled ?: true, + ) + val targetIndex = + indexOfFirst { it.isSystemHomeMixedTimeline } + .takeIf { it >= 0 } + ?: 0 + return tabsWithoutSystemGroup + .toMutableList() + .apply { + add(minOf(targetIndex, size), systemGroup) + } +} diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelinePersistenceMapper.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelinePersistenceMapper.kt new file mode 100644 index 0000000000..2b42baeb0b --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelinePersistenceMapper.kt @@ -0,0 +1,133 @@ +package dev.dimension.flare.data.model.tab + +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineCatalog +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineRef +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineTabDescriptor +import dev.dimension.flare.model.MicroBlogKey + +internal class TimelinePersistenceMapper( + private val catalog: TimelineCatalog, +) { + fun toTabItem(descriptor: TimelineTabDescriptor.Source): SourceTimelineTabItemV2 = + SourceTimelineTabItemV2.fromSource( + source = toSourceRef(descriptor), + ref = descriptor.ref, + ) + + fun toTabItem(source: TimelineSourceRef): SourceTimelineTabItemV2 = + SourceTimelineTabItemV2.fromSource( + source = source, + ref = runCatching { decode(source) }.getOrNull(), + ) + + fun toTabItem(slot: TimelineSlot): TimelineTabItemV2 = + when (val content = slot.content) { + is TimelineSlotContent.Source -> { + SourceTimelineTabItemV2.fromSlot( + slot = slot, + source = content.source, + ref = runCatching { decode(content.source) }.getOrNull(), + ) + } + + is TimelineSlotContent.Group -> { + GroupTimelineTabItemV2( + id = slot.id, + children = content.children.map { toTabItem(it) }, + mergePolicy = content.mergePolicy, + source = content.source, + presentation = slot.presentation, + title = slot.title, + icon = slot.icon, + appearancePatch = slot.presentation.appearance, + enabled = slot.presentation.enabled, + ) + } + } + + fun toSlot( + descriptor: TimelineTabDescriptor.Source, + presentation: TimelinePresentation = TimelinePresentation(), + ): TimelineSlot = + toSourceRef(descriptor).toSlot( + slotId = descriptor.id, + presentation = presentation, + ) + + fun toSlot(item: TimelineTabItemV2): TimelineSlot = + when (item) { + is SourceTimelineTabItemV2 -> { + val source = + item.source + ?: throw IllegalArgumentException("Runtime timeline tab cannot be persisted: ${item.id}") + source.toSlot( + slotId = item.id, + presentation = item.presentation ?: TimelinePresentation(), + ) + } + + is GroupTimelineTabItemV2 -> { + TimelineSlot( + id = item.id, + content = + TimelineSlotContent.Group( + children = item.children.map { toSlot(it) }, + source = item.source, + mergePolicy = item.mergePolicy, + ), + presentation = item.presentation, + ) + } + } + + fun resolveAccountKey(slot: TimelineSlot): MicroBlogKey? = + when (val content = slot.content) { + is TimelineSlotContent.Source -> { + resolveAccountKey(content.source) + } + + is TimelineSlotContent.Group -> { + null + } + } + + fun resolveAccountKey(item: TimelineTabItemV2): MicroBlogKey? = + when (item) { + is SourceTimelineTabItemV2 -> { + item.ref + ?.data + ?.let { it as? TimelineSpec.AccountData } + ?.accountKey + ?: item.source?.let(::resolveAccountKey) + } + + is GroupTimelineTabItemV2 -> { + null + } + } + + fun resolveAccountKey(source: TimelineSourceRef): MicroBlogKey? = + runCatching { decode(source) } + .getOrNull() + ?.data + ?.let { it as? TimelineSpec.AccountData } + ?.accountKey + + fun decode(source: TimelineSourceRef): TimelineRef = + catalog.decode( + specId = source.specId, + encodedData = source.data, + ) + + fun toSourceRef(descriptor: TimelineTabDescriptor.Source): TimelineSourceRef { + val encoded = catalog.encode(descriptor.ref) + return TimelineSourceRef( + id = descriptor.id, + specId = encoded.specId, + title = descriptor.display.title, + icon = descriptor.display.icon, + data = encoded.data, + ) + } +} diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelinePresenterFactory.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelinePresenterFactory.kt new file mode 100644 index 0000000000..43bae62dbd --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelinePresenterFactory.kt @@ -0,0 +1,58 @@ +package dev.dimension.flare.data.model.tab + +import dev.dimension.flare.data.datasource.microblog.timeline.AccountTimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.StandaloneTimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineRef +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.ui.presenter.home.AccountTimelinePresenter +import dev.dimension.flare.ui.presenter.home.MixedTimelinePresenter +import dev.dimension.flare.ui.presenter.home.StandaloneTimelinePresenter +import dev.dimension.flare.ui.presenter.home.SystemHomeMixedTimelinePresenter +import dev.dimension.flare.ui.presenter.home.TimelinePresenter + +internal class TimelinePresenterFactory( + private val timelinePersistenceMapper: TimelinePersistenceMapper, +) { + fun create(tab: TimelineTabItemV2): TimelinePresenter = + when (tab) { + is SourceTimelineTabItemV2 -> create(tab) + is GroupTimelineTabItemV2 -> create(tab) + }.also { + it.bindTimelineTabItemId(tab.id) + } + + private fun create(tab: SourceTimelineTabItemV2): TimelinePresenter = + tab.runtimePresenterFactory?.invoke() + ?: tab.ref?.let(::create) + ?: tab.source?.let(timelinePersistenceMapper::decode)?.let(::create) + ?: throw IllegalArgumentException("Runtime timeline tab has no presenter factory: ${tab.id}") + + private fun create(tab: GroupTimelineTabItemV2): TimelinePresenter = + when (tab.source) { + GroupSource.SystemHome -> SystemHomeMixedTimelinePresenter(id = tab.id) + GroupSource.Manual -> MixedTimelinePresenter(id = tab.id) + } + + private fun create(ref: TimelineRef): TimelinePresenter = + when (val spec = ref.spec) { + is AccountTimelineSpec<*> -> { + AccountTimelinePresenter( + accountKey = spec.accountKey(ref.data), + loaderFactory = { service -> spec.createLoader(service, ref.data) }, + ) + } + + is StandaloneTimelineSpec<*> -> { + StandaloneTimelinePresenter { context -> + spec.createLoader( + context = context, + data = ref.data, + ) + } + } + + else -> { + throw IllegalArgumentException("Unsupported timeline spec type: ${spec::class}") + } + } +} diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelineResolver.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelineResolver.kt new file mode 100644 index 0000000000..0c534d3184 --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/TimelineResolver.kt @@ -0,0 +1,15 @@ +package dev.dimension.flare.data.model.tab + +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.presenter.home.TimelinePresenter + +public class TimelineResolver internal constructor( + private val timelinePersistenceMapper: TimelinePersistenceMapper, + private val timelinePresenterFactory: TimelinePresenterFactory, +) { + public fun toTabItem(source: TimelineSourceRef): SourceTimelineTabItemV2 = timelinePersistenceMapper.toTabItem(source) + + public fun createPresenter(item: TimelineTabItemV2): TimelinePresenter = timelinePresenterFactory.create(item) + + public fun resolveAccountKey(item: TimelineTabItemV2): MicroBlogKey? = timelinePersistenceMapper.resolveAccountKey(item) +} diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/XqtTimelineSource.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/XqtTimelineSource.kt new file mode 100644 index 0000000000..ce52288da4 --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/XqtTimelineSource.kt @@ -0,0 +1,25 @@ +package dev.dimension.flare.data.model.tab + +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.platform.XqtTimelineSpecs +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.asText +import dev.dimension.flare.ui.model.asType +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.encodeToHexString +import kotlinx.serialization.protobuf.ProtoBuf + +@OptIn(ExperimentalSerializationApi::class) +public fun xqtDeviceFollowTimelineSource(accountKey: MicroBlogKey): TimelineSourceRef { + val spec = XqtTimelineSpecs.deviceFollow + val data = TimelineSpec.AccountBasedData(accountKey) + return TimelineSourceRef( + id = "${spec.id}:$accountKey", + specId = spec.id, + title = UiStrings.Posts.asText(), + icon = UiIcon.List.asType(), + data = ProtoBuf.encodeToHexString(spec.serializer, data), + ) +} diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountServiceProvider.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountServiceProvider.kt new file mode 100644 index 0000000000..8532f67bed --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountServiceProvider.kt @@ -0,0 +1,115 @@ +package dev.dimension.flare.data.repository + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import dev.dimension.flare.common.Locale +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource +import dev.dimension.flare.model.AccountType +import dev.dimension.flare.model.NoActiveAccountException +import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.ui.model.UiAccount +import dev.dimension.flare.ui.model.UiState +import dev.dimension.flare.ui.model.collectAsUiState +import dev.dimension.flare.ui.model.takeSuccess +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull + +@Composable +internal fun accountProvider( + accountType: AccountType, + repository: AccountRepository, +): State> = + produceState>( + initialValue = UiState.Loading(), + key1 = accountType, + ) { + when (accountType) { + AccountType.Guest, + is AccountType.GuestHost, + -> { + flowOf( + UiState.Error( + NoActiveAccountException, + ), + ) + } + + is AccountType.Specific -> { + repository.getFlow(accountType.accountKey) + } + }.collect { + value = it + } + } + +@Composable +internal fun accountServiceProvider( + accountType: AccountType, + repository: AccountRepository, +): UiState = + remember( + accountType, + ) { + accountServiceFlow( + accountType = accountType, + repository = repository, + ) + }.collectAsUiState().value + +@OptIn(ExperimentalCoroutinesApi::class) +internal fun accountServiceFlow( + accountType: AccountType, + repository: AccountRepository, +): Flow = + when (accountType) { + AccountType.Guest -> { + flowOf( + repository.guestDataSource( + type = PlatformType.Mastodon, + host = "mastodon.social", + locale = Locale.language, + ), + ) + } + + is AccountType.GuestHost -> { + flowOf( + repository.guestDataSource( + type = PlatformType.Mastodon, + host = accountType.host, + locale = Locale.language, + ), + ) + } + + is AccountType.Specific -> { + repository + .getFlow(accountType.accountKey) + .mapNotNull { it.takeSuccess() } + .distinctUntilChangedBy { it.accountKey } + .map { account -> repository.getOrCreateDataSource(account) } + } + } + +internal fun activeAccountFlow(repository: AccountRepository): Flow = + repository + .activeAccount + .map { it.takeSuccess() } + .distinctUntilChangedBy { it?.accountKey } + +internal fun allAccountServicesFlow(repository: AccountRepository): Flow> = + repository.allAccounts.map { accounts -> + accounts + .map { + repository.getOrCreateDataSource(it) + }.toImmutableList() + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountTabSyncCoordinator.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountTabSyncCoordinator.kt similarity index 57% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountTabSyncCoordinator.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountTabSyncCoordinator.kt index c6ec63e4b4..4afa982ff0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountTabSyncCoordinator.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountTabSyncCoordinator.kt @@ -1,28 +1,24 @@ package dev.dimension.flare.data.repository -import dev.dimension.flare.data.datasource.microblog.datasource.TimelineTabConfigurationDataSource -import dev.dimension.flare.data.model.MixedTimelineTabItem -import dev.dimension.flare.data.model.TabSettings -import dev.dimension.flare.data.model.TimelineTabItem +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineTabProvider +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.model.tab.GroupSource -import dev.dimension.flare.data.model.tab.SYSTEM_HOME_MIXED_TIMELINE_ID -import dev.dimension.flare.data.model.tab.TimelineMergePolicy -import dev.dimension.flare.data.model.tab.TimelinePresentation -import dev.dimension.flare.data.model.tab.TimelineResolver +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelineSlot import dev.dimension.flare.data.model.tab.TimelineSlotContent +import dev.dimension.flare.data.model.tab.normalizeSystemHomeMixedTimeline import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiAccount -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch internal class AccountTabSyncCoordinator( private val accountRepository: AccountRepository, - private val settingsRepository: SettingsRepository, + private val appDataStore: AppDataStore, private val coroutineScope: CoroutineScope, - private val timelineResolver: TimelineResolver, + private val timelinePersistenceMapper: TimelinePersistenceMapper, ) { init { coroutineScope.launch { @@ -45,7 +41,7 @@ internal class AccountTabSyncCoordinator( accountRepository.allAccounts .first() .mapTo(linkedSetOf()) { it.accountKey } - settingsRepository.updateTabSettingsV2 { + appDataStore.updateTabSettingsV2 { copy( homeSlots = homeSlots @@ -58,14 +54,16 @@ internal class AccountTabSyncCoordinator( } private suspend fun addDefaultTabs(account: UiAccount) { + val service = accountRepository.getOrCreateDataSource(account) val defaultSlots = - (accountRepository.getOrCreateDataSource(account) as? TimelineTabConfigurationDataSource) - ?.defaultTabs + (service as? TimelineTabProvider) + ?.defaultTimelineTabs + ?.map(timelinePersistenceMapper::toSlot) .orEmpty() if (defaultSlots.isEmpty()) { return } - settingsRepository.updateTabSettingsV2 { + appDataStore.updateTabSettingsV2 { val shouldEnableSystemHomeMixedTimeline = homeSlots.anySystemHomeMixedTimeline() || homeSlots.countNonSystemHomeTabs() < 2 @@ -84,7 +82,7 @@ internal class AccountTabSyncCoordinator( } private suspend fun removeTabsForAccount(accountKey: MicroBlogKey) { - settingsRepository.updateTabSettingsV2 { + appDataStore.updateTabSettingsV2 { copy( homeSlots = homeSlots @@ -109,7 +107,7 @@ internal class AccountTabSyncCoordinator( private fun TimelineSlot.cleanupAccountSlots(shouldKeep: (MicroBlogKey?) -> Boolean): TimelineSlot? = when (val slotContent = content) { is TimelineSlotContent.Source -> { - if (shouldKeep(timelineResolver.resolveAccountKey(this))) { + if (shouldKeep(timelinePersistenceMapper.resolveAccountKey(this))) { this } else { null @@ -132,71 +130,8 @@ internal class AccountTabSyncCoordinator( } } -internal fun List.normalizeSystemHomeMixedTimeline(enabled: Boolean): List { - val deduplicatedSlots = distinctBy { it.id } - val existingSystemHomeGroup = deduplicatedSlots.firstOrNull { it.isSystemHomeMixedTimeline() } - val slotsWithoutSystemHomeGroup = deduplicatedSlots.filterNot { it.isSystemHomeMixedTimeline() } - if (!enabled || slotsWithoutSystemHomeGroup.size < 2) { - return slotsWithoutSystemHomeGroup - } - - val systemHomeGroup = - TimelineSlot( - id = SYSTEM_HOME_MIXED_TIMELINE_ID, - content = - TimelineSlotContent.Group( - children = slotsWithoutSystemHomeGroup, - source = GroupSource.SystemHome, - mergePolicy = - (existingSystemHomeGroup?.content as? TimelineSlotContent.Group)?.mergePolicy - ?: TimelineMergePolicy.TimePerPage, - ), - presentation = existingSystemHomeGroup?.presentation ?: TimelinePresentation(), - ) - val targetIndex = deduplicatedSlots.indexOfFirst { it.isSystemHomeMixedTimeline() }.takeIf { it >= 0 } ?: 0 - return slotsWithoutSystemHomeGroup - .toMutableList() - .apply { - add(minOf(targetIndex, size), systemHomeGroup) - } -} - private fun List.anySystemHomeMixedTimeline(): Boolean = any { it.isSystemHomeMixedTimeline() } private fun List.countNonSystemHomeTabs(): Int = count { !it.isSystemHomeMixedTimeline() } private fun TimelineSlot.isSystemHomeMixedTimeline(): Boolean = (content as? TimelineSlotContent.Group)?.source == GroupSource.SystemHome - -internal fun TabSettings.sanitizeDuplicateTabKeys(): TabSettings { - val sanitizedTabs = - mainTabs - .mapNotNull { it.sanitizeDuplicateTabKeys() } - .distinctBy { it.key } - return if (sanitizedTabs == mainTabs) { - this - } else { - copy(mainTabs = sanitizedTabs) - } -} - -private fun TimelineTabItem.sanitizeDuplicateTabKeys(): TimelineTabItem? = - when (this) { - is MixedTimelineTabItem -> { - val sanitizedSubTabs = - subTimelineTabItem - .mapNotNull { it.sanitizeDuplicateTabKeys() } - .distinctBy { it.key } - .toImmutableList() - if (sanitizedSubTabs.isEmpty()) { - null - } else if (sanitizedSubTabs == subTimelineTabItem) { - this - } else { - copy(subTimelineTabItem = sanitizedSubTabs) - } - } - - else -> { - this - } - } diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/repository/AppDataStoreTimelineExtensions.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/repository/AppDataStoreTimelineExtensions.kt new file mode 100644 index 0000000000..cf0aeb97ef --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/data/repository/AppDataStoreTimelineExtensions.kt @@ -0,0 +1,49 @@ +package dev.dimension.flare.data.repository + +import dev.dimension.flare.data.datastore.AppDataStore +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper +import dev.dimension.flare.data.model.tab.TimelineTabItemV2 +import dev.dimension.flare.data.model.tab.findById +import dev.dimension.flare.data.model.tab.isSystemHomeMixedTimeline +import dev.dimension.flare.data.model.tab.withSystemHomeMixedTimelineEnabled +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.map + +internal fun AppDataStore.homeTimelineTabs(timelinePersistenceMapper: TimelinePersistenceMapper): Flow> = + tabSettingsV2 + .distinctUntilChangedBy { it.homeSlots } + .map { settings -> + val tabs = + settings.homeSlots + .map { timelinePersistenceMapper.toTabItem(it) } + tabs.withSystemHomeMixedTimelineEnabled( + enabled = tabs.any { it.isSystemHomeMixedTimeline }, + ) + } + +internal fun AppDataStore.homeTimelineTab( + id: String, + timelinePersistenceMapper: TimelinePersistenceMapper, +): Flow = + homeTimelineTabs(timelinePersistenceMapper).map { tabs -> + tabs.findById(id) + } + +internal suspend fun AppDataStore.replaceHomeTimelineTabs( + tabs: List, + timelinePersistenceMapper: TimelinePersistenceMapper, +) { + updateTabSettingsV2 { + val normalizedTabs = + tabs.withSystemHomeMixedTimelineEnabled( + enabled = tabs.any { it.isSystemHomeMixedTimeline }, + ) + copy( + homeSlots = + normalizedTabs + .distinctBy { it.id } + .map { timelinePersistenceMapper.toSlot(it) }, + ) + } +} diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/di/AppModule.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/di/AppModule.kt new file mode 100644 index 0000000000..641c37590f --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/di/AppModule.kt @@ -0,0 +1,14 @@ +package dev.dimension.flare.di + +internal fun appModule() = + listOf( + platformModule, + foundationDatabaseModule, + settingsDataModule, + accountDataModule, + draftDataModule, + localDataModule, + aiDataModule, + translationDataModule, + commonModule, + ) diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/di/CommonModule.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/di/CommonModule.kt new file mode 100644 index 0000000000..ab9634a0eb --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/di/CommonModule.kt @@ -0,0 +1,48 @@ +package dev.dimension.flare.di + +import dev.dimension.flare.common.PlatformDispatchers +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineCatalog +import dev.dimension.flare.data.datasource.rss.RssTimelineSpecs +import dev.dimension.flare.data.draft.SendDraftUseCase +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper +import dev.dimension.flare.data.model.tab.TimelinePresenterFactory +import dev.dimension.flare.data.model.tab.TimelineResolver +import dev.dimension.flare.data.network.rss.Readability +import dev.dimension.flare.data.repository.AccountTabSyncCoordinator +import dev.dimension.flare.model.SocialPlatformRegistry +import dev.dimension.flare.model.defaultSocialPlatformRegistry +import dev.dimension.flare.ui.presenter.compose.ComposeUseCase +import kotlinx.coroutines.CoroutineScope +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +internal val commonModule = + module { + single { defaultSocialPlatformRegistry } + single { + TimelineCatalog( + get().specs.flatMap { it.timelineSpecs } + RssTimelineSpecs.timelineSpecs, + ) + } + single(createdAtStart = true) { AccountTabSyncCoordinator(get(), get(), get(), get()) } + single { CoroutineScope(PlatformDispatchers.IO) } + single { + val accountRepository = get() + SendDraftUseCase( + draftRepository = get(), + draftMediaStore = get(), + findAccount = accountRepository::find, + composeDraft = { account, data, progress -> + (accountRepository.getOrCreateDataSource(account) as? AuthenticatedMicroblogDataSource) + ?.compose(data = data, progress = progress) + }, + ) + } + singleOf(::ComposeUseCase) + singleOf(::Readability) + single { TimelinePresenterFactory(get()) } + single { TimelinePersistenceMapper(get()) } + single { TimelineResolver(get(), get()) } + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/di/PlatformModule.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/di/PlatformModule.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/di/PlatformModule.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/di/PlatformModule.kt diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/model/PlatformSpec.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/model/PlatformSpec.kt new file mode 100644 index 0000000000..7dd23c9b6f --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/model/PlatformSpec.kt @@ -0,0 +1,18 @@ +package dev.dimension.flare.model + +import dev.dimension.flare.data.platform.BlueskySocialPlatformPlugin +import dev.dimension.flare.data.platform.MastodonSocialPlatformPlugin +import dev.dimension.flare.data.platform.MisskeySocialPlatformPlugin +import dev.dimension.flare.data.platform.VvoSocialPlatformPlugin +import dev.dimension.flare.data.platform.XqtSocialPlatformPlugin + +internal expect val defaultSocialPlatformRegistry: SocialPlatformRegistry + +internal val defaultSocialPlatformPlugins: List = + listOf( + MastodonSocialPlatformPlugin, + MisskeySocialPlatformPlugin, + BlueskySocialPlatformPlugin, + XqtSocialPlatformPlugin, + VvoSocialPlatformPlugin, + ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiRssSource.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiRssSource.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiRssSource.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiRssSource.kt index 3497c34977..464bc14ce0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiRssSource.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiRssSource.kt @@ -1,8 +1,8 @@ package dev.dimension.flare.ui.model import androidx.compose.runtime.Immutable -import dev.dimension.flare.data.database.app.model.RssDisplayMode import dev.dimension.flare.data.database.app.model.SubscriptionType +import dev.dimension.flare.model.RssDisplayMode import dev.dimension.flare.model.vvo import dev.dimension.flare.model.vvoHost import dev.dimension.flare.model.vvoHostLong diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiStateCacheExt.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiStateCacheExt.kt new file mode 100644 index 0000000000..92c54dcf35 --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiStateCacheExt.kt @@ -0,0 +1,32 @@ +package dev.dimension.flare.ui.model + +import androidx.paging.LoadState +import dev.dimension.flare.common.CacheData +import dev.dimension.flare.common.CacheState +import dev.dimension.flare.common.CacheableState +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine + +internal fun CacheableState.toUi(): UiState = + data?.let { + UiState.Success(it) + } ?: run { + when (val state = refreshState) { + is LoadState.Error -> UiState.Error(state.error) + LoadState.Loading -> UiState.Loading() + is LoadState.NotLoading -> UiState.Error(IllegalStateException("Data is null")) + } + } + +internal fun CacheData.toUi(): Flow> = + combine(data, refreshState) { data, refresh -> + if (data is CacheState.Success) { + UiState.Success(data.data) + } else { + when (refresh) { + is LoadState.Error -> UiState.Error(refresh.error) + LoadState.Loading -> UiState.Loading() + is LoadState.NotLoading -> UiState.Error(IllegalStateException("Data is null")) + } + } + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Render.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Render.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Render.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Render.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/EnvironmentSettingsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/EnvironmentSettingsPresenter.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/EnvironmentSettingsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/EnvironmentSettingsPresenter.kt index e55b9f40a7..14d1f17d51 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/EnvironmentSettingsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/EnvironmentSettingsPresenter.kt @@ -2,10 +2,10 @@ package dev.dimension.flare.ui.presenter import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.datastore.model.AppSettings import dev.dimension.flare.data.model.appearance.GlobalAppearance import dev.dimension.flare.data.model.appearance.TimelineAppearance -import dev.dimension.flare.data.repository.SettingsRepository import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.collectAsUiState import org.koin.core.component.KoinComponent @@ -14,7 +14,7 @@ import org.koin.core.component.inject public class EnvironmentSettingsPresenter : PresenterBase(), KoinComponent { - private val repository: SettingsRepository by inject() + private val repository: AppDataStore by inject() @Composable override fun body(): State { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ExportDataPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ExportDataPresenter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ExportDataPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ExportDataPresenter.kt diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ExportSettingsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ExportSettingsPresenter.kt new file mode 100644 index 0000000000..3f3fef29f5 --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ExportSettingsPresenter.kt @@ -0,0 +1,20 @@ +package dev.dimension.flare.ui.presenter + +import androidx.compose.runtime.Composable +import dev.dimension.flare.data.datastore.AppDataStore +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +public class ExportSettingsPresenter : + PresenterBase(), + KoinComponent { + private val appDataStore: AppDataStore by inject() + + @Composable + override fun body(): ExportState = + object : ExportState { + override suspend fun export(): String = this@ExportSettingsPresenter.export() + } + + public suspend fun export(): String = appDataStore.exportSettings() +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/HomeTabsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/HomeTabsPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/HomeTabsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/HomeTabsPresenter.kt index 469e33139e..10b925b607 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/HomeTabsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/HomeTabsPresenter.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.ui.presenter import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.repository.activeAccountFlow import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.collectAsUiState diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/HomeTimelineWithTabsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/HomeTimelineWithTabsPresenter.kt similarity index 78% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/HomeTimelineWithTabsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/HomeTimelineWithTabsPresenter.kt index 87cf911295..d3984ad4ce 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/HomeTimelineWithTabsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/HomeTimelineWithTabsPresenter.kt @@ -3,19 +3,21 @@ package dev.dimension.flare.ui.presenter import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.model.IconType import dev.dimension.flare.data.model.tab.SourceTimelineTabItemV2 +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.SettingsRepository +import dev.dimension.flare.data.repository.homeTimelineTabs import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.UiStrings import dev.dimension.flare.ui.model.asText import dev.dimension.flare.ui.model.collectAsUiState +import dev.dimension.flare.ui.presenter.home.AccountTimelinePresenter import dev.dimension.flare.ui.presenter.home.ActiveAccountPresenter -import dev.dimension.flare.ui.presenter.home.HomeTimelinePresenter import dev.dimension.flare.ui.presenter.home.UserState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -28,8 +30,9 @@ import org.koin.core.component.inject public class HomeTimelineWithTabsPresenter : PresenterBase(), KoinComponent { - private val settingsRepository by inject() + private val appDataStore by inject() private val accountRepository by inject() + private val timelinePersistenceMapper by inject() public interface State : UserState { public val tabState: UiState> @@ -42,7 +45,7 @@ public class HomeTimelineWithTabsPresenter : } private val tabsState by lazy { - settingsRepository.homeTimelineTabs.combine(isLoggedInFlow) { tabs, isLoggedIn -> + appDataStore.homeTimelineTabs(timelinePersistenceMapper).combine(isLoggedInFlow) { tabs, isLoggedIn -> tabs .withGuestMastodonHomeFallback(isLoggedIn = isLoggedIn) .toImmutableList() @@ -80,6 +83,8 @@ internal val guestMastodonHomeTimelineTab: TimelineTabItemV2 title = UiStrings.Home.asText(), icon = IconType.Material(UiIcon.Home), createPresenter = { - HomeTimelinePresenter(AccountType.GuestHost(DEFAULT_GUEST_MASTODON_HOST)) + AccountTimelinePresenter(AccountType.GuestHost(DEFAULT_GUEST_MASTODON_HOST)) { service -> + service.homeTimeline() + } }, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ImportDataPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ImportDataPresenter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ImportDataPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ImportDataPresenter.kt diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ImportSettingsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ImportSettingsPresenter.kt new file mode 100644 index 0000000000..65039a15b3 --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ImportSettingsPresenter.kt @@ -0,0 +1,25 @@ +package dev.dimension.flare.ui.presenter + +import androidx.compose.runtime.Composable +import dev.dimension.flare.data.datastore.AppDataStore +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +public class ImportSettingsPresenter( + private val jsonContent: String, +) : PresenterBase(), + KoinComponent { + private val appDataStore: AppDataStore by inject() + + @Composable + override fun body(): ImportState = + object : ImportState { + override suspend fun import() { + this@ImportSettingsPresenter.import() + } + } + + public suspend fun import() { + appDataStore.importSettings(jsonContent) + } +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/PinTabsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/PinTabsPresenter.kt similarity index 82% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/PinTabsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/PinTabsPresenter.kt index 346b9a7da0..7143026cfa 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/PinTabsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/PinTabsPresenter.kt @@ -2,10 +2,10 @@ package dev.dimension.flare.ui.presenter import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import dev.dimension.flare.data.model.tab.TimelineResolver +import dev.dimension.flare.data.datastore.AppDataStore +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelineSlot import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import dev.dimension.flare.data.repository.SettingsRepository import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.collectAsUiState import dev.dimension.flare.ui.model.map @@ -24,8 +24,8 @@ public class PinTabs internal constructor( public abstract class PinTabsPresenter : PresenterBase>(), KoinComponent { - private val settingsRepository by inject() - private val timelineResolver by inject() + private val appDataStore by inject() + private val timelinePersistenceMapper by inject() private val appScope: CoroutineScope by inject() public interface State { @@ -40,7 +40,7 @@ public abstract class PinTabsPresenter : @Composable override fun body(): State { - val tabSettings by settingsRepository.tabSettingsV2.collectAsUiState() + val tabSettings by appDataStore.tabSettingsV2.collectAsUiState() val pins = tabSettings.map { PinTabs(it.homeSlots, ::matches) @@ -51,7 +51,7 @@ public abstract class PinTabsPresenter : override fun pinTab(item: T) { appScope.launch { - settingsRepository.updateTabSettingsV2 { + appDataStore.updateTabSettingsV2 { if (homeSlots.any { matches(it, item) }) { return@updateTabSettingsV2 this } @@ -65,7 +65,7 @@ public abstract class PinTabsPresenter : override fun unpinTab(item: T) { appScope.launch { - settingsRepository.updateTabSettingsV2 { + appDataStore.updateTabSettingsV2 { copy( homeSlots = homeSlots.filterNot { matches(it, item) }, ) @@ -73,7 +73,7 @@ public abstract class PinTabsPresenter : } } - override fun timelineTabItem(item: T): TimelineTabItemV2 = timelineResolver.toTabItem(getTimelineTabItem(item)) + override fun timelineTabItem(item: T): TimelineTabItemV2 = timelinePersistenceMapper.toTabItem(getTimelineTabItem(item)) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/SettingsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/SettingsPresenter.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/SettingsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/SettingsPresenter.kt index 87e43d3c37..7c9daf60fd 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/SettingsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/SettingsPresenter.kt @@ -3,6 +3,7 @@ package dev.dimension.flare.ui.presenter import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.datastore.model.AppSettings import dev.dimension.flare.data.model.AvatarShape import dev.dimension.flare.data.model.PostActionStyle @@ -12,7 +13,6 @@ import dev.dimension.flare.data.model.VideoAutoplay import dev.dimension.flare.data.model.appearance.AppearanceKey import dev.dimension.flare.data.model.appearance.AppearanceKeys import dev.dimension.flare.data.model.appearance.AppearancePatch -import dev.dimension.flare.data.repository.SettingsRepository import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.collectAsUiState import kotlinx.coroutines.Dispatchers @@ -24,7 +24,7 @@ import org.koin.core.component.inject public class SettingsPresenter : PresenterBase(), KoinComponent { - private val repository: SettingsRepository by inject() + private val repository: AppDataStore by inject() @Composable override fun body(): State { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/State.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/State.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/State.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/State.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposePresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposePresenter.kt index 7d08b23b6a..a5fb7c8882 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposePresenter.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import dev.dimension.flare.common.combineLatestFlowLists +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource import dev.dimension.flare.data.datasource.microblog.ComposeConfig import dev.dimension.flare.data.datasource.microblog.ComposeData @@ -18,13 +19,14 @@ import dev.dimension.flare.data.datasource.microblog.ComposeType import dev.dimension.flare.data.datasource.microblog.datasource.PostDataSource import dev.dimension.flare.data.datasource.microblog.datasource.UserDataSource import dev.dimension.flare.data.datastore.AppDataStore -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.DraftRepository +import dev.dimension.flare.data.datastore.model.ComposeVisibility +import dev.dimension.flare.data.draft.DraftRepository +import dev.dimension.flare.data.draft.RestoreDraftUseCase import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.data.repository.newDraftGroupId import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.model.draft.newDraftGroupId import dev.dimension.flare.ui.model.EmojiData import dev.dimension.flare.ui.model.UiAccount import dev.dimension.flare.ui.model.UiDraft @@ -562,7 +564,7 @@ public class ComposePresenter( LaunchedEffect(Unit) { appDataStore.composeConfigData.data.firstOrNull()?.let { data -> if (!hasExplicitVisibility) { - visibility = data.visibility + visibility = data.visibility.toUiVisibility() } } } @@ -602,6 +604,15 @@ public class ComposePresenter( } } +private fun ComposeVisibility.toUiVisibility(): UiTimelineV2.Post.Visibility = + when (this) { + ComposeVisibility.Public -> UiTimelineV2.Post.Visibility.Public + ComposeVisibility.Home -> UiTimelineV2.Post.Visibility.Home + ComposeVisibility.Followers -> UiTimelineV2.Post.Visibility.Followers + ComposeVisibility.Specified -> UiTimelineV2.Post.Visibility.Specified + ComposeVisibility.Channel -> UiTimelineV2.Post.Visibility.Channel + } + @Immutable public interface VisibilityState { public val visibility: UiTimelineV2.Post.Visibility @@ -617,24 +628,6 @@ public interface VisibilityState { public fun clear() } -@Immutable -public sealed class ComposeStatus { - public abstract val statusKey: MicroBlogKey - - public data class Quote( - override val statusKey: MicroBlogKey, - ) : ComposeStatus() - - public open class Reply( - override val statusKey: MicroBlogKey, - ) : ComposeStatus() - - public data class VVOComment( - override val statusKey: MicroBlogKey, - val rootId: String, - ) : Reply(statusKey) -} - @Immutable public abstract class ComposeState( public val canSend: Boolean, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposeUseCase.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposeUseCase.kt similarity index 73% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposeUseCase.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposeUseCase.kt index 8de6797120..133ec5bf8c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposeUseCase.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposeUseCase.kt @@ -1,15 +1,19 @@ package dev.dimension.flare.ui.presenter.compose -import androidx.compose.runtime.Immutable +import dev.dimension.flare.common.DebugRepository import dev.dimension.flare.common.InAppNotification import dev.dimension.flare.common.Message +import dev.dimension.flare.common.tryRun import dev.dimension.flare.data.datasource.microblog.ComposeData import dev.dimension.flare.data.datastore.AppDataStore -import dev.dimension.flare.data.repository.DebugRepository -import dev.dimension.flare.data.repository.newDraftGroupId -import dev.dimension.flare.data.repository.toComposeDraftBundle -import dev.dimension.flare.data.repository.tryRun +import dev.dimension.flare.data.datastore.model.ComposeVisibility +import dev.dimension.flare.data.draft.ComposeProgressState +import dev.dimension.flare.data.draft.SaveDraftUseCase +import dev.dimension.flare.data.draft.SendDraftUseCase +import dev.dimension.flare.model.draft.newDraftGroupId +import dev.dimension.flare.model.draft.toComposeDraftBundle import dev.dimension.flare.ui.model.UiAccount +import dev.dimension.flare.ui.model.UiTimelineV2 import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -60,7 +64,7 @@ internal class ComposeUseCase( progress.invoke(ComposeProgressState.Progress(0, 1)) appDataStore.composeConfigData.updateData { it.copy( - visibility = data.visibility, + visibility = data.visibility.toComposeVisibility(), lastAccounts = if (data.referenceStatus == null) { accounts.map { account -> account.accountKey } @@ -90,19 +94,11 @@ internal class ComposeUseCase( } } -@Immutable -internal sealed interface ComposeProgressState { - @Immutable - data object Success : ComposeProgressState - - @Immutable - data class Progress( - val current: Int, - val max: Int, - ) : ComposeProgressState - - @Immutable - data class Error( - val throwable: Throwable, - ) : ComposeProgressState -} +private fun UiTimelineV2.Post.Visibility.toComposeVisibility(): ComposeVisibility = + when (this) { + UiTimelineV2.Post.Visibility.Public -> ComposeVisibility.Public + UiTimelineV2.Post.Visibility.Home -> ComposeVisibility.Home + UiTimelineV2.Post.Visibility.Followers -> ComposeVisibility.Followers + UiTimelineV2.Post.Visibility.Specified -> ComposeVisibility.Specified + UiTimelineV2.Post.Visibility.Channel -> ComposeVisibility.Channel + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/EmojiHistoryPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/EmojiHistoryPresenter.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/EmojiHistoryPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/EmojiHistoryPresenter.kt index 2126759fbd..87c19f4254 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/EmojiHistoryPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/EmojiHistoryPresenter.kt @@ -4,8 +4,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.database.cache.CacheDatabase -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiEmoji diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/InitialTextResolver.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/InitialTextResolver.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/InitialTextResolver.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/InitialTextResolver.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/DMConversationPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/DMConversationPresenter.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/DMConversationPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/DMConversationPresenter.kt index 23bf18ba70..04cfef2d4a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/DMConversationPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/DMConversationPresenter.kt @@ -9,8 +9,8 @@ import androidx.paging.compose.collectAsLazyPagingItems import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.collectAsState import dev.dimension.flare.common.toPagingState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.DirectMessageDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/DMListPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/DMListPresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/DMListPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/DMListPresenter.kt index 2236c4a22f..c3f4eb50a6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/DMListPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/DMListPresenter.kt @@ -9,8 +9,8 @@ import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.isRefreshing import dev.dimension.flare.common.onSuccess import dev.dimension.flare.common.toPagingState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.DirectMessageDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiDMRoom diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/UserDMConversationPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/UserDMConversationPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/UserDMConversationPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/UserDMConversationPresenter.kt index 54e6febea1..8123a89c96 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/UserDMConversationPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/dm/UserDMConversationPresenter.kt @@ -3,8 +3,8 @@ package dev.dimension.flare.ui.presenter.dm import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.DirectMessageDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AccountTimelinePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AccountTimelinePresenter.kt new file mode 100644 index 0000000000..059942846b --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AccountTimelinePresenter.kt @@ -0,0 +1,50 @@ +package dev.dimension.flare.ui.presenter.home + +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.database.app.AppDatabase +import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource +import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineLoaderContext +import dev.dimension.flare.data.repository.accountServiceFlow +import dev.dimension.flare.model.AccountType +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiTimelineV2 +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import org.koin.core.component.inject + +internal class AccountTimelinePresenter( + private val accountType: AccountType, + private val loaderFactory: (service: MicroblogDataSource) -> RemoteLoader, +) : TimelinePresenter() { + constructor( + accountKey: MicroBlogKey, + loaderFactory: (service: MicroblogDataSource) -> RemoteLoader, + ) : this(AccountType.Specific(accountKey), loaderFactory) + + private val accountRepository: AccountRepository by inject() + + override val loader: Flow> by lazy { + accountServiceFlow( + accountType = accountType, + repository = accountRepository, + ).map(loaderFactory) + } +} + +internal class StandaloneTimelinePresenter( + private val loaderFactory: (context: TimelineLoaderContext) -> Flow>, +) : TimelinePresenter() { + private val appDatabase: AppDatabase by inject() + private val cacheDatabase: CacheDatabase by inject() + + override val loader: Flow> by lazy { + loaderFactory( + TimelineLoaderContext( + appDatabase = appDatabase, + cacheDatabase = cacheDatabase, + ), + ) + } +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/ActiveAccountPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/ActiveAccountPresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/ActiveAccountPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/ActiveAccountPresenter.kt index 33c2aa4eaf..f52249e413 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/ActiveAccountPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/ActiveAccountPresenter.kt @@ -3,8 +3,8 @@ package dev.dimension.flare.ui.presenter.home import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.UserDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiProfile diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AllNotificationBadgePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AllNotificationBadgePresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AllNotificationBadgePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AllNotificationBadgePresenter.kt index 657363fe51..edd440f431 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AllNotificationBadgePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AllNotificationBadgePresenter.kt @@ -5,8 +5,8 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import dev.dimension.flare.common.collectAsState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.NotificationDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.allAccountServicesFlow import dev.dimension.flare.ui.presenter.PresenterBase import kotlinx.coroutines.delay diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AllNotificationPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AllNotificationPresenter.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AllNotificationPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AllNotificationPresenter.kt index 22ee95de83..6ddd4fe0c6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AllNotificationPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/AllNotificationPresenter.kt @@ -9,12 +9,12 @@ import androidx.compose.runtime.remember import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.combineLatestFlowLists import dev.dimension.flare.common.refreshSuspend +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource import dev.dimension.flare.data.datasource.microblog.NotificationFilter import dev.dimension.flare.data.datasource.microblog.datasource.NotificationDataSource import dev.dimension.flare.data.datasource.microblog.datasource.UserDataSource import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.data.repository.allAccountServicesFlow import dev.dimension.flare.model.AccountType diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DeepLinkPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DeepLinkPresenter.kt similarity index 82% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DeepLinkPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DeepLinkPresenter.kt index 4d09c5ed3e..0f97800e6c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DeepLinkPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DeepLinkPresenter.kt @@ -7,13 +7,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import dev.dimension.flare.common.deeplink.DeepLinkMapping -import dev.dimension.flare.data.database.cache.model.TranslationDisplayMode +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.translation.TranslationDisplayMode import dev.dimension.flare.data.datasource.microblog.datasource.PostDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.data.translation.PreTranslationService import dev.dimension.flare.model.AccountType -import dev.dimension.flare.model.spec +import dev.dimension.flare.model.SocialPlatformRegistry import dev.dimension.flare.ui.model.DeeplinkEvent import dev.dimension.flare.ui.presenter.PresenterBase import dev.dimension.flare.ui.route.DeeplinkRoute @@ -32,6 +32,7 @@ public class DeepLinkPresenter( KoinComponent { private val accountRepository: AccountRepository by inject() private val preTranslationService: PreTranslationService by inject() + private val platformRegistry: SocialPlatformRegistry by inject() @androidx.compose.runtime.Immutable public interface State { @@ -41,8 +42,13 @@ public class DeepLinkPresenter( private val patternFlow by lazy { accountRepository.allAccounts.map { it - .associateWith { - it.platformType.spec.deepLinkPatterns(it.accountKey.host) + .associate { account -> + account.accountKey to + platformRegistry + .deepLinkPatterns( + type = account.platformType, + host = account.accountKey.host, + ) }.toImmutableMap() } } @@ -55,20 +61,22 @@ public class DeepLinkPresenter( if (DeeplinkEvent.isDeeplinkEvent(url)) { val event = DeeplinkEvent.parse(url) if (event != null) { + val postEvent = event.postEvent + val translationEvent = event.translationEvent when { - event.postEvent != null -> { + postEvent != null -> { accountServiceFlow( accountType = AccountType.Specific(event.accountKey), repository = accountRepository, ).firstOrNull()?.let { service -> if (service is PostDataSource) { - service.postEventHandler.handleEvent(event.postEvent) + service.postEventHandler.handleEvent(postEvent) } } } - event.translationEvent is DeeplinkEvent.TranslationEvent.RetryTranslation -> { - with(event.translationEvent) { + translationEvent is DeeplinkEvent.TranslationEvent.RetryTranslation -> { + with(translationEvent) { preTranslationService.setStatusDisplayMode( accountType = AccountType.Specific(event.accountKey), statusKey = statusKey, @@ -81,8 +89,8 @@ public class DeepLinkPresenter( } } - event.translationEvent is DeeplinkEvent.TranslationEvent.Translate -> { - with(event.translationEvent) { + translationEvent is DeeplinkEvent.TranslationEvent.Translate -> { + with(translationEvent) { preTranslationService.setStatusDisplayMode( accountType = AccountType.Specific(event.accountKey), statusKey = statusKey, @@ -95,10 +103,10 @@ public class DeepLinkPresenter( } } - event.translationEvent is DeeplinkEvent.TranslationEvent.ShowOriginal -> { + translationEvent is DeeplinkEvent.TranslationEvent.ShowOriginal -> { preTranslationService.setStatusDisplayMode( accountType = AccountType.Specific(event.accountKey), - statusKey = event.translationEvent.statusKey, + statusKey = translationEvent.statusKey, mode = TranslationDisplayMode.Original, ) } @@ -133,12 +141,7 @@ public class DeepLinkPresenter( val route = DeeplinkRoute.DeepLinkAccountPicker( originalUrl = url, - data = - matches - .map { - it.key.accountKey to it.value.deepLink(it.key.accountKey) - }.toMap() - .toImmutableMap(), + data = matches, ) withContext(Dispatchers.Main) { onRoute.invoke(route) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DirectMessageBadgePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DirectMessageBadgePresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DirectMessageBadgePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DirectMessageBadgePresenter.kt index c51b7bf7eb..8ae5f169b5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DirectMessageBadgePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DirectMessageBadgePresenter.kt @@ -4,8 +4,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import dev.dimension.flare.common.collectAsState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.DirectMessageDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiState diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DiscoverPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DiscoverPresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DiscoverPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DiscoverPresenter.kt index b1c02edde5..0249b41f3c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DiscoverPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DiscoverPresenter.kt @@ -16,11 +16,11 @@ import dev.dimension.flare.common.combineLatestFlowLists import dev.dimension.flare.common.emptyFlow import dev.dimension.flare.common.refreshSuspend import dev.dimension.flare.common.toPagingState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource import dev.dimension.flare.data.datasource.microblog.datasource.UserDataSource import dev.dimension.flare.data.datasource.microblog.paging.toPagingSource import dev.dimension.flare.data.datasource.microblog.pagingConfig -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.data.repository.allAccountServicesFlow import dev.dimension.flare.model.AccountType @@ -112,7 +112,9 @@ public class DiscoverPresenter : private fun statusFlow(scope: CoroutineScope) = selectedAccountTypeFlow.flatMapLatest { accountType -> - DiscoverStatusTimelinePresenter(accountType).createPager(scope) + AccountTimelinePresenter(accountType) { service -> + service.discoverStatuses() + }.createPager(scope) } @Composable diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/FavIconPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/FavIconPresenter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/FavIconPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/FavIconPresenter.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/GroupConfigPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/GroupConfigPresenter.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/GroupConfigPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/GroupConfigPresenter.kt index 1a288bc47f..622b93a5c2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/GroupConfigPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/GroupConfigPresenter.kt @@ -3,6 +3,7 @@ package dev.dimension.flare.ui.presenter.home import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.model.IconType import dev.dimension.flare.data.model.appearance.AppearancePatch import dev.dimension.flare.data.model.appearance.toBag @@ -11,12 +12,11 @@ import dev.dimension.flare.data.model.tab.GroupTimelineTabItemV2 import dev.dimension.flare.data.model.tab.TabSettingsV2 import dev.dimension.flare.data.model.tab.TimelineFilterConfig import dev.dimension.flare.data.model.tab.TimelineMergePolicy +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelinePresentation -import dev.dimension.flare.data.model.tab.TimelineResolver import dev.dimension.flare.data.model.tab.TimelineSlot import dev.dimension.flare.data.model.tab.TimelineSlotContent import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import dev.dimension.flare.data.repository.SettingsRepository import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.presenter.PresenterBase import kotlinx.collections.immutable.ImmutableList @@ -29,9 +29,9 @@ import org.koin.core.component.inject public class GroupConfigPresenter : PresenterBase(), KoinComponent { - private val settingsRepository: SettingsRepository by inject() + private val appDataStore: AppDataStore by inject() private val appScope: CoroutineScope by inject() - private val timelineResolver: TimelineResolver by inject() + private val timelinePersistenceMapper: TimelinePersistenceMapper by inject() @Composable override fun body(): State { @@ -55,7 +55,7 @@ public class GroupConfigPresenter : defaultGroupName: String, ) { appScope.launch { - settingsRepository.updateTabSettingsV2 { + appDataStore.updateTabSettingsV2 { upsertGroupConfig( initialItem = initialItem, name = name, @@ -66,7 +66,7 @@ public class GroupConfigPresenter : mergePolicy = mergePolicy, filterConfig = filterConfig, defaultGroupName = defaultGroupName, - timelineResolver = timelineResolver, + timelinePersistenceMapper = timelinePersistenceMapper, ) } } @@ -102,7 +102,7 @@ internal fun TabSettingsV2.upsertGroupConfig( mergePolicy: TimelineMergePolicy = initialItem?.mergePolicy ?: TimelineMergePolicy.TimePerPage, filterConfig: TimelineFilterConfig = initialItem?.filterConfig ?: TimelineFilterConfig(), defaultGroupName: String, - timelineResolver: TimelineResolver, + timelinePersistenceMapper: TimelinePersistenceMapper, ): TabSettingsV2 { val deduplicatedTabs = tabs.distinctBy { it.id } if (deduplicatedTabs.isEmpty()) { @@ -117,7 +117,7 @@ internal fun TabSettingsV2.upsertGroupConfig( } } - val childSlots = deduplicatedTabs.map { timelineResolver.toSlot(it) } + val childSlots = deduplicatedTabs.map { timelinePersistenceMapper.toSlot(it) } val newGroup = buildGroupSlot(name, icon, appearancePatch, enabled, mergePolicy, filterConfig, defaultGroupName, childSlots) val currentSlots = homeSlots.toMutableList() val targetIndex = diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTabItemPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTabItemPresenter.kt similarity index 78% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTabItemPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTabItemPresenter.kt index 82883c6d09..3c427d434b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTabItemPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTabItemPresenter.kt @@ -3,8 +3,10 @@ package dev.dimension.flare.ui.presenter.home import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import dev.dimension.flare.data.datastore.AppDataStore +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import dev.dimension.flare.data.repository.SettingsRepository +import dev.dimension.flare.data.repository.homeTimelineTab import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.flattenUiState import dev.dimension.flare.ui.presenter.PresenterBase @@ -18,7 +20,8 @@ public class HomeTabItemPresenter( private val id: String, ) : PresenterBase(), KoinComponent { - private val settingsRepository: SettingsRepository by inject() + private val appDataStore: AppDataStore by inject() + private val timelinePersistenceMapper: TimelinePersistenceMapper by inject() public interface State { public val tabItem: UiState @@ -31,7 +34,7 @@ public class HomeTabItemPresenter( if (id == guestMastodonHomeTimelineTab.id) { flowOf(UiState.Success(guestMastodonHomeTimelineTab)) } else { - settingsRepository.homeTimelineTab(id).map { + appDataStore.homeTimelineTab(id, timelinePersistenceMapper).map { if (it == null) { UiState.Error(Exception("Tab not found")) } else { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTabSettingsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTabSettingsPresenter.kt similarity index 72% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTabSettingsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTabSettingsPresenter.kt index c9042f6a26..9cab269c99 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTabSettingsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTabSettingsPresenter.kt @@ -2,8 +2,11 @@ package dev.dimension.flare.ui.presenter.home import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import dev.dimension.flare.data.datastore.AppDataStore +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import dev.dimension.flare.data.repository.SettingsRepository +import dev.dimension.flare.data.repository.homeTimelineTabs +import dev.dimension.flare.data.repository.replaceHomeTimelineTabs import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.collectAsUiState import dev.dimension.flare.ui.presenter.PresenterBase @@ -18,11 +21,13 @@ import org.koin.core.component.inject public class HomeTabSettingsPresenter : PresenterBase(), KoinComponent { - private val settingsRepository: SettingsRepository by inject() + private val appDataStore: AppDataStore by inject() + private val timelinePersistenceMapper: TimelinePersistenceMapper by inject() private val appScope: CoroutineScope by inject() private val homeTimelineTabs by lazy { - settingsRepository.homeTimelineTabs + appDataStore + .homeTimelineTabs(timelinePersistenceMapper) .map { it.toImmutableList() } } @@ -35,7 +40,7 @@ public class HomeTabSettingsPresenter : override fun replaceHomeTimelineTabs(tabs: List) { appScope.launch { - settingsRepository.replaceHomeTimelineTabs(tabs) + appDataStore.replaceHomeTimelineTabs(tabs, timelinePersistenceMapper) } } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/LoggedInPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/LoggedInPresenter.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/LoggedInPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/LoggedInPresenter.kt index 93f01287c4..2312a62979 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/LoggedInPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/LoggedInPresenter.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.ui.presenter.home import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.collectAsUiState import dev.dimension.flare.ui.presenter.PresenterBase diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/MixedTimelinePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/MixedTimelinePresenter.kt similarity index 74% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/MixedTimelinePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/MixedTimelinePresenter.kt index 06583cd269..01fb662f7f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/MixedTimelinePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/MixedTimelinePresenter.kt @@ -1,15 +1,20 @@ package dev.dimension.flare.ui.presenter.home import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.datasource.microblog.MicroblogTimelineMergePolicy import dev.dimension.flare.data.datasource.microblog.MixedRemoteMediator import dev.dimension.flare.data.datasource.microblog.paging.CacheableRemoteLoader import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader import dev.dimension.flare.data.datasource.microblog.paging.notSupported +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.model.tab.GroupTimelineTabItemV2 import dev.dimension.flare.data.model.tab.TimelineMergePolicy +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper +import dev.dimension.flare.data.model.tab.TimelinePresenterFactory import dev.dimension.flare.data.model.tab.TimelineTabItemV2 import dev.dimension.flare.data.model.tab.isSystemHomeMixedTimeline -import dev.dimension.flare.data.repository.SettingsRepository +import dev.dimension.flare.data.repository.homeTimelineTab +import dev.dimension.flare.data.repository.homeTimelineTabs import dev.dimension.flare.ui.model.UiTimelineV2 import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -39,15 +44,17 @@ public class MixedTimelinePresenter( ) private val database: CacheDatabase by inject() - private val settingsRepository: SettingsRepository by inject() + private val appDataStore: AppDataStore by inject() + private val timelinePersistenceMapper: TimelinePersistenceMapper by inject() + private val timelinePresenterFactory: TimelinePresenterFactory by inject() init { bindTimelineTabItemId(id) } private val groupTabFlow: Flow by lazy { - settingsRepository - .homeTimelineTab(groupId) + appDataStore + .homeTimelineTab(groupId, timelinePersistenceMapper) .map { it as? GroupTimelineTabItemV2 } } @@ -67,7 +74,7 @@ public class MixedTimelinePresenter( }.distinctUntilChanged { old, new -> old.orEmpty().map { it.id } == new.orEmpty().map { it.id } }.flatMapLatest { tabs -> - val presenters = tabs?.map { it.createPresenter() } ?: fallbackSubTimelinePresenter + val presenters = tabs?.map(timelinePresenterFactory::create) ?: fallbackSubTimelinePresenter if (presenters.isEmpty()) { flowOf(emptyList()) } else { @@ -85,7 +92,7 @@ public class MixedTimelinePresenter( MixedRemoteMediator( database = database, mediators = loaders.filterIsInstance>(), - mergePolicy = mergePolicy, + mergePolicy = mergePolicy.toMicroblogPolicy(), ) } } @@ -98,15 +105,17 @@ public class SystemHomeMixedTimelinePresenter( private val groupId = id private val database: CacheDatabase by inject() - private val settingsRepository: SettingsRepository by inject() + private val appDataStore: AppDataStore by inject() + private val timelinePersistenceMapper: TimelinePersistenceMapper by inject() + private val timelinePresenterFactory: TimelinePresenterFactory by inject() init { bindTimelineTabItemId(id) } private val groupTabFlow: Flow by lazy { - settingsRepository - .homeTimelineTab(groupId) + appDataStore + .homeTimelineTab(groupId, timelinePersistenceMapper) .map { it as? GroupTimelineTabItemV2 } } @@ -118,14 +127,15 @@ public class SystemHomeMixedTimelinePresenter( @OptIn(ExperimentalCoroutinesApi::class) private val subTimelineLoadersFlow: Flow>> by lazy { - settingsRepository.homeTimelineTabs + appDataStore + .homeTimelineTabs(timelinePersistenceMapper) .map { tabs -> tabs .filterNot { it.isSystemHomeMixedTimeline } .filter { it.enabled } }.distinctUntilChangedByTabIds() .flatMapLatest { tabs -> - val presenters = tabs.map { it.createPresenter() } + val presenters = tabs.map(timelinePresenterFactory::create) if (presenters.isEmpty()) { flowOf(emptyList()) } else { @@ -143,7 +153,7 @@ public class SystemHomeMixedTimelinePresenter( MixedRemoteMediator( database = database, mediators = loaders.filterIsInstance>(), - mergePolicy = mergePolicy, + mergePolicy = mergePolicy.toMicroblogPolicy(), ) } } @@ -153,3 +163,10 @@ private fun Flow>.distinctUntilChangedByTabIds(): Flow old.map { it.id } == new.map { it.id } } + +private fun TimelineMergePolicy.toMicroblogPolicy(): MicroblogTimelineMergePolicy = + when (this) { + TimelineMergePolicy.Time -> MicroblogTimelineMergePolicy.Time + TimelineMergePolicy.TimePerPage -> MicroblogTimelineMergePolicy.TimePerPage + TimelineMergePolicy.Staggered -> MicroblogTimelineMergePolicy.Staggered + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/NotificationBadgePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/NotificationBadgePresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/NotificationBadgePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/NotificationBadgePresenter.kt index 6c128e1121..c894421674 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/NotificationBadgePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/NotificationBadgePresenter.kt @@ -5,8 +5,8 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import dev.dimension.flare.common.collectAsState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.NotificationDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiState diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchHistoryPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchHistoryPresenter.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchHistoryPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchHistoryPresenter.kt index 74992f6e00..ad971e41d8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchHistoryPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchHistoryPresenter.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import dev.dimension.flare.common.ImmutableListWrapper -import dev.dimension.flare.data.repository.SearchHistoryRepository +import dev.dimension.flare.data.local.SearchHistoryRepository import dev.dimension.flare.ui.model.UiSearchHistory import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.collectAsUiState diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchPresenter.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchPresenter.kt index 3c10f9f36a..3732bbda2c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchPresenter.kt @@ -14,11 +14,11 @@ import dev.dimension.flare.common.cachePagingState import dev.dimension.flare.common.combineLatestFlowLists import dev.dimension.flare.common.emptyFlow import dev.dimension.flare.common.refreshSuspend +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource import dev.dimension.flare.data.datasource.microblog.datasource.UserDataSource import dev.dimension.flare.data.datasource.microblog.paging.toPagingSource import dev.dimension.flare.data.datasource.microblog.pagingConfig -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.data.repository.allAccountServicesFlow import dev.dimension.flare.model.AccountType diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchStatusTimelinePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchStatusTimelinePresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchStatusTimelinePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchStatusTimelinePresenter.kt index 0c1b6013c9..f0a5ba2504 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchStatusTimelinePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SearchStatusTimelinePresenter.kt @@ -1,9 +1,9 @@ package dev.dimension.flare.ui.presenter.home +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.paging.PagingRequest import dev.dimension.flare.data.datasource.microblog.paging.PagingResult import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiTimelineV2 diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SecondaryTabsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SecondaryTabsPresenter.kt similarity index 70% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SecondaryTabsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SecondaryTabsPresenter.kt index 6345893f82..6ea1b10f87 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SecondaryTabsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/SecondaryTabsPresenter.kt @@ -4,13 +4,14 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import dev.dimension.flare.common.combineLatestFlowLists +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource -import dev.dimension.flare.data.datasource.microblog.datasource.TimelineTabConfigurationDataSource import dev.dimension.flare.data.datasource.microblog.datasource.UserDataSource -import dev.dimension.flare.data.model.tab.ShortcutSpec -import dev.dimension.flare.data.model.tab.TimelineResolver +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineShortcutDescriptor +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineTabDescriptor +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineTabProvider +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.allAccountServicesFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiIcon @@ -63,7 +64,7 @@ public class SecondaryTabsPresenter : } private val accountRepository: AccountRepository by inject() - private val timelineResolver: TimelineResolver by inject() + private val timelinePersistenceMapper: TimelinePersistenceMapper by inject() private val itemsFlow by lazy { allAccountServicesFlow(accountRepository) @@ -93,11 +94,11 @@ public class SecondaryTabsPresenter : tabs = ( listOf( - ShortcutSpec( + Tab( title = UiStrings.Me, icon = UiIcon.Profile, - target = - ShortcutSpec.Target.Route( + destination = + Destination.Route( DeeplinkRoute.Profile.User( accountType = AccountType.Specific(service.accountKey), userKey = user.key, @@ -105,11 +106,11 @@ public class SecondaryTabsPresenter : ), ), ) + - (service as? TimelineTabConfigurationDataSource) - ?.shortcuts + (service as? TimelineTabProvider) + ?.timelineShortcuts + ?.mapNotNull(::toTab) .orEmpty() - ).mapNotNull(::toTab) - .toImmutableList(), + ).toImmutableList(), ) } } @@ -132,22 +133,40 @@ public class SecondaryTabsPresenter : } } - private fun toTab(shortcut: ShortcutSpec): Tab? = + private fun toTab(shortcut: TimelineShortcutDescriptor): Tab? = when (val target = shortcut.target) { - is ShortcutSpec.Target.Route -> { + is TimelineShortcutDescriptor.Target.Route -> { Tab( title = shortcut.title, icon = shortcut.icon, - destination = Destination.Route(target.route), + destination = Destination.Route(target.toDeeplinkRoute() ?: return null), ) } - is ShortcutSpec.Target.Timeline -> { + is TimelineShortcutDescriptor.Target.Timeline -> { Tab( title = shortcut.title, icon = shortcut.icon, - destination = Destination.Timeline(timelineResolver.toTabItem(target.source)), + destination = + Destination.Timeline( + timelinePersistenceMapper.toTabItem( + TimelineTabDescriptor.Source( + ref = target.ref, + display = target.display, + ), + ), + ), ) } } + + private fun TimelineShortcutDescriptor.Target.Route.toDeeplinkRoute(): DeeplinkRoute? = + when (id) { + TimelineShortcutDescriptor.RouteIds.ALL_LISTS -> accountKey?.let(DeeplinkRoute::AllLists) + TimelineShortcutDescriptor.RouteIds.ALL_DIRECT_MESSAGES -> accountKey?.let(DeeplinkRoute::AllDirectMessages) + TimelineShortcutDescriptor.RouteIds.BLUESKY_ALL_FEEDS -> accountKey?.let(DeeplinkRoute.Bluesky::AllFeeds) + TimelineShortcutDescriptor.RouteIds.MISSKEY_ALL_ANTENNAS -> accountKey?.let(DeeplinkRoute.Misskey::AllAntennas) + TimelineShortcutDescriptor.RouteIds.MISSKEY_ALL_CHANNELS -> accountKey?.let(DeeplinkRoute.Misskey::AllChannels) + else -> null + } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenter.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenter.kt index 00d51731b5..70593bbe61 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenter.kt @@ -13,38 +13,38 @@ import androidx.paging.map import dev.dimension.flare.common.InAppNotification import dev.dimension.flare.common.Message import dev.dimension.flare.common.PagingState +import dev.dimension.flare.common.PlatformDispatchers import dev.dimension.flare.common.cachePagingState import dev.dimension.flare.common.emptyFlow import dev.dimension.flare.common.onEmpty import dev.dimension.flare.common.onError import dev.dimension.flare.common.onSuccess import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.database.cache.mapper.TimelinePagingMapper import dev.dimension.flare.data.database.cache.model.DbStatusWithReference -import dev.dimension.flare.data.database.cache.model.TranslationDisplayOptions import dev.dimension.flare.data.datasource.microblog.paging.CacheableRemoteLoader import dev.dimension.flare.data.datasource.microblog.paging.NotSupportRemoteLoader import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.microblog.paging.TimelinePagingMapper import dev.dimension.flare.data.datasource.microblog.paging.TimelineRemoteMediator import dev.dimension.flare.data.datasource.microblog.paging.toPagingSource import dev.dimension.flare.data.datasource.microblog.pagingConfig import dev.dimension.flare.data.datastore.AppDataStore +import dev.dimension.flare.data.local.KeywordFilterPattern +import dev.dimension.flare.data.local.LocalFilterRepository import dev.dimension.flare.data.model.tab.TimelineFilterConfig +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelinePostContent import dev.dimension.flare.data.model.tab.TimelinePostKind -import dev.dimension.flare.data.repository.KeywordFilterPattern -import dev.dimension.flare.data.repository.LocalFilterRepository -import dev.dimension.flare.data.repository.LoginExpiredException -import dev.dimension.flare.data.repository.SettingsRepository +import dev.dimension.flare.data.repository.homeTimelineTab +import dev.dimension.flare.data.settings.TranslationSettingsSupport +import dev.dimension.flare.data.translation.TranslationDisplayOptions import dev.dimension.flare.data.translation.PreTranslationService -import dev.dimension.flare.data.translation.TranslationSettingsSupport +import dev.dimension.flare.model.LoginExpiredException import dev.dimension.flare.ui.model.UiMedia import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.presenter.PresenterBase import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.IO import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.catch @@ -65,7 +65,7 @@ public abstract class TimelinePresenter : private val database: CacheDatabase by inject() private val appDataStore: AppDataStore by inject() private val preTranslationService: PreTranslationService by inject() - private val settingsRepository: SettingsRepository by inject() + private val timelinePersistenceMapper: TimelinePersistenceMapper by inject() private val localFilterRepository: LocalFilterRepository by inject() private val inAppNotification: InAppNotification by inject() @@ -78,7 +78,8 @@ public abstract class TimelinePresenter : private val timelineFilterConfigFlow: Flow by lazy { observeTimelineFilterConfig( - settingsRepository = settingsRepository, + appDataStore = appDataStore, + timelinePersistenceMapper = timelinePersistenceMapper, timelineTabItemIdFlow = timelineTabItemIdFlow, ) } @@ -104,7 +105,7 @@ public abstract class TimelinePresenter : ).cachedIn(scope).flatMapLatest { pagingData -> translationSettingsFlow .map { translationDisplayOptions -> - withContext(Dispatchers.IO) { + withContext(PlatformDispatchers.IO) { pagingData.map { item -> TimelinePagingMapper.toUi( item = item, @@ -279,7 +280,8 @@ internal fun UiTimelineV2.Post.traits(): TimelinePostTraits { @OptIn(ExperimentalCoroutinesApi::class) internal fun observeTimelineFilterConfig( - settingsRepository: SettingsRepository, + appDataStore: AppDataStore, + timelinePersistenceMapper: TimelinePersistenceMapper, timelineTabItemIdFlow: Flow, ): Flow = timelineTabItemIdFlow @@ -288,8 +290,8 @@ internal fun observeTimelineFilterConfig( if (id == null) { flowOf(TimelineFilterConfig()) } else { - settingsRepository - .homeTimelineTab(id) + appDataStore + .homeTimelineTab(id, timelinePersistenceMapper) .map { it?.filterConfig ?: TimelineFilterConfig() } .distinctUntilChanged() } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/UserPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/UserPresenter.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/UserPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/UserPresenter.kt index 5d5791486b..92fc71875d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/UserPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/UserPresenter.kt @@ -2,13 +2,13 @@ package dev.dimension.flare.ui.presenter.home import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource import dev.dimension.flare.data.datasource.microblog.datasource.UserDataSource -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.NoActiveAccountException import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.NoActiveAccountException import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.flattenUiState import dev.dimension.flare.ui.model.toUi diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedPresenter.kt similarity index 76% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedPresenter.kt index fa9f855dd3..fc5bcd9de0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedPresenter.kt @@ -7,9 +7,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.refreshSuspend -import dev.dimension.flare.data.datasource.bluesky.BlueskyDataSource +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.bluesky.BlueskyFeedDataSource import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType @@ -41,22 +41,14 @@ public class BlueskyFeedPresenter( private val accountRepository: AccountRepository by inject() private val timelinePresenter by lazy { - object : TimelinePresenter() { - override val loader: Flow> by lazy { - accountServiceFlow(accountType, accountRepository) - .map { - require(it is BlueskyDataSource) - it.feedTimelineLoader(uri) - } - } - } + createBlueskyFeedTimeline(accountType, uri, accountRepository) } @OptIn(ExperimentalCoroutinesApi::class) private val infoFlow by lazy { accountServiceFlow(accountType, accountRepository) .flatMapLatest { - require(it is BlueskyDataSource) + require(it is BlueskyFeedDataSource) it.feedHandler.listInfo(uri).toUi() }.map { it.mapNotNull { it } @@ -72,7 +64,7 @@ public class BlueskyFeedPresenter( val subscribed = serviceState .flatMap { - require(it is BlueskyDataSource) + require(it is BlueskyFeedDataSource) remember(it) { it.feedHandler.cacheData }.collectAsUiState().value @@ -94,7 +86,7 @@ public class BlueskyFeedPresenter( override fun subscribe(list: UiList.Feed) { serviceState.onSuccess { scope.launch { - require(it is BlueskyDataSource) + require(it is BlueskyFeedDataSource) it.subscribeFeed(list) } } @@ -103,7 +95,7 @@ public class BlueskyFeedPresenter( override fun unsubscribe(list: UiList.Feed) { serviceState.onSuccess { scope.launch { - require(it is BlueskyDataSource) + require(it is BlueskyFeedDataSource) it.unsubscribeFeed(list) } } @@ -112,7 +104,7 @@ public class BlueskyFeedPresenter( override fun favorite(list: UiList.Feed) { serviceState.onSuccess { scope.launch { - require(it is BlueskyDataSource) + require(it is BlueskyFeedDataSource) it.favouriteFeed(list) } } @@ -121,7 +113,7 @@ public class BlueskyFeedPresenter( override fun unfavorite(list: UiList.Feed) { serviceState.onSuccess { scope.launch { - require(it is BlueskyDataSource) + require(it is BlueskyFeedDataSource) it.favouriteFeed(list) } } @@ -130,6 +122,33 @@ public class BlueskyFeedPresenter( } } +public fun createBlueskyFeedTimeline( + accountType: AccountType, + uri: String, +): TimelinePresenter = + createBlueskyFeedTimeline( + accountType = accountType, + uri = uri, + accountRepository = null, + ) + +private fun createBlueskyFeedTimeline( + accountType: AccountType, + uri: String, + accountRepository: AccountRepository?, +): TimelinePresenter = + object : TimelinePresenter() { + private val injectedAccountRepository: AccountRepository by inject() + + override val loader: Flow> by lazy { + accountServiceFlow(accountType, accountRepository ?: injectedAccountRepository) + .map { + require(it is BlueskyFeedDataSource) + it.feedTimelineLoader(uri) + } + } + } + @Immutable public interface BlueskyFeedState { public val info: UiState diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedsPresenter.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedsPresenter.kt index 4f2e2a6b19..8baaf9ee25 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedsPresenter.kt @@ -12,8 +12,8 @@ import androidx.paging.compose.collectAsLazyPagingItems import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.refreshSuspend import dev.dimension.flare.common.toPagingState -import dev.dimension.flare.data.datasource.bluesky.BlueskyDataSource -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.bluesky.BlueskyFeedDataSource import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiList @@ -38,7 +38,7 @@ public class BlueskyFeedsPresenter( val myFeeds = serviceState .map { service -> - require(service is BlueskyDataSource) + require(service is BlueskyFeedDataSource) val flow = remember(service) { service.feedHandler.data.cachedIn(scope) @@ -48,7 +48,7 @@ public class BlueskyFeedsPresenter( val popularFeeds = serviceState .map { service -> - require(service is BlueskyDataSource) + require(service is BlueskyFeedDataSource) remember(service, query) { service.popularFeeds(query = query, scope = scope) }.collectAsLazyPagingItems() @@ -70,7 +70,7 @@ public class BlueskyFeedsPresenter( override fun subscribe(list: UiList.Feed) { serviceState.onSuccess { scope.launch { - require(it is BlueskyDataSource) + require(it is BlueskyFeedDataSource) it.subscribeFeed(list) } } @@ -79,7 +79,7 @@ public class BlueskyFeedsPresenter( override fun unsubscribe(list: UiList.Feed) { serviceState.onSuccess { scope.launch { - require(it is BlueskyDataSource) + require(it is BlueskyFeedDataSource) it.unsubscribeFeed(list) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelListPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelListPresenter.kt similarity index 78% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelListPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelListPresenter.kt index b2ef0dd141..63d1d617db 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelListPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelListPresenter.kt @@ -11,18 +11,14 @@ import androidx.paging.compose.collectAsLazyPagingItems import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.refreshSuspend import dev.dimension.flare.common.toPagingState -import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource -import dev.dimension.flare.data.model.IconType -import dev.dimension.flare.data.model.tab.TimelineResolver -import dev.dimension.flare.data.model.tab.TimelineSpec +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.misskey.MisskeyChannelDataSource +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import dev.dimension.flare.data.platform.MisskeyPlatformSpec -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.platform.toTimelineTabDescriptor import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.model.UiList -import dev.dimension.flare.ui.model.UiText import dev.dimension.flare.ui.model.map import dev.dimension.flare.ui.model.onSuccess import dev.dimension.flare.ui.presenter.PresenterBase @@ -37,7 +33,7 @@ public class MisskeyChannelListPresenter( ) : PresenterBase(), KoinComponent { private val accountRepository: AccountRepository by inject() - private val timelineResolver: TimelineResolver by inject() + private val timelinePersistenceMapper: TimelinePersistenceMapper by inject() public interface State { public val type: Type @@ -77,7 +73,7 @@ public class MisskeyChannelListPresenter( val data = serviceState .map { service -> - require(service is MisskeyDataSource) + require(service is MisskeyChannelDataSource) remember(type) { when (type) { State.Type.Following -> service.channelHandler.data.cachedIn(scope) @@ -96,12 +92,8 @@ public class MisskeyChannelListPresenter( } override fun timelineTabItem(item: UiList.Channel): TimelineTabItemV2 = - timelineResolver.toTabItem( - MisskeyPlatformSpec.channelTimelineSpec.target( - data = TimelineSpec.AccountResourceData((accountType as AccountType.Specific).accountKey, item.id), - title = UiText.Raw(item.title), - icon = item.banner?.let { IconType.Url(it) } ?: IconType.Material(UiIcon.Channel), - ), + timelinePersistenceMapper.toTabItem( + item.toTimelineTabDescriptor((accountType as AccountType.Specific).accountKey), ) override suspend fun refreshSuspend() { @@ -117,7 +109,7 @@ public class MisskeyChannelListPresenter( override fun follow(list: UiList.Channel) { serviceState.onSuccess { scope.launch { - require(it is MisskeyDataSource) + require(it is MisskeyChannelDataSource) it.followChannel(list) } } @@ -126,7 +118,7 @@ public class MisskeyChannelListPresenter( override fun unfollow(list: UiList.Channel) { serviceState.onSuccess { scope.launch { - require(it is MisskeyDataSource) + require(it is MisskeyChannelDataSource) it.unfollowChannel(list) } } @@ -135,7 +127,7 @@ public class MisskeyChannelListPresenter( override fun favorite(list: UiList.Channel) { serviceState.onSuccess { scope.launch { - require(it is MisskeyDataSource) + require(it is MisskeyChannelDataSource) it.favoriteChannel(list) } } @@ -144,7 +136,7 @@ public class MisskeyChannelListPresenter( override fun unfavorite(list: UiList.Channel) { serviceState.onSuccess { scope.launch { - require(it is MisskeyDataSource) + require(it is MisskeyChannelDataSource) it.unfavoriteChannel(list) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelPresenter.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelPresenter.kt index 8a32be32c9..bcacb0bf8e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelPresenter.kt @@ -7,9 +7,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.refreshSuspend +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.datasource.misskey.MisskeyChannelDataSource import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType @@ -45,7 +45,7 @@ public class MisskeyChannelPresenter( override val loader: Flow> by lazy { accountServiceFlow(accountType, accountRepository) .map { - require(it is MisskeyDataSource) + require(it is MisskeyChannelDataSource) it.channelTimelineLoader(channelId) } } @@ -56,7 +56,7 @@ public class MisskeyChannelPresenter( private val infoFlow by lazy { accountServiceFlow(accountType, accountRepository) .flatMapLatest { - require(it is MisskeyDataSource) + require(it is MisskeyChannelDataSource) it.channelHandler.listInfo(channelId).toUi() }.map { it.mapNotNull { it } @@ -72,7 +72,7 @@ public class MisskeyChannelPresenter( val followed = serviceState .flatMap { - require(it is MisskeyDataSource) + require(it is MisskeyChannelDataSource) remember(it) { it.channelHandler.cacheData }.collectAsUiState().value @@ -91,7 +91,7 @@ public class MisskeyChannelPresenter( override fun follow(list: UiList) { serviceState.onSuccess { scope.launch { - require(it is MisskeyDataSource) + require(it is MisskeyChannelDataSource) it.followChannel(list) } } @@ -100,7 +100,7 @@ public class MisskeyChannelPresenter( override fun unfollow(list: UiList) { serviceState.onSuccess { scope.launch { - require(it is MisskeyDataSource) + require(it is MisskeyChannelDataSource) it.unfollowChannel(list) } } @@ -109,7 +109,7 @@ public class MisskeyChannelPresenter( override fun favorite(list: UiList) { serviceState.onSuccess { scope.launch { - require(it is MisskeyDataSource) + require(it is MisskeyChannelDataSource) it.favoriteChannel(list) } } @@ -118,7 +118,7 @@ public class MisskeyChannelPresenter( override fun unfavorite(list: UiList) { serviceState.onSuccess { scope.launch { - require(it is MisskeyDataSource) + require(it is MisskeyChannelDataSource) it.unfavoriteChannel(list) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/CheckRssSourcePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/CheckRssSourcePresenter.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/CheckRssSourcePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/CheckRssSourcePresenter.kt index fb9c54b082..4e090da75d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/CheckRssSourcePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/CheckRssSourcePresenter.kt @@ -9,6 +9,7 @@ import dev.dimension.flare.data.network.mastodon.GuestMastodonService import dev.dimension.flare.data.network.mastodon.MastodonInstanceService import dev.dimension.flare.data.network.mastodon.MastodonPlatformDetector import dev.dimension.flare.data.network.rss.RssService +import dev.dimension.flare.model.RssDisplayMode import dev.dimension.flare.ui.model.UiRssSource import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.flattenUiState @@ -104,7 +105,7 @@ public class CheckRssSourcePresenter( title = RssService.fetch(it).title.trim(), lastUpdate = Instant.DISTANT_PAST.toUi(), favIcon = icon, - displayMode = dev.dimension.flare.data.database.app.model.RssDisplayMode.FULL_CONTENT, + displayMode = RssDisplayMode.FULL_CONTENT, ) } }.awaitAll() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/EditRssSourcePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/EditRssSourcePresenter.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/EditRssSourcePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/EditRssSourcePresenter.kt index e451d21a39..11d11fdf94 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/EditRssSourcePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/EditRssSourcePresenter.kt @@ -8,8 +8,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import dev.dimension.flare.data.database.app.AppDatabase import dev.dimension.flare.data.database.app.model.DbRssSources -import dev.dimension.flare.data.database.app.model.RssDisplayMode import dev.dimension.flare.data.database.app.model.SubscriptionType +import dev.dimension.flare.model.RssDisplayMode import dev.dimension.flare.ui.model.UiRssSource import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.collectAsUiState @@ -269,7 +269,7 @@ public class EditRssSourcePresenter( val canSave = when (val state = checkRssSourcePresenterState.state) { is UiState.Success -> { - when (state.data) { + when (val data = state.data) { is RssState.RssFeed -> { true } @@ -287,11 +287,11 @@ public class EditRssSourcePresenter( } is RssState.RssSources -> { - state.data.sources.isNotEmpty() + data.sources.isNotEmpty() } is RssState.MastodonInstance -> { - state.data.availableTimelines.isNotEmpty() + data.availableTimelines.isNotEmpty() } } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/ExportOPMLPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/ExportOPMLPresenter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/ExportOPMLPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/ExportOPMLPresenter.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/ImportOPMLPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/ImportOPMLPresenter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/ImportOPMLPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/ImportOPMLPresenter.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssDetailPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssDetailPresenter.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssDetailPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssDetailPresenter.kt index 9ec12b5c46..21740aba44 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssDetailPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssDetailPresenter.kt @@ -10,7 +10,6 @@ import dev.dimension.flare.data.network.rss.Readability import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiState -import dev.dimension.flare.ui.model.mapper.fromRss import dev.dimension.flare.ui.presenter.PresenterBase import dev.dimension.flare.ui.presenter.status.LogStatusHistoryPresenter import kotlinx.coroutines.flow.map diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssDetailTranslatePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssDetailTranslatePresenter.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssDetailTranslatePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssDetailTranslatePresenter.kt index 9e9f5b963b..8ffb49034b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssDetailTranslatePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssDetailTranslatePresenter.kt @@ -6,9 +6,9 @@ import androidx.compose.runtime.produceState import com.fleeksoft.ksoup.nodes.Node import com.fleeksoft.ksoup.nodes.TextNode import dev.dimension.flare.common.Locale +import dev.dimension.flare.data.ai.AiCompletionService import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.datastore.model.AppSettings -import dev.dimension.flare.data.network.ai.AiCompletionService import dev.dimension.flare.data.translation.AiPlaceholderTranslationSupport import dev.dimension.flare.data.translation.TranslationPromptFormatter import dev.dimension.flare.data.translation.TranslationProvider @@ -64,7 +64,7 @@ public class RssDetailTranslatePresenter( private suspend fun translate(): Pair, UiState> { val settings = runCatching { - appDataStore.appSettingsStore.data.first() + appDataStore.appSettings.first() }.getOrElse { return UiState.Error(it) to UiState.Error(it) } diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssSourcePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssSourcePresenter.kt new file mode 100644 index 0000000000..c205c71c13 --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssSourcePresenter.kt @@ -0,0 +1,51 @@ +package dev.dimension.flare.ui.presenter.home.rss + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import dev.dimension.flare.data.database.app.AppDatabase +import dev.dimension.flare.ui.model.UiRssSource +import dev.dimension.flare.ui.model.UiState +import dev.dimension.flare.ui.model.collectAsUiState +import dev.dimension.flare.ui.model.map +import dev.dimension.flare.ui.model.mapper.render +import dev.dimension.flare.ui.presenter.PresenterBase +import dev.dimension.flare.ui.presenter.home.TimelineState +import kotlinx.coroutines.flow.map +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +public class RssSourcePresenter( + private val id: Int, +) : PresenterBase(), + KoinComponent { + private val appDatabase by inject() + + @androidx.compose.runtime.Immutable + public interface State { + public val data: UiState + public val timelineState: UiState + } + + @Composable + override fun body(): State { + val data by remember(id) { + appDatabase + .rssSourceDao() + .get(id) + .map { + it.render() + } + }.collectAsUiState() + val timelineState = + data.map { + remember(it.url, it.type) { + createSubscriptionTimeline(it.type, it.url) + }.body() + } + return object : State { + override val data = data + override val timelineState = timelineState + } + } +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssSourcesPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssSourcesPresenter.kt similarity index 88% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssSourcesPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssSourcesPresenter.kt index 48f7801f19..e40d99bd3d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssSourcesPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssSourcesPresenter.kt @@ -8,10 +8,11 @@ import androidx.compose.runtime.rememberCoroutineScope import dev.dimension.flare.data.database.app.AppDatabase import dev.dimension.flare.data.database.app.model.DbRssSources import dev.dimension.flare.data.database.app.model.SubscriptionType +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineTabDescriptor +import dev.dimension.flare.data.datasource.rss.RssTimelineSpecs +import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.model.tab.TimelineSlot import dev.dimension.flare.data.model.tab.TimelineSlotContent -import dev.dimension.flare.data.platform.RssTimelineSpecs -import dev.dimension.flare.data.repository.SettingsRepository import dev.dimension.flare.ui.model.UiRssSource import dev.dimension.flare.ui.model.mapper.render import dev.dimension.flare.ui.presenter.PresenterBase @@ -28,7 +29,7 @@ public class RssSourcesPresenter : PresenterBase(), KoinComponent { private val appDatabase by inject() - private val settingsRepository by inject() + private val appDataStore by inject() @androidx.compose.runtime.Immutable public interface State { @@ -84,7 +85,7 @@ public class RssSourcesPresenter : .firstOrNull { it.id == id } appDatabase.rssSourceDao().delete(id) source?.let { - settingsRepository.removeHomeTimelineTabForRssSource(it) + appDataStore.removeHomeTimelineTabForRssSource(it) } } } @@ -92,18 +93,18 @@ public class RssSourcesPresenter : } } -private suspend fun SettingsRepository.removeHomeTimelineTabForRssSource(source: DbRssSources) { +private suspend fun AppDataStore.removeHomeTimelineTabForRssSource(source: DbRssSources) { val sourceId = when (source.type) { SubscriptionType.RSS -> { RssTimelineSpecs.rss - .target(RssTimelineSpecs.RssData(source.url)) + .toTimelineTabDescriptor(RssTimelineSpecs.RssData(source.url)) .id } else -> { RssTimelineSpecs.subscription - .target( + .toTimelineTabDescriptor( RssTimelineSpecs.SubscriptionData( subscriptionUrl = source.url, subscriptionType = source.type, diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/TimelinePresenterFactories.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/TimelinePresenterFactories.kt new file mode 100644 index 0000000000..3450c8b480 --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/TimelinePresenterFactories.kt @@ -0,0 +1,40 @@ +package dev.dimension.flare.ui.presenter.home.rss + +import dev.dimension.flare.data.database.app.model.DbRssSources +import dev.dimension.flare.data.database.app.model.SubscriptionType +import dev.dimension.flare.data.datasource.rss.RssDataSource +import dev.dimension.flare.data.datasource.rss.RssTimelineSpecs +import dev.dimension.flare.ui.presenter.home.StandaloneTimelinePresenter +import dev.dimension.flare.ui.presenter.home.TimelinePresenter +import kotlinx.coroutines.flow.flowOf + +public fun createAllRssTimeline(): TimelinePresenter = + StandaloneTimelinePresenter { context -> + RssTimelineSpecs.allRss.createLoader( + context = context, + data = RssTimelineSpecs.AllRssData, + ) + } + +public fun createRssTimeline(url: String): TimelinePresenter = + StandaloneTimelinePresenter { + flowOf(RssDataSource.fetchLoader(url)) + } + +public fun createSubscriptionTimeline( + type: SubscriptionType, + url: String, +): TimelinePresenter = + StandaloneTimelinePresenter { + flowOf( + RssDataSource.fetchLoader( + DbRssSources( + url = url, + title = null, + icon = null, + lastUpdate = 0, + type = type, + ), + ), + ) + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/TwitterArticlePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/TwitterArticlePresenter.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/TwitterArticlePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/TwitterArticlePresenter.kt index ebd8e19560..0aa723dd65 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/TwitterArticlePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/TwitterArticlePresenter.kt @@ -4,8 +4,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState -import dev.dimension.flare.data.datasource.xqt.XQTDataSource -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.xqt.XqtContentDataSource import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiState @@ -48,7 +48,7 @@ public class TwitterArticlePresenter( } is UiState.Success -> { - val service = serviceState.data as? XQTDataSource + val service = serviceState.data as? XqtContentDataSource if (service == null) { UiState.Error(IllegalStateException("Twitter article requires an XQT account")) } else { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AllListPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AllListPresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AllListPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AllListPresenter.kt index 58d44bd1bd..9193275218 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AllListPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AllListPresenter.kt @@ -10,8 +10,8 @@ import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.isRefreshing import dev.dimension.flare.common.refreshSuspend import dev.dimension.flare.common.toPagingState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.ListDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiList diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AntennasListPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AntennasListPresenter.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AntennasListPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AntennasListPresenter.kt index dd4b692957..4dcb545dc6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AntennasListPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AntennasListPresenter.kt @@ -8,8 +8,8 @@ import androidx.paging.compose.collectAsLazyPagingItems import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.refreshSuspend import dev.dimension.flare.common.toPagingState -import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.misskey.MisskeyAntennaDataSource import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiList @@ -42,7 +42,7 @@ public class AntennasListPresenter( service .map { remember { - require(it is MisskeyDataSource) + require(it is MisskeyAntennaDataSource) it.antennasList().cachedIn(scope) }.collectAsLazyPagingItems() }.toPagingState() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/CreateListPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/CreateListPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/CreateListPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/CreateListPresenter.kt index c01de920f5..c658fa12df 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/CreateListPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/CreateListPresenter.kt @@ -2,10 +2,10 @@ package dev.dimension.flare.ui.presenter.list import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.ListDataSource import dev.dimension.flare.data.datasource.microblog.list.ListMetaData import dev.dimension.flare.data.datasource.microblog.list.ListMetaDataType -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiState diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/DeleteListPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/DeleteListPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/DeleteListPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/DeleteListPresenter.kt index c4879c6d29..039a0475a1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/DeleteListPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/DeleteListPresenter.kt @@ -2,8 +2,8 @@ package dev.dimension.flare.ui.presenter.list import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.ListDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.presenter.PresenterBase diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/EditAccountListPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/EditAccountListPresenter.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/EditAccountListPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/EditAccountListPresenter.kt index 49fd29e743..367835e2c4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/EditAccountListPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/EditAccountListPresenter.kt @@ -10,8 +10,8 @@ import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.filter import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.toPagingState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.ListDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/EditListMemberPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/EditListMemberPresenter.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/EditListMemberPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/EditListMemberPresenter.kt index d2a025fa4b..d36aafa43e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/EditListMemberPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/EditListMemberPresenter.kt @@ -12,10 +12,10 @@ import androidx.paging.map import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.emptyFlow import dev.dimension.flare.common.toPagingState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.ListDataSource import dev.dimension.flare.data.datasource.microblog.paging.toPagingSource import dev.dimension.flare.data.datasource.microblog.pagingConfig -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListEditPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListEditPresenter.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListEditPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListEditPresenter.kt index d216173670..f1f9f3732d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListEditPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListEditPresenter.kt @@ -5,10 +5,10 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import dev.dimension.flare.common.refreshSuspend +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.ListDataSource import dev.dimension.flare.data.datasource.microblog.list.ListMetaData import dev.dimension.flare.data.datasource.microblog.list.ListMetaDataType -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiState diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListInfoPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListInfoPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListInfoPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListInfoPresenter.kt index da6d4acd3d..8d4fdf513f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListInfoPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListInfoPresenter.kt @@ -4,8 +4,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember import dev.dimension.flare.common.collectAsState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.ListDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiList diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListMembersPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListMembersPresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListMembersPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListMembersPresenter.kt index c6bbc87c8f..4cbc65fad8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListMembersPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListMembersPresenter.kt @@ -10,8 +10,8 @@ import androidx.paging.compose.collectAsLazyPagingItems import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.emptyFlow import dev.dimension.flare.common.toPagingState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.ListDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiProfile diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/PinnableTimelineTabPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/PinnableTimelineTabPresenter.kt similarity index 72% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/PinnableTimelineTabPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/PinnableTimelineTabPresenter.kt index e119eddaa5..c0336ddf66 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/PinnableTimelineTabPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/PinnableTimelineTabPresenter.kt @@ -5,15 +5,17 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.map import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.toPagingState -import dev.dimension.flare.data.datasource.microblog.datasource.PinnableTimelineTabDataSource -import dev.dimension.flare.data.datasource.microblog.datasource.PinnableTimelineTabSection -import dev.dimension.flare.data.datasource.microblog.datasource.TimelineTabConfigurationDataSource +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.microblog.timeline.PinnableTimelineProvider +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineTabProvider +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiState @@ -24,6 +26,7 @@ import dev.dimension.flare.ui.presenter.PresenterBase import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -49,10 +52,16 @@ public class PinnableTimelineTabPresenter( ) private val accountRepository: AccountRepository by inject() + private val timelinePersistenceMapper: TimelinePersistenceMapper by inject() private data class Sections( val builtInTabs: ImmutableList, - val pinnableTabs: List, + val pinnableTabs: List, + ) + + private data class ResolvedPinnableTimelineTabSection( + val title: UiStrings, + val data: Flow>, ) private val sectionsFlow by lazy { @@ -62,13 +71,23 @@ public class PinnableTimelineTabPresenter( ).map { service -> Sections( builtInTabs = - (service as? TimelineTabConfigurationDataSource) + (service as? TimelineTabProvider) ?.builtInTimelineTabs + ?.map(timelinePersistenceMapper::toTabItem) + ?.toImmutableList() ?: persistentListOf(), pinnableTabs = - (service as? PinnableTimelineTabDataSource) + (service as? PinnableTimelineProvider) ?.pinnableTimelineTabs - .orEmpty(), + ?.map { section -> + ResolvedPinnableTimelineTabSection( + title = section.title, + data = + section.data.map { paging -> + paging.map(timelinePersistenceMapper::toTabItem) + }, + ) + }.orEmpty(), ) } } diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/TimelinePresenterFactories.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/TimelinePresenterFactories.kt new file mode 100644 index 0000000000..b5b00cacf9 --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/TimelinePresenterFactories.kt @@ -0,0 +1,34 @@ +package dev.dimension.flare.ui.presenter.list + +import dev.dimension.flare.data.datasource.microblog.datasource.ListDataSource +import dev.dimension.flare.data.platform.MisskeyTimelineDataSource +import dev.dimension.flare.model.AccountType +import dev.dimension.flare.ui.presenter.home.AccountTimelinePresenter +import dev.dimension.flare.ui.presenter.home.TimelinePresenter + +public fun createListTimeline( + accountType: AccountType, + listId: String, +): TimelinePresenter = + AccountTimelinePresenter(accountType) { service -> + require(service is ListDataSource) + service.listTimeline(listId = listId) + } + +public fun createMisskeyAntennaTimeline( + accountType: AccountType, + id: String, +): TimelinePresenter = + AccountTimelinePresenter(accountType) { service -> + require(service is MisskeyTimelineDataSource) + service.antennasTimelineLoader(id) + } + +public fun createMisskeyChannelTimeline( + accountType: AccountType, + id: String, +): TimelinePresenter = + AccountTimelinePresenter(accountType) { service -> + require(service is MisskeyTimelineDataSource) + service.channelTimelineLoader(id) + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/BlueskyLoginPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/BlueskyLoginPresenter.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/BlueskyLoginPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/BlueskyLoginPresenter.kt index e24abd25a7..f1ff1bb49b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/BlueskyLoginPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/BlueskyLoginPresenter.kt @@ -9,8 +9,8 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import com.atproto.server.CreateSessionRequest import com.atproto.server.CreateSessionResponse +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.network.bluesky.BlueskyService -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiAccount import dev.dimension.flare.ui.presenter.PresenterBase diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/BlueskyOAuthLoginPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/BlueskyOAuthLoginPresenter.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/BlueskyOAuthLoginPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/BlueskyOAuthLoginPresenter.kt index dd40af892d..f168998713 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/BlueskyOAuthLoginPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/BlueskyOAuthLoginPresenter.kt @@ -6,9 +6,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.network.bluesky.OAuthCodeChallengeMethodS256 import dev.dimension.flare.data.network.ktorClient -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiAccount import dev.dimension.flare.ui.presenter.PresenterBase diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/InstanceMetadataPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/InstanceMetadataPresenter.kt similarity index 71% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/InstanceMetadataPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/InstanceMetadataPresenter.kt index f02bd9dd87..22e2b15775 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/InstanceMetadataPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/InstanceMetadataPresenter.kt @@ -5,18 +5,23 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import dev.dimension.flare.data.repository.tryRun +import dev.dimension.flare.common.tryRun import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.model.spec +import dev.dimension.flare.model.SocialPlatformRegistry import dev.dimension.flare.ui.model.UiInstanceMetadata import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.presenter.PresenterBase import kotlinx.coroutines.flow.flow +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject public class InstanceMetadataPresenter( private val host: String, private val platformType: PlatformType = PlatformType.Mastodon, -) : PresenterBase() { +) : PresenterBase(), + KoinComponent { + private val platformRegistry: SocialPlatformRegistry by inject() + @Immutable public interface State { public val data: UiState @@ -24,11 +29,15 @@ public class InstanceMetadataPresenter( @Composable override fun body(): State { - val data by remember(host, platformType) { + val data by remember( + host, + platformType, + platformRegistry, + ) { flow { tryRun { emit(UiState.Loading()) - platformType.spec.instanceMetadata(host) + platformRegistry.instanceMetadata(platformType, host) }.fold( onSuccess = { emit(UiState.Success(it)) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MastodonCallbackPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MastodonCallbackPresenter.kt similarity index 83% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MastodonCallbackPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MastodonCallbackPresenter.kt index 0dfa0a1c70..e1e2f1f1a6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MastodonCallbackPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MastodonCallbackPresenter.kt @@ -7,12 +7,14 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.account.ApplicationRepository import dev.dimension.flare.data.network.mastodon.MastodonOAuthService -import dev.dimension.flare.data.network.nodeinfo.NodeInfoService -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.ApplicationRepository +import dev.dimension.flare.data.network.mastodon.api.model.CreateApplicationResponse +import dev.dimension.flare.data.nodeinfo.NodeInfoService import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.ui.model.MastodonApplicationCredential import dev.dimension.flare.ui.model.UiAccount import dev.dimension.flare.ui.model.UiApplication import dev.dimension.flare.ui.model.UiState @@ -72,9 +74,9 @@ public class MastodonCallbackPresenter( website = "https://github.com/DimensionDev/Flare", redirect_uri = DeeplinkRoute.Companion.Callback.MASTODON, ) - val accessTokenResponse = service.getAccessToken(code, application.application) - requireNotNull(accessTokenResponse.accessToken) { "Invalid access token" } - val user = service.verify(accessToken = accessTokenResponse.accessToken) + val accessTokenResponse = service.getAccessToken(code, application.application.toCreateApplicationResponse()) + val accessToken = requireNotNull(accessTokenResponse.accessToken) { "Invalid access token" } + val user = service.verify(accessToken = accessToken) val id = user.id requireNotNull(id) { "Invalid user id" } val nodeInfo = NodeInfoService.fetchNodeInfo(host) @@ -96,7 +98,7 @@ public class MastodonCallbackPresenter( credential = UiAccount.Mastodon.Credential( instance = host, - accessToken = accessTokenResponse.accessToken, + accessToken = accessToken, forkType = forkType, nodeType = nodeInfo, ), @@ -133,7 +135,7 @@ internal suspend fun mastodonLoginUseCase( val application = applicationRepository.findByHost(host)?.let { if (it is UiApplication.Mastodon) { - it.application + it.application.toCreateApplicationResponse() } else { null } @@ -149,3 +151,14 @@ internal suspend fun mastodonLoginUseCase( val target = service.getWebOAuthUrl(application) launchOAuth(target) } + +private fun MastodonApplicationCredential.toCreateApplicationResponse(): CreateApplicationResponse = + CreateApplicationResponse( + id = id, + name = name, + website = website, + redirectURI = redirectURI, + clientID = clientID, + clientSecret = clientSecret, + vapidKey = vapidKey, + ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MisskeyCallbackPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MisskeyCallbackPresenter.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MisskeyCallbackPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MisskeyCallbackPresenter.kt index 1eb9ad6d4d..4425eccc04 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MisskeyCallbackPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/MisskeyCallbackPresenter.kt @@ -6,10 +6,10 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.account.ApplicationRepository import dev.dimension.flare.data.network.misskey.MisskeyOauthService -import dev.dimension.flare.data.network.nodeinfo.NodeInfoService -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.ApplicationRepository +import dev.dimension.flare.data.nodeinfo.NodeInfoService import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType import dev.dimension.flare.ui.model.UiAccount @@ -72,9 +72,9 @@ public class MisskeyCallbackPresenter( host = host, session = session, ).check() - requireNotNull(response.ok) { "No response" } - require(response.ok) { "Response is not ok" } - requireNotNull(response.token) { "No token" } + val ok = requireNotNull(response.ok) { "No response" } + require(ok) { "Response is not ok" } + val token = requireNotNull(response.token) { "No token" } val id = response.user?.id requireNotNull(id) { "No user id" } val nodeInfo = NodeInfoService.fetchNodeInfo(host) @@ -90,7 +90,7 @@ public class MisskeyCallbackPresenter( credential = UiAccount.Misskey.Credential( host = host, - accessToken = response.token, + accessToken = token, nodeType = nodeInfo, ), ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/NodeInfoPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/NodeInfoPresenter.kt similarity index 74% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/NodeInfoPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/NodeInfoPresenter.kt index a78d4e0c4d..d3404f64f4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/NodeInfoPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/NodeInfoPresenter.kt @@ -18,7 +18,10 @@ import dev.dimension.flare.common.toPagingState import dev.dimension.flare.data.datasource.microblog.RecommendInstancePagingSource import dev.dimension.flare.data.datasource.microblog.pagingConfig import dev.dimension.flare.data.network.nodeinfo.NodeData -import dev.dimension.flare.data.network.nodeinfo.NodeInfoService +import dev.dimension.flare.data.network.nodeinfo.detectPlatformType +import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.model.SocialPlatformRegistry +import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.model.UiInstance import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.isSuccess @@ -29,8 +32,14 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +public class NodeInfoPresenter : + PresenterBase(), + KoinComponent { + private val platformRegistry: SocialPlatformRegistry by inject() -public class NodeInfoPresenter : PresenterBase() { @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) @Composable override fun body(): NodeInfoState { @@ -47,7 +56,7 @@ public class NodeInfoPresenter : PresenterBase() { Pager( config = pagingConfig, ) { - RecommendInstancePagingSource() + RecommendInstancePagingSource(platformRegistry) }.flow.cachedIn(scope), filterFlow, ) { pagingData, filter -> @@ -58,12 +67,15 @@ public class NodeInfoPresenter : PresenterBase() { } }.collectAsLazyPagingItems() - val detectedPlatformType by remember(filterFlow) { + val detectedPlatformType by remember( + filterFlow, + platformRegistry, + ) { filterFlow.flatMapLatest { flow { runCatching { emit(UiState.Loading()) - NodeInfoService.detectPlatformType(it) + platformRegistry.detectPlatformType(it) }.onSuccess { emit(UiState.Success(it)) }.onFailure { @@ -78,6 +90,13 @@ public class NodeInfoPresenter : PresenterBase() { override val detectedPlatformType = detectedPlatformType override val canNext = detectedPlatformType.isSuccess + override fun platformIcon(type: PlatformType): UiIcon = platformRegistry.metadata(type).icon + + override fun agreementUrl( + type: PlatformType, + host: String, + ): String? = platformRegistry.agreementUrl(type, host) + override fun setFilter(value: String) { if (filter != value) { filter = value @@ -93,5 +112,12 @@ public interface NodeInfoState { public val detectedPlatformType: UiState public val canNext: Boolean + public fun platformIcon(type: PlatformType): UiIcon + + public fun agreementUrl( + type: PlatformType, + host: String, + ): String? + public fun setFilter(value: String) } diff --git a/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/NostrLoginState.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/NostrLoginState.kt new file mode 100644 index 0000000000..e62202756a --- /dev/null +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/NostrLoginState.kt @@ -0,0 +1,24 @@ +package dev.dimension.flare.ui.presenter.login + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable + +@Immutable +public interface NostrLoginState { + public val loading: Boolean + public val error: Throwable? + public val amberAvailable: Boolean + public val qrConnectUri: String? + public val qrWaitingForApproval: Boolean + + public fun login(input: String) + + public fun connectAmber() + + public fun startQrLogin() + + public fun cancelQrLogin() +} + +@Composable +internal expect fun nostrLoginState(toHome: () -> Unit): NostrLoginState diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/ServiceSelectPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/ServiceSelectPresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/ServiceSelectPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/ServiceSelectPresenter.kt index 204b7610ad..eca7d53ad1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/ServiceSelectPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/ServiceSelectPresenter.kt @@ -7,7 +7,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import dev.dimension.flare.data.repository.ApplicationRepository +import dev.dimension.flare.data.account.ApplicationRepository import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.presenter.PresenterBase import kotlinx.coroutines.launch @@ -23,7 +23,7 @@ public class ServiceSelectPresenter( @Composable override fun body(): ServiceSelectState { val nodeInfoState = remember { NodeInfoPresenter() }.body() - val nostrLoginState = remember { NostrLoginPresenter(toHome) }.body() + val nostrLoginState = nostrLoginState(toHome) val blueskyLoginState = remember { BlueskyLoginPresenter(toHome) }.body() val blueskyOauthLoginState = remember { BlueskyOAuthLoginPresenter(toHome) }.body() val mastodonLoginState = mastodonLoginPresenter(toHome) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/VVOLoginPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/VVOLoginPresenter.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/VVOLoginPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/VVOLoginPresenter.kt index ee59776149..a55a107454 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/VVOLoginPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/VVOLoginPresenter.kt @@ -7,8 +7,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.network.vvo.VVOService -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.vvoHost import dev.dimension.flare.ui.model.UiAccount @@ -61,9 +61,10 @@ public class VVOLoginPresenter( ) { val service = VVOService(flowOf(chocolate)) val config = service.config() - val uid = config.data?.uid + val data = config.data + val uid = data?.uid requireNotNull(uid) { "uid is null" } - val st = config.data.st + val st = data.st requireNotNull(st) { "st is null" } val profile = service.profileInfo(uid, st) requireNotNull(profile.data) { "profile is null" } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/XQTLoginPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/XQTLoginPresenter.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/XQTLoginPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/XQTLoginPresenter.kt index 3ceba983ec..0fadee7657 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/XQTLoginPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/XQTLoginPresenter.kt @@ -7,10 +7,10 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.xqt.userById import dev.dimension.flare.data.network.xqt.XQTService import dev.dimension.flare.data.network.xqt.model.User -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.xqtHost import dev.dimension.flare.ui.model.UiAccount diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/podcast/PodcastListPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/podcast/PodcastListPresenter.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/podcast/PodcastListPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/podcast/PodcastListPresenter.kt index 8d28f0aa6b..27a2df1d06 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/podcast/PodcastListPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/podcast/PodcastListPresenter.kt @@ -4,8 +4,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember -import dev.dimension.flare.data.datasource.xqt.XQTDataSource -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.xqt.XqtContentDataSource import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiPodcast @@ -33,7 +33,7 @@ public class PodcastListPresenter( val service = accountServiceProvider(accountType, accountRepository) val podcasts = service.flatMap { - require(it is XQTDataSource) + require(it is XqtContentDataSource) remember(it) { flow { emit(UiState.Loading()) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/podcast/PodcastPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/podcast/PodcastPresenter.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/podcast/PodcastPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/podcast/PodcastPresenter.kt index b1fd230ec4..5a7ea9a6c0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/podcast/PodcastPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/podcast/PodcastPresenter.kt @@ -3,8 +3,8 @@ package dev.dimension.flare.ui.presenter.podcast import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue -import dev.dimension.flare.data.datasource.xqt.XQTDataSource -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.xqt.XqtContentDataSource import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiPodcast @@ -32,7 +32,7 @@ public class PodcastPresenter( accountType = accountType, repository = accountRepository, ).map { - require(it is XQTDataSource) + require(it is XqtContentDataSource) it.podcast(id).fold( onSuccess = { UiState.Success(it) }, onFailure = { UiState.Error(it) }, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/BlockUserPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/BlockUserPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/BlockUserPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/BlockUserPresenter.kt index d5c650bca0..a311af35dd 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/BlockUserPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/BlockUserPresenter.kt @@ -2,8 +2,8 @@ package dev.dimension.flare.ui.presenter.profile import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.RelationDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/FollowingPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/FollowingPresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/FollowingPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/FollowingPresenter.kt index cf45a9c332..77188349b8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/FollowingPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/FollowingPresenter.kt @@ -8,11 +8,11 @@ import androidx.paging.compose.collectAsLazyPagingItems import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.onSuccess import dev.dimension.flare.common.toPagingState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader import dev.dimension.flare.data.datasource.microblog.paging.toPagingSource import dev.dimension.flare.data.datasource.microblog.pagingConfig -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/MuteUserPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/MuteUserPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/MuteUserPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/MuteUserPresenter.kt index 66135dc487..c20da4c959 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/MuteUserPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/MuteUserPresenter.kt @@ -2,8 +2,8 @@ package dev.dimension.flare.ui.presenter.profile import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.RelationDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/ProfileMediaPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/ProfileMediaPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/ProfileMediaPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/ProfileMediaPresenter.kt index bc1230a05e..e712439b78 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/ProfileMediaPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/ProfileMediaPresenter.kt @@ -9,13 +9,13 @@ import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.flatMap import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.toPagingState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.NoActiveAccountException import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.NoActiveAccountException import dev.dimension.flare.ui.model.UiMedia import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.presenter.PresenterBase diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/ProfilePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/ProfilePresenter.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/ProfilePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/ProfilePresenter.kt index dba629d2df..5517487f9c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/ProfilePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/ProfilePresenter.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import dev.dimension.flare.common.collectAsState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.ActionMenu import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource import dev.dimension.flare.data.datasource.microblog.DirectMessageDataSource @@ -15,12 +16,11 @@ import dev.dimension.flare.data.datasource.microblog.datasource.RelationDataSour import dev.dimension.flare.data.datasource.microblog.datasource.UserDataSource import dev.dimension.flare.data.datasource.microblog.loader.RelationActionType import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.NoActiveAccountException import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.NoActiveAccountException import dev.dimension.flare.ui.model.ClickEvent import dev.dimension.flare.ui.model.UiHandle import dev.dimension.flare.ui.model.UiIcon diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/UnblockUserPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/UnblockUserPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/UnblockUserPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/UnblockUserPresenter.kt index 3c77c0ebbc..db831941d8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/UnblockUserPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/UnblockUserPresenter.kt @@ -2,8 +2,8 @@ package dev.dimension.flare.ui.presenter.profile import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.RelationDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/UnmuteUserPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/UnmuteUserPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/UnmuteUserPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/UnmuteUserPresenter.kt index 6a16f9e51b..a4ae1491e3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/UnmuteUserPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/profile/UnmuteUserPresenter.kt @@ -2,8 +2,8 @@ package dev.dimension.flare.ui.presenter.profile import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.RelationDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/server/AiTLDRPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/server/AiTLDRPresenter.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/server/AiTLDRPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/server/AiTLDRPresenter.kt index 3b93c1c9c2..a00ab6389e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/server/AiTLDRPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/server/AiTLDRPresenter.kt @@ -3,9 +3,9 @@ package dev.dimension.flare.ui.presenter.server import androidx.compose.runtime.Composable import androidx.compose.runtime.produceState import dev.dimension.flare.common.Locale +import dev.dimension.flare.data.ai.AiCompletionService import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.datastore.model.AiPromptDefaults -import dev.dimension.flare.data.network.ai.AiCompletionService import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.presenter.PresenterBase import kotlinx.coroutines.flow.first @@ -26,7 +26,7 @@ public class AiTLDRPresenter( value = runCatching { val aiConfig = - appDataStore.appSettingsStore.data + appDataStore.appSettings .first() .aiConfig if (!aiConfig.tldr) { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AccountsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AccountsPresenter.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AccountsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AccountsPresenter.kt index 289a413683..915c008abc 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AccountsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AccountsPresenter.kt @@ -4,9 +4,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import dev.dimension.flare.common.combineLatestFlowLists +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource import dev.dimension.flare.data.datasource.microblog.datasource.UserDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiConfigPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiConfigPresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiConfigPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiConfigPresenter.kt index 2f0b59aadf..98cfe3b402 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiConfigPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiConfigPresenter.kt @@ -10,10 +10,10 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow -import dev.dimension.flare.common.OnDeviceAI +import dev.dimension.flare.data.ai.OnDeviceAI +import dev.dimension.flare.data.ai.OpenAIService import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.datastore.model.AppSettings -import dev.dimension.flare.data.network.ai.OpenAIService import dev.dimension.flare.data.translation.PreTranslationContentRules import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.presenter.PresenterBase @@ -128,7 +128,7 @@ public class AiConfigPresenter : @Composable override fun body(): State { val scope = rememberCoroutineScope() - val appSettings by remember { appDataStore.appSettingsStore.data } + val appSettings by remember { appDataStore.appSettings } .collectAsState(AppSettings(version = "")) var openAIModels by remember { mutableStateOf>>(UiState.Success(persistentListOf())) @@ -195,11 +195,11 @@ public class AiConfigPresenter : fun update(block: AppSettings.AiConfig.() -> AppSettings.AiConfig) { scope.launch { withContext(Dispatchers.Main) { - appDataStore.appSettingsStore.updateData { current -> - current.copy( + appDataStore.updateAppSettings { + copy( aiConfig = block - .invoke(current.aiConfig) + .invoke(aiConfig) .normalized(), ) } @@ -210,11 +210,11 @@ public class AiConfigPresenter : fun updateTranslateConfig(block: AppSettings.TranslateConfig.() -> AppSettings.TranslateConfig) { scope.launch { withContext(Dispatchers.Main) { - appDataStore.appSettingsStore.updateData { current -> - current.copy( + appDataStore.updateAppSettings { + copy( translateConfig = block - .invoke(current.translateConfig) + .invoke(translateConfig) .normalized(), ) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiTranslationTestPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiTranslationTestPresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiTranslationTestPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiTranslationTestPresenter.kt index 745a32b5d8..5d08c04244 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiTranslationTestPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiTranslationTestPresenter.kt @@ -9,9 +9,9 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import dev.dimension.flare.common.Locale import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.common.tryRun +import dev.dimension.flare.data.ai.AiCompletionService import dev.dimension.flare.data.datastore.AppDataStore -import dev.dimension.flare.data.network.ai.AiCompletionService -import dev.dimension.flare.data.repository.tryRun import dev.dimension.flare.data.translation.AiPlaceholderTranslationSupport import dev.dimension.flare.data.translation.TranslationPromptFormatter import dev.dimension.flare.data.translation.TranslationProvider @@ -69,7 +69,7 @@ public class AiTranslationTestPresenter : translatedText = null val result = tryRun { - val settings = appDataStore.appSettingsStore.data.first() + val settings = appDataStore.appSettings.first() val targetLanguage = Locale.language val sourceJson = sampleText.toTranslationJson(targetLanguage) val promptTemplate = diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AppearancePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AppearancePresenter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AppearancePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AppearancePresenter.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/DevModePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/DevModePresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/DevModePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/DevModePresenter.kt index 436cccfae1..b1a64663bd 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/DevModePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/DevModePresenter.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import dev.dimension.flare.data.repository.DebugRepository +import dev.dimension.flare.common.DebugRepository import dev.dimension.flare.ui.presenter.PresenterBase import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/ExportAppDatabasePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/ExportAppDatabasePresenter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/ExportAppDatabasePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/ExportAppDatabasePresenter.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/ImportAppDatabasePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/ImportAppDatabasePresenter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/ImportAppDatabasePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/ImportAppDatabasePresenter.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/LocalCacheSearchPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/LocalCacheSearchPresenter.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/LocalCacheSearchPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/LocalCacheSearchPresenter.kt index 495b50af1d..de3feade5b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/LocalCacheSearchPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/LocalCacheSearchPresenter.kt @@ -11,9 +11,9 @@ import androidx.paging.filter import androidx.paging.map import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.toPagingState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.datasource.microblog.pagingConfig -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.UiState diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/LocalFilterPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/LocalFilterPresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/LocalFilterPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/LocalFilterPresenter.kt index cdff423d81..cbeb75e8d9 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/LocalFilterPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/LocalFilterPresenter.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import dev.dimension.flare.data.repository.LocalFilterRepository +import dev.dimension.flare.data.local.LocalFilterRepository import dev.dimension.flare.ui.model.UiKeywordFilter import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.collectAsUiState diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/OpenAIModelsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/OpenAIModelsPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/OpenAIModelsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/OpenAIModelsPresenter.kt index 43731ec004..5b21ad77d1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/OpenAIModelsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/OpenAIModelsPresenter.kt @@ -8,8 +8,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow -import dev.dimension.flare.data.network.ai.OpenAIService -import dev.dimension.flare.data.repository.tryRun +import dev.dimension.flare.common.tryRun +import dev.dimension.flare.data.ai.OpenAIService import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.presenter.PresenterBase import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/StoragePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/StoragePresenter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/StoragePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/StoragePresenter.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/splash/SplashPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/splash/SplashPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/splash/SplashPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/splash/SplashPresenter.kt index 276c92c177..9578b71781 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/splash/SplashPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/splash/SplashPresenter.kt @@ -7,7 +7,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.flattenUiState import dev.dimension.flare.ui.presenter.PresenterBase diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/DownloadPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/DownloadPresenter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/DownloadPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/DownloadPresenter.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/LogStatusHistoryPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/LogStatusHistoryPresenter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/LogStatusHistoryPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/LogStatusHistoryPresenter.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/LogUserHistoryPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/LogUserHistoryPresenter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/LogUserHistoryPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/LogUserHistoryPresenter.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/StatusContextPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/StatusContextPresenter.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/StatusContextPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/StatusContextPresenter.kt index 5ab87cdada..7efe2bfd04 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/StatusContextPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/StatusContextPresenter.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.connect import dev.dimension.flare.data.database.cache.model.DbPagingTimeline @@ -11,7 +12,6 @@ import dev.dimension.flare.data.database.cache.model.DbStatus import dev.dimension.flare.data.datasource.microblog.datasource.PostDataSource import dev.dimension.flare.data.datasource.microblog.paging.CacheableRemoteLoader import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.DbAccountType diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/StatusPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/StatusPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/StatusPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/StatusPresenter.kt index 8c148b627e..b790e0ca5d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/StatusPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/StatusPresenter.kt @@ -4,8 +4,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember import dev.dimension.flare.common.collectAsState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.datasource.PostDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/TranslatePresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/TranslatePresenter.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/TranslatePresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/TranslatePresenter.kt index 09ce5f515f..a4e50b93bc 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/TranslatePresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/TranslatePresenter.kt @@ -4,21 +4,21 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.produceState import dev.dimension.flare.common.Locale import dev.dimension.flare.common.decodeJson +import dev.dimension.flare.common.tryRun +import dev.dimension.flare.data.ai.AiCompletionService import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.model.DbTranslation -import dev.dimension.flare.data.database.cache.model.TranslationDisplayMode -import dev.dimension.flare.data.database.cache.model.TranslationEntityType -import dev.dimension.flare.data.database.cache.model.TranslationPayload -import dev.dimension.flare.data.database.cache.model.TranslationStatus -import dev.dimension.flare.data.database.cache.model.sourceHash +import dev.dimension.flare.data.translation.TranslationDisplayMode +import dev.dimension.flare.data.translation.TranslationEntityType +import dev.dimension.flare.data.translation.TranslationPayload +import dev.dimension.flare.data.translation.TranslationStatus +import dev.dimension.flare.data.translation.sourceHash import dev.dimension.flare.data.datastore.AppDataStore -import dev.dimension.flare.data.network.ai.AiCompletionService -import dev.dimension.flare.data.repository.tryRun +import dev.dimension.flare.data.datastore.model.translationProviderCacheKey import dev.dimension.flare.data.translation.AiPlaceholderTranslationSupport import dev.dimension.flare.data.translation.TranslationPromptFormatter import dev.dimension.flare.data.translation.TranslationProvider import dev.dimension.flare.data.translation.TranslationResponseSanitizer -import dev.dimension.flare.data.translation.translationProviderCacheKey import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiState @@ -55,7 +55,7 @@ public class TranslatePresenter( value = tryRun { val settings = - appDataStore.appSettingsStore.data + appDataStore.appSettings .first() val providerCacheKey = settings.translationProviderCacheKey() cachedTranslation(providerCacheKey)?.let { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/VVOCommentPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/VVOCommentPresenter.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/VVOCommentPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/VVOCommentPresenter.kt index 0b1586056e..03a2401584 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/VVOCommentPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/VVOCommentPresenter.kt @@ -9,10 +9,10 @@ import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.collectAsState import dev.dimension.flare.common.onSuccess import dev.dimension.flare.common.toPagingState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.paging.toPagingSource import dev.dimension.flare.data.datasource.microblog.pagingConfig -import dev.dimension.flare.data.datasource.vvo.VVODataSource -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.datasource.vvo.VvoStatusDataSource import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey @@ -40,7 +40,7 @@ public class VVOCommentPresenter( serviceState .flatMap { service -> remember(commentKey, accountType) { - require(service is VVODataSource) + require(service is VvoStatusDataSource) service.comment(commentKey) }.collectAsState().toUi().map { remember(it) { @@ -58,7 +58,7 @@ public class VVOCommentPresenter( serviceState .map { service -> remember(service) { - require(service is VVODataSource) + require(service is VvoStatusDataSource) Pager(config = pagingConfig) { service.commentChild(commentKey = commentKey).toPagingSource() }.flow diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/VVOStatusDetailPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/VVOStatusDetailPresenter.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/VVOStatusDetailPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/VVOStatusDetailPresenter.kt index a3cd94813c..0cd0793655 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/VVOStatusDetailPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/VVOStatusDetailPresenter.kt @@ -10,10 +10,10 @@ import androidx.paging.map import dev.dimension.flare.common.PagingState import dev.dimension.flare.common.collectAsState import dev.dimension.flare.common.toPagingState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.paging.toPagingSource import dev.dimension.flare.data.datasource.microblog.pagingConfig -import dev.dimension.flare.data.datasource.vvo.VVODataSource -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.datasource.vvo.VvoStatusDataSource import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey @@ -42,7 +42,7 @@ public class VVOStatusDetailPresenter( service .flatMap { remember(statusKey, accountType) { - require(it is VVODataSource) + require(it is VvoStatusDataSource) it.status(statusKey) }.collectAsState(UiState.Loading()).value } @@ -51,7 +51,7 @@ public class VVOStatusDetailPresenter( val extendedText = service.flatMap { - require(it is VVODataSource) + require(it is VvoStatusDataSource) remember(statusKey, accountType) { it.statusExtendedText(statusKey) }.collectAsState(UiState.Loading()).value @@ -60,7 +60,7 @@ public class VVOStatusDetailPresenter( val actualStatus = status.flatMap { item -> service.map { service -> - require(service is VVODataSource) + require(service is VvoStatusDataSource) when (extendedText) { is UiState.Error -> { item @@ -90,7 +90,7 @@ public class VVOStatusDetailPresenter( val repost = service .map { - require(it is VVODataSource) + require(it is VvoStatusDataSource) remember(statusKey, accountType) { Pager(config = pagingConfig) { it.statusRepost(statusKey = statusKey).toPagingSource() @@ -111,7 +111,7 @@ public class VVOStatusDetailPresenter( val comment = service .map { - require(it is VVODataSource) + require(it is VvoStatusDataSource) remember(statusKey, accountType) { Pager(config = pagingConfig) { it.statusComment(statusKey = statusKey).toPagingSource() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/AddReactionPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/AddReactionPresenter.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/AddReactionPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/AddReactionPresenter.kt index 334e3d7f02..adbbc7efc5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/AddReactionPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/AddReactionPresenter.kt @@ -4,15 +4,15 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember import dev.dimension.flare.common.collectAsState +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource import dev.dimension.flare.data.datasource.microblog.ComposeType -import dev.dimension.flare.data.datasource.microblog.PostEvent import dev.dimension.flare.data.datasource.microblog.datasource.PostDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.EmojiData +import dev.dimension.flare.ui.model.PostEvent import dev.dimension.flare.ui.model.UiEmoji import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.UiTimelineV2 diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/BlueskyReportStatusPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/BlueskyReportStatusPresenter.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/BlueskyReportStatusPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/BlueskyReportStatusPresenter.kt index 6218c0d494..5738cd06d8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/BlueskyReportStatusPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/BlueskyReportStatusPresenter.kt @@ -6,8 +6,9 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import dev.dimension.flare.data.datasource.bluesky.BlueskyDataSource -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.bluesky.BlueskyReportDataSource +import dev.dimension.flare.data.datasource.bluesky.BlueskyReportReason import dev.dimension.flare.data.repository.accountServiceProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey @@ -37,7 +38,7 @@ public class BlueskyReportStatusPresenter( override fun body(): BlueskyReportStatusState { val service = accountServiceProvider(accountType = accountType, repository = accountRepository).map { service -> - service as BlueskyDataSource + service as BlueskyReportDataSource } val status = remember(statusKey, accountType) { @@ -60,7 +61,7 @@ public class BlueskyReportStatusPresenter( service.onSuccess { scope.launch { if (status is UiTimelineV2.Post) { - it.report(status.statusKey, value) + it.report(status.statusKey, BlueskyReportReason.valueOf(value.name)) } } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/DeleteStatusPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/DeleteStatusPresenter.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/DeleteStatusPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/DeleteStatusPresenter.kt index d32db11a90..dd59d16ac6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/DeleteStatusPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/DeleteStatusPresenter.kt @@ -2,9 +2,9 @@ package dev.dimension.flare.ui.presenter.status.action import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource import dev.dimension.flare.data.datasource.microblog.datasource.PostDataSource -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/MastodonReportPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/MastodonReportPresenter.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/MastodonReportPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/MastodonReportPresenter.kt index 0f5221e5fc..2275c84714 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/MastodonReportPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/MastodonReportPresenter.kt @@ -2,8 +2,8 @@ package dev.dimension.flare.ui.presenter.status.action import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable -import dev.dimension.flare.data.datasource.mastodon.MastodonDataSource -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.mastodon.MastodonReportDataSource import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey @@ -34,7 +34,7 @@ public class MastodonReportPresenter( accountType = accountType, repository = accountRepository, ).mapNotNull { - it as? MastodonDataSource + it as? MastodonReportDataSource }.firstOrNull() ?.report(userKey, statusKey) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/MisskeyReportPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/MisskeyReportPresenter.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/MisskeyReportPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/MisskeyReportPresenter.kt index bcb210def0..afad48d07f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/MisskeyReportPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/action/MisskeyReportPresenter.kt @@ -2,8 +2,8 @@ package dev.dimension.flare.ui.presenter.status.action import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable -import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.datasource.misskey.MisskeyReportDataSource import dev.dimension.flare.data.repository.accountServiceFlow import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey @@ -34,7 +34,7 @@ public class MisskeyReportPresenter( accountType = accountType, repository = accountRepository, ).mapNotNull { - it as? MisskeyDataSource + it as? MisskeyReportDataSource }.firstOrNull() ?.report(userKey, statusKey, comment) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/screen/bluesky/BlueskyFeedsWithTabsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/screen/bluesky/BlueskyFeedsWithTabsPresenter.kt similarity index 73% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/screen/bluesky/BlueskyFeedsWithTabsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/screen/bluesky/BlueskyFeedsWithTabsPresenter.kt index 012216c7ab..576c042104 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/screen/bluesky/BlueskyFeedsWithTabsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/screen/bluesky/BlueskyFeedsWithTabsPresenter.kt @@ -6,37 +6,28 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelineSlot -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.model.tab.toSlot -import dev.dimension.flare.data.platform.BlueskyPlatformSpec +import dev.dimension.flare.data.platform.toTimelineTabDescriptor import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.model.UiList -import dev.dimension.flare.ui.model.UiText import dev.dimension.flare.ui.presenter.PinTabsPresenter import dev.dimension.flare.ui.presenter.PresenterBase import dev.dimension.flare.ui.presenter.home.bluesky.BlueskyFeedsPresenter import dev.dimension.flare.ui.presenter.home.bluesky.BlueskyFeedsState import dev.dimension.flare.ui.presenter.invoke import kotlinx.coroutines.launch +import org.koin.core.component.inject public class BlueskyFeedsWithTabsPresenter( private val accountType: AccountType, ) : PresenterBase() { private val pinTabsPresenter by lazy { object : PinTabsPresenter() { + private val timelinePersistenceMapper by inject() + override fun getTimelineTabItem(item: UiList): TimelineSlot = - BlueskyPlatformSpec.feedTimelineSpec - .target( - data = TimelineSpec.AccountResourceData(specificAccountKey(), item.id), - title = UiText.Raw(item.title), - icon = - (item as? UiList.Feed)?.avatar?.let { - IconType.Url(it) - } ?: IconType.Material(UiIcon.Feeds), - ).toSlot() + timelinePersistenceMapper.toSlot((item as UiList.Feed).toTimelineTabDescriptor(specificAccountKey())) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/screen/list/AllListWithTabsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/screen/list/AllListWithTabsPresenter.kt similarity index 76% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/screen/list/AllListWithTabsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/screen/list/AllListWithTabsPresenter.kt index 84cf7cac45..1a5fdb6ddb 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/screen/list/AllListWithTabsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/screen/list/AllListWithTabsPresenter.kt @@ -2,11 +2,12 @@ package dev.dimension.flare.ui.screen.list import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import dev.dimension.flare.data.datasource.microblog.timeline.CommonTimelineSpecs +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineTabDescriptor import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelineSlot -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.model.tab.toSlot -import dev.dimension.flare.data.platform.CommonTimelineSpecs import dev.dimension.flare.model.AccountType import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.model.UiList @@ -16,22 +17,26 @@ import dev.dimension.flare.ui.presenter.PresenterBase import dev.dimension.flare.ui.presenter.invoke import dev.dimension.flare.ui.presenter.list.AllListPresenter import dev.dimension.flare.ui.presenter.list.AllListState +import org.koin.core.component.inject public class AllListWithTabsPresenter( private val accountType: AccountType, ) : PresenterBase() { private val pinTabsPresenter by lazy { object : PinTabsPresenter() { + private val timelinePersistenceMapper by inject() + override fun getTimelineTabItem(item: UiList): TimelineSlot = - CommonTimelineSpecs.list - .target( + timelinePersistenceMapper.toSlot( + CommonTimelineSpecs.list.toTimelineTabDescriptor( data = TimelineSpec.AccountResourceData(specificAccountKey(), item.id), title = UiText.Raw(item.title), icon = (item as? UiList.List)?.avatar?.let { IconType.Url(it) } ?: IconType.Material(UiIcon.List), - ).toSlot() + ), + ) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/screen/misskey/MisskeyAntennasListWithTabsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/screen/misskey/MisskeyAntennasListWithTabsPresenter.kt similarity index 68% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/screen/misskey/MisskeyAntennasListWithTabsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/screen/misskey/MisskeyAntennasListWithTabsPresenter.kt index 17ce652088..871777e85e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/screen/misskey/MisskeyAntennasListWithTabsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/screen/misskey/MisskeyAntennasListWithTabsPresenter.kt @@ -2,31 +2,25 @@ package dev.dimension.flare.ui.screen.misskey import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelineSlot -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.model.tab.toSlot -import dev.dimension.flare.data.platform.MisskeyPlatformSpec +import dev.dimension.flare.data.platform.toTimelineTabDescriptor import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.model.UiList -import dev.dimension.flare.ui.model.UiText import dev.dimension.flare.ui.presenter.PinTabsPresenter import dev.dimension.flare.ui.presenter.PresenterBase import dev.dimension.flare.ui.presenter.list.AntennasListPresenter +import org.koin.core.component.inject public class MisskeyAntennasListWithTabsPresenter( private val accountType: AccountType, ) : PresenterBase() { private val pinTabsPresenter by lazy { object : PinTabsPresenter() { + private val timelinePersistenceMapper by inject() + override fun getTimelineTabItem(item: UiList): TimelineSlot = - MisskeyPlatformSpec.antennaTimelineSpec - .target( - data = TimelineSpec.AccountResourceData(specificAccountKey(), item.id), - title = UiText.Raw(item.title), - icon = IconType.Material(UiIcon.List), - ).toSlot() + timelinePersistenceMapper.toSlot((item as UiList.Antenna).toTimelineTabDescriptor(specificAccountKey())) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/screen/rss/RssListWithTabsPresenter.kt b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/screen/rss/RssListWithTabsPresenter.kt similarity index 78% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/screen/rss/RssListWithTabsPresenter.kt rename to presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/screen/rss/RssListWithTabsPresenter.kt index 4ec7238f7e..1698e5d0c6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/screen/rss/RssListWithTabsPresenter.kt +++ b/presentation/features/src/commonMain/kotlin/dev/dimension/flare/ui/screen/rss/RssListWithTabsPresenter.kt @@ -4,10 +4,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember import dev.dimension.flare.data.database.app.model.SubscriptionType +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineTabDescriptor +import dev.dimension.flare.data.datasource.rss.RssTimelineSpecs import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelineSlot -import dev.dimension.flare.data.model.tab.toSlot -import dev.dimension.flare.data.platform.RssTimelineSpecs import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.model.UiRssSource import dev.dimension.flare.ui.model.UiText @@ -15,21 +16,25 @@ import dev.dimension.flare.ui.presenter.PinTabsPresenter import dev.dimension.flare.ui.presenter.PresenterBase import dev.dimension.flare.ui.presenter.home.rss.RssSourcesPresenter import dev.dimension.flare.ui.presenter.invoke +import org.koin.core.component.inject public class RssListWithTabsPresenter : PresenterBase() { private val pinTabsPresenter by lazy { object : PinTabsPresenter() { + private val timelinePersistenceMapper by inject() + override fun getTimelineTabItem(item: UiRssSource): TimelineSlot = if (item.type == SubscriptionType.RSS) { - RssTimelineSpecs.rss - .target( + timelinePersistenceMapper.toSlot( + RssTimelineSpecs.rss.toTimelineTabDescriptor( data = RssTimelineSpecs.RssData(item.url), title = UiText.Raw(item.title ?: item.url), icon = item.favIcon?.let { IconType.Url(it) } ?: IconType.Material(UiIcon.Rss), - ).toSlot() + ), + ) } else { - RssTimelineSpecs.subscription - .target( + timelinePersistenceMapper.toSlot( + RssTimelineSpecs.subscription.toTimelineTabDescriptor( data = RssTimelineSpecs.SubscriptionData( subscriptionUrl = item.url, @@ -43,7 +48,8 @@ public class RssListWithTabsPresenter : PresenterBase(), KoinComponent { - private val timelineResolver: TimelineResolver by inject() + private val timelinePersistenceMapper: TimelinePersistenceMapper by inject() @Composable override fun body(): State { @@ -59,8 +60,8 @@ public class AllTabsPresenter( ( listOfNotNull( if (rssSources.sources.isNotEmpty()) { - timelineResolver.toTabItem( - RssTimelineSpecs.allRss.target( + timelinePersistenceMapper.toTabItem( + RssTimelineSpecs.allRss.toTimelineTabDescriptor( data = RssTimelineSpecs.AllRssData, ), ) @@ -68,7 +69,7 @@ public class AllTabsPresenter( null }, ) + - rssSources.sources.map { source -> source.toTimelineTabItemV2(timelineResolver) } + rssSources.sources.map { source -> source.toTimelineTabItemV2(timelinePersistenceMapper) } ).toImmutableList() } @@ -99,7 +100,7 @@ public class AllTabsPresenter( } } -private fun UiRssSource.toTimelineTabItemV2(timelineResolver: TimelineResolver): TimelineTabItemV2 { +private fun UiRssSource.toTimelineTabItemV2(timelinePersistenceMapper: TimelinePersistenceMapper): TimelineTabItemV2 { val title = UiText.Raw(title ?: url) val icon = favIcon?.let { IconType.Url(it) } @@ -110,13 +111,13 @@ private fun UiRssSource.toTimelineTabItemV2(timelineResolver: TimelineResolver): } val source = if (type == SubscriptionType.RSS) { - RssTimelineSpecs.rss.target( + RssTimelineSpecs.rss.toTimelineTabDescriptor( data = RssTimelineSpecs.RssData(url), title = title, icon = icon, ) } else { - RssTimelineSpecs.subscription.target( + RssTimelineSpecs.subscription.toTimelineTabDescriptor( data = RssTimelineSpecs.SubscriptionData( subscriptionUrl = url, @@ -126,5 +127,5 @@ private fun UiRssSource.toTimelineTabItemV2(timelineResolver: TimelineResolver): icon = icon, ) } - return timelineResolver.toTabItem(source) + return timelinePersistenceMapper.toTabItem(source) } diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/common/PagingStateTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/common/PagingStateTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/common/PagingStateTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/common/PagingStateTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/common/TestFormatter.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/common/TestFormatter.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/common/TestFormatter.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/common/TestFormatter.kt diff --git a/presentation/features/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMappingTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMappingTest.kt new file mode 100644 index 0000000000..1b5a57d7fb --- /dev/null +++ b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMappingTest.kt @@ -0,0 +1,296 @@ +package dev.dimension.flare.common.deeplink + +import dev.dimension.flare.model.AccountType +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.model.defaultSocialPlatformRegistry +import dev.dimension.flare.model.xqtHost +import dev.dimension.flare.ui.route.DeeplinkRoute +import io.ktor.http.Url +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.persistentMapOf +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class DeepLinkMappingTest { + private fun PlatformType.deepLinkPatterns(host: String) = defaultSocialPlatformRegistry.deepLinkPatterns(this, host) + + private fun profileRoute( + accountKey: MicroBlogKey, + userName: String, + host: String = accountKey.host, + ) = DeeplinkRoute.Profile.UserNameWithHost( + accountType = AccountType.Specific(accountKey), + userName = userName, + host = host, + ) + + private fun postRoute( + accountKey: MicroBlogKey, + id: String, + ) = DeeplinkRoute.Status.Detail( + accountType = AccountType.Specific(accountKey), + statusKey = MicroBlogKey(id, accountKey.host), + ) + + private fun blueskyPostRoute( + accountKey: MicroBlogKey, + handle: String, + id: String, + ) = DeeplinkRoute.Status.Detail( + accountType = AccountType.Specific(accountKey), + statusKey = MicroBlogKey("at://$handle/app.bsky.feed.post/$id", accountKey.host), + ) + + private fun postMediaRoute( + accountKey: MicroBlogKey, + id: String, + index: Int, + ) = DeeplinkRoute.Media.StatusMedia( + accountType = AccountType.Specific(accountKey), + statusKey = MicroBlogKey(id, accountKey.host), + index = index, + preview = null, + ) + + @Test + fun mastodonPatternsAreGeneratedInOrder() { + val host = "mastodon.social" + + val patterns = PlatformType.Mastodon.deepLinkPatterns(host) + + assertEquals(2, patterns.size) + + val profile = patterns[0] + assertEquals(DeepLinkMapping.Type.Profile.serializer(), profile.serializer) + assertEquals(Url("https://$host/@{handle}"), profile.uriPattern) + + val post = patterns[1] + assertEquals(DeepLinkMapping.Type.Post.serializer(), post.serializer) + assertEquals(Url("https://$host/@{handle}/{id}"), post.uriPattern) + } + + @Test + fun misskeyPatternsUseNotesRoute() { + val host = "misskey.example" + + val patterns = PlatformType.Misskey.deepLinkPatterns(host) + + assertEquals(2, patterns.size) + + val profile = patterns[0] + assertEquals(DeepLinkMapping.Type.Profile.serializer(), profile.serializer) + assertEquals(Url("https://$host/@{handle}"), profile.uriPattern) + + val post = patterns[1] + assertEquals(DeepLinkMapping.Type.Post.serializer(), post.serializer) + assertEquals(Url("https://$host/notes/{id}"), post.uriPattern) + } + + @Test + fun blueskyPatternsIncludeProfileAndPost() { + val host = "bsky.example" + + val patterns = PlatformType.Bluesky.deepLinkPatterns(host) + + assertEquals(2, patterns.size) + + val profile = patterns[0] + assertEquals(DeepLinkMapping.Type.Profile.serializer(), profile.serializer) + assertEquals(Url("https://$host/profile/{handle}"), profile.uriPattern) + + val post = patterns[1] + assertEquals(DeepLinkMapping.Type.BlueskyPost.serializer(), post.serializer) + assertEquals(Url("https://$host/profile/{handle}/post/{id}"), post.uriPattern) + } + + @Test + fun xqtPatternsUseStatusRoute() { + val host = xqtHost + + val patterns = PlatformType.xQt.deepLinkPatterns(host) + + assertEquals(12, patterns.size) + + val profile = patterns[0] + assertEquals(DeepLinkMapping.Type.Profile.serializer(), profile.serializer) + assertEquals(Url("https://$host/{handle}"), profile.uriPattern) + + val post = patterns[4] + assertEquals(DeepLinkMapping.Type.Post.serializer(), post.serializer) + assertEquals(Url("https://$host/{handle}/status/{id}"), post.uriPattern) + + val media = patterns[8] + assertEquals(DeepLinkMapping.Type.PostMedia.serializer(), media.serializer) + assertEquals(Url("https://$host/{handle}/status/{id}/photo/{index}"), media.uriPattern) + } + + @Test + fun vvoHasNoPatterns() { + val patterns = PlatformType.VVo.deepLinkPatterns("irrelevant") + + assertTrue(patterns.isEmpty()) + } + + @Test + fun matchesReturnsAccountProfile() { + val mastodonAccountKey = MicroBlogKey(id = "1", host = "mastodon.social") + val misskeyAccountKey = MicroBlogKey(id = "2", host = "misskey.example") + val mapping = + persistentMapOf( + mastodonAccountKey to + PlatformType.Mastodon.deepLinkPatterns(mastodonAccountKey.host), + misskeyAccountKey to + PlatformType.Misskey.deepLinkPatterns(misskeyAccountKey.host), + ) + + val matches = DeepLinkMapping.matches("https://mastodon.social/@alice", mapping) + + assertEquals(1, matches.size) + assertEquals(profileRoute(mastodonAccountKey, "alice"), matches[mastodonAccountKey]) + } + + @Test + fun matchesMultipleAccountsWithSameHost() { + val account1 = MicroBlogKey(id = "1", host = "mastodon.social") + val account2 = MicroBlogKey(id = "2", host = "mastodon.social") + val mapping: + ImmutableMap>> = + persistentMapOf( + account1 to + PlatformType.Mastodon.deepLinkPatterns(account1.host), + account2 to + PlatformType.Mastodon.deepLinkPatterns(account2.host), + ) + + val matches = DeepLinkMapping.matches("https://mastodon.social/@alice", mapping) + + assertEquals(2, matches.size) + assertEquals(profileRoute(account1, "alice"), matches[account1]) + assertEquals(profileRoute(account2, "alice"), matches[account2]) + } + + @Test + fun matchesReturnsEmptyForNonMatchingUrl() { + val accountKey = MicroBlogKey(id = "1", host = "mastodon.social") + val mapping: + ImmutableMap>> = + persistentMapOf( + accountKey to + PlatformType.Mastodon.deepLinkPatterns(accountKey.host), + ) + + // URL containing none of the valid hosts + assertTrue(DeepLinkMapping.matches("https://google.com", mapping).isEmpty()) + + // URL containing a valid host but invalid path + assertTrue(DeepLinkMapping.matches("https://mastodon.social/about", mapping).isEmpty()) + } + + @Test + fun matchesRealWorldLinks() { + val mastodonAccountKey = MicroBlogKey(id = "1", host = "mastodon.example") + val misskeyAccountKey = MicroBlogKey(id = "2", host = "misskey.example") + val bskyAccountKey = MicroBlogKey(id = "3", host = "bsky.example") + val xAccountKey = MicroBlogKey(id = "4", host = xqtHost) + + val mapping: + ImmutableMap>> = + persistentMapOf( + mastodonAccountKey to + PlatformType.Mastodon.deepLinkPatterns(mastodonAccountKey.host), + misskeyAccountKey to + PlatformType.Misskey.deepLinkPatterns(misskeyAccountKey.host), + bskyAccountKey to + PlatformType.Bluesky.deepLinkPatterns(bskyAccountKey.host), + xAccountKey to + PlatformType.xQt.deepLinkPatterns(xAccountKey.host), + ) + + // https://mastodon.example/@alice + val mastodonProfileMatch = + DeepLinkMapping.matches("https://mastodon.example/@alice", mapping) + assertEquals( + profileRoute(mastodonAccountKey, "alice"), + mastodonProfileMatch[mastodonAccountKey], + ) + + // https://mastodon.example/@alice/12345 + val mastodonPostMatch = + DeepLinkMapping.matches("https://mastodon.example/@alice/12345", mapping) + assertEquals( + postRoute(mastodonAccountKey, "12345"), + mastodonPostMatch[mastodonAccountKey], + ) + + // https://misskey.example/@bob + val misskeyProfileMatch = DeepLinkMapping.matches("https://misskey.example/@bob", mapping) + assertEquals( + profileRoute(misskeyAccountKey, "bob"), + misskeyProfileMatch[misskeyAccountKey], + ) + + // https://misskey.example/notes/12345 + val misskeyPostMatch = + DeepLinkMapping.matches("https://misskey.example/notes/12345", mapping) + assertEquals(postRoute(misskeyAccountKey, "12345"), misskeyPostMatch[misskeyAccountKey]) + + // https://bsky.example/profile/alice.bsky.social + val bskyProfileMatch = + DeepLinkMapping.matches("https://bsky.example/profile/alice.bsky.social", mapping) + assertEquals( + profileRoute(bskyAccountKey, "alice.bsky.social"), + bskyProfileMatch[bskyAccountKey], + ) + + // https://bsky.example/profile/alice.bsky.social/post/12345 + val bskyPostMatch = + DeepLinkMapping.matches( + "https://bsky.example/profile/alice.bsky.social/post/12345", + mapping, + ) + assertEquals( + blueskyPostRoute(bskyAccountKey, "alice.bsky.social", "12345"), + bskyPostMatch[bskyAccountKey], + ) + + // https://x.example/alice + val xProfileMatch = DeepLinkMapping.matches("https://$xqtHost/alice", mapping) + assertEquals(profileRoute(xAccountKey, "alice"), xProfileMatch[xAccountKey]) + + // https://x.example/alice/status/12345 + val xPostMatch = DeepLinkMapping.matches("https://$xqtHost/alice/status/12345", mapping) + assertEquals(postRoute(xAccountKey, "12345"), xPostMatch[xAccountKey]) + + // https://x.example/alice/status/12345/photo/1 + val xPostPhotoMatch = + DeepLinkMapping.matches("https://$xqtHost/alice/status/12345/photo/1", mapping) + assertEquals(postMediaRoute(xAccountKey, "12345", 1), xPostPhotoMatch[xAccountKey]) + } + + @Test + fun matchesGeneratesRoutesForDecodedTypes() { + val accountKey = MicroBlogKey(id = "1", host = "mastodon.social") + val mapping = + persistentMapOf( + accountKey to PlatformType.Mastodon.deepLinkPatterns(accountKey.host), + ) + + assertEquals( + profileRoute(accountKey, "alice"), + DeepLinkMapping.matches("https://mastodon.social/@alice", mapping)[accountKey], + ) + + assertEquals( + profileRoute(accountKey, "bob", "misskey.io"), + DeepLinkMapping.matches("https://mastodon.social/@bob@misskey.io", mapping)[accountKey], + ) + + assertEquals( + postRoute(accountKey, "12345"), + DeepLinkMapping.matches("https://mastodon.social/@alice/12345", mapping)[accountKey], + ) + } +} diff --git a/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/PostEventActionMenu.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/PostEventActionMenu.kt new file mode 100644 index 0000000000..6116edbeed --- /dev/null +++ b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/PostEventActionMenu.kt @@ -0,0 +1,183 @@ +package dev.dimension.flare.data.datasource.microblog + +import dev.dimension.flare.ui.model.PostEvent +import dev.dimension.flare.ui.model.mapper.blueskyBookmark +import dev.dimension.flare.ui.model.mapper.blueskyLike +import dev.dimension.flare.ui.model.mapper.blueskyReblog +import dev.dimension.flare.ui.model.mapper.mastodonBookmark +import dev.dimension.flare.ui.model.mapper.mastodonLike +import dev.dimension.flare.ui.model.mapper.mastodonRepost +import dev.dimension.flare.ui.model.mapper.misskeyFavourite +import dev.dimension.flare.ui.model.mapper.misskeyReact +import dev.dimension.flare.ui.model.mapper.misskeyRenote +import dev.dimension.flare.ui.model.mapper.nostrLike +import dev.dimension.flare.ui.model.mapper.nostrRepost +import dev.dimension.flare.ui.model.mapper.vvoFavorite +import dev.dimension.flare.ui.model.mapper.vvoLike +import dev.dimension.flare.ui.model.mapper.vvoLikeComment +import dev.dimension.flare.ui.model.mapper.xqtBookmark +import dev.dimension.flare.ui.model.mapper.xqtLike +import dev.dimension.flare.ui.model.mapper.xqtRetweet + +internal fun PostEvent.nextActionMenu(): ActionMenu.Item? = + when (this) { + is PostEvent.Mastodon.Reblog -> { + ActionMenu.mastodonRepost( + reblogged = !reblogged, + reblogsCount = count + if (!reblogged) 1 else -1, + accountKey = accountKey, + statusKey = postKey, + ) + } + + is PostEvent.Mastodon.Like -> { + ActionMenu.mastodonLike( + favourited = !liked, + favouritesCount = count + if (!liked) 1 else -1, + accountKey = accountKey, + statusKey = postKey, + ) + } + + is PostEvent.Mastodon.Bookmark -> { + ActionMenu.mastodonBookmark( + bookmarked = !bookmarked, + accountKey = accountKey, + statusKey = postKey, + ) + } + + is PostEvent.Misskey.React -> { + ActionMenu.misskeyReact( + postKey = postKey, + hasReacted = !hasReacted, + reaction = reaction, + count = (count + if (!hasReacted) 1 else -1).coerceAtLeast(0), + accountKey = accountKey, + ) + } + + is PostEvent.Misskey.Renote -> { + ActionMenu.misskeyRenote( + postKey = postKey, + count = count + 1, + accountKey = accountKey, + ) + } + + is PostEvent.Misskey.Favourite -> { + ActionMenu.misskeyFavourite( + postKey = postKey, + favourited = !favourited, + accountKey = accountKey, + ) + } + + is PostEvent.Bluesky.Reblog -> { + ActionMenu.blueskyReblog( + accountKey = accountKey, + postKey = postKey, + cid = cid, + uri = uri, + count = count + if (repostUri == null) 1 else -1, + repostUri = if (repostUri == null) "" else null, + ) + } + + is PostEvent.Bluesky.Like -> { + ActionMenu.blueskyLike( + accountKey = accountKey, + postKey = postKey, + cid = cid, + uri = uri, + count = count + if (likedUri == null) 1 else -1, + likedUri = if (likedUri == null) "" else null, + ) + } + + is PostEvent.Bluesky.Bookmark -> { + ActionMenu.blueskyBookmark( + accountKey = accountKey, + postKey = postKey, + cid = cid, + uri = uri, + bookmarked = !bookmarked, + count = count + if (!bookmarked) 1 else -1, + ) + } + + is PostEvent.XQT.Retweet -> { + ActionMenu.xqtRetweet( + statusKey = postKey, + retweeted = !retweeted, + count = (count + if (!retweeted) 1 else -1).coerceAtLeast(0), + accountKey = accountKey, + ) + } + + is PostEvent.XQT.Like -> { + ActionMenu.xqtLike( + statusKey = postKey, + liked = !liked, + count = (count + if (!liked) 1 else -1).coerceAtLeast(0), + accountKey = accountKey, + ) + } + + is PostEvent.XQT.Bookmark -> { + ActionMenu.xqtBookmark( + statusKey = postKey, + bookmarked = !bookmarked, + count = (count + if (!bookmarked) 1 else -1).coerceAtLeast(0), + accountKey = accountKey, + ) + } + + is PostEvent.VVO.Like -> { + ActionMenu.vvoLike( + statusKey = postKey, + liked = !liked, + count = (count + if (!liked) 1 else -1).coerceAtLeast(0), + accountKey = accountKey, + ) + } + + is PostEvent.VVO.LikeComment -> { + ActionMenu.vvoLikeComment( + statusKey = postKey, + liked = !liked, + count = (count + if (!liked) 1 else -1).coerceAtLeast(0), + accountKey = accountKey, + ) + } + + is PostEvent.VVO.Favorite -> { + ActionMenu.vvoFavorite( + statusKey = postKey, + favorited = !favorited, + accountKey = accountKey, + ) + } + + is PostEvent.Nostr.Repost -> { + ActionMenu.nostrRepost( + statusKey = postKey, + repostEventId = if (repostEventId == null) "" else null, + count = (count + if (repostEventId == null) 1 else -1).coerceAtLeast(0), + accountKey = accountKey, + ) + } + + is PostEvent.Nostr.Like -> { + ActionMenu.nostrLike( + statusKey = postKey, + reactionEventId = if (reactionEventId == null) "" else null, + count = (count + if (reactionEventId == null) 1 else -1).coerceAtLeast(0), + accountKey = accountKey, + ) + } + + else -> { + null + } + } diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyTimelinePrependTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyTimelinePrependTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyTimelinePrependTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyTimelinePrependTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datastore/model/TranslateConfigSerializationTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/datastore/model/TranslateConfigSerializationTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/datastore/model/TranslateConfigSerializationTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/datastore/model/TranslateConfigSerializationTest.kt diff --git a/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/TimelineTabAppearanceTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/TimelineTabAppearanceTest.kt new file mode 100644 index 0000000000..5a8f1d82a7 --- /dev/null +++ b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/model/appearance/TimelineTabAppearanceTest.kt @@ -0,0 +1,43 @@ +package dev.dimension.flare.data.model.appearance + +import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.data.model.tab.SourceTimelineTabItemV2 +import dev.dimension.flare.data.model.tab.resolveTimelineAppearance +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiText +import kotlin.test.Test +import kotlin.test.assertEquals + +class TimelineTabAppearanceTest { + @Test + fun timelineTabItemV2ResolvesTimelineAppearanceFromItemPatch() { + val item = + SourceTimelineTabItemV2.runtime( + id = "test", + title = UiText.Raw("Test"), + icon = IconType.Material(UiIcon.List), + appearancePatch = + AppearancePatch.EMPTY + .set(AppearanceKeys.ShowNumbers, false) + .set(AppearanceKeys.AbsoluteTimestamp, true), + createPresenter = { error("unused in test") }, + ) + val base = + TimelineAppearance( + showNumbers = true, + absoluteTimestamp = false, + aiConfig = TimelineAppearance.AiConfig(translation = true), + lineLimit = 7, + ) + + assertEquals( + TimelineAppearance( + showNumbers = false, + absoluteTimestamp = true, + aiConfig = TimelineAppearance.AiConfig(translation = true), + lineLimit = 7, + ), + item.resolveTimelineAppearance(base), + ) + } +} diff --git a/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelineCatalogMigrationCompatibilityTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelineCatalogMigrationCompatibilityTest.kt new file mode 100644 index 0000000000..bb810dd9d0 --- /dev/null +++ b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelineCatalogMigrationCompatibilityTest.kt @@ -0,0 +1,108 @@ +package dev.dimension.flare.data.model.tab + +import dev.dimension.flare.data.database.app.model.SubscriptionType +import dev.dimension.flare.data.datasource.microblog.timeline.CommonTimelineSpecs +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineCatalog +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.rss.RssTimelineSpecs +import dev.dimension.flare.data.platform.BlueskyTimelineSpecs +import dev.dimension.flare.data.platform.MastodonTimelineSpecs +import dev.dimension.flare.data.platform.MisskeyTimelineSpecs +import dev.dimension.flare.data.platform.VvoTimelineSpecs +import dev.dimension.flare.data.platform.XqtTimelineSpecs +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.defaultSocialPlatformRegistry +import dev.dimension.flare.ui.model.asText +import kotlin.test.Test +import kotlin.test.assertEquals + +class TimelineCatalogMigrationCompatibilityTest { + private val accountKey = MicroBlogKey(id = "alice", host = "example.com") + private val catalog = + TimelineCatalog( + defaultSocialPlatformRegistry.specs.flatMap { it.timelineSpecs } + RssTimelineSpecs.timelineSpecs, + ) + private val mapper = TimelinePersistenceMapper(catalog) + + @Test + fun legacyMigrationTimelineRefsDecodeWithDefaultCatalog() { + val refs = + listOf( + sourceRef(CommonTimelineSpecs.home, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(CommonTimelineSpecs.discover, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(CommonTimelineSpecs.list, TimelineSpec.AccountResourceData(accountKey, "list-1")), + sourceRef(MastodonTimelineSpecs.local, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(MastodonTimelineSpecs.federated, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(MastodonTimelineSpecs.bookmark, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(MastodonTimelineSpecs.favourite, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(MisskeyTimelineSpecs.local, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(MisskeyTimelineSpecs.global, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(MisskeyTimelineSpecs.hybrid, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(MisskeyTimelineSpecs.favourite, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(MisskeyTimelineSpecs.antenna, TimelineSpec.AccountResourceData(accountKey, "antenna-1")), + sourceRef(MisskeyTimelineSpecs.channel, TimelineSpec.AccountResourceData(accountKey, "channel-1")), + sourceRef(XqtTimelineSpecs.featured, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(XqtTimelineSpecs.bookmark, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(XqtTimelineSpecs.deviceFollow, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(BlueskyTimelineSpecs.feed, TimelineSpec.AccountResourceData(accountKey, "at://feed")), + sourceRef(BlueskyTimelineSpecs.bookmark, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(VvoTimelineSpecs.favorite, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(VvoTimelineSpecs.liked, TimelineSpec.AccountBasedData(accountKey)), + sourceRef(RssTimelineSpecs.rss, RssTimelineSpecs.RssData("https://example.com/feed.xml")), + sourceRef(RssTimelineSpecs.allRss, RssTimelineSpecs.AllRssData), + sourceRef( + RssTimelineSpecs.subscription, + RssTimelineSpecs.SubscriptionData( + subscriptionUrl = "https://mastodon.example/public", + subscriptionType = SubscriptionType.MASTODON_PUBLIC, + ), + ), + ) + + assertEquals( + listOf( + "common.home", + "common.discover", + "common.list", + "mastodon.local", + "mastodon.public", + "mastodon.bookmark", + "mastodon.favourite", + "misskey.local", + "misskey.global", + "misskey.hybrid", + "misskey.favourite", + "misskey.antenna", + "misskey.channel", + "xqt.featured", + "xqt.bookmark", + "xqt.device_follow", + "bluesky.feed", + "bluesky.bookmark", + "vvo.favorite", + "vvo.liked", + "rss.feed", + "rss.all", + "rss.subscription", + ), + refs.map { it.specId }, + ) + refs.forEach { ref -> + assertEquals(ref.specId, mapper.decode(ref).spec.id) + } + } + + private fun sourceRef( + spec: TimelineSpec, + data: T, + ): TimelineSourceRef { + val encoded = catalog.encode(spec.ref(data)) + return TimelineSourceRef( + id = "${encoded.specId}:${encoded.stableKey}", + specId = encoded.specId, + title = spec.title.asText(), + icon = spec.icon, + data = encoded.data, + ) + } +} diff --git a/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelinePersistenceMapperTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelinePersistenceMapperTest.kt new file mode 100644 index 0000000000..8600934fc5 --- /dev/null +++ b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelinePersistenceMapperTest.kt @@ -0,0 +1,234 @@ +package dev.dimension.flare.data.model.tab + +import dev.dimension.flare.data.datasource.microblog.paging.notSupported +import dev.dimension.flare.data.datasource.microblog.timeline.AccountTimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineCatalog +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineDisplay +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineTabDescriptor +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineTabDescriptor +import dev.dimension.flare.data.datasource.rss.RssTimelineSpecs +import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.UiText +import dev.dimension.flare.ui.model.asType +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertIs +import kotlin.test.assertNull + +class TimelinePersistenceMapperTest { + private val homeSpec = + AccountTimelineSpec( + id = "common.home", + title = UiStrings.Home, + icon = UiIcon.Home.asType(), + serializer = + dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec.AccountBasedData + .serializer(), + stableKeyFactory = { it.accountKey.toString() }, + loaderFactory = { _, _ -> notSupported() }, + ) + + private val mapper = + TimelinePersistenceMapper( + catalog = TimelineCatalog(listOf(homeSpec) + RssTimelineSpecs.timelineSpecs), + ) + + @Test + fun sourceDescriptorProjectsToStorageSlotWithoutDatasourceStorageTypes() { + val accountKey = MicroBlogKey("home", "example.com") + val descriptor = + TimelineTabDescriptor.Source( + ref = + homeSpec.ref( + dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec + .AccountBasedData(accountKey), + ), + display = + TimelineDisplay( + title = UiText.Raw("Home"), + icon = IconType.FavIcon(accountKey.host), + ), + ) + + val slot = mapper.toSlot(descriptor) + val source = assertIs(slot.content).source + + assertEquals("common.home:$accountKey", slot.id) + assertEquals(slot.id, source.id) + assertEquals("common.home", source.specId) + assertEquals(UiText.Raw("Home"), source.title) + assertEquals(IconType.FavIcon(accountKey.host), source.icon) + } + + @Test + fun sourceSlotRoundTripsThroughCurrentTimelineItemBridge() { + val accountKey = MicroBlogKey("home", "example.com") + val descriptor = + TimelineTabDescriptor.Source( + ref = + homeSpec.ref( + dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec + .AccountBasedData(accountKey), + ), + display = + TimelineDisplay( + title = UiText.Raw("Home"), + icon = IconType.FavIcon(accountKey.host), + ), + ) + val slot = mapper.toSlot(descriptor) + + val item = mapper.toTabItem(slot) + val persistedAgain = mapper.toSlot(item) + + assertIs(item) + assertEquals(slot, persistedAgain) + } + + @Test + fun rssSourceSlotDecodesToTypedRuntimeRef() { + val descriptor = + RssTimelineSpecs.rss.toTimelineTabDescriptor( + data = RssTimelineSpecs.RssData("https://example.com/feed.xml"), + title = UiText.Raw("Example"), + icon = IconType.Material(UiIcon.Rss), + ) + val slot = mapper.toSlot(descriptor) + + val item = assertIs(mapper.toTabItem(slot)) + + assertEquals(descriptor.ref, item.ref) + } + + @Test + fun sourceSlotResolvesAccountKeyOnlyForAccountTimelineData() { + val accountKey = MicroBlogKey("home", "example.com") + val accountSlot = + mapper.toSlot( + homeSpec.toTimelineTabDescriptor( + dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec + .AccountBasedData(accountKey), + ), + ) + val rssSlot = + mapper.toSlot( + RssTimelineSpecs.rss.toTimelineTabDescriptor( + data = RssTimelineSpecs.RssData("https://example.com/feed.xml"), + title = UiText.Raw("Example"), + icon = IconType.Material(UiIcon.Rss), + ), + ) + + assertEquals(accountKey, mapper.resolveAccountKey(accountSlot)) + assertNull(mapper.resolveAccountKey(rssSlot)) + } + + @Test + fun runtimeOnlySourceTabCannotBePersisted() { + val runtimeTab = + SourceTimelineTabItemV2.runtime( + id = "runtime:now", + title = UiText.Raw("Runtime"), + icon = UiIcon.Home.asType(), + createPresenter = { error("unused") }, + ) + + assertFailsWith { + mapper.toSlot(runtimeTab) + } + } + + @Test + fun systemHomeMixedTimelinePersistsAsGroupSlot() { + val first = + mapper.toTabItem( + mapper.toSlot( + homeSpec.toTimelineTabDescriptor( + dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec.AccountBasedData( + MicroBlogKey("first", "example.com"), + ), + ), + ), + ) + val second = + mapper.toTabItem( + mapper.toSlot( + homeSpec.toTimelineTabDescriptor( + dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec.AccountBasedData( + MicroBlogKey("second", "example.com"), + ), + ), + ), + ) + + val mixed = + listOf(first, second) + .withSystemHomeMixedTimelineEnabled( + enabled = true, + mergePolicy = TimelineMergePolicy.Staggered, + ).first() + + val groupItem = assertIs(mixed) + val groupSlot = mapper.toSlot(groupItem) + val groupContent = assertIs(groupSlot.content) + + assertEquals(SYSTEM_HOME_MIXED_TIMELINE_ID, groupSlot.id) + assertEquals(GroupSource.SystemHome, groupContent.source) + assertEquals(TimelineMergePolicy.Staggered, groupContent.mergePolicy) + assertEquals(listOf(first.id, second.id), groupContent.children.map { it.id }) + } + + @Test + fun manualGroupRoundTripPreservesDisabledChildren() { + val first = + mapper.toTabItem( + mapper.toSlot( + homeSpec.toTimelineTabDescriptor( + dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec.AccountBasedData( + MicroBlogKey("first", "example.com"), + ), + ), + ), + ) + val disabledSecond = + mapper + .toTabItem( + mapper.toSlot( + homeSpec.toTimelineTabDescriptor( + dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec.AccountBasedData( + MicroBlogKey("second", "example.com"), + ), + ), + ), + ).withPresentationOverrides( + title = "Second", + icon = UiIcon.Home.asType(), + enabled = false, + ) + + val groupSlot = + mapper.toSlot( + GroupTimelineTabItemV2( + id = "manual", + children = listOf(first, disabledSecond), + mergePolicy = TimelineMergePolicy.Time, + source = GroupSource.Manual, + presentation = TimelinePresentation(), + title = UiText.Raw("Manual"), + icon = UiIcon.Rss.asType(), + appearancePatch = null, + enabled = true, + ), + ) + + val groupItem = assertIs(mapper.toTabItem(groupSlot)) + + assertEquals(listOf(first.id, disabledSecond.id), groupItem.children.map { it.id }) + assertEquals(listOf(true, false), groupItem.children.map { it.enabled }) + assertEquals(groupSlot, mapper.toSlot(groupItem)) + } +} diff --git a/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelinePresenterFactoryTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelinePresenterFactoryTest.kt new file mode 100644 index 0000000000..de9fcdcbb5 --- /dev/null +++ b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/model/tab/TimelinePresenterFactoryTest.kt @@ -0,0 +1,145 @@ +package dev.dimension.flare.data.model.tab + +import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader +import dev.dimension.flare.data.datasource.microblog.paging.notSupported +import dev.dimension.flare.data.datasource.microblog.timeline.AccountTimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.StandaloneTimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineCatalog +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineDisplay +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineTabDescriptor +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineTabDescriptor +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.UiText +import dev.dimension.flare.ui.model.UiTimelineV2 +import dev.dimension.flare.ui.model.asType +import dev.dimension.flare.ui.presenter.home.AccountTimelinePresenter +import dev.dimension.flare.ui.presenter.home.StandaloneTimelinePresenter +import dev.dimension.flare.ui.presenter.home.TimelinePresenter +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.serialization.Serializable +import kotlin.test.Test +import kotlin.test.assertFailsWith +import kotlin.test.assertIs +import kotlin.test.assertSame + +class TimelinePresenterFactoryTest { + private val accountSpec = + AccountTimelineSpec( + id = "common.home", + title = UiStrings.Home, + icon = UiIcon.Home.asType(), + serializer = TimelineSpec.AccountBasedData.serializer(), + stableKeyFactory = { it.accountKey.toString() }, + loaderFactory = { _, _ -> notSupported() }, + ) + private val standaloneSpec = + StandaloneTimelineSpec( + id = "standalone.fake", + title = UiStrings.Posts, + icon = UiIcon.List.asType(), + serializer = StandaloneData.serializer(), + stableKeyFactory = { it.value }, + loaderFactory = { _, _ -> flowOf(notSupported()) }, + ) + private val mapper = + TimelinePersistenceMapper( + catalog = TimelineCatalog(listOf(accountSpec, standaloneSpec)), + ) + private val factory = TimelinePresenterFactory(mapper) + + @Test + fun createsAccountTimelinePresenterFromTypedSourceTab() { + val item = + mapper.toTabItem( + accountSpec.toTimelineTabDescriptor( + TimelineSpec.AccountBasedData(MicroBlogKey("home", "example.com")), + ), + ) + + assertIs(factory.create(item)) + } + + @Test + fun createsStandaloneTimelinePresenterFromTypedSourceTab() { + val item = + mapper.toTabItem( + standaloneSpec.toTimelineTabDescriptor(StandaloneData("standalone")), + ) + + assertIs(factory.create(item)) + } + + @Test + fun createsPresenterFromStoredSourceWhenRuntimeRefIsAbsent() { + val descriptor = + accountSpec.toTimelineTabDescriptor( + TimelineSpec.AccountBasedData(MicroBlogKey("home", "example.com")), + ) + val source = mapper.toSourceRef(descriptor) + val slot = mapper.toSlot(descriptor) + val item = SourceTimelineTabItemV2.fromSlot(slot, source, ref = null) + + assertIs(factory.create(item)) + } + + @Test + fun returnsRuntimePresenterForRuntimeOnlyTab() { + val presenter = FakeTimelinePresenter() + val item = + SourceTimelineTabItemV2.runtime( + id = "runtime:fake", + title = UiText.Raw("Runtime"), + icon = UiIcon.Home.asType(), + createPresenter = { presenter }, + ) + + assertSame(presenter, factory.create(item)) + } + + @Test + fun rejectsUnsupportedTimelineSpec() { + val unsupportedSpec = UnsupportedTimelineSpec() + val unsupportedMapper = + TimelinePersistenceMapper( + catalog = TimelineCatalog(listOf(unsupportedSpec)), + ) + val unsupportedFactory = TimelinePresenterFactory(unsupportedMapper) + val item = + unsupportedMapper.toTabItem( + TimelineTabDescriptor.Source( + ref = unsupportedSpec.ref(StandaloneData("unsupported")), + display = + TimelineDisplay( + title = UiText.Raw("Unsupported"), + icon = UiIcon.Home.asType(), + ), + ), + ) + + assertFailsWith { + unsupportedFactory.create(item) + } + } + + private class FakeTimelinePresenter : TimelinePresenter() { + override val loader: Flow> = flowOf(notSupported()) + } + + private class UnsupportedTimelineSpec : TimelineSpec { + override val id: String = "unsupported.fake" + override val title: UiStrings = UiStrings.Posts + override val icon = UiIcon.Home.asType() + override val serializer = StandaloneData.serializer() + + override fun stableKey(data: StandaloneData): String = data.value + } + + @Serializable + private data class StandaloneData( + val value: String, + ) : TimelineSpec.Data +} diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationTestConfig.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationTestConfig.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationTestConfig.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/data/translation/TranslationTestConfig.kt diff --git a/presentation/features/src/commonTest/kotlin/dev/dimension/flare/model/DefaultSocialPlatformRegistryTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/model/DefaultSocialPlatformRegistryTest.kt new file mode 100644 index 0000000000..a7afff2f2d --- /dev/null +++ b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/model/DefaultSocialPlatformRegistryTest.kt @@ -0,0 +1,49 @@ +package dev.dimension.flare.model + +import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource +import dev.dimension.flare.ui.model.UiAccount +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertIs + +class DefaultSocialPlatformRegistryTest { + @Test + fun publicDefaultPluginsCreateDataSourcesForTheirAccounts() { + val accounts = + listOf( + UiAccount.Mastodon( + accountKey = MicroBlogKey("mastodon-user", "mastodon.example"), + instance = "mastodon.example", + ), + UiAccount.Misskey( + accountKey = MicroBlogKey("misskey-user", "misskey.example"), + host = "misskey.example", + ), + UiAccount.Bluesky( + accountKey = MicroBlogKey("did:plc:test", "bsky.social"), + ), + UiAccount.XQT( + accountKey = MicroBlogKey("x-user", "x.com"), + ), + UiAccount.VVo( + accountKey = MicroBlogKey("weibo-user", "weibo.com"), + ), + ) + + accounts.forEach { account -> + assertIs(defaultSocialPlatformRegistry.createDataSource(account)) + } + } + + @Test + fun publicDefaultPluginsExposeTimelineSpecsToCatalogAssembly() { + val specIds = defaultSocialPlatformRegistry.specs.flatMap { it.timelineSpecs }.map { it.id } + + assertContains(specIds, "common.home") + assertContains(specIds, "mastodon.local") + assertContains(specIds, "misskey.hybrid") + assertContains(specIds, "bluesky.feed") + assertContains(specIds, "xqt.featured") + assertContains(specIds, "vvo.favorite") + } +} diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/CacheDataToUiTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/CacheDataToUiTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/CacheDataToUiTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/CacheDataToUiTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyFacetsTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyFacetsTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyFacetsTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyFacetsTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyNotificationRenderTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyNotificationRenderTest.kt similarity index 95% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyNotificationRenderTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyNotificationRenderTest.kt index e3f0434187..e04958cfe3 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyNotificationRenderTest.kt +++ b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyNotificationRenderTest.kt @@ -94,14 +94,14 @@ class BlueskyNotificationRenderTest { val repostResult = listOf(repostNotification).render(accountKey, references).single() val repostUserList = assertIs(repostResult) assertNotNull(repostUserList.message) - assertNotNull(repostUserList.post) - assertEquals("reposted content", repostUserList.post.content.innerText) + val repostPost = assertNotNull(repostUserList.post) + assertEquals("reposted content", repostPost.content.innerText) val likeResult = listOf(likeNotification).render(accountKey, references).single() val likeUserList = assertIs(likeResult) assertNotNull(likeUserList.message) - assertNotNull(likeUserList.post) - assertEquals("reposted content", likeUserList.post.content.innerText) + val likePost = assertNotNull(likeUserList.post) + assertEquals("reposted content", likePost.content.innerText) } @Test @@ -161,10 +161,10 @@ class BlueskyNotificationRenderTest { ) val renderedPost = assertIs(result.single()) assertEquals("post-$index", renderedPost.content.innerText) - assertNotNull(renderedPost.message) + val message = assertNotNull(renderedPost.message) assertEquals( "did:plc:author$index", - renderedPost.message.user + message.user ?.key ?.id, ) @@ -190,11 +190,11 @@ class BlueskyNotificationRenderTest { ) val result = listOf(notification).render(accountKey, references = persistentMapOf()) val user = assertIs(result.single()) - assertNotNull(user.message) + val message = assertNotNull(user.message) assertEquals("did:plc:user$index", user.value.key.id) assertEquals( "did:plc:user$index", - user.message.user + message.user ?.key ?.id, ) diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyRenderTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyRenderTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyRenderTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyRenderTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyUiMappingTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyUiMappingTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyUiMappingTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/BlueskyUiMappingTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/MastodonRenderTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/MastodonRenderTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/MastodonRenderTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/MastodonRenderTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/MisskeyRenderTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/MisskeyRenderTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/MisskeyRenderTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/MisskeyRenderTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/RssDateParserTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/RssDateParserTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/RssDateParserTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/RssDateParserTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/VVORenderTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/VVORenderTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/VVORenderTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/VVORenderTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/XQTRenderTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/XQTRenderTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/XQTRenderTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/XQTRenderTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/XQTTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/XQTTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/XQTTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/model/mapper/XQTTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/compose/InitialTextResolverTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/compose/InitialTextResolverTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/compose/InitialTextResolverTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/compose/InitialTextResolverTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/GroupConfigPresenterTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/GroupConfigPresenterTest.kt similarity index 59% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/GroupConfigPresenterTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/GroupConfigPresenterTest.kt index ef77940172..0bfccb78c5 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/GroupConfigPresenterTest.kt +++ b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/GroupConfigPresenterTest.kt @@ -1,40 +1,39 @@ package dev.dimension.flare.ui.presenter.home -import dev.dimension.flare.data.model.HomeTimelineTabItem +import dev.dimension.flare.data.datasource.microblog.timeline.CommonTimelineSpecs +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineCatalog +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineTabDescriptor import dev.dimension.flare.data.model.IconType -import dev.dimension.flare.data.model.ListTimelineTabItem -import dev.dimension.flare.data.model.TabMetaData -import dev.dimension.flare.data.model.TitleType import dev.dimension.flare.data.model.tab.GroupTimelineTabItemV2 import dev.dimension.flare.data.model.tab.TabSettingsV2 import dev.dimension.flare.data.model.tab.TimelineMergePolicy -import dev.dimension.flare.data.model.tab.TimelineResolver -import dev.dimension.flare.data.model.tab.toTimelineSlotOrNull -import dev.dimension.flare.model.AccountType +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.defaultSocialPlatformRegistry import dev.dimension.flare.ui.model.UiIcon import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs class GroupConfigPresenterTest { + private val timelinePersistenceMapper = + TimelinePersistenceMapper( + TimelineCatalog(defaultSocialPlatformRegistry.specs.flatMap { it.timelineSpecs }), + ) + @Test fun upsertGroupConfigReplacesDuplicateKeyInsteadOfAppending() { - val timelineResolver = TimelineResolver() val accountKey = MicroBlogKey("1872639344760254464", "x.com") - val accountType = AccountType.Specific(accountKey) - val homeTab = timelineResolver.toTabItem(HomeTimelineTabItem(accountType).toTimelineSlotOrNull()!!) + val homeTab = + timelinePersistenceMapper.toTabItem( + CommonTimelineSpecs.home.toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)), + ) val listTab = - timelineResolver.toTabItem( - ListTimelineTabItem( - account = accountType, - listId = "1681353064253640704", - metaData = - TabMetaData( - title = TitleType.Text("list"), - icon = IconType.Material(UiIcon.List), - ), - ).toTimelineSlotOrNull()!!, + timelinePersistenceMapper.toTabItem( + CommonTimelineSpecs + .list + .toTimelineTabDescriptor(TimelineSpec.AccountResourceData(accountKey, "1681353064253640704")), ) val existingGroup = TabSettingsV2() @@ -46,14 +45,14 @@ class GroupConfigPresenterTest { enabled = true, tabs = listOf(homeTab, listTab), defaultGroupName = "Group", - timelineResolver = timelineResolver, + timelinePersistenceMapper = timelinePersistenceMapper, ).homeSlots .single() - .let(timelineResolver::toTabItem) + .let(timelinePersistenceMapper::toTabItem) val existingGroupItem = assertIs(existingGroup) val updated = - TabSettingsV2(homeSlots = listOf(timelineResolver.toSlot(existingGroupItem))) + TabSettingsV2(homeSlots = listOf(timelinePersistenceMapper.toSlot(existingGroupItem))) .upsertGroupConfig( initialItem = null, name = "엑스", @@ -63,12 +62,12 @@ class GroupConfigPresenterTest { tabs = listOf(homeTab, homeTab, listTab), mergePolicy = TimelineMergePolicy.Staggered, defaultGroupName = "Group", - timelineResolver = timelineResolver, + timelinePersistenceMapper = timelinePersistenceMapper, ) assertEquals(1, updated.homeSlots.size) assertEquals(existingGroupItem.id, updated.homeSlots.single().id) - val updatedGroup = assertIs(timelineResolver.toTabItem(updated.homeSlots.single())) + val updatedGroup = assertIs(timelinePersistenceMapper.toTabItem(updated.homeSlots.single())) assertEquals( listOf(homeTab.id, listTab.id), updatedGroup.children.map { it.id }, diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenterFilterTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenterFilterTest.kt similarity index 98% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenterFilterTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenterFilterTest.kt index 8ff4099f69..987aa0785a 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenterFilterTest.kt +++ b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenterFilterTest.kt @@ -1,10 +1,10 @@ package dev.dimension.flare.ui.presenter.home import dev.dimension.flare.common.TestFormatter +import dev.dimension.flare.data.local.KeywordFilterPattern import dev.dimension.flare.data.model.tab.TimelineFilterConfig import dev.dimension.flare.data.model.tab.TimelinePostContent import dev.dimension.flare.data.model.tab.TimelinePostKind -import dev.dimension.flare.data.repository.KeywordFilterPattern import dev.dimension.flare.ui.humanizer.PlatformFormatter import dev.dimension.flare.ui.model.UiMedia import dev.dimension.flare.ui.model.createSampleStatus diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/settings/AiConfigPresenterTest.kt b/presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/settings/AiConfigPresenterTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/settings/AiConfigPresenterTest.kt rename to presentation/features/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/settings/AiConfigPresenterTest.kt diff --git a/shared/src/jvmMain/kotlin/dev/dimension/flare/data/network/nostr/AmberSignerBridge.jvm.kt b/presentation/features/src/jvmMain/kotlin/dev/dimension/flare/data/network/nostr/AmberSignerBridge.jvm.kt similarity index 100% rename from shared/src/jvmMain/kotlin/dev/dimension/flare/data/network/nostr/AmberSignerBridge.jvm.kt rename to presentation/features/src/jvmMain/kotlin/dev/dimension/flare/data/network/nostr/AmberSignerBridge.jvm.kt diff --git a/shared/src/jvmMain/kotlin/dev/dimension/flare/di/PlatformModule.jvm.kt b/presentation/features/src/jvmMain/kotlin/dev/dimension/flare/di/PlatformModule.jvm.kt similarity index 61% rename from shared/src/jvmMain/kotlin/dev/dimension/flare/di/PlatformModule.jvm.kt rename to presentation/features/src/jvmMain/kotlin/dev/dimension/flare/di/PlatformModule.jvm.kt index 736d78b718..508195c496 100644 --- a/shared/src/jvmMain/kotlin/dev/dimension/flare/di/PlatformModule.jvm.kt +++ b/presentation/features/src/jvmMain/kotlin/dev/dimension/flare/di/PlatformModule.jvm.kt @@ -1,17 +1,19 @@ package dev.dimension.flare.di -import dev.dimension.flare.common.JvmOnDeviceAI -import dev.dimension.flare.common.OnDeviceAI import dev.dimension.flare.data.database.DriverFactory +import dev.dimension.flare.data.datasource.nostr.DatabaseNostrCache +import dev.dimension.flare.data.datasource.nostr.NostrCache import dev.dimension.flare.data.datastore.AppDataStore +import dev.dimension.flare.data.io.FileStorage import dev.dimension.flare.data.io.JvmPlatformPathProducer -import dev.dimension.flare.data.io.PlatformPathProducer +import dev.dimension.flare.data.io.OkioFileStorage import dev.dimension.flare.data.network.nostr.AmberSignerBridge import dev.dimension.flare.data.network.nostr.JvmAmberSignerBridge -import dev.dimension.flare.shared.image.ImageCompressor -import dev.dimension.flare.shared.image.JvmImageCompressor +import dev.dimension.flare.media.ImageCompressor +import dev.dimension.flare.media.JvmImageCompressor import dev.dimension.flare.ui.humanizer.JVMFormatter import dev.dimension.flare.ui.humanizer.PlatformFormatter +import okio.FileSystem import org.koin.core.module.Module import org.koin.core.module.dsl.singleOf import org.koin.dsl.bind @@ -19,11 +21,11 @@ import org.koin.dsl.module internal actual val platformModule: Module = module { - singleOf(::AppDataStore) + single { AppDataStore(get()) } singleOf(::DriverFactory) - singleOf(::JvmPlatformPathProducer) bind PlatformPathProducer::class + single { OkioFileStorage(FileSystem.SYSTEM, JvmPlatformPathProducer()) } singleOf(::JVMFormatter) bind PlatformFormatter::class singleOf(::JvmImageCompressor) bind ImageCompressor::class - singleOf(::JvmOnDeviceAI) bind OnDeviceAI::class singleOf(::JvmAmberSignerBridge) bind AmberSignerBridge::class + single { DatabaseNostrCache(get()) } } diff --git a/shared/src/jvmMain/kotlin/dev/dimension/flare/shared/image/JvmImageCompressor.kt b/presentation/features/src/jvmMain/kotlin/dev/dimension/flare/media/JvmImageCompressor.kt similarity index 96% rename from shared/src/jvmMain/kotlin/dev/dimension/flare/shared/image/JvmImageCompressor.kt rename to presentation/features/src/jvmMain/kotlin/dev/dimension/flare/media/JvmImageCompressor.kt index cc9321e546..1239fdb557 100644 --- a/shared/src/jvmMain/kotlin/dev/dimension/flare/shared/image/JvmImageCompressor.kt +++ b/presentation/features/src/jvmMain/kotlin/dev/dimension/flare/media/JvmImageCompressor.kt @@ -1,6 +1,6 @@ -package dev.dimension.flare.shared.image +package dev.dimension.flare.media -import kotlinx.coroutines.Dispatchers +import dev.dimension.flare.common.PlatformDispatchers import kotlinx.coroutines.withContext import java.awt.Graphics2D import java.awt.Image @@ -18,7 +18,7 @@ public class JvmImageCompressor : ImageCompressor { maxSize: Long, maxDimensions: Pair, ): ByteArray = - withContext(Dispatchers.IO) { + withContext(PlatformDispatchers.IO) { val inputStream = ByteArrayInputStream(imageBytes) var image = ImageIO.read(inputStream) ?: throw IllegalArgumentException("Failed to decode image") diff --git a/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/RobolectricTest.jvm.kt b/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/RobolectricTest.jvm.kt new file mode 100644 index 0000000000..5328330f7b --- /dev/null +++ b/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/RobolectricTest.jvm.kt @@ -0,0 +1,3 @@ +package dev.dimension.flare + +actual open class RobolectricTest actual constructor() diff --git a/shared/src/jvmTest/kotlin/dev/dimension/flare/TestFileHelper.jvm.kt b/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/TestFileHelper.jvm.kt similarity index 88% rename from shared/src/jvmTest/kotlin/dev/dimension/flare/TestFileHelper.jvm.kt rename to presentation/features/src/jvmTest/kotlin/dev/dimension/flare/TestFileHelper.jvm.kt index d45bafca0e..6adcb48ea7 100644 --- a/shared/src/jvmTest/kotlin/dev/dimension/flare/TestFileHelper.jvm.kt +++ b/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/TestFileHelper.jvm.kt @@ -1,7 +1,7 @@ package dev.dimension.flare -import dev.dimension.flare.common.FileItem -import dev.dimension.flare.common.FileType +import dev.dimension.flare.data.io.FileItem +import dev.dimension.flare.data.io.FileType import okio.Path import okio.Path.Companion.toPath import java.io.File diff --git a/shared/src/jvmTest/kotlin/dev/dimension/flare/common/SerializationFormatBenchmarkTest.kt b/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/common/SerializationFormatBenchmarkTest.kt similarity index 99% rename from shared/src/jvmTest/kotlin/dev/dimension/flare/common/SerializationFormatBenchmarkTest.kt rename to presentation/features/src/jvmTest/kotlin/dev/dimension/flare/common/SerializationFormatBenchmarkTest.kt index 005c214910..16b321110b 100644 --- a/shared/src/jvmTest/kotlin/dev/dimension/flare/common/SerializationFormatBenchmarkTest.kt +++ b/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/common/SerializationFormatBenchmarkTest.kt @@ -1,8 +1,8 @@ package dev.dimension.flare.common import com.fleeksoft.ksoup.nodes.Element +import dev.dimension.flare.data.database.cache.mapper.TimelinePagingMapper import dev.dimension.flare.data.datasource.microblog.ActionMenu -import dev.dimension.flare.data.datasource.microblog.paging.TimelinePagingMapper import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType @@ -424,7 +424,7 @@ class SerializationFormatBenchmarkTest { multiple = true, ownVotes = persistentListOf(1), voteEvent = - dev.dimension.flare.data.datasource.microblog.PostEvent.Mastodon.Vote( + dev.dimension.flare.ui.model.PostEvent.Mastodon.Vote( id = "vote-${statusKey.id}", accountKey = accountKey, postKey = statusKey, diff --git a/shared/src/jvmTest/kotlin/dev/dimension/flare/shared/image/JvmImageCompressorTest.kt b/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/media/JvmImageCompressorTest.kt similarity index 97% rename from shared/src/jvmTest/kotlin/dev/dimension/flare/shared/image/JvmImageCompressorTest.kt rename to presentation/features/src/jvmTest/kotlin/dev/dimension/flare/media/JvmImageCompressorTest.kt index dcadf329e0..5ae83d6b89 100644 --- a/shared/src/jvmTest/kotlin/dev/dimension/flare/shared/image/JvmImageCompressorTest.kt +++ b/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/media/JvmImageCompressorTest.kt @@ -1,4 +1,4 @@ -package dev.dimension.flare.shared.image +package dev.dimension.flare.media import kotlinx.coroutines.test.runTest import java.awt.image.BufferedImage diff --git a/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/model/DefaultSocialPlatformRegistryJvmTest.kt b/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/model/DefaultSocialPlatformRegistryJvmTest.kt new file mode 100644 index 0000000000..5705ac84c4 --- /dev/null +++ b/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/model/DefaultSocialPlatformRegistryJvmTest.kt @@ -0,0 +1,19 @@ +package dev.dimension.flare.model + +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals + +class DefaultSocialPlatformRegistryJvmTest { + @Test + fun nonWebRegistryRegistersNostrPlugin() { + assertContains(defaultSocialPlatformRegistry.loginPlatformTypes, PlatformType.Nostr) + } + + @Test + fun nonWebRegistryIncludesNostrTimelineSpecs() { + val specIds = defaultSocialPlatformRegistry.requireSpec(PlatformType.Nostr).timelineSpecs.map { it.id } + + assertEquals(listOf("common.home"), specIds) + } +} diff --git a/shared/src/jvmTest/kotlin/dev/dimension/flare/ui/model/DeeplinkEventTest.kt b/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/ui/model/DeeplinkEventTest.kt similarity index 100% rename from shared/src/jvmTest/kotlin/dev/dimension/flare/ui/model/DeeplinkEventTest.kt rename to presentation/features/src/jvmTest/kotlin/dev/dimension/flare/ui/model/DeeplinkEventTest.kt diff --git a/shared/src/jvmTest/kotlin/dev/dimension/flare/ui/model/UiPollTest.kt b/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/ui/model/UiPollTest.kt similarity index 96% rename from shared/src/jvmTest/kotlin/dev/dimension/flare/ui/model/UiPollTest.kt rename to presentation/features/src/jvmTest/kotlin/dev/dimension/flare/ui/model/UiPollTest.kt index ae2e81b974..dc51dacaba 100644 --- a/shared/src/jvmTest/kotlin/dev/dimension/flare/ui/model/UiPollTest.kt +++ b/presentation/features/src/jvmTest/kotlin/dev/dimension/flare/ui/model/UiPollTest.kt @@ -1,7 +1,7 @@ package dev.dimension.flare.ui.model -import dev.dimension.flare.data.datasource.microblog.PostEvent import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.PostEvent import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlin.test.Test diff --git a/presentation/features/src/nonWebMain/kotlin/dev/dimension/flare/model/PlatformSpec.nonWeb.kt b/presentation/features/src/nonWebMain/kotlin/dev/dimension/flare/model/PlatformSpec.nonWeb.kt new file mode 100644 index 0000000000..9eb5da1092 --- /dev/null +++ b/presentation/features/src/nonWebMain/kotlin/dev/dimension/flare/model/PlatformSpec.nonWeb.kt @@ -0,0 +1,8 @@ +package dev.dimension.flare.model + +import dev.dimension.flare.data.platform.NostrSocialPlatformPlugin + +internal actual val defaultSocialPlatformRegistry: SocialPlatformRegistry = + SocialPlatformRegistry( + listOf(NostrSocialPlatformPlugin) + defaultSocialPlatformPlugins, + ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/NostrLoginPresenter.kt b/presentation/features/src/nonWebMain/kotlin/dev/dimension/flare/ui/presenter/login/NostrLoginPresenter.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/NostrLoginPresenter.kt rename to presentation/features/src/nonWebMain/kotlin/dev/dimension/flare/ui/presenter/login/NostrLoginPresenter.kt index 3bbb721365..a26967eab2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/login/NostrLoginPresenter.kt +++ b/presentation/features/src/nonWebMain/kotlin/dev/dimension/flare/ui/presenter/login/NostrLoginPresenter.kt @@ -2,16 +2,15 @@ package dev.dimension.flare.ui.presenter.login import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import dev.dimension.flare.data.account.AccountRepository import dev.dimension.flare.data.network.nostr.AmberSignerBridge import dev.dimension.flare.data.network.nostr.NostrService import dev.dimension.flare.data.network.nostr.defaultNostrRelays -import dev.dimension.flare.data.repository.AccountRepository import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiAccount import dev.dimension.flare.ui.presenter.PresenterBase @@ -173,19 +172,5 @@ public class NostrLoginPresenter( } } -@Immutable -public interface NostrLoginState { - public val loading: Boolean - public val error: Throwable? - public val amberAvailable: Boolean - public val qrConnectUri: String? - public val qrWaitingForApproval: Boolean - - public fun login(input: String) - - public fun connectAmber() - - public fun startQrLogin() - - public fun cancelQrLogin() -} +@Composable +internal actual fun nostrLoginState(toHome: () -> Unit): NostrLoginState = remember(toHome) { NostrLoginPresenter(toHome) }.body() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/NostrRelaysPresenter.kt b/presentation/features/src/nonWebMain/kotlin/dev/dimension/flare/ui/presenter/settings/NostrRelaysPresenter.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/NostrRelaysPresenter.kt rename to presentation/features/src/nonWebMain/kotlin/dev/dimension/flare/ui/presenter/settings/NostrRelaysPresenter.kt index f8e65ffaf0..dde97d9506 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/NostrRelaysPresenter.kt +++ b/presentation/features/src/nonWebMain/kotlin/dev/dimension/flare/ui/presenter/settings/NostrRelaysPresenter.kt @@ -4,7 +4,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.data.account.AccountRepository +import dev.dimension.flare.data.account.credentialFlow import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiAccount import dev.dimension.flare.ui.model.UiState diff --git a/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/RobolectricTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/RobolectricTest.kt new file mode 100644 index 0000000000..98a07fbab7 --- /dev/null +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/RobolectricTest.kt @@ -0,0 +1,3 @@ +package dev.dimension.flare + +expect open class RobolectricTest() diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/TestFileHelper.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/TestFileHelper.kt similarity index 75% rename from shared/src/commonTest/kotlin/dev/dimension/flare/TestFileHelper.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/TestFileHelper.kt index 44f46e1b72..4b044d0ea2 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/TestFileHelper.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/TestFileHelper.kt @@ -1,7 +1,7 @@ package dev.dimension.flare -import dev.dimension.flare.common.FileItem -import dev.dimension.flare.common.FileType +import dev.dimension.flare.data.io.FileItem +import dev.dimension.flare.data.io.FileType import okio.Path internal expect fun createTestRootPath(): Path diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/database/cache/dao/MessageDaoTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/database/cache/dao/MessageDaoTest.kt similarity index 97% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/database/cache/dao/MessageDaoTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/database/cache/dao/MessageDaoTest.kt index de94264b33..8a38270298 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/database/cache/dao/MessageDaoTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/database/cache/dao/MessageDaoTest.kt @@ -1,13 +1,12 @@ package dev.dimension.flare.data.database.cache.dao import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import dev.dimension.flare.RobolectricTest import dev.dimension.flare.common.TestFormatter import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.model.DbDirectMessageTimeline import dev.dimension.flare.data.database.cache.model.DbMessageItem -import dev.dimension.flare.memoryDatabaseBuilder +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType @@ -43,7 +42,6 @@ class MessageDaoTest : RobolectricTest() { db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() startKoin { diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDaoTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDaoTest.kt similarity index 97% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDaoTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDaoTest.kt index 4e112496cf..1885b3db20 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDaoTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDaoTest.kt @@ -1,16 +1,15 @@ package dev.dimension.flare.data.database.cache.dao import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import dev.dimension.flare.RobolectricTest import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.model.DbTranslation -import dev.dimension.flare.data.database.cache.model.TranslationDisplayMode -import dev.dimension.flare.data.database.cache.model.TranslationEntityType -import dev.dimension.flare.data.database.cache.model.TranslationPayload -import dev.dimension.flare.data.database.cache.model.TranslationStatus +import dev.dimension.flare.data.translation.TranslationDisplayMode +import dev.dimension.flare.data.translation.TranslationEntityType +import dev.dimension.flare.data.translation.TranslationPayload +import dev.dimension.flare.data.translation.TranslationStatus import dev.dimension.flare.data.database.cache.model.profileTranslationEntityKey -import dev.dimension.flare.memoryDatabaseBuilder +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.render.toUiPlainText import kotlinx.coroutines.Dispatchers @@ -31,7 +30,6 @@ class TranslationDaoTest : RobolectricTest() { db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() } diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/database/cache/mapper/MicroblogTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/database/cache/mapper/MicroblogTest.kt similarity index 98% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/database/cache/mapper/MicroblogTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/database/cache/mapper/MicroblogTest.kt index 59547e6c77..463843f807 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/database/cache/mapper/MicroblogTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/database/cache/mapper/MicroblogTest.kt @@ -4,32 +4,31 @@ import androidx.paging.PagingConfig import androidx.paging.PagingSource import androidx.paging.testing.TestPager import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import dev.dimension.flare.RobolectricTest import dev.dimension.flare.common.Locale import dev.dimension.flare.common.TestFormatter import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.database.cache.mapper.TimelinePagingMapper import dev.dimension.flare.data.database.cache.model.DbStatus import dev.dimension.flare.data.database.cache.model.DbStatusReference import dev.dimension.flare.data.database.cache.model.DbStatusReferenceWithStatus import dev.dimension.flare.data.database.cache.model.DbStatusWithReference import dev.dimension.flare.data.database.cache.model.DbTranslation -import dev.dimension.flare.data.database.cache.model.TranslationDisplayMode -import dev.dimension.flare.data.database.cache.model.TranslationDisplayOptions -import dev.dimension.flare.data.database.cache.model.TranslationEntityType -import dev.dimension.flare.data.database.cache.model.TranslationPayload -import dev.dimension.flare.data.database.cache.model.TranslationStatus -import dev.dimension.flare.data.database.cache.model.sourceHash +import dev.dimension.flare.data.translation.TranslationDisplayMode +import dev.dimension.flare.data.translation.TranslationEntityType +import dev.dimension.flare.data.translation.TranslationPayload +import dev.dimension.flare.data.translation.TranslationStatus import dev.dimension.flare.data.database.cache.model.translationEntityKey -import dev.dimension.flare.data.database.cache.model.translationPayload +import dev.dimension.flare.data.translation.sourceHash +import dev.dimension.flare.data.database.cache.mapper.translationPayload +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.data.datasource.microblog.ActionMenu -import dev.dimension.flare.data.datasource.microblog.paging.TimelinePagingMapper import dev.dimension.flare.data.datastore.model.AppSettings +import dev.dimension.flare.data.datastore.model.cacheKey +import dev.dimension.flare.data.translation.TranslationDisplayOptions import dev.dimension.flare.data.network.nostr.NostrService import dev.dimension.flare.data.network.nostr.bech32PublicKey import dev.dimension.flare.data.translation.PreTranslationStoreSupport -import dev.dimension.flare.data.translation.cacheKey -import dev.dimension.flare.memoryDatabaseBuilder import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.ReferenceType @@ -76,7 +75,6 @@ class MicroblogTest : RobolectricTest() { db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() @@ -131,8 +129,8 @@ class MicroblogTest : RobolectricTest() { db.statusDao().get(post.statusKey, AccountType.Specific(accountKey)).first() assertNotNull(savedStatus) assertEquals(post.statusKey, savedStatus.content.statusKey) - requireNotNull(savedStatus.text) - kotlin.test.assertTrue(savedStatus.text.contains("status text")) + val savedStatusText = requireNotNull(savedStatus.text) + kotlin.test.assertTrue(savedStatusText.contains("status text")) } @Test @@ -223,8 +221,8 @@ class MicroblogTest : RobolectricTest() { val savedStatus = db.statusDao().get(statusKey, AccountType.Specific(accountKey)).first() assertNotNull(savedStatus) - requireNotNull(savedStatus.text) - assertTrue(savedStatus.text.contains("new status text")) + val savedStatusText = requireNotNull(savedStatus.text) + assertTrue(savedStatusText.contains("new status text")) } @Test diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/MixedRemoteMediatorTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/MixedRemoteMediatorTest.kt similarity index 96% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/MixedRemoteMediatorTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/MixedRemoteMediatorTest.kt index d44e78e743..47d2b5297e 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/MixedRemoteMediatorTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/MixedRemoteMediatorTest.kt @@ -6,37 +6,35 @@ import androidx.paging.PagingConfig import androidx.paging.PagingSource import androidx.paging.PagingState import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import dev.dimension.flare.RobolectricTest import dev.dimension.flare.common.Locale -import dev.dimension.flare.common.OnDeviceAI import dev.dimension.flare.common.TestFormatter import dev.dimension.flare.common.decodeJson import dev.dimension.flare.common.encodeJson import dev.dimension.flare.createTestRootPath +import dev.dimension.flare.data.ai.AiCompletionService +import dev.dimension.flare.data.ai.OnDeviceAI +import dev.dimension.flare.data.ai.OpenAIService import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.database.cache.mapper.TimelinePagingMapper import dev.dimension.flare.data.database.cache.model.DbStatusWithReference -import dev.dimension.flare.data.database.cache.model.TranslationDisplayOptions -import dev.dimension.flare.data.database.cache.model.TranslationEntityType -import dev.dimension.flare.data.database.cache.model.TranslationStatus +import dev.dimension.flare.data.translation.TranslationEntityType +import dev.dimension.flare.data.translation.TranslationStatus +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.data.datasource.microblog.paging.CacheableRemoteLoader import dev.dimension.flare.data.datasource.microblog.paging.PagingRequest import dev.dimension.flare.data.datasource.microblog.paging.PagingResult -import dev.dimension.flare.data.datasource.microblog.paging.TimelinePagingMapper import dev.dimension.flare.data.datasource.microblog.paging.TimelineRemoteMediator import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.datastore.model.AppSettings -import dev.dimension.flare.data.io.PlatformPathProducer -import dev.dimension.flare.data.model.tab.TimelineMergePolicy -import dev.dimension.flare.data.network.ai.AiCompletionService -import dev.dimension.flare.data.network.ai.OpenAIService +import dev.dimension.flare.data.io.OkioFileStorage +import dev.dimension.flare.data.translation.TranslationDisplayOptions import dev.dimension.flare.data.translation.OnlinePreTranslationService import dev.dimension.flare.data.translation.PreTranslationContentRules import dev.dimension.flare.data.translation.PreTranslationService import dev.dimension.flare.data.translation.PreTranslationStoreSupport import dev.dimension.flare.data.translation.aiPreTranslateConfig import dev.dimension.flare.deleteTestRootPath -import dev.dimension.flare.memoryDatabaseBuilder import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType @@ -66,7 +64,8 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import kotlinx.coroutines.yield -import okio.Path +import okio.FileSystem +import okio.SYSTEM import org.koin.core.context.startKoin import org.koin.core.context.stopKoin import org.koin.dsl.module @@ -84,15 +83,6 @@ import kotlin.time.Instant @OptIn(ExperimentalCoroutinesApi::class) class MixedRemoteMediatorTest : RobolectricTest() { private val root = createTestRootPath() - private val pathProducer = - object : PlatformPathProducer { - override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) - - override fun draftMediaFile( - groupId: String, - fileName: String, - ): Path = root.resolve("draft_media").resolve(groupId).resolve(fileName) - } private lateinit var db: CacheDatabase @@ -108,7 +98,6 @@ class MixedRemoteMediatorTest : RobolectricTest() { db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() } @@ -379,7 +368,7 @@ class MixedRemoteMediatorTest : RobolectricTest() { } } - val mediator = MixedRemoteMediator(db, listOf(first, second), TimelineMergePolicy.Staggered) + val mediator = MixedRemoteMediator(db, listOf(first, second), MicroblogTimelineMergePolicy.Staggered) val result = mediator.load(pageSize = 20, request = PagingRequest.Refresh) assertEquals( @@ -421,7 +410,7 @@ class MixedRemoteMediatorTest : RobolectricTest() { } } - val mixed = MixedRemoteMediator(db, listOf(loader), TimelineMergePolicy.Time) + val mixed = MixedRemoteMediator(db, listOf(loader), MicroblogTimelineMergePolicy.Time) val timelineRemoteMediator = TimelineRemoteMediator(loader = mixed, database = db, allowLongText = false) val state = PagingState( @@ -657,9 +646,9 @@ class MixedRemoteMediatorTest : RobolectricTest() { @Test fun refreshSchedulesPreTranslationForRootAndReplyReference() = runTest { - val appDataStore = AppDataStore(pathProducer) - appDataStore.appSettingsStore.updateData { - it.copy( + val appDataStore = AppDataStore(OkioFileStorage(FileSystem.SYSTEM, root)) + appDataStore.updateAppSettings { + copy( language = Locale.language, translateConfig = aiPreTranslateConfig(), aiConfig = @@ -765,9 +754,9 @@ class MixedRemoteMediatorTest : RobolectricTest() { @Test fun homeTimelineSkipsPreTranslationForLongTextPosts() = runTest { - val appDataStore = AppDataStore(pathProducer) - appDataStore.appSettingsStore.updateData { - it.copy( + val appDataStore = AppDataStore(OkioFileStorage(FileSystem.SYSTEM, root)) + appDataStore.updateAppSettings { + copy( language = Locale.language, translateConfig = aiPreTranslateConfig(), aiConfig = @@ -847,9 +836,9 @@ class MixedRemoteMediatorTest : RobolectricTest() { @Test fun homeTimelineSkipsAiTranslationWhenSourceLanguageMatchesTargetLanguage() = runTest { - val appDataStore = AppDataStore(pathProducer) - appDataStore.appSettingsStore.updateData { - it.copy( + val appDataStore = AppDataStore(OkioFileStorage(FileSystem.SYSTEM, root)) + appDataStore.updateAppSettings { + copy( language = Locale.language, translateConfig = aiPreTranslateConfig(), aiConfig = @@ -937,9 +926,9 @@ class MixedRemoteMediatorTest : RobolectricTest() { fun homeTimelineRequeuesExcludedLanguageSkippedTranslationAfterExclusionRemoved() = runBlocking { val excludedLanguage = nonTargetLanguageTag() - val appDataStore = AppDataStore(pathProducer) - appDataStore.appSettingsStore.updateData { - it.copy( + val appDataStore = AppDataStore(OkioFileStorage(FileSystem.SYSTEM, root)) + appDataStore.updateAppSettings { + copy( language = Locale.language, translateConfig = aiPreTranslateConfig().copy( @@ -989,10 +978,10 @@ class MixedRemoteMediatorTest : RobolectricTest() { } assertEquals(PreTranslationStoreSupport.SKIPPED_EXCLUDED_LANGUAGE_REASON, skippedTranslation.statusReason) - appDataStore.appSettingsStore.updateData { - it.copy( + appDataStore.updateAppSettings { + copy( translateConfig = - it.translateConfig.copy( + translateConfig.copy( autoTranslateExcludedLanguages = emptyList(), ), ) @@ -1021,9 +1010,9 @@ class MixedRemoteMediatorTest : RobolectricTest() { @Test fun homeTimelineAcceptsAiSkippedTranslationResult() = runBlocking { - val appDataStore = AppDataStore(pathProducer) - appDataStore.appSettingsStore.updateData { - it.copy( + val appDataStore = AppDataStore(OkioFileStorage(FileSystem.SYSTEM, root)) + appDataStore.updateAppSettings { + copy( language = Locale.language, translateConfig = aiPreTranslateConfig(), aiConfig = @@ -1113,9 +1102,9 @@ class MixedRemoteMediatorTest : RobolectricTest() { @Test fun homeTimelineSkipsPreTranslationForNonTranslatableOnlyPosts() = runTest { - val appDataStore = AppDataStore(pathProducer) - appDataStore.appSettingsStore.updateData { - it.copy( + val appDataStore = AppDataStore(OkioFileStorage(FileSystem.SYSTEM, root)) + appDataStore.updateAppSettings { + copy( language = Locale.language, translateConfig = aiPreTranslateConfig(), aiConfig = @@ -1220,9 +1209,9 @@ class MixedRemoteMediatorTest : RobolectricTest() { ), ) - val appDataStore = AppDataStore(pathProducer) - appDataStore.appSettingsStore.updateData { - it.copy( + val appDataStore = AppDataStore(OkioFileStorage(FileSystem.SYSTEM, root)) + appDataStore.updateAppSettings { + copy( language = Locale.language, translateConfig = aiPreTranslateConfig(), aiConfig = @@ -1259,9 +1248,9 @@ class MixedRemoteMediatorTest : RobolectricTest() { @Test fun queuedPreTranslationWritesPendingBeforeExecutionStarts() { runBlocking { - val appDataStore = AppDataStore(pathProducer) - appDataStore.appSettingsStore.updateData { - it.copy( + val appDataStore = AppDataStore(OkioFileStorage(FileSystem.SYSTEM, root)) + appDataStore.updateAppSettings { + copy( language = Locale.language, translateConfig = aiPreTranslateConfig(), aiConfig = @@ -1335,9 +1324,9 @@ class MixedRemoteMediatorTest : RobolectricTest() { @Test fun providerSwitchCancelsOldQueueAndLetsNewProviderCompleteImmediately() { runBlocking { - val appDataStore = AppDataStore(pathProducer) - appDataStore.appSettingsStore.updateData { - it.copy( + val appDataStore = AppDataStore(OkioFileStorage(FileSystem.SYSTEM, root)) + appDataStore.updateAppSettings { + copy( language = Locale.language, translateConfig = aiPreTranslateConfig(), aiConfig = @@ -1414,10 +1403,10 @@ class MixedRemoteMediatorTest : RobolectricTest() { started.await() } - appDataStore.appSettingsStore.updateData { - it.copy( + appDataStore.updateAppSettings { + copy( translateConfig = - it.translateConfig.copy( + translateConfig.copy( provider = AppSettings.TranslateConfig.Provider.GoogleWeb, ), ) @@ -1626,7 +1615,7 @@ class MixedRemoteMediatorTest : RobolectricTest() { name = "test", icon = null, ), - displayMode = dev.dimension.flare.data.database.app.model.RssDisplayMode.FULL_CONTENT, + displayMode = dev.dimension.flare.model.RssDisplayMode.FULL_CONTENT, accountType = AccountType.Guest, ) diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/PostEventHandlerTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/PostEventHandlerTest.kt similarity index 83% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/PostEventHandlerTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/PostEventHandlerTest.kt index 88295a8712..bf732f2b22 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/PostEventHandlerTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/PostEventHandlerTest.kt @@ -1,20 +1,20 @@ package dev.dimension.flare.data.datasource.microblog import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import dev.dimension.flare.RobolectricTest import dev.dimension.flare.common.SerializableImmutableList import dev.dimension.flare.common.TestFormatter import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.model.DbPagingTimeline import dev.dimension.flare.data.database.cache.model.DbStatus +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.data.datasource.microblog.handler.PostEventHandler -import dev.dimension.flare.memoryDatabaseBuilder import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType import dev.dimension.flare.ui.humanizer.PlatformFormatter import dev.dimension.flare.ui.model.ClickEvent +import dev.dimension.flare.ui.model.PostEvent import dev.dimension.flare.ui.model.UiHandle import dev.dimension.flare.ui.model.UiNumber import dev.dimension.flare.ui.model.UiPoll @@ -55,7 +55,6 @@ class PostEventHandlerTest : RobolectricTest() { db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() @@ -81,16 +80,28 @@ class PostEventHandlerTest : RobolectricTest() { ) } - val original = createPost(actions = persistentListOf(createMenuItem(updateKey = "like", count = 1))) + val original = createPost(actions = persistentListOf(createMenuItem(updateKey = "mastodon_like_$postKey", count = 1))) insertPost(original) - handler = PostEventHandler(accountType = AccountType.Specific(accountKey), handler = fakeRemoteHandler) - handler.handleEvent(TestUpdateMenuEvent(postKey = postKey, updateKey = "like", nextCount = 2)) + handler = + PostEventHandler( + accountType = AccountType.Specific(accountKey), + handler = fakeRemoteHandler, + optimisticActionMenu = { it.nextActionMenu() }, + ) + handler.handleEvent( + PostEvent.Mastodon.Like( + postKey = postKey, + liked = false, + accountKey = accountKey, + count = 1, + ), + ) advanceUntilIdle() val updated = readPost() assertNotNull(updated) - val updatedLike = updated.actions.filterIsInstance().first { it.updateKey == "like" } + val updatedLike = updated.actions.filterIsInstance().first { it.updateKey == "mastodon_like_$postKey" } assertEquals(2, updatedLike.count?.value) assertEquals(1, fakeRemoteHandler.callCount) } @@ -108,17 +119,29 @@ class PostEventHandlerTest : RobolectricTest() { ) } - val original = createPost(actions = persistentListOf(createMenuItem(updateKey = "like", count = 1))) + val original = createPost(actions = persistentListOf(createMenuItem(updateKey = "mastodon_like_$postKey", count = 1))) insertPost(original) fakeRemoteHandler.shouldFail = true - handler = PostEventHandler(accountType = AccountType.Specific(accountKey), handler = fakeRemoteHandler) - handler.handleEvent(TestUpdateMenuEvent(postKey = postKey, updateKey = "like", nextCount = 2)) + handler = + PostEventHandler( + accountType = AccountType.Specific(accountKey), + handler = fakeRemoteHandler, + optimisticActionMenu = { it.nextActionMenu() }, + ) + handler.handleEvent( + PostEvent.Mastodon.Like( + postKey = postKey, + liked = false, + accountKey = accountKey, + count = 1, + ), + ) advanceUntilIdle() val reverted = readPost() assertNotNull(reverted) - val like = reverted.actions.filterIsInstance().first { it.updateKey == "like" } + val like = reverted.actions.filterIsInstance().first { it.updateKey == "mastodon_like_$postKey" } assertEquals(1, like.count?.value) assertEquals(1, fakeRemoteHandler.callCount) } @@ -151,7 +174,12 @@ class PostEventHandlerTest : RobolectricTest() { ) insertPost(createPost(poll = poll)) - handler = PostEventHandler(accountType = AccountType.Specific(accountKey), handler = fakeRemoteHandler) + handler = + PostEventHandler( + accountType = AccountType.Specific(accountKey), + handler = fakeRemoteHandler, + optimisticActionMenu = { it.nextActionMenu() }, + ) handler.handleEvent( PostEvent.Mastodon.Vote( id = "poll-1", @@ -195,7 +223,12 @@ class PostEventHandlerTest : RobolectricTest() { ), ) - handler = PostEventHandler(accountType = AccountType.Specific(accountKey), handler = fakeRemoteHandler) + handler = + PostEventHandler( + accountType = AccountType.Specific(accountKey), + handler = fakeRemoteHandler, + optimisticActionMenu = { it.nextActionMenu() }, + ) handler.deleteFromCache(postKey) val saved = db.statusDao().get(postKey, AccountType.Specific(accountKey)).first() @@ -292,17 +325,4 @@ class PostEventHandlerTest : RobolectricTest() { } } } - - private data class TestUpdateMenuEvent( - override val postKey: MicroBlogKey, - val updateKey: String, - val nextCount: Long, - ) : UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.Item( - updateKey = updateKey, - text = ActionMenu.Item.Text.Raw("updated"), - count = UiNumber(nextCount), - ) - } } diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/EmojiHandlerTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/EmojiHandlerTest.kt similarity index 97% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/EmojiHandlerTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/EmojiHandlerTest.kt index 0df72cc082..27d06ae966 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/EmojiHandlerTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/EmojiHandlerTest.kt @@ -2,14 +2,13 @@ package dev.dimension.flare.data.datasource.microblog.handler import androidx.paging.LoadState import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import dev.dimension.flare.RobolectricTest import dev.dimension.flare.common.CacheState import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.model.DbEmoji import dev.dimension.flare.data.database.cache.model.EmojiContent +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.data.datasource.microblog.loader.EmojiLoader -import dev.dimension.flare.memoryDatabaseBuilder import dev.dimension.flare.ui.model.UiEmoji import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableMap @@ -43,7 +42,6 @@ class EmojiHandlerTest : RobolectricTest() { db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListHandlerTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListHandlerTest.kt similarity index 98% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListHandlerTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListHandlerTest.kt index 5fc0b6b498..97d663919b 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListHandlerTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListHandlerTest.kt @@ -2,15 +2,14 @@ package dev.dimension.flare.data.datasource.microblog.handler import androidx.paging.testing.asSnapshot import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import dev.dimension.flare.RobolectricTest import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.data.datasource.microblog.list.ListMetaData import dev.dimension.flare.data.datasource.microblog.list.ListMetaDataType import dev.dimension.flare.data.datasource.microblog.loader.ListLoader import dev.dimension.flare.data.datasource.microblog.paging.PagingRequest import dev.dimension.flare.data.datasource.microblog.paging.PagingResult -import dev.dimension.flare.memoryDatabaseBuilder import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiList @@ -46,7 +45,6 @@ class ListHandlerTest : RobolectricTest() { db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListMemberHandlerTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListMemberHandlerTest.kt similarity index 99% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListMemberHandlerTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListMemberHandlerTest.kt index a9e8cdbbd6..1aa8a7c319 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListMemberHandlerTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListMemberHandlerTest.kt @@ -1,17 +1,16 @@ package dev.dimension.flare.data.datasource.microblog.handler import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import dev.dimension.flare.RobolectricTest import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.connect import dev.dimension.flare.data.database.cache.mapper.toDbUser import dev.dimension.flare.data.database.cache.mapper.upsertUsers import dev.dimension.flare.data.database.cache.model.DbListMember +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.data.datasource.microblog.loader.ListMemberLoader import dev.dimension.flare.data.datasource.microblog.paging.PagingRequest import dev.dimension.flare.data.datasource.microblog.paging.PagingResult -import dev.dimension.flare.memoryDatabaseBuilder import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType import dev.dimension.flare.ui.humanizer.PlatformFormatter @@ -50,7 +49,6 @@ class ListMemberHandlerTest : RobolectricTest() { db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandlerTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandlerTest.kt similarity index 94% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandlerTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandlerTest.kt index e31c8ba1c9..be07dbfda6 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandlerTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandlerTest.kt @@ -1,33 +1,34 @@ package dev.dimension.flare.data.datasource.microblog.handler import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import dev.dimension.flare.RobolectricTest import dev.dimension.flare.common.CacheState import dev.dimension.flare.common.Locale -import dev.dimension.flare.common.OnDeviceAI import dev.dimension.flare.common.TestFormatter import dev.dimension.flare.common.decodeJson import dev.dimension.flare.createTestRootPath +import dev.dimension.flare.data.ai.AiCompletionService +import dev.dimension.flare.data.ai.OnDeviceAI +import dev.dimension.flare.data.ai.OpenAIService import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.database.cache.mapper.TimelinePagingMapper import dev.dimension.flare.data.database.cache.mapper.saveToDatabase import dev.dimension.flare.data.database.cache.model.DbPagingTimeline import dev.dimension.flare.data.database.cache.model.DbStatus import dev.dimension.flare.data.database.cache.model.DbStatusReference -import dev.dimension.flare.data.database.cache.model.TranslationEntityType -import dev.dimension.flare.data.database.cache.model.TranslationStatus +import dev.dimension.flare.data.translation.TranslationEntityType +import dev.dimension.flare.data.translation.TranslationStatus +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.data.datasource.microblog.loader.PostLoader -import dev.dimension.flare.data.datasource.microblog.paging.TimelinePagingMapper import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.datastore.model.AppSettings -import dev.dimension.flare.data.io.PlatformPathProducer -import dev.dimension.flare.data.network.ai.AiCompletionService -import dev.dimension.flare.data.network.ai.OpenAIService +import dev.dimension.flare.data.io.OkioFileStorage +import dev.dimension.flare.data.settings.TranslationSettingsProviderImpl import dev.dimension.flare.data.translation.OnlinePreTranslationService import dev.dimension.flare.data.translation.PreTranslationService +import dev.dimension.flare.data.translation.TranslationSettingsProvider import dev.dimension.flare.data.translation.aiPreTranslateConfig import dev.dimension.flare.deleteTestRootPath -import dev.dimension.flare.memoryDatabaseBuilder import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType @@ -53,7 +54,8 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest -import okio.Path +import okio.FileSystem +import okio.SYSTEM import org.koin.core.context.startKoin import org.koin.core.context.stopKoin import org.koin.dsl.module @@ -69,15 +71,6 @@ import kotlin.uuid.Uuid @OptIn(ExperimentalCoroutinesApi::class) class PostHandlerTest : RobolectricTest() { private val root = createTestRootPath() - private val pathProducer = - object : PlatformPathProducer { - override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) - - override fun draftMediaFile( - groupId: String, - fileName: String, - ): Path = root.resolve("draft_media").resolve(groupId).resolve(fileName) - } private lateinit var db: CacheDatabase private lateinit var appDataStore: AppDataStore @@ -94,11 +87,10 @@ class PostHandlerTest : RobolectricTest() { db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() - appDataStore = AppDataStore(pathProducer) + appDataStore = AppDataStore(OkioFileStorage(FileSystem.SYSTEM, root)) fakeLoader = FakePostLoader() onDeviceAI = FakePostOnDeviceAI() } @@ -120,6 +112,7 @@ class PostHandlerTest : RobolectricTest() { module { single { db } single { appDataStore } + single { TranslationSettingsProviderImpl(get()) } single { detachedScope } single { onDeviceAI } single { OpenAIService() } @@ -399,8 +392,8 @@ class PostHandlerTest : RobolectricTest() { fun postRefreshPreTranslatesLongTextWhenOpenedInDetail() = runTest { startTestKoin(this@runTest) - appDataStore.appSettingsStore.updateData { - it.copy( + appDataStore.updateAppSettings { + copy( language = "zh-CN", translateConfig = aiPreTranslateConfig(), aiConfig = diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/RelationHandlerTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/RelationHandlerTest.kt similarity index 98% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/RelationHandlerTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/RelationHandlerTest.kt index e3b2e30d51..e2153bb528 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/RelationHandlerTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/RelationHandlerTest.kt @@ -2,14 +2,13 @@ package dev.dimension.flare.data.datasource.microblog.handler import androidx.paging.LoadState import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import dev.dimension.flare.RobolectricTest import dev.dimension.flare.common.CacheState import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.model.DbUserRelation +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.data.datasource.microblog.loader.RelationActionType import dev.dimension.flare.data.datasource.microblog.loader.RelationLoader -import dev.dimension.flare.memoryDatabaseBuilder import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiRelation @@ -46,7 +45,6 @@ class RelationHandlerTest : RobolectricTest() { db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandlerTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandlerTest.kt similarity index 92% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandlerTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandlerTest.kt index e452cc666d..bf73515aba 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandlerTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandlerTest.kt @@ -2,39 +2,40 @@ package dev.dimension.flare.data.datasource.microblog.handler import androidx.paging.LoadState import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import dev.dimension.flare.RobolectricTest import dev.dimension.flare.common.CacheState import dev.dimension.flare.common.Locale -import dev.dimension.flare.common.OnDeviceAI import dev.dimension.flare.common.TestFormatter import dev.dimension.flare.common.decodeJson import dev.dimension.flare.createTestRootPath +import dev.dimension.flare.data.ai.AiCompletionService +import dev.dimension.flare.data.ai.OnDeviceAI +import dev.dimension.flare.data.ai.OpenAIService import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.model.DbTranslation import dev.dimension.flare.data.database.cache.model.DbUser -import dev.dimension.flare.data.database.cache.model.TranslationEntityType -import dev.dimension.flare.data.database.cache.model.TranslationPayload -import dev.dimension.flare.data.database.cache.model.TranslationStatus -import dev.dimension.flare.data.database.cache.model.sourceHash +import dev.dimension.flare.data.translation.TranslationEntityType +import dev.dimension.flare.data.translation.TranslationPayload +import dev.dimension.flare.data.translation.TranslationStatus import dev.dimension.flare.data.database.cache.model.translationEntityKey -import dev.dimension.flare.data.database.cache.model.translationPayload +import dev.dimension.flare.data.translation.sourceHash +import dev.dimension.flare.data.database.cache.mapper.translationPayload +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.data.datasource.microblog.loader.UserLoader import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.datastore.model.AppSettings -import dev.dimension.flare.data.io.PlatformPathProducer -import dev.dimension.flare.data.network.ai.AiCompletionService -import dev.dimension.flare.data.network.ai.OpenAIService +import dev.dimension.flare.data.datastore.model.cacheKey +import dev.dimension.flare.data.io.OkioFileStorage +import dev.dimension.flare.data.settings.TranslationSettingsProviderImpl import dev.dimension.flare.data.translation.OnlinePreTranslationService import dev.dimension.flare.data.translation.PreTranslationBatchDocument import dev.dimension.flare.data.translation.PreTranslationBatchPayload import dev.dimension.flare.data.translation.PreTranslationContentRules import dev.dimension.flare.data.translation.PreTranslationService import dev.dimension.flare.data.translation.PreTranslationStoreSupport +import dev.dimension.flare.data.translation.TranslationSettingsProvider import dev.dimension.flare.data.translation.aiPreTranslateConfig -import dev.dimension.flare.data.translation.cacheKey import dev.dimension.flare.deleteTestRootPath -import dev.dimension.flare.memoryDatabaseBuilder import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType import dev.dimension.flare.ui.humanizer.PlatformFormatter @@ -54,7 +55,8 @@ import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest -import okio.Path +import okio.FileSystem +import okio.SYSTEM import org.koin.core.context.startKoin import org.koin.core.context.stopKoin import org.koin.dsl.module @@ -69,15 +71,6 @@ import kotlin.test.assertTrue @OptIn(ExperimentalCoroutinesApi::class) class UserHandlerTest : RobolectricTest() { private val root = createTestRootPath() - private val pathProducer = - object : PlatformPathProducer { - override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) - - override fun draftMediaFile( - groupId: String, - fileName: String, - ): Path = root.resolve("draft_media").resolve(groupId).resolve(fileName) - } private lateinit var db: CacheDatabase private lateinit var appDataStore: AppDataStore @@ -95,10 +88,9 @@ class UserHandlerTest : RobolectricTest() { db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() - appDataStore = AppDataStore(pathProducer) + appDataStore = AppDataStore(OkioFileStorage(FileSystem.SYSTEM, root)) loader = FakeUserLoader() onDeviceAI = FakeOnDeviceAI() @@ -108,6 +100,7 @@ class UserHandlerTest : RobolectricTest() { module { single { db } single { appDataStore } + single { TranslationSettingsProviderImpl(get()) } single { CoroutineScope(Dispatchers.Unconfined) } single { onDeviceAI } single { OpenAIService() } @@ -252,8 +245,8 @@ class UserHandlerTest : RobolectricTest() { content = profile, ), ) - appDataStore.appSettingsStore.updateData { - it.copy( + appDataStore.updateAppSettings { + copy( language = "zh-CN", translateConfig = aiPreTranslateConfig(), aiConfig = @@ -291,8 +284,8 @@ class UserHandlerTest : RobolectricTest() { description = "Original profile bio".toUiPlainText(), ) loader.nextById = expected - appDataStore.appSettingsStore.updateData { - it.copy( + appDataStore.updateAppSettings { + copy( language = "zh-CN", translateConfig = aiPreTranslateConfig(), aiConfig = @@ -329,8 +322,8 @@ class UserHandlerTest : RobolectricTest() { sourceLanguages = persistentListOf(excludedLanguage), ) loader.nextById = expected - appDataStore.appSettingsStore.updateData { - it.copy( + appDataStore.updateAppSettings { + copy( language = "zh-CN", translateConfig = aiPreTranslateConfig().copy( @@ -370,8 +363,8 @@ class UserHandlerTest : RobolectricTest() { description = "Retry profile bio".toUiPlainText(), ) loader.nextById = expected - appDataStore.appSettingsStore.updateData { - it.copy( + appDataStore.updateAppSettings { + copy( language = "zh-CN", translateConfig = aiPreTranslateConfig(), aiConfig = @@ -427,8 +420,8 @@ class UserHandlerTest : RobolectricTest() { description = "Auto retry profile bio".toUiPlainText(), ) loader.nextById = expected - appDataStore.appSettingsStore.updateData { - it.copy( + appDataStore.updateAppSettings { + copy( language = "zh-CN", translateConfig = aiPreTranslateConfig(), aiConfig = diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/rss/RssTimelineRemoteMediatorTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/rss/RssTimelineRemoteMediatorTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/rss/RssTimelineRemoteMediatorTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/data/datasource/rss/RssTimelineRemoteMediatorTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/SettingsImportExportPresenterTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/SettingsImportExportPresenterTest.kt similarity index 52% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/SettingsImportExportPresenterTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/SettingsImportExportPresenterTest.kt index f481e9602e..36f23c0045 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/SettingsImportExportPresenterTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/SettingsImportExportPresenterTest.kt @@ -1,47 +1,45 @@ package dev.dimension.flare.ui.presenter import dev.dimension.flare.createTestRootPath +import dev.dimension.flare.data.datasource.microblog.timeline.CommonTimelineSpecs +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineCatalog +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineTabDescriptor +import dev.dimension.flare.data.datasource.rss.RssTimelineSpecs import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.datastore.model.AppSettings -import dev.dimension.flare.data.io.PlatformPathProducer -import dev.dimension.flare.data.model.AppearanceSettings -import dev.dimension.flare.data.model.HomeTimelineTabItem -import dev.dimension.flare.data.model.IconType -import dev.dimension.flare.data.model.LegacyAppearanceSettingsAndTabsExport -import dev.dimension.flare.data.model.LegacyAppearanceSettingsExport -import dev.dimension.flare.data.model.LegacySettingsExport -import dev.dimension.flare.data.model.Mastodon +import dev.dimension.flare.data.io.OkioFileStorage import dev.dimension.flare.data.model.SettingsExport -import dev.dimension.flare.data.model.TabMetaData -import dev.dimension.flare.data.model.TabSettings import dev.dimension.flare.data.model.Theme -import dev.dimension.flare.data.model.TitleType import dev.dimension.flare.data.model.appearance.AppearanceBag import dev.dimension.flare.data.model.tab.GroupSource import dev.dimension.flare.data.model.tab.GroupTimelineTabItemV2 -import dev.dimension.flare.data.model.tab.SYSTEM_HOME_MIXED_TIMELINE_ID import dev.dimension.flare.data.model.tab.TabSettingsV2 import dev.dimension.flare.data.model.tab.TimelineFilterConfig import dev.dimension.flare.data.model.tab.TimelineMergePolicy +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelinePostKind import dev.dimension.flare.data.model.tab.TimelinePresentation -import dev.dimension.flare.data.model.tab.TimelineResolver import dev.dimension.flare.data.model.tab.TimelineSlot import dev.dimension.flare.data.model.tab.TimelineSlotContent -import dev.dimension.flare.data.model.tab.toTimelineSlotOrNull -import dev.dimension.flare.data.repository.SettingsRepository +import dev.dimension.flare.data.platform.MastodonTimelineSpecs +import dev.dimension.flare.data.repository.homeTimelineTab import dev.dimension.flare.deleteTestRootPath -import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.model.defaultSocialPlatformRegistry import kotlinx.coroutines.async import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.jsonObject +import okio.FileSystem import okio.Path +import okio.SYSTEM import org.koin.core.context.startKoin import org.koin.core.context.stopKoin import org.koin.dsl.module @@ -54,31 +52,24 @@ import kotlin.test.assertTrue class SettingsImportExportPresenterTest { private val json = Json { ignoreUnknownKeys = true } + private val timelinePersistenceMapper = + TimelinePersistenceMapper( + catalog = + TimelineCatalog( + defaultSocialPlatformRegistry.specs.flatMap { it.timelineSpecs } + RssTimelineSpecs.timelineSpecs, + ), + ) private lateinit var root: Path - private lateinit var settingsRepository: SettingsRepository + private lateinit var appDataStore: AppDataStore @BeforeTest fun setup() { root = createTestRootPath() - val pathProducer = - object : PlatformPathProducer { - override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) - - override fun draftMediaFile( - groupId: String, - fileName: String, - ): Path = root.resolve(groupId).resolve(fileName) - } - settingsRepository = - SettingsRepository( - pathProducer = pathProducer, - appDataStore = AppDataStore(pathProducer), - timelineResolver = TimelineResolver(), - ) + appDataStore = AppDataStore(OkioFileStorage(FileSystem.SYSTEM, root)) startKoin { modules( module { - single { settingsRepository } + single { appDataStore } }, ) } @@ -94,7 +85,7 @@ class SettingsImportExportPresenterTest { fun exportUsesTabSettingsV2FieldOnly() = runTest { val slot = homeSlot() - settingsRepository.updateTabSettingsV2 { + appDataStore.updateTabSettingsV2 { TabSettingsV2(homeSlots = listOf(slot)) } @@ -119,7 +110,7 @@ class SettingsImportExportPresenterTest { runTest { val oldSlot = localSlot() val newSlot = homeSlot() - settingsRepository.updateTabSettingsV2 { + appDataStore.updateTabSettingsV2 { TabSettingsV2(homeSlots = listOf(oldSlot)) } val exported = @@ -133,7 +124,7 @@ class SettingsImportExportPresenterTest { ImportSettingsPresenter(exported).import() - val settings = settingsRepository.tabSettingsV2.first() + val settings = appDataStore.tabSettingsV2.first() assertEquals(listOf(newSlot.id), settings.homeSlots.map { it.id }) } @@ -142,103 +133,36 @@ class SettingsImportExportPresenterTest { runTest { val newSlot = homeSlot() val exported = - json.encodeToString( - LegacyAppearanceSettingsExport( - appearanceSettings = - AppearanceSettings( - theme = Theme.DARK, - showNumbers = false, - ), - appSettings = AppSettings(version = "legacy-appearance"), - tabSettingsV2 = TabSettingsV2(homeSlots = listOf(newSlot)), - ), - ) + buildJsonObject { + put( + "appearanceSettings", + buildJsonObject { + put("theme", JsonPrimitive("DARK")) + put("showNumbers", JsonPrimitive(false)) + }, + ) + put( + "appSettings", + json.encodeToJsonElement(AppSettings.serializer(), AppSettings(version = "legacy-appearance")), + ) + put( + "tabSettingsV2", + json.encodeToJsonElement(TabSettingsV2.serializer(), TabSettingsV2(homeSlots = listOf(newSlot))), + ) + }.toString() ImportSettingsPresenter(exported).import() - assertEquals(Theme.DARK, settingsRepository.globalAppearance.first().theme) - assertEquals(false, settingsRepository.timelineAppearance.first().showNumbers) + assertEquals(Theme.DARK, appDataStore.globalAppearance.first().theme) + assertEquals(false, appDataStore.timelineAppearance.first().showNumbers) assertTrue( - settingsRepository.appearanceBag + appDataStore.appearanceBag .first() .entries .isNotEmpty(), ) } - @Test - fun importLegacyV1SettingsMigratesTabsToV2WithMixedTimelineDisabled() = - runTest { - val accountKey = MicroBlogKey(id = "alice", host = "example.com") - val exported = - json.encodeToString( - LegacyAppearanceSettingsAndTabsExport( - appearanceSettings = AppearanceSettings(), - appSettings = AppSettings(version = "v1"), - tabSettings = - TabSettings( - enableMixedTimeline = false, - mainTabs = - listOf( - HomeTimelineTabItem(AccountType.Specific(accountKey)), - Mastodon.LocalTimelineTabItem( - account = AccountType.Specific(accountKey), - metaData = localMetaData(), - ), - ), - ), - ), - ) - - ImportSettingsPresenter(exported).import() - - val settings = settingsRepository.tabSettingsV2.first() - assertEquals( - listOf( - "common.home:$accountKey", - "mastodon.local:$accountKey", - ), - settings.homeSlots.map { it.id }, - ) - } - - @Test - fun importLegacyV1SettingsMigratesTabsToV2WithMixedTimelineEnabled() = - runTest { - val accountKey = MicroBlogKey(id = "alice", host = "example.com") - val exported = - json.encodeToString( - LegacySettingsExport( - appearanceBag = AppearanceBag(), - appSettings = AppSettings(version = "v1"), - tabSettings = - TabSettings( - enableMixedTimeline = true, - mainTabs = - listOf( - HomeTimelineTabItem(AccountType.Specific(accountKey)), - Mastodon.LocalTimelineTabItem( - account = AccountType.Specific(accountKey), - metaData = localMetaData(), - ), - ), - ), - ), - ) - - ImportSettingsPresenter(exported).import() - - val settings = settingsRepository.tabSettingsV2.first() - assertEquals( - listOf( - SYSTEM_HOME_MIXED_TIMELINE_ID, - "common.home:$accountKey", - "mastodon.local:$accountKey", - ), - settings.homeSlots.map { it.id }, - ) - } - @Test fun homeTimelineTabResolvesNestedItemsByIdAndEmitsConfigUpdates() = runTest { @@ -271,12 +195,12 @@ class SettingsImportExportPresenterTest { ), ), ) - settingsRepository.updateTabSettingsV2 { + appDataStore.updateTabSettingsV2 { TabSettingsV2(homeSlots = listOf(initialGroup)) } - val groupItem = settingsRepository.homeTimelineTab(initialGroup.id).first() as? GroupTimelineTabItemV2 - val childItem = settingsRepository.homeTimelineTab(initialChild.id).first() + val groupItem = appDataStore.homeTimelineTab(initialGroup.id, timelinePersistenceMapper).first() as? GroupTimelineTabItemV2 + val childItem = appDataStore.homeTimelineTab(initialChild.id, timelinePersistenceMapper).first() assertEquals(TimelineMergePolicy.Staggered, groupItem?.mergePolicy) assertEquals(listOf(TimelinePostKind.Quote), groupItem?.filterConfig?.excludedKinds) assertEquals(listOf(TimelinePostKind.Reply), childItem?.filterConfig?.excludedKinds) @@ -308,10 +232,13 @@ class SettingsImportExportPresenterTest { ), ), ) - val nextGroup = async { settingsRepository.homeTimelineTab(initialGroup.id).drop(1).first() as? GroupTimelineTabItemV2 } - val nextChild = async { settingsRepository.homeTimelineTab(initialChild.id).drop(1).first() } + val nextGroup = + async { + appDataStore.homeTimelineTab(initialGroup.id, timelinePersistenceMapper).drop(1).first() as? GroupTimelineTabItemV2 + } + val nextChild = async { appDataStore.homeTimelineTab(initialChild.id, timelinePersistenceMapper).drop(1).first() } - settingsRepository.updateTabSettingsV2 { + appDataStore.updateTabSettingsV2 { TabSettingsV2(homeSlots = listOf(updatedGroup)) } @@ -323,21 +250,16 @@ class SettingsImportExportPresenterTest { } private fun homeSlot() = - HomeTimelineTabItem(AccountType.Specific(MicroBlogKey(id = "home", host = "example.com"))) - .toTimelineSlotOrNull() - ?: error("Home slot should be migratable") + timelinePersistenceMapper.toSlot( + CommonTimelineSpecs.home.toTimelineTabDescriptor( + TimelineSpec.AccountBasedData(MicroBlogKey(id = "home", host = "example.com")), + ), + ) private fun localSlot() = - Mastodon - .LocalTimelineTabItem( - account = AccountType.Specific(MicroBlogKey(id = "local", host = "example.com")), - metaData = localMetaData(), - ).toTimelineSlotOrNull() - ?: error("Local slot should be migratable") - - private fun localMetaData() = - TabMetaData( - title = TitleType.Localized(TitleType.Localized.LocalizedKey.MastodonLocal), - icon = IconType.Material(UiIcon.Local), + timelinePersistenceMapper.toSlot( + MastodonTimelineSpecs.local.toTimelineTabDescriptor( + TimelineSpec.AccountBasedData(MicroBlogKey(id = "local", host = "example.com")), + ), ) } diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenterBindingTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenterBindingTest.kt similarity index 63% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenterBindingTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenterBindingTest.kt index 3fa6dd2e5c..a1a45b85e3 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenterBindingTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenterBindingTest.kt @@ -1,19 +1,21 @@ package dev.dimension.flare.ui.presenter.home import dev.dimension.flare.createTestRootPath +import dev.dimension.flare.data.datasource.microblog.timeline.CommonTimelineSpecs +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineCatalog +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineTabDescriptor +import dev.dimension.flare.data.datasource.rss.RssTimelineSpecs import dev.dimension.flare.data.datastore.AppDataStore -import dev.dimension.flare.data.io.PlatformPathProducer -import dev.dimension.flare.data.model.HomeTimelineTabItem +import dev.dimension.flare.data.io.OkioFileStorage import dev.dimension.flare.data.model.tab.TabSettingsV2 import dev.dimension.flare.data.model.tab.TimelineFilterConfig +import dev.dimension.flare.data.model.tab.TimelinePersistenceMapper import dev.dimension.flare.data.model.tab.TimelinePostKind import dev.dimension.flare.data.model.tab.TimelinePresentation -import dev.dimension.flare.data.model.tab.TimelineResolver -import dev.dimension.flare.data.model.tab.toTimelineSlotOrNull -import dev.dimension.flare.data.repository.SettingsRepository import dev.dimension.flare.deleteTestRootPath -import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.model.defaultSocialPlatformRegistry import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map @@ -22,34 +24,29 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest +import okio.FileSystem import okio.Path +import okio.SYSTEM import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals class TimelinePresenterBindingTest { + private val timelinePersistenceMapper = + TimelinePersistenceMapper( + catalog = + TimelineCatalog( + defaultSocialPlatformRegistry.specs.flatMap { it.timelineSpecs } + RssTimelineSpecs.timelineSpecs, + ), + ) private lateinit var root: Path - private lateinit var settingsRepository: SettingsRepository + private lateinit var appDataStore: AppDataStore @BeforeTest fun setup() { root = createTestRootPath() - val pathProducer = - object : PlatformPathProducer { - override fun dataStoreFile(fileName: String): Path = root.resolve(fileName) - - override fun draftMediaFile( - groupId: String, - fileName: String, - ): Path = root.resolve(groupId).resolve(fileName) - } - settingsRepository = - SettingsRepository( - pathProducer = pathProducer, - appDataStore = AppDataStore(pathProducer), - timelineResolver = TimelineResolver(), - ) + appDataStore = AppDataStore(OkioFileStorage(FileSystem.SYSTEM, root)) } @AfterTest @@ -69,7 +66,7 @@ class TimelinePresenterBindingTest { excludedKinds = listOf(TimelinePostKind.Reply), ), ) - settingsRepository.updateTabSettingsV2 { + appDataStore.updateTabSettingsV2 { TabSettingsV2(homeSlots = listOf(firstSlot)) } @@ -78,7 +75,8 @@ class TimelinePresenterBindingTest { val job = backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { observeTimelineFilterConfig( - settingsRepository = settingsRepository, + appDataStore = appDataStore, + timelinePersistenceMapper = timelinePersistenceMapper, timelineTabItemIdFlow = timelineTabItemIdFlow, ).map { it.excludedKinds } .take(2) @@ -101,11 +99,13 @@ class TimelinePresenterBindingTest { private fun homeSlot( id: String, filterConfig: TimelineFilterConfig, - ) = requireNotNull( - HomeTimelineTabItem(AccountType.Specific(MicroBlogKey(id = "home", host = "example.com"))) - .toTimelineSlotOrNull(), - ).copy( - id = id, - presentation = TimelinePresentation(filterConfig = filterConfig), - ) + ) = timelinePersistenceMapper + .toSlot( + CommonTimelineSpecs.home.toTimelineTabDescriptor( + TimelineSpec.AccountBasedData(MicroBlogKey(id = "home", host = "example.com")), + ), + ).copy( + id = id, + presentation = TimelinePresentation(filterConfig = filterConfig), + ) } diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/rss/ExportOPMLPresenterTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/home/rss/ExportOPMLPresenterTest.kt similarity index 95% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/rss/ExportOPMLPresenterTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/home/rss/ExportOPMLPresenterTest.kt index 857990b34c..5a30aa70e1 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/rss/ExportOPMLPresenterTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/home/rss/ExportOPMLPresenterTest.kt @@ -1,13 +1,12 @@ package dev.dimension.flare.ui.presenter.home.rss import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import dev.dimension.flare.RobolectricTest import dev.dimension.flare.data.database.app.AppDatabase import dev.dimension.flare.data.database.app.model.DbRssSources -import dev.dimension.flare.memoryDatabaseBuilder +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.ui.model.UiState import dev.dimension.flare.ui.model.isSuccess import dev.dimension.flare.ui.model.takeSuccess @@ -35,7 +34,6 @@ class ExportOPMLPresenterTest : RobolectricTest() { val db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() this.db = db diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/rss/ImportOPMLPresenterTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/home/rss/ImportOPMLPresenterTest.kt similarity index 98% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/rss/ImportOPMLPresenterTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/home/rss/ImportOPMLPresenterTest.kt index f9e3bd8187..c3cf316df0 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/home/rss/ImportOPMLPresenterTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/home/rss/ImportOPMLPresenterTest.kt @@ -1,14 +1,13 @@ package dev.dimension.flare.ui.presenter.home.rss import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import dev.dimension.flare.RobolectricTest import dev.dimension.flare.common.TestFormatter import dev.dimension.flare.data.database.app.AppDatabase import dev.dimension.flare.data.database.app.model.DbRssSources -import dev.dimension.flare.memoryDatabaseBuilder +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.ui.humanizer.PlatformFormatter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -35,7 +34,6 @@ class ImportOPMLPresenterTest : RobolectricTest() { val db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() this.db = db diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/settings/ExportAppDatabasePresenterTest.kt b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/settings/ExportAppDatabasePresenterTest.kt similarity index 96% rename from shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/settings/ExportAppDatabasePresenterTest.kt rename to presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/settings/ExportAppDatabasePresenterTest.kt index 7ceffc5741..3b4828fe4e 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/ui/presenter/settings/ExportAppDatabasePresenterTest.kt +++ b/presentation/features/src/nonWebTest/kotlin/dev/dimension/flare/ui/presenter/settings/ExportAppDatabasePresenterTest.kt @@ -1,7 +1,6 @@ package dev.dimension.flare.ui.presenter.settings import androidx.room3.Room -import androidx.sqlite.driver.bundled.BundledSQLiteDriver import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import dev.dimension.flare.RobolectricTest @@ -12,7 +11,7 @@ import dev.dimension.flare.data.database.app.model.DbApplication import dev.dimension.flare.data.database.app.model.DbKeywordFilter import dev.dimension.flare.data.database.app.model.DbRssSources import dev.dimension.flare.data.database.app.model.DbSearchHistory -import dev.dimension.flare.memoryDatabaseBuilder +import dev.dimension.flare.data.database.memoryDatabaseBuilder import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType import dev.dimension.flare.ui.presenter.ExportState @@ -40,7 +39,6 @@ class ExportAppDatabasePresenterTest : RobolectricTest() { val db = Room .memoryDatabaseBuilder() - .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.Unconfined) .build() this.db = db diff --git a/presentation/features/src/wasmJsMain/kotlin/dev/dimension/flare/di/PlatformModule.wasmJs.kt b/presentation/features/src/wasmJsMain/kotlin/dev/dimension/flare/di/PlatformModule.wasmJs.kt new file mode 100644 index 0000000000..bf9a3e49fa --- /dev/null +++ b/presentation/features/src/wasmJsMain/kotlin/dev/dimension/flare/di/PlatformModule.wasmJs.kt @@ -0,0 +1,52 @@ +@file:Suppress("UNUSED_PARAMETER") + +package dev.dimension.flare.di + +import dev.dimension.flare.common.InAppNotification +import dev.dimension.flare.common.Message +import dev.dimension.flare.data.database.DriverFactory +import dev.dimension.flare.data.datastore.AppDataStore +import dev.dimension.flare.data.io.FileStorage +import dev.dimension.flare.data.io.InMemoryFileStorage +import dev.dimension.flare.media.ImageCompressor +import dev.dimension.flare.ui.humanizer.PlatformFormatter +import dev.dimension.flare.ui.humanizer.WebFormatter +import org.koin.core.module.Module +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +internal actual val platformModule: Module = + module { + single { AppDataStore(get()) } + singleOf(::DriverFactory) + single { InMemoryFileStorage() } + single { WebFormatter } + single { WebImageCompressor } + single { NoopInAppNotification } + } + +private data object WebImageCompressor : ImageCompressor { + override suspend fun compress( + imageBytes: ByteArray, + maxSize: Long, + maxDimensions: Pair, + ): ByteArray = imageBytes +} + +private data object NoopInAppNotification : InAppNotification { + override fun onProgress( + message: Message, + progress: Int, + total: Int, + ) { + } + + override fun onSuccess(message: Message) { + } + + override fun onError( + message: Message, + throwable: Throwable, + ) { + } +} diff --git a/presentation/features/src/wasmJsMain/kotlin/dev/dimension/flare/model/PlatformSpec.wasmJs.kt b/presentation/features/src/wasmJsMain/kotlin/dev/dimension/flare/model/PlatformSpec.wasmJs.kt new file mode 100644 index 0000000000..8f4f57a810 --- /dev/null +++ b/presentation/features/src/wasmJsMain/kotlin/dev/dimension/flare/model/PlatformSpec.wasmJs.kt @@ -0,0 +1,4 @@ +package dev.dimension.flare.model + +internal actual val defaultSocialPlatformRegistry: SocialPlatformRegistry = + SocialPlatformRegistry(defaultSocialPlatformPlugins) diff --git a/presentation/features/src/wasmJsMain/kotlin/dev/dimension/flare/ui/presenter/login/NostrLoginState.wasmJs.kt b/presentation/features/src/wasmJsMain/kotlin/dev/dimension/flare/ui/presenter/login/NostrLoginState.wasmJs.kt new file mode 100644 index 0000000000..3ce3d0bd3e --- /dev/null +++ b/presentation/features/src/wasmJsMain/kotlin/dev/dimension/flare/ui/presenter/login/NostrLoginState.wasmJs.kt @@ -0,0 +1,29 @@ +package dev.dimension.flare.ui.presenter.login + +import androidx.compose.runtime.Composable + +@Composable +@Suppress("UNUSED_PARAMETER") +internal actual fun nostrLoginState(toHome: () -> Unit): NostrLoginState = DisabledNostrLoginState + +private data object DisabledNostrLoginState : NostrLoginState { + override val loading: Boolean = false + override val error: Throwable? = null + override val amberAvailable: Boolean = false + override val qrConnectUri: String? = null + override val qrWaitingForApproval: Boolean = false + + override fun login( + @Suppress("UNUSED_PARAMETER") input: String, + ) { + } + + override fun connectAmber() { + } + + override fun startQrLogin() { + } + + override fun cancelQrLogin() { + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index dbd0b4321a..69d4706278 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,7 +23,44 @@ dependencyResolutionManagement { rootProject.name = "Flare" include(":app") -include(":shared") include(":compose-ui") include(":desktopApp") +include(":core:common") +include(":core:humanizer") +include(":core:model") +include(":foundation:deeplink") +include(":foundation:network") +include(":foundation:database") +include(":foundation:datastore") +include(":foundation:filesystem") +include(":modules:account:model") +include(":modules:account:api") +include(":modules:account:data") +include(":modules:draft:model") +include(":modules:draft:data") +include(":modules:draft:presentation") +include(":modules:settings:data") +include(":modules:translation:model") +include(":modules:translation:api") +include(":modules:translation:data") +include(":modules:ai:data") +include(":modules:local:data") +include(":modules:local:model") +include(":social:api") +include(":social:microblog") +include(":social:model") +include(":social:nodeinfo") +include(":social:mastodon") +include(":social:rss:model") +include(":social:rss") +include(":social:misskey") +include(":social:bluesky") +include(":social:nostr") +include(":social:xqt") +include(":social:vvo") +include(":ui:model") +include(":ui:richtext") +include(":ui:presenter-runtime") +include(":presentation:features") +include(":web:presenter-export") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") diff --git a/shared/src/androidHostTest/kotlin/dev/dimension/flare/DatabaseHelper.android.kt b/shared/src/androidHostTest/kotlin/dev/dimension/flare/DatabaseHelper.android.kt deleted file mode 100644 index 57c3b519fc..0000000000 --- a/shared/src/androidHostTest/kotlin/dev/dimension/flare/DatabaseHelper.android.kt +++ /dev/null @@ -1,19 +0,0 @@ -package dev.dimension.flare - -import androidx.room3.Room -import androidx.room3.RoomDatabase -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import kotlin.test.Ignore -import kotlin.reflect.KClass - -internal actual fun Room.memoryDatabaseBuilder(databaseClass: KClass): RoomDatabase.Builder = - Room.inMemoryDatabaseBuilder( - InstrumentationRegistry.getInstrumentation().context, - databaseClass.java, - ) - -@RunWith(RobolectricTestRunner::class) -@Ignore -actual open class RobolectricTest actual constructor() diff --git a/shared/src/androidJvmMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.androidJvm.kt b/shared/src/androidJvmMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.androidJvm.kt deleted file mode 100644 index 454a51461a..0000000000 --- a/shared/src/androidJvmMain/kotlin/dev/dimension/flare/ui/render/UiDateTime.androidJvm.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.dimension.flare.ui.render - -import kotlin.time.Instant - -internal actual typealias PlatformDateTime = Instant - -internal actual fun Instant.toPlatform(): PlatformDateTime = this diff --git a/shared/src/androidMain/kotlin/dev/dimension/flare/common/BuildConfig.android.kt b/shared/src/androidMain/kotlin/dev/dimension/flare/common/BuildConfig.android.kt deleted file mode 100644 index 98767fbbe9..0000000000 --- a/shared/src/androidMain/kotlin/dev/dimension/flare/common/BuildConfig.android.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.dimension.flare.common - - -internal actual object BuildConfig { - actual val debug: Boolean - get() = false -} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/dev/dimension/flare/data/repository/DraftMediaStore.android.kt b/shared/src/androidMain/kotlin/dev/dimension/flare/data/repository/DraftMediaStore.android.kt deleted file mode 100644 index 5df81ab255..0000000000 --- a/shared/src/androidMain/kotlin/dev/dimension/flare/data/repository/DraftMediaStore.android.kt +++ /dev/null @@ -1,15 +0,0 @@ -package dev.dimension.flare.data.repository - -import dev.dimension.flare.common.FileItem -import dev.dimension.flare.common.FileType - -internal actual fun draftFileItem( - path: String, - name: String?, - type: FileType, -): FileItem = - FileItem( - name = name, - type = type, - source = FileItem.Source.PathSource(path), - ) diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/common/FileItem.apple.kt b/shared/src/appleMain/kotlin/dev/dimension/flare/common/FileItem.apple.kt deleted file mode 100644 index dafae4f580..0000000000 --- a/shared/src/appleMain/kotlin/dev/dimension/flare/common/FileItem.apple.kt +++ /dev/null @@ -1,37 +0,0 @@ -package dev.dimension.flare.common - -import okio.FileSystem -import okio.Path.Companion.toPath - -public actual class FileItem internal constructor( - internal actual val name: String?, - private val loader: suspend () -> ByteArray, - internal actual val type: FileType, - internal actual val mimeType: String? = null, -) { - public constructor( - name: String?, - data: ByteArray, - type: FileType, - mimeType: String? = null, - ) : this( - name = name, - loader = { data }, - type = type, - mimeType = mimeType, - ) - - internal constructor( - name: String?, - path: String, - type: FileType, - mimeType: String? = null, - ) : this( - name = name, - loader = { FileSystem.SYSTEM.read(path.toPath()) { readByteArray() } }, - type = type, - mimeType = mimeType, - ) - - internal actual suspend fun readBytes(): ByteArray = loader() -} diff --git a/shared/src/appleMain/kotlin/dev/dimension/flare/data/repository/DraftMediaStore.apple.kt b/shared/src/appleMain/kotlin/dev/dimension/flare/data/repository/DraftMediaStore.apple.kt deleted file mode 100644 index 097fc81f66..0000000000 --- a/shared/src/appleMain/kotlin/dev/dimension/flare/data/repository/DraftMediaStore.apple.kt +++ /dev/null @@ -1,18 +0,0 @@ -package dev.dimension.flare.data.repository - -import dev.dimension.flare.common.FileItem -import dev.dimension.flare.common.FileType -import okio.Path.Companion.toPath - -internal actual fun draftFileItem( - path: String, - name: String?, - type: FileType, -): FileItem { - val filePath = path.toPath() - return FileItem( - name = name ?: filePath.name, - path = path, - type = type, - ) -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/BasePagingSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/common/BasePagingSource.kt deleted file mode 100644 index 2a16a79325..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/BasePagingSource.kt +++ /dev/null @@ -1,25 +0,0 @@ -package dev.dimension.flare.common - -import androidx.paging.PagingSource -import dev.dimension.flare.data.repository.DebugRepository -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.withContext - -internal abstract class BasePagingSource : PagingSource() { - override suspend fun load(params: LoadParams): LoadResult = - withContext(Dispatchers.IO) { - try { - doLoad(params) - } catch (e: Exception) { - onError(e) - DebugRepository.error(e) - LoadResult.Error(e) - } - } - - abstract suspend fun doLoad(params: LoadParams): LoadResult - - protected open fun onError(e: Throwable) { - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/BaseRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/common/BaseRemoteMediator.kt deleted file mode 100644 index 33a5580404..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/BaseRemoteMediator.kt +++ /dev/null @@ -1,35 +0,0 @@ -package dev.dimension.flare.common - -import androidx.paging.ExperimentalPagingApi -import androidx.paging.LoadType -import androidx.paging.PagingState -import androidx.paging.RemoteMediator -import dev.dimension.flare.data.repository.DebugRepository -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.withContext - -@OptIn(ExperimentalPagingApi::class) -internal abstract class BaseRemoteMediator : RemoteMediator() { - final override suspend fun load( - loadType: LoadType, - state: PagingState, - ): MediatorResult = - withContext(Dispatchers.IO) { - try { - doLoad(loadType, state) - } catch (e: Exception) { - onError(e) - DebugRepository.error(e) - MediatorResult.Error(e) - } - } - - abstract suspend fun doLoad( - loadType: LoadType, - state: PagingState, - ): MediatorResult - - protected open fun onError(e: Throwable) { - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/BuildConfig.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/common/BuildConfig.kt deleted file mode 100644 index 3bb4172ca9..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/BuildConfig.kt +++ /dev/null @@ -1,5 +0,0 @@ -package dev.dimension.flare.common - -internal expect object BuildConfig { - val debug: Boolean -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/FileItem.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/common/FileItem.kt deleted file mode 100644 index d5b33f0fce..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/FileItem.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.dimension.flare.common - -public expect class FileItem { - internal suspend fun readBytes(): ByteArray - - internal val name: String? - internal val type: FileType - internal val mimeType: String? -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/FileNameSanitizer.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/common/FileNameSanitizer.kt deleted file mode 100644 index fa8630a2ba..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/FileNameSanitizer.kt +++ /dev/null @@ -1,3 +0,0 @@ -package dev.dimension.flare.common - -internal fun String.sanitizeFileName(): String = replace(Regex("[^A-Za-z0-9._-]"), "_") diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/Locale.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/common/Locale.kt deleted file mode 100644 index bb759325be..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/Locale.kt +++ /dev/null @@ -1,5 +0,0 @@ -package dev.dimension.flare.common - -internal expect object Locale { - val language: String -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/Protobuf.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/common/Protobuf.kt deleted file mode 100644 index 96f1f44c1e..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/Protobuf.kt +++ /dev/null @@ -1,27 +0,0 @@ -package dev.dimension.flare.common - -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.decodeFromByteArray -import kotlinx.serialization.decodeFromHexString -import kotlinx.serialization.encodeToByteArray -import kotlinx.serialization.encodeToHexString -import kotlinx.serialization.protobuf.ProtoBuf - -@OptIn(ExperimentalSerializationApi::class) -internal inline fun T.encodeProtobuf(): ByteArray = ProtoBuf.encodeToByteArray(this) - -@OptIn(ExperimentalSerializationApi::class) -internal inline fun ByteArray.decodeProtobuf(): T = ProtoBuf.decodeFromByteArray(this) - -@OptIn(ExperimentalSerializationApi::class) -internal inline fun String.decodeProtobuf(): T = ProtoBuf.decodeFromHexString(this) - -@OptIn(ExperimentalSerializationApi::class) -internal inline fun String.decodeProtobuf(serializer: KSerializer): T = ProtoBuf.decodeFromHexString(serializer, this) - -@OptIn(ExperimentalSerializationApi::class) -internal inline fun T.encodeProtobufToString(): String = ProtoBuf.encodeToHexString(this) - -@OptIn(ExperimentalSerializationApi::class) -internal inline fun T.encodeProtobufToString(serializer: KSerializer): String = ProtoBuf.encodeToHexString(serializer, this) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMapping.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMapping.kt deleted file mode 100644 index e3bf1077d8..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMapping.kt +++ /dev/null @@ -1,106 +0,0 @@ -package dev.dimension.flare.common.deeplink - -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.UiAccount -import dev.dimension.flare.ui.route.DeeplinkRoute -import io.ktor.http.Url -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.ImmutableMap -import kotlinx.collections.immutable.persistentMapOf -import kotlinx.serialization.Serializable - -internal object DeepLinkMapping { - sealed interface Type { - fun deepLink(accountKey: MicroBlogKey): DeeplinkRoute - - @Serializable - data class Profile( - val handle: String, - ) : Type { - override fun deepLink(accountKey: MicroBlogKey): DeeplinkRoute { - if (handle.contains('@')) { - val (name, host) = MicroBlogKey.valueOf(handle) - return DeeplinkRoute.Profile.UserNameWithHost( - accountType = AccountType.Specific(accountKey), - userName = name, - host = host, - ) - } else { - return DeeplinkRoute.Profile.UserNameWithHost( - accountType = AccountType.Specific(accountKey), - userName = handle, - host = accountKey.host, - ) - } - } - } - - @Serializable - data class Post( - val handle: String? = null, - val id: String, - ) : Type { - override fun deepLink(accountKey: MicroBlogKey): DeeplinkRoute = - DeeplinkRoute.Status.Detail( - accountType = AccountType.Specific(accountKey), - statusKey = MicroBlogKey(id, accountKey.host), - ) - } - - @Serializable - data class BlueskyPost( - val handle: String, - val id: String, - ) : Type { - override fun deepLink(accountKey: MicroBlogKey): DeeplinkRoute = - DeeplinkRoute.Status.Detail( - accountType = AccountType.Specific(accountKey), - statusKey = - MicroBlogKey( - "at://$handle/app.bsky.feed.post/$id", - accountKey.host, - ), - ) - } - - @Serializable - data class PostMedia( - val handle: String, - val id: String, - val index: Int, - ) : Type { - override fun deepLink(accountKey: MicroBlogKey): DeeplinkRoute = - DeeplinkRoute.Media.StatusMedia( - accountType = AccountType.Specific(accountKey), - statusKey = MicroBlogKey(id, accountKey.host), - index = index, - preview = null, - ) - } - } - - fun matches( - url: String, - mapping: ImmutableMap>>, - ): ImmutableMap { - val request = DeepLinkRequest(Url(url)) - - val resultBuilder = persistentMapOf().builder() - - mapping.forEach { (account, patterns) -> - val matchType = - patterns.firstNotNullOfOrNull { pattern -> - DeepLinkMatcher(request, pattern).match()?.let { match -> - KeyDecoder(match.args).decodeSerializableValue(match.serializer) - } - } - - if (matchType != null) { - resultBuilder[account] = matchType - } - } - - return resultBuilder.build() - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/ProvideDatabase.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/ProvideDatabase.kt deleted file mode 100644 index 3655c97a79..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/ProvideDatabase.kt +++ /dev/null @@ -1,25 +0,0 @@ -package dev.dimension.flare.data.database - -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import dev.dimension.flare.data.database.app.AppDatabase -import dev.dimension.flare.data.database.cache.CacheDatabase -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO - -internal fun provideAppDatabase(driverFactory: DriverFactory): AppDatabase = - driverFactory - .createBuilder("app.db") - .addMigrations(AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10) - .setDriver(BundledSQLiteDriver()) - .setQueryCoroutineContext(Dispatchers.IO) - .build() - -internal const val CACHE_DATABASE_NAME = "cache.db" - -internal fun provideCacheDatabase(driverFactory: DriverFactory): CacheDatabase = - driverFactory - .createBuilder(CACHE_DATABASE_NAME, isCache = true) - .fallbackToDestructiveMigration(dropAllTables = true) - .setDriver(BundledSQLiteDriver()) - .setQueryCoroutineContext(Dispatchers.IO) - .build() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/AccountTypeConverter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/AccountTypeConverter.kt deleted file mode 100644 index 6c7ea92adb..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/AccountTypeConverter.kt +++ /dev/null @@ -1,51 +0,0 @@ -package dev.dimension.flare.data.database.adapter - -import androidx.room3.TypeConverter -import dev.dimension.flare.common.decodeJson -import dev.dimension.flare.common.decodeProtobuf -import dev.dimension.flare.common.encodeJson -import dev.dimension.flare.common.encodeProtobuf -import dev.dimension.flare.model.DbAccountType -import dev.dimension.flare.ui.model.UiDMItem -import dev.dimension.flare.ui.model.UiDMRoom -import dev.dimension.flare.ui.model.UiProfile -import dev.dimension.flare.ui.model.UiRelation -import dev.dimension.flare.ui.model.UiTimelineV2 - -internal class AccountTypeConverter { - @TypeConverter - fun fromString(value: String): DbAccountType = value.decodeJson() - - @TypeConverter - fun fromEnum(value: DbAccountType): String = value.encodeJson() - - @TypeConverter - fun fromUiProfile(value: UiProfile): ByteArray = value.encodeProtobuf() - - @TypeConverter - fun toUiProfile(value: ByteArray): UiProfile = value.decodeProtobuf() - - @TypeConverter - fun fromUiTimelineV2(value: UiTimelineV2): ByteArray = value.encodeProtobuf() - - @TypeConverter - fun toUiTimelineV2(value: ByteArray): UiTimelineV2 = value.decodeProtobuf() - - @TypeConverter - fun fromUiDMRoom(value: UiDMRoom): ByteArray = value.encodeProtobuf() - - @TypeConverter - fun toUiDMRoom(value: ByteArray): UiDMRoom = value.decodeProtobuf() - - @TypeConverter - fun fromUiDMItem(value: UiDMItem): ByteArray = value.encodeProtobuf() - - @TypeConverter - fun toUiDMItem(value: ByteArray): UiDMItem = value.decodeProtobuf() - - @TypeConverter - fun fromUiRelation(value: UiRelation): ByteArray = value.encodeProtobuf() - - @TypeConverter - fun toUiRelation(value: ByteArray): UiRelation = value.decodeProtobuf() -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/MicroBlogKeyConverter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/MicroBlogKeyConverter.kt deleted file mode 100644 index f1c6bc6596..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/MicroBlogKeyConverter.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.dimension.flare.data.database.adapter - -import dev.dimension.flare.model.MicroBlogKey - -internal class MicroBlogKeyConverter { - @androidx.room3.TypeConverter - fun fromString(value: String): MicroBlogKey = MicroBlogKey.valueOf(value) - - @androidx.room3.TypeConverter - fun fromEnum(value: MicroBlogKey): String = value.toString() -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/PlatformTypeConverter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/PlatformTypeConverter.kt deleted file mode 100644 index 31ddc3a7fa..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/PlatformTypeConverter.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.dimension.flare.data.database.adapter - -import dev.dimension.flare.model.PlatformType - -internal class PlatformTypeConverter { - @androidx.room3.TypeConverter - fun fromString(value: String): PlatformType = PlatformType.valueOf(value) - - @androidx.room3.TypeConverter - fun fromEnum(value: PlatformType): String = value.name -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/SubscriptionTypeConverter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/SubscriptionTypeConverter.kt deleted file mode 100644 index f76b83ed5e..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/adapter/SubscriptionTypeConverter.kt +++ /dev/null @@ -1,20 +0,0 @@ -package dev.dimension.flare.data.database.adapter - -import dev.dimension.flare.data.database.app.model.RssDisplayMode -import dev.dimension.flare.data.database.app.model.SubscriptionType - -internal class SubscriptionTypeConverter { - @androidx.room3.TypeConverter - fun fromString(value: String): SubscriptionType = SubscriptionType.valueOf(value) - - @androidx.room3.TypeConverter - fun fromEnum(value: SubscriptionType): String = value.name -} - -internal class RssDisplayModeConverter { - @androidx.room3.TypeConverter - fun fromString(value: String): RssDisplayMode = RssDisplayMode.valueOf(value) - - @androidx.room3.TypeConverter - fun fromEnum(value: RssDisplayMode): String = value.name -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbPagingTimeline.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbPagingTimeline.kt deleted file mode 100644 index d13946d5bd..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbPagingTimeline.kt +++ /dev/null @@ -1,96 +0,0 @@ -package dev.dimension.flare.data.database.cache.model - -import androidx.room3.Embedded -import androidx.room3.Entity -import androidx.room3.Index -import androidx.room3.PrimaryKey -import androidx.room3.Relation -import androidx.room3.TypeConverter -import dev.dimension.flare.model.ReferenceType -import kotlin.time.Instant -import kotlin.uuid.Uuid - -@Entity( - indices = [ - Index( - value = ["statusId", "pagingKey"], - unique = true, - ), - Index( - value = ["pagingKey", "sortId"], - ), - ], -) -internal data class DbPagingTimeline( - val pagingKey: String, - val statusId: String, - val sortId: Long, - @PrimaryKey - val _id: String = Uuid.random().toString(), -) - -@Entity -internal data class DbPagingKey( - @PrimaryKey - val pagingKey: String, - val nextKey: String? = null, - val prevKey: String? = null, -) - -internal data class DbPagingTimelineWithStatus( - @Embedded - val timeline: DbPagingTimeline, - @Relation( - parentColumn = "statusId", - entityColumn = "id", - entity = DbStatus::class, - ) - val status: DbStatusWithReference, -) - -internal data class DbStatusWithUser( - @Embedded - val data: DbStatus, - @Relation( - parentColumn = "id", - entityColumn = "entityKey", - entity = DbTranslation::class, - ) - val translations: List = emptyList(), -) - -internal data class DbStatusReferenceWithStatus( - @Embedded - val reference: DbStatusReference, - @Relation( - parentColumn = "referenceStatusId", - entityColumn = "id", - entity = DbStatus::class, - ) - val status: DbStatusWithUser?, -) - -internal data class DbStatusWithReference( - @Embedded - val status: DbStatusWithUser, - @Relation( - parentColumn = "id", - entityColumn = "statusId", - entity = DbStatusReference::class, - ) - val references: List, -) - -internal class StatusConverter { - @TypeConverter - fun fromReferenceType(value: ReferenceType): String = value.name - - @TypeConverter - fun toReferenceType(value: String): ReferenceType = ReferenceType.valueOf(value) - - @TypeConverter - fun fromTimestamp(value: Instant): Long = value.toEpochMilliseconds() - - @TypeConverter - fun toTimestamp(value: Long): Instant = Instant.fromEpochMilliseconds(value) -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbTranslation.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbTranslation.kt deleted file mode 100644 index 973132fc86..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbTranslation.kt +++ /dev/null @@ -1,107 +0,0 @@ -package dev.dimension.flare.data.database.cache.model - -import androidx.room3.ColumnInfo -import androidx.room3.Entity -import androidx.room3.Index -import androidx.room3.PrimaryKey -import androidx.room3.TypeConverter -import dev.dimension.flare.common.decodeJson -import dev.dimension.flare.common.encodeJson -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.UiProfile -import dev.dimension.flare.ui.render.UiRichText -import kotlinx.serialization.Serializable - -@Entity( - indices = [ - Index(value = ["entityType", "entityKey", "targetLanguage"], unique = true), - Index(value = ["entityType", "entityKey"]), - Index(value = ["status"]), - Index(value = ["targetLanguage"]), - ], -) -internal data class DbTranslation( - val entityType: TranslationEntityType, - val entityKey: String, - val targetLanguage: String, - val sourceHash: String, - val status: TranslationStatus, - val displayMode: TranslationDisplayMode = TranslationDisplayMode.Auto, - @ColumnInfo(typeAffinity = ColumnInfo.TEXT) - val payload: TranslationPayload? = null, - val statusReason: String? = null, - val attemptCount: Int = 0, - val updatedAt: Long, - @PrimaryKey - val id: String = "${entityType.name}:$entityKey:$targetLanguage", -) - -@Serializable -internal enum class TranslationEntityType { - Status, - Profile, -} - -@Serializable -internal enum class TranslationStatus { - Pending, - Translating, - Completed, - Failed, - Skipped, -} - -@Serializable -internal enum class TranslationDisplayMode { - Auto, - Original, - Translated, -} - -@Serializable -internal data class TranslationPayload( - val content: UiRichText? = null, - val contentWarning: UiRichText? = null, - val title: UiRichText? = null, - val description: UiRichText? = null, -) - -internal class TranslationConverters { - @TypeConverter - fun fromEntityType(value: TranslationEntityType): String = value.name - - @TypeConverter - fun toEntityType(value: String): TranslationEntityType = TranslationEntityType.valueOf(value) - - @TypeConverter - fun fromStatus(value: TranslationStatus): String = value.name - - @TypeConverter - fun toStatus(value: String): TranslationStatus = TranslationStatus.valueOf(value) - - @TypeConverter - fun fromDisplayMode(value: TranslationDisplayMode): String = value.name - - @TypeConverter - fun toDisplayMode(value: String): TranslationDisplayMode = TranslationDisplayMode.valueOf(value) - - @TypeConverter - fun fromPayload(value: TranslationPayload?): String? = value?.encodeJson(TranslationPayload.serializer()) - - @TypeConverter - fun toPayload(value: String?): TranslationPayload? = value?.decodeJson(TranslationPayload.serializer()) -} - -internal fun DbStatus.translationEntityKey(): String = id - -internal fun statusTranslationEntityKey( - accountType: AccountType, - statusKey: MicroBlogKey, -): String = "${accountType}_$statusKey" - -internal fun DbUser.translationEntityKey(): String = profileTranslationEntityKey(userKey) - -internal fun UiProfile.translationEntityKey(): String = profileTranslationEntityKey(key) - -internal fun profileTranslationEntityKey(userKey: MicroBlogKey): String = "profile:$userKey" diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/AuthenticatedMicroblogDataSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/AuthenticatedMicroblogDataSource.kt deleted file mode 100644 index 0380352a8c..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/AuthenticatedMicroblogDataSource.kt +++ /dev/null @@ -1,20 +0,0 @@ -package dev.dimension.flare.data.datasource.microblog - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.UiTimelineV2 - -internal interface AuthenticatedMicroblogDataSource : MicroblogDataSource { - val accountKey: MicroBlogKey - - fun notification(type: NotificationFilter = NotificationFilter.All): RemoteLoader - - val supportedNotificationFilter: List - - suspend fun compose( - data: ComposeData, - progress: () -> Unit, - ) - - fun composeConfig(type: ComposeType): ComposeConfig -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MicroblogDataSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MicroblogDataSource.kt deleted file mode 100644 index 01bba94c2b..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MicroblogDataSource.kt +++ /dev/null @@ -1,35 +0,0 @@ -package dev.dimension.flare.data.datasource.microblog - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.UiHashtag -import dev.dimension.flare.ui.model.UiProfile -import dev.dimension.flare.ui.model.UiTimelineV2 -import kotlinx.collections.immutable.ImmutableList - -internal interface MicroblogDataSource { - fun homeTimeline(): RemoteLoader - - fun userTimeline( - userKey: MicroBlogKey, - mediaOnly: Boolean = false, - ): RemoteLoader - - fun context(statusKey: MicroBlogKey): RemoteLoader - - fun searchStatus(query: String): RemoteLoader - - fun searchUser(query: String): RemoteLoader - - fun discoverUsers(): RemoteLoader - - fun discoverStatuses(): RemoteLoader - - fun discoverHashtags(): RemoteLoader - - fun following(userKey: MicroBlogKey): RemoteLoader - - fun fans(userKey: MicroBlogKey): RemoteLoader - - fun profileTabs(userKey: MicroBlogKey): ImmutableList -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/PostEvent.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/PostEvent.kt deleted file mode 100644 index cacbee1fe6..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/PostEvent.kt +++ /dev/null @@ -1,436 +0,0 @@ -package dev.dimension.flare.data.datasource.microblog - -import dev.dimension.flare.common.SerializableImmutableList -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.mapper.blueskyBookmark -import dev.dimension.flare.ui.model.mapper.blueskyLike -import dev.dimension.flare.ui.model.mapper.blueskyReblog -import dev.dimension.flare.ui.model.mapper.mastodonBookmark -import dev.dimension.flare.ui.model.mapper.mastodonLike -import dev.dimension.flare.ui.model.mapper.mastodonRepost -import dev.dimension.flare.ui.model.mapper.misskeyFavourite -import dev.dimension.flare.ui.model.mapper.misskeyReact -import dev.dimension.flare.ui.model.mapper.misskeyRenote -import dev.dimension.flare.ui.model.mapper.nostrLike -import dev.dimension.flare.ui.model.mapper.nostrRepost -import dev.dimension.flare.ui.model.mapper.vvoFavorite -import dev.dimension.flare.ui.model.mapper.vvoLike -import dev.dimension.flare.ui.model.mapper.vvoLikeComment -import dev.dimension.flare.ui.model.mapper.xqtBookmark -import dev.dimension.flare.ui.model.mapper.xqtLike -import dev.dimension.flare.ui.model.mapper.xqtRetweet -import kotlinx.collections.immutable.toImmutableList -import kotlinx.serialization.Serializable - -@Serializable -internal sealed interface PostEvent { - val postKey: MicroBlogKey - - @Serializable - sealed interface PollEvent : PostEvent { - val accountKey: MicroBlogKey - val options: SerializableImmutableList - - fun copyWithOptions(options: List): PollEvent - } - - @Serializable - sealed interface Mastodon : PostEvent { - @Serializable - data class Reblog( - override val postKey: MicroBlogKey, - val reblogged: Boolean, - val count: Long, - val accountKey: MicroBlogKey, - ) : Mastodon, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.mastodonRepost( - reblogged = !reblogged, - reblogsCount = count + if (!reblogged) 1 else -1, - accountKey = accountKey, - statusKey = postKey, - ) - } - - @Serializable - data class Like( - override val postKey: MicroBlogKey, - val liked: Boolean, - val accountKey: MicroBlogKey, - val count: Long, - ) : Mastodon, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.mastodonLike( - favourited = !liked, - favouritesCount = count + if (!liked) 1 else -1, - accountKey = accountKey, - statusKey = postKey, - ) - } - - @Serializable - data class Bookmark( - override val postKey: MicroBlogKey, - val bookmarked: Boolean, - val accountKey: MicroBlogKey, - ) : Mastodon, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.mastodonBookmark( - bookmarked = !bookmarked, - accountKey = accountKey, - statusKey = postKey, - ) - } - - @Serializable - data class Vote( - val id: String, - override val accountKey: MicroBlogKey, - override val postKey: MicroBlogKey, - override val options: SerializableImmutableList, - ) : Mastodon, - PollEvent { - override fun copyWithOptions(options: List): PollEvent = copy(options = options.toImmutableList()) - } - - @Serializable - data class AcceptFollowRequest( - override val postKey: MicroBlogKey, - val userKey: MicroBlogKey, - ) : Mastodon - - @Serializable - data class RejectFollowRequest( - override val postKey: MicroBlogKey, - val userKey: MicroBlogKey, - ) : Mastodon - } - - @Serializable - sealed interface Pleroma : PostEvent { - @Serializable - data class React( - override val postKey: MicroBlogKey, - val hasReacted: Boolean, - val reaction: String, - ) : Pleroma - } - - @Serializable - sealed interface Misskey : PostEvent { - @Serializable - data class React( - override val postKey: MicroBlogKey, - val hasReacted: Boolean, - val reaction: String, - val count: Long = 0, - val accountKey: MicroBlogKey? = null, - ) : Misskey, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.misskeyReact( - postKey = postKey, - hasReacted = !hasReacted, - reaction = reaction, - count = (count + if (!hasReacted) 1 else -1).coerceAtLeast(0), - accountKey = accountKey, - ) - } - - @Serializable - data class Renote( - override val postKey: MicroBlogKey, - val count: Long = 0, - val accountKey: MicroBlogKey? = null, - ) : Misskey, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.misskeyRenote( - postKey = postKey, - count = count + 1, - accountKey = accountKey, - ) - } - - @Serializable - data class Vote( - override val accountKey: MicroBlogKey, - override val postKey: MicroBlogKey, - override val options: SerializableImmutableList, - ) : Misskey, - PollEvent { - override fun copyWithOptions(options: List): PollEvent = copy(options = options.toImmutableList()) - } - - @Serializable - data class Favourite( - override val postKey: MicroBlogKey, - val favourited: Boolean, - val accountKey: MicroBlogKey? = null, - ) : Misskey, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.misskeyFavourite( - postKey = postKey, - favourited = !favourited, - accountKey = accountKey, - ) - } - - @Serializable - data class AcceptFollowRequest( - override val postKey: MicroBlogKey, - val userKey: MicroBlogKey, - val notificationStatusKey: MicroBlogKey, - ) : Misskey - - @Serializable - data class RejectFollowRequest( - override val postKey: MicroBlogKey, - val userKey: MicroBlogKey, - val notificationStatusKey: MicroBlogKey, - ) : Misskey - } - - @Serializable - sealed interface Bluesky : PostEvent { - @Serializable - data class Reblog( - override val postKey: MicroBlogKey, - val count: Long, - val cid: String, - val uri: String, - val repostUri: String?, - val accountKey: MicroBlogKey, - ) : Bluesky, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.blueskyReblog( - accountKey = accountKey, - postKey = postKey, - cid = cid, - uri = uri, - count = count + if (repostUri == null) 1 else -1, - repostUri = - if (repostUri == null) { - "" - } else { - null - }, - ) - } - - @Serializable - data class Like( - override val postKey: MicroBlogKey, - val cid: String, - val uri: String, - val likedUri: String?, - val count: Long, - val accountKey: MicroBlogKey, - ) : Bluesky, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.blueskyLike( - accountKey = accountKey, - postKey = postKey, - cid = cid, - uri = uri, - count = count + if (likedUri == null) 1 else -1, - likedUri = - if (likedUri == null) { - "" - } else { - null - }, - ) - } - - @Serializable - data class Bookmark( - override val postKey: MicroBlogKey, - val uri: String, - val cid: String, - val bookmarked: Boolean, - val accountKey: MicroBlogKey, - val count: Long, - ) : Bluesky, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.blueskyBookmark( - accountKey = accountKey, - postKey = postKey, - cid = cid, - uri = uri, - bookmarked = !bookmarked, - count = count + if (!bookmarked) 1 else -1, - ) - } - } - - @Serializable - sealed interface XQT : PostEvent { - @Serializable - data class Retweet( - override val postKey: MicroBlogKey, - val retweeted: Boolean, - val count: Long = 0, - val accountKey: MicroBlogKey, - ) : XQT, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.xqtRetweet( - statusKey = postKey, - retweeted = !retweeted, - count = (count + if (!retweeted) 1 else -1).coerceAtLeast(0), - accountKey = accountKey, - ) - } - - @Serializable - data class Like( - override val postKey: MicroBlogKey, - val liked: Boolean, - val count: Long = 0, - val accountKey: MicroBlogKey, - ) : XQT, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.xqtLike( - statusKey = postKey, - liked = !liked, - count = (count + if (!liked) 1 else -1).coerceAtLeast(0), - accountKey = accountKey, - ) - } - - @Serializable - data class Bookmark( - override val postKey: MicroBlogKey, - val bookmarked: Boolean, - val count: Long = 0, - val accountKey: MicroBlogKey, - ) : XQT, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.xqtBookmark( - statusKey = postKey, - bookmarked = !bookmarked, - count = (count + if (!bookmarked) 1 else -1).coerceAtLeast(0), - accountKey = accountKey, - ) - } - } - - @Serializable - sealed interface VVO : PostEvent { - @Serializable - data class Like( - override val postKey: MicroBlogKey, - val liked: Boolean, - val count: Long = 0, - val accountKey: MicroBlogKey, - ) : VVO, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.vvoLike( - statusKey = postKey, - liked = !liked, - count = (count + if (!liked) 1 else -1).coerceAtLeast(0), - accountKey = accountKey, - ) - } - - @Serializable - data class LikeComment( - override val postKey: MicroBlogKey, - val liked: Boolean, - val count: Long = 0, - val accountKey: MicroBlogKey, - ) : VVO, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.vvoLikeComment( - statusKey = postKey, - liked = !liked, - count = (count + if (!liked) 1 else -1).coerceAtLeast(0), - accountKey = accountKey, - ) - } - - @Serializable - data class Favorite( - override val postKey: MicroBlogKey, - val favorited: Boolean, - val accountKey: MicroBlogKey, - ) : VVO, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.vvoFavorite( - statusKey = postKey, - favorited = !favorited, - accountKey = accountKey, - ) - } - } - - @Serializable - sealed interface Nostr : PostEvent { - @Serializable - data class Repost( - override val postKey: MicroBlogKey, - val repostEventId: String?, - val count: Long = 0, - val accountKey: MicroBlogKey, - ) : Nostr, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.nostrRepost( - statusKey = postKey, - repostEventId = - if (repostEventId == null) { - "" - } else { - null - }, - count = (count + if (repostEventId == null) 1 else -1).coerceAtLeast(0), - accountKey = accountKey, - ) - } - - @Serializable - data class Like( - override val postKey: MicroBlogKey, - val reactionEventId: String?, - val count: Long = 0, - val accountKey: MicroBlogKey, - ) : Nostr, - UpdatePostActionMenuEvent { - override fun nextActionMenu(): ActionMenu.Item = - ActionMenu.nostrLike( - statusKey = postKey, - reactionEventId = - if (reactionEventId == null) { - "" - } else { - null - }, - count = (count + if (reactionEventId == null) 1 else -1).coerceAtLeast(0), - accountKey = accountKey, - ) - } - - @Serializable - data class Report( - override val postKey: MicroBlogKey, - val accountKey: MicroBlogKey, - ) : Nostr - } -} - -internal interface UpdatePostActionMenuEvent : PostEvent { - fun nextActionMenu(): ActionMenu.Item -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/RecommendInstancePagingSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/RecommendInstancePagingSource.kt deleted file mode 100644 index 3e635e9d4f..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/RecommendInstancePagingSource.kt +++ /dev/null @@ -1,146 +0,0 @@ -package dev.dimension.flare.data.datasource.microblog - -import androidx.paging.PagingState -import dev.dimension.flare.common.BasePagingSource -import dev.dimension.flare.data.network.mastodon.JoinMastodonService -import dev.dimension.flare.data.network.mastodon.MastodonInstanceService -import dev.dimension.flare.data.network.misskey.JoinMisskeyService -import dev.dimension.flare.data.repository.tryRun -import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.ui.model.UiInstance -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope - -internal class RecommendInstancePagingSource : BasePagingSource() { - override fun getRefreshKey(state: PagingState): Int? = null - - override suspend fun doLoad(params: LoadParams): LoadResult { - val instances = - coroutineScope { - listOf( - async { - tryRun { - JoinMastodonService.servers().map { - UiInstance( - name = it.domain, - description = it.description, - iconUrl = null, - domain = it.domain, - type = PlatformType.Mastodon, - bannerUrl = it.proxiedThumbnail, - usersCount = it.totalUsers, - ) - } - }.getOrDefault(emptyList()) - }, - async { - tryRun { - JoinMisskeyService.instances().instancesInfos.map { - UiInstance( - name = it.name, - description = it.description, - iconUrl = it.meta?.iconURL, - domain = it.url, - type = PlatformType.Misskey, - bannerUrl = it.meta?.bannerURL, - usersCount = - it.stats?.usersCount ?: it.nodeinfo - ?.usage - ?.users - ?.total ?: 0, - ) - } - }.getOrDefault(emptyList()) - }, - async { - tryRun { - MastodonInstanceService("https://pawoo.net/").instance().let { - listOf( - UiInstance( - name = it.domain ?: "pawoo.net", - description = it.title, - iconUrl = - it.thumbnail?.url, - domain = it.domain ?: "pawoo.net", - type = PlatformType.Mastodon, - bannerUrl = - it.thumbnail?.url, - usersCount = it.usage?.users?.activeMonth ?: 0, - ), - ) - } - }.getOrDefault(emptyList()) - }, - ).awaitAll().flatMap { it } - } - val mstdnJP = - instances.firstOrNull { it.domain == "mstdn.jp" } ?: run { - UiInstance( - name = "mstdn.jp", - description = "mstdn.jp", - iconUrl = null, - domain = "mstdn.jp", - type = PlatformType.Mastodon, - bannerUrl = null, - usersCount = 0, - ) - } - val pawoo = - instances.firstOrNull { it.domain == "pawoo.net" } ?: run { - UiInstance( - name = "pawoo.net", - description = "pawoo.net", - iconUrl = null, - domain = "pawoo.net", - type = PlatformType.Mastodon, - bannerUrl = null, - usersCount = 0, - ) - } - val extra = - listOf( - mstdnJP, - pawoo, - UiInstance( - name = "X", - description = - "From breaking news and entertainment to sports and politics," + - " get the full story with all the live commentary.", - iconUrl = null, - domain = "x.com", - type = PlatformType.xQt, - bannerUrl = null, - usersCount = 0, - ), - UiInstance( - name = "Bluesky", - description = - "The web. Email. RSS feeds. XMPP chats. " + - "What all these technologies had in common is they allowed people to freely interact " + - "and create content, without a single intermediary.", - iconUrl = null, - domain = "bsky.social", - type = PlatformType.Bluesky, - bannerUrl = null, - usersCount = 0, - ), - UiInstance( - name = "Nostr", - description = - "A decentralized network based on cryptographic keypairs and that is not peer-to-peer, " + - "it is super simple and scalable and therefore has a chance of working.", - iconUrl = null, - domain = "nostr", - type = PlatformType.Nostr, - bannerUrl = null, - usersCount = 0, - ), - ) - return LoadResult.Page( - data = extra + (instances.sortedByDescending { it.usersCount }.filter { it !in extra }), - prevKey = null, - nextKey = null, - ) - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/ListDataSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/ListDataSource.kt deleted file mode 100644 index f9dc0e6a5a..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/ListDataSource.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.dimension.flare.data.datasource.microblog.datasource - -import dev.dimension.flare.data.datasource.microblog.handler.ListHandler -import dev.dimension.flare.data.datasource.microblog.handler.ListMemberHandler -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.ui.model.UiList -import dev.dimension.flare.ui.model.UiTimelineV2 - -internal interface ListDataSource { - fun listTimeline(listId: String): RemoteLoader - - val listHandler: ListHandler - val listMemberHandler: ListMemberHandler -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/PinnableTimelineTabDataSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/PinnableTimelineTabDataSource.kt deleted file mode 100644 index 6b3351cc4f..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/PinnableTimelineTabDataSource.kt +++ /dev/null @@ -1,15 +0,0 @@ -package dev.dimension.flare.data.datasource.microblog.datasource - -import androidx.paging.PagingData -import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import dev.dimension.flare.ui.model.UiStrings -import kotlinx.coroutines.flow.Flow - -internal interface PinnableTimelineTabDataSource { - val pinnableTimelineTabs: List -} - -internal data class PinnableTimelineTabSection( - val title: UiStrings, - val data: Flow>, -) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/TimelineTabConfigurationDataSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/TimelineTabConfigurationDataSource.kt deleted file mode 100644 index ee03966d65..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/TimelineTabConfigurationDataSource.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.dimension.flare.data.datasource.microblog.datasource - -import dev.dimension.flare.data.model.tab.ShortcutSpec -import dev.dimension.flare.data.model.tab.TimelineSlot -import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import kotlinx.collections.immutable.ImmutableList - -internal interface TimelineTabConfigurationDataSource { - val defaultTabs: ImmutableList - val builtInTimelineTabs: ImmutableList - val shortcuts: ImmutableList -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/NotificationLoader.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/NotificationLoader.kt deleted file mode 100644 index c95d377968..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/NotificationLoader.kt +++ /dev/null @@ -1,5 +0,0 @@ -package dev.dimension.flare.data.datasource.microblog.loader - -internal interface NotificationLoader { - suspend fun notificationBadgeCount(): Int -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/RelationLoader.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/RelationLoader.kt deleted file mode 100644 index c3638e723e..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/RelationLoader.kt +++ /dev/null @@ -1,28 +0,0 @@ -package dev.dimension.flare.data.datasource.microblog.loader - -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.UiRelation - -internal enum class RelationActionType { - Follow, - Block, - Mute, -} - -internal interface RelationLoader { - val supportedTypes: Set - - suspend fun relation(userKey: MicroBlogKey): UiRelation - - suspend fun follow(userKey: MicroBlogKey) - - suspend fun unfollow(userKey: MicroBlogKey) - - suspend fun block(userKey: MicroBlogKey) - - suspend fun unblock(userKey: MicroBlogKey) - - suspend fun mute(userKey: MicroBlogKey) - - suspend fun unmute(userKey: MicroBlogKey) -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/UserLoader.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/UserLoader.kt deleted file mode 100644 index cac7ef9e3f..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/UserLoader.kt +++ /dev/null @@ -1,10 +0,0 @@ -package dev.dimension.flare.data.datasource.microblog.loader - -import dev.dimension.flare.ui.model.UiHandle -import dev.dimension.flare.ui.model.UiProfile - -internal interface UserLoader { - suspend fun userByHandleAndHost(uiHandle: UiHandle): UiProfile - - suspend fun userById(id: String): UiProfile -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/CacheableRemoteLoader.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/CacheableRemoteLoader.kt deleted file mode 100644 index 84cc4caac8..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/CacheableRemoteLoader.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.dimension.flare.data.datasource.microblog.paging - -internal interface CacheableRemoteLoader : RemoteLoader { - val pagingKey: String - val supportPrepend: Boolean - get() = false -} - -internal interface ReportableRemoteLoader { - var reportError: ((Throwable) -> Unit)? -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/rss/RssDataSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/rss/RssDataSource.kt deleted file mode 100644 index cbf6a826fa..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/rss/RssDataSource.kt +++ /dev/null @@ -1,66 +0,0 @@ -package dev.dimension.flare.data.datasource.rss - -import dev.dimension.flare.common.Locale -import dev.dimension.flare.data.database.app.AppDatabase -import dev.dimension.flare.data.database.app.model.DbRssSources -import dev.dimension.flare.data.database.app.model.SubscriptionType -import dev.dimension.flare.data.datasource.guest.mastodon.GuestPublicTimelineRemoteMediator -import dev.dimension.flare.data.datasource.guest.mastodon.GuestTrendsRemoteMediator -import dev.dimension.flare.data.datasource.microblog.paging.CacheableRemoteLoader -import dev.dimension.flare.ui.model.UiTimelineV2 -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -internal object RssDataSource : - KoinComponent { - private val appDatabase: AppDatabase by inject() - - fun fetchLoader(url: String) = - RssTimelineRemoteMediator( - url = url, - fetchSource = { - appDatabase - .rssSourceDao() - .getByUrl(it) - .firstOrNull() - }, - ) - - fun fetchLoader(subscription: DbRssSources): CacheableRemoteLoader = - when (subscription.type) { - SubscriptionType.RSS -> { - RssTimelineRemoteMediator( - url = subscription.url, - fetchSource = { - appDatabase - .rssSourceDao() - .getByUrl(it) - .firstOrNull() - }, - ) - } - - SubscriptionType.MASTODON_TRENDS -> { - GuestTrendsRemoteMediator( - host = subscription.url, - locale = Locale.language, - ) - } - - SubscriptionType.MASTODON_PUBLIC -> { - GuestPublicTimelineRemoteMediator( - host = subscription.url, - locale = Locale.language, - local = false, - ) - } - - SubscriptionType.MASTODON_LOCAL -> { - GuestPublicTimelineRemoteMediator( - host = subscription.url, - locale = Locale.language, - local = true, - ) - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/AppDataStore.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/AppDataStore.kt deleted file mode 100644 index 0c8afbd39d..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/AppDataStore.kt +++ /dev/null @@ -1,55 +0,0 @@ -package dev.dimension.flare.data.datastore - -import androidx.datastore.core.DataStore -import androidx.datastore.core.DataStoreFactory -import androidx.datastore.core.okio.OkioStorage -import dev.dimension.flare.common.protobufSerializer -import dev.dimension.flare.data.datastore.model.AppSettings -import dev.dimension.flare.data.datastore.model.ComposeConfigData -import dev.dimension.flare.data.datastore.model.FlareConfig -import dev.dimension.flare.data.io.PlatformPathProducer -import okio.FileSystem -import okio.SYSTEM - -internal class AppDataStore( - private val platformPathProducer: PlatformPathProducer, -) { - val flareDataStore: DataStore by lazy { - DataStoreFactory.create( - storage = - OkioStorage( - fileSystem = FileSystem.SYSTEM, - serializer = protobufSerializer(FlareConfig()), - producePath = { - platformPathProducer.dataStoreFile("flare_config.pb") - }, - ), - ) - } - - val composeConfigData: DataStore by lazy { - DataStoreFactory.create( - storage = - OkioStorage( - fileSystem = FileSystem.SYSTEM, - serializer = protobufSerializer(ComposeConfigData()), - producePath = { - platformPathProducer.dataStoreFile("compose_config.pb") - }, - ), - ) - } - - val appSettingsStore: DataStore by lazy { - DataStoreFactory.create( - storage = - OkioStorage( - fileSystem = FileSystem.SYSTEM, - serializer = protobufSerializer(AppSettings(version = "")), - producePath = { - platformPathProducer.dataStoreFile("app_settings.pb") - }, - ), - ) - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/ComposeConfigData.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/ComposeConfigData.kt deleted file mode 100644 index cd5ef90291..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/ComposeConfigData.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.dimension.flare.data.datastore.model - -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.UiTimelineV2 -import kotlinx.serialization.Serializable - -@Serializable -internal data class ComposeConfigData( - val visibility: UiTimelineV2.Post.Visibility = - UiTimelineV2.Post.Visibility.Public, - val lastAccounts: List = emptyList(), -) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/AppearanceSettings.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/AppearanceSettings.kt deleted file mode 100644 index 09c9e0bdd5..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/AppearanceSettings.kt +++ /dev/null @@ -1,93 +0,0 @@ -package dev.dimension.flare.data.model - -import kotlinx.serialization.Serializable - -/** - * Legacy v1 appearance settings model kept only for migrating old persisted data. - * New code should use the appearance bag / patch based settings model instead. - */ -@Serializable -internal data class AppearanceSettings( - val theme: Theme = Theme.SYSTEM, - val dynamicTheme: Boolean = true, - val colorSeed: ULong = 0x02EBD2u, - val avatarShape: AvatarShape = AvatarShape.CIRCLE, - @Deprecated( - "Use postActionStyle instead", - ReplaceWith("postActionStyle"), - DeprecationLevel.ERROR, - ) - val showActions: Boolean = true, - val pureColorMode: Boolean = true, - val showNumbers: Boolean = true, - val showLinkPreview: Boolean = true, - val showMedia: Boolean = true, - val showSensitiveContent: Boolean = false, - val videoAutoplay: VideoAutoplay = VideoAutoplay.WIFI, - val expandMediaSize: Boolean = true, - val compatLinkPreview: Boolean = false, - val fontSizeDiff: Float = 0f, - val lineHeightDiff: Float = 0f, - val showComposeInHomeTimeline: Boolean = true, - val bottomBarStyle: BottomBarStyle = BottomBarStyle.Classic, - val bottomBarBehavior: BottomBarBehavior = BottomBarBehavior.MinimizeOnScroll, - val inAppBrowser: Boolean = true, - val fullWidthPost: Boolean = false, - val postActionStyle: PostActionStyle = PostActionStyle.LeftAligned, - val absoluteTimestamp: Boolean = false, - val showPlatformLogo: Boolean = true, - val timelineDisplayMode: TimelineDisplayMode = TimelineDisplayMode.Card, -) { - companion object { - // for iOS - val Default: AppearanceSettings = AppearanceSettings() - } -} - -@Serializable -public enum class PostActionStyle { - Hidden, - LeftAligned, - RightAligned, - Stretch, -} - -@Serializable -public enum class BottomBarStyle { - Floating, - Classic, -} - -@Serializable -public enum class BottomBarBehavior { - AlwaysShow, - HideOnScroll, - MinimizeOnScroll, -} - -@Serializable -public enum class Theme { - LIGHT, - DARK, - SYSTEM, -} - -@Serializable -public enum class AvatarShape { - CIRCLE, - SQUARE, -} - -@Serializable -public enum class VideoAutoplay { - ALWAYS, - WIFI, - NEVER, -} - -@Serializable -public enum class TimelineDisplayMode { - Card, - Plain, - Gallery, -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/SettingsExport.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/SettingsExport.kt deleted file mode 100644 index b451966767..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/SettingsExport.kt +++ /dev/null @@ -1,34 +0,0 @@ -package dev.dimension.flare.data.model - -import dev.dimension.flare.data.datastore.model.AppSettings -import dev.dimension.flare.data.model.appearance.AppearanceBag -import dev.dimension.flare.data.model.tab.TabSettingsV2 -import kotlinx.serialization.Serializable - -@Serializable -internal data class SettingsExport( - val appearanceBag: AppearanceBag, - val appSettings: AppSettings, - val tabSettingsV2: TabSettingsV2, -) - -@Serializable -internal data class LegacySettingsExport( - val appearanceBag: AppearanceBag, - val appSettings: AppSettings, - val tabSettings: TabSettings, -) - -@Serializable -internal data class LegacyAppearanceSettingsExport( - val appearanceSettings: AppearanceSettings, - val appSettings: AppSettings, - val tabSettingsV2: TabSettingsV2, -) - -@Serializable -internal data class LegacyAppearanceSettingsAndTabsExport( - val appearanceSettings: AppearanceSettings, - val appSettings: AppSettings, - val tabSettings: TabSettings, -) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceMigration.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceMigration.kt deleted file mode 100644 index 3912cc9e8a..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceMigration.kt +++ /dev/null @@ -1,44 +0,0 @@ -package dev.dimension.flare.data.model.appearance - -import androidx.datastore.core.DataStore -import dev.dimension.flare.data.io.PlatformPathProducer -import dev.dimension.flare.data.model.AppearanceSettings -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.withContext -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromByteArray -import kotlinx.serialization.protobuf.ProtoBuf -import okio.FileSystem -import okio.SYSTEM - -@OptIn(ExperimentalSerializationApi::class) -internal suspend fun migrateAppearanceV1ToV2( - pathProducer: PlatformPathProducer, - bagStore: DataStore, -) { - withContext(Dispatchers.IO) { - val v1Path = pathProducer.dataStoreFile("appearance_settings.pb") - val fs = FileSystem.SYSTEM - if (!fs.exists(v1Path)) return@withContext - if (bagStore.data - .first() - .entries - .isNotEmpty() - ) { - runCatching { fs.delete(v1Path) } - return@withContext - } - - val v1 = - runCatching { - val v1Bytes = fs.read(v1Path) { readByteArray() } - ProtoBuf.decodeFromByteArray(v1Bytes) - }.getOrNull() - if (v1 != null) { - bagStore.updateData { v1.toPatch().toBag() } - } - runCatching { fs.delete(v1Path) } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceSynthesizer.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceSynthesizer.kt deleted file mode 100644 index ee4d43697a..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/appearance/AppearanceSynthesizer.kt +++ /dev/null @@ -1,88 +0,0 @@ -package dev.dimension.flare.data.model.appearance - -import dev.dimension.flare.data.model.AppearanceSettings -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.decodeFromHexString -import kotlinx.serialization.encodeToHexString -import kotlinx.serialization.protobuf.ProtoBuf - -internal fun AppearancePatch.toAppearanceSettings(): AppearanceSettings = - AppearanceSettings( - theme = get(AppearanceKeys.Theme), - dynamicTheme = get(AppearanceKeys.DynamicTheme), - colorSeed = get(AppearanceKeys.ColorSeed), - avatarShape = get(AppearanceKeys.AvatarShape), - pureColorMode = get(AppearanceKeys.PureColorMode), - showNumbers = get(AppearanceKeys.ShowNumbers), - showLinkPreview = get(AppearanceKeys.ShowLinkPreview), - showMedia = get(AppearanceKeys.ShowMedia), - showSensitiveContent = get(AppearanceKeys.ShowSensitiveContent), - videoAutoplay = get(AppearanceKeys.VideoAutoplay), - expandMediaSize = get(AppearanceKeys.ExpandMediaSize), - compatLinkPreview = get(AppearanceKeys.CompatLinkPreview), - fontSizeDiff = get(AppearanceKeys.FontSizeDiff), - lineHeightDiff = get(AppearanceKeys.LineHeightDiff), - showComposeInHomeTimeline = get(AppearanceKeys.ShowComposeInHomeTimeline), - bottomBarStyle = get(AppearanceKeys.BottomBarStyle), - bottomBarBehavior = get(AppearanceKeys.BottomBarBehavior), - inAppBrowser = get(AppearanceKeys.InAppBrowser), - fullWidthPost = get(AppearanceKeys.FullWidthPost), - postActionStyle = get(AppearanceKeys.PostActionStyle), - absoluteTimestamp = get(AppearanceKeys.AbsoluteTimestamp), - showPlatformLogo = get(AppearanceKeys.ShowPlatformLogo), - timelineDisplayMode = get(AppearanceKeys.TimelineDisplayMode), - ) - -internal fun AppearanceSettings.toPatch(): AppearancePatch = - AppearancePatch.EMPTY - .set(AppearanceKeys.Theme, theme) - .set(AppearanceKeys.DynamicTheme, dynamicTheme) - .set(AppearanceKeys.ColorSeed, colorSeed) - .set(AppearanceKeys.AvatarShape, avatarShape) - .set(AppearanceKeys.PureColorMode, pureColorMode) - .set(AppearanceKeys.ShowNumbers, showNumbers) - .set(AppearanceKeys.ShowLinkPreview, showLinkPreview) - .set(AppearanceKeys.ShowMedia, showMedia) - .set(AppearanceKeys.ShowSensitiveContent, showSensitiveContent) - .set(AppearanceKeys.VideoAutoplay, videoAutoplay) - .set(AppearanceKeys.ExpandMediaSize, expandMediaSize) - .set(AppearanceKeys.CompatLinkPreview, compatLinkPreview) - .set(AppearanceKeys.FontSizeDiff, fontSizeDiff) - .set(AppearanceKeys.LineHeightDiff, lineHeightDiff) - .set(AppearanceKeys.ShowComposeInHomeTimeline, showComposeInHomeTimeline) - .set(AppearanceKeys.BottomBarStyle, bottomBarStyle) - .set(AppearanceKeys.BottomBarBehavior, bottomBarBehavior) - .set(AppearanceKeys.InAppBrowser, inAppBrowser) - .set(AppearanceKeys.FullWidthPost, fullWidthPost) - .set(AppearanceKeys.PostActionStyle, postActionStyle) - .set(AppearanceKeys.AbsoluteTimestamp, absoluteTimestamp) - .set(AppearanceKeys.ShowPlatformLogo, showPlatformLogo) - .set(AppearanceKeys.TimelineDisplayMode, timelineDisplayMode) - -@OptIn(ExperimentalSerializationApi::class) -internal fun AppearanceBag.toPatch(): AppearancePatch { - var patch = AppearancePatch.EMPTY - for ((id, bytes) in entries) { - val key = AppearanceKeys[id] ?: continue - val value = - runCatching { - ProtoBuf.decodeFromHexString(key.serializer, bytes) - }.getOrNull() ?: continue - @Suppress("UNCHECKED_CAST") - patch = patch.set(key as AppearanceKey, value) - } - return patch -} - -@OptIn(ExperimentalSerializationApi::class) -internal fun AppearancePatch.toBag(): AppearanceBag = - AppearanceBag( - entries = - explicitEntries - .mapNotNull { (id, value) -> - val key = AppearanceKeys[id] ?: return@mapNotNull null - @Suppress("UNCHECKED_CAST") - id to ProtoBuf.encodeToHexString(key.serializer as KSerializer, value) - }.toMap(), - ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/Timeline.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/Timeline.kt deleted file mode 100644 index 4137a4e896..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/model/tab/Timeline.kt +++ /dev/null @@ -1,587 +0,0 @@ -package dev.dimension.flare.data.model.tab - -import androidx.compose.runtime.Immutable -import dev.dimension.flare.data.model.IconType -import dev.dimension.flare.data.model.appearance.AppearanceBag -import dev.dimension.flare.data.model.appearance.AppearancePatch -import dev.dimension.flare.data.model.appearance.TimelineAppearance -import dev.dimension.flare.data.model.appearance.toBag -import dev.dimension.flare.data.model.appearance.toPatch -import dev.dimension.flare.data.model.appearance.withPatch -import dev.dimension.flare.data.platform.RssTimelineSpecs -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.model.spec -import dev.dimension.flare.ui.model.UiIcon -import dev.dimension.flare.ui.model.UiStrings -import dev.dimension.flare.ui.model.UiText -import dev.dimension.flare.ui.model.asText -import dev.dimension.flare.ui.model.asType -import dev.dimension.flare.ui.presenter.home.MixedTimelinePresenter -import dev.dimension.flare.ui.presenter.home.SystemHomeMixedTimelinePresenter -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import dev.dimension.flare.ui.route.DeeplinkRoute -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromHexString -import kotlinx.serialization.encodeToHexString -import kotlinx.serialization.protobuf.ProtoBuf - -@Immutable -@Serializable -internal data class TabSettingsV2( - val homeSlots: List = emptyList(), -) - -@Immutable -@Serializable -public data class TimelineFilterConfig( - val excludedKinds: List = emptyList(), - val excludedContents: List = emptyList(), -) - -@Immutable -@Serializable -public enum class TimelinePostKind { - Original, - Reply, - Repost, - Quote, -} - -@Immutable -@Serializable -public enum class TimelinePostContent { - Text, - Image, - Video, - Other, -} - -@Immutable -public sealed interface TimelineTabItemV2 { - public val id: String - public val title: UiText - public val icon: IconType - public val appearancePatch: AppearancePatch? - public val enabled: Boolean - public val filterConfig: TimelineFilterConfig - - // for iOS and Compose call sites - public val key: String get() = id - - public fun createPresenter(): TimelinePresenter - - public fun withPresentationOverrides( - title: String, - icon: IconType, - appearancePatch: AppearancePatch? = this.appearancePatch, - enabled: Boolean = this.enabled, - filterConfig: TimelineFilterConfig = this.filterConfig, - ): TimelineTabItemV2 -} - -public fun TimelineTabItemV2.resolveTimelineAppearance(base: TimelineAppearance): TimelineAppearance = base.withPatch(appearancePatch) - -@Immutable -public class SourceTimelineTabItemV2 private constructor( - override val id: String, - public val source: TimelineSourceRef?, - internal val presentation: TimelinePresentation?, - override val title: UiText, - override val icon: IconType, - override val appearancePatch: AppearancePatch?, - override val enabled: Boolean, - private val presenterFactory: () -> TimelinePresenter, -) : TimelineTabItemV2 { - override val filterConfig: TimelineFilterConfig - get() = presentation?.filterConfig ?: TimelineFilterConfig() - - override fun createPresenter(): TimelinePresenter = presenterFactory().also { it.bindTimelineTabItemId(id) } - - override fun withPresentationOverrides( - title: String, - icon: IconType, - appearancePatch: AppearancePatch?, - enabled: Boolean, - filterConfig: TimelineFilterConfig, - ): TimelineTabItemV2 { - val updatedPresentation = - presentation?.withOverrides( - titleOverride = title, - iconOverride = icon, - appearancePatch = appearancePatch, - enabled = enabled, - filterConfig = filterConfig, - ) ?: TimelinePresentation( - titleOverride = title, - iconOverride = icon, - appearanceOverride = appearancePatch?.takeUnless { it == AppearancePatch.EMPTY }?.toBag(), - enabled = enabled, - filterConfig = filterConfig, - ) - return SourceTimelineTabItemV2( - id = id, - source = source, - presentation = updatedPresentation, - title = UiText.Raw(title), - icon = icon, - appearancePatch = updatedPresentation.appearance, - enabled = updatedPresentation.enabled, - presenterFactory = presenterFactory, - ) - } - - public companion object { - public fun runtime( - id: String, - title: UiText, - icon: IconType, - appearancePatch: AppearancePatch? = null, - enabled: Boolean = true, - createPresenter: () -> TimelinePresenter, - ): SourceTimelineTabItemV2 = - SourceTimelineTabItemV2( - id = id, - source = null, - presentation = null, - title = title, - icon = icon, - appearancePatch = appearancePatch, - enabled = enabled, - presenterFactory = createPresenter, - ) - - internal fun fromSlot( - slot: TimelineSlot, - source: TimelineSourceRef, - presenterFactory: () -> TimelinePresenter, - ): SourceTimelineTabItemV2 = - SourceTimelineTabItemV2( - id = slot.id, - source = source, - presentation = slot.presentation, - title = slot.title, - icon = slot.icon, - appearancePatch = slot.presentation.appearance, - enabled = slot.presentation.enabled, - presenterFactory = presenterFactory, - ) - - internal fun fromSource( - source: TimelineSourceRef, - presenterFactory: () -> TimelinePresenter, - ): SourceTimelineTabItemV2 = - SourceTimelineTabItemV2( - id = source.id, - source = source, - presentation = null, - title = source.title, - icon = source.icon, - appearancePatch = null, - enabled = true, - presenterFactory = presenterFactory, - ) - } -} - -@Immutable -public class GroupTimelineTabItemV2 internal constructor( - override val id: String, - public val children: List, - public val mergePolicy: TimelineMergePolicy, - internal val source: GroupSource, - internal val presentation: TimelinePresentation, - override val title: UiText, - override val icon: IconType, - override val appearancePatch: AppearancePatch?, - override val enabled: Boolean, -) : TimelineTabItemV2 { - override val filterConfig: TimelineFilterConfig - get() = presentation.filterConfig - - override fun createPresenter(): TimelinePresenter = - when (source) { - GroupSource.SystemHome -> { - SystemHomeMixedTimelinePresenter(id = id) - } - - GroupSource.Manual -> { - MixedTimelinePresenter(id = id) - } - } - - override fun withPresentationOverrides( - title: String, - icon: IconType, - appearancePatch: AppearancePatch?, - enabled: Boolean, - filterConfig: TimelineFilterConfig, - ): TimelineTabItemV2 { - val updatedPresentation = - presentation.withOverrides( - titleOverride = title, - iconOverride = icon, - appearancePatch = appearancePatch, - enabled = enabled, - filterConfig = filterConfig, - ) - return GroupTimelineTabItemV2( - id = id, - children = children, - mergePolicy = mergePolicy, - source = source, - presentation = updatedPresentation, - title = UiText.Raw(title), - icon = icon, - appearancePatch = updatedPresentation.appearance, - enabled = updatedPresentation.enabled, - ) - } -} - -public val TimelineTabItemV2.isSystemHomeMixedTimeline: Boolean - get() = this is GroupTimelineTabItemV2 && source == GroupSource.SystemHome - -internal fun TimelineTabItemV2.findById(id: String): TimelineTabItemV2? = - when (this) { - is SourceTimelineTabItemV2 -> takeIf { this.id == id } - is GroupTimelineTabItemV2 -> takeIf { this.id == id } ?: children.firstNotNullOfOrNull { it.findById(id) } - } - -internal fun List.findById(id: String): TimelineTabItemV2? = firstNotNullOfOrNull { it.findById(id) } - -public fun List.withSystemHomeMixedTimelineEnabled( - enabled: Boolean, - mergePolicy: TimelineMergePolicy? = null, - filterConfig: TimelineFilterConfig? = null, -): List { - val existingGroup = filterIsInstance().firstOrNull { it.source == GroupSource.SystemHome } - val tabsWithoutSystemGroup = filterNot { it.isSystemHomeMixedTimeline } - if (!enabled || tabsWithoutSystemGroup.size < 2) { - return tabsWithoutSystemGroup - } - - val systemGroup = - GroupTimelineTabItemV2( - id = SYSTEM_HOME_MIXED_TIMELINE_ID, - children = tabsWithoutSystemGroup, - mergePolicy = mergePolicy ?: existingGroup?.mergePolicy ?: TimelineMergePolicy.TimePerPage, - source = GroupSource.SystemHome, - presentation = - (existingGroup?.presentation ?: TimelinePresentation()).copy( - filterConfig = filterConfig ?: existingGroup?.filterConfig ?: TimelineFilterConfig(), - ), - title = existingGroup?.title ?: UiStrings.MixedTimeline.asText(), - icon = existingGroup?.icon ?: UiIcon.Rss.asType(), - appearancePatch = existingGroup?.appearancePatch, - enabled = existingGroup?.enabled ?: true, - ) - val targetIndex = - indexOfFirst { it.isSystemHomeMixedTimeline } - .takeIf { it >= 0 } - ?: 0 - return tabsWithoutSystemGroup - .toMutableList() - .apply { - add(minOf(targetIndex, size), systemGroup) - } -} - -internal const val SYSTEM_HOME_MIXED_TIMELINE_ID = "mixed_timeline_system_home" - -@Immutable -@Serializable -public data class TimelineSlot( - val id: String, - val content: TimelineSlotContent, - val presentation: TimelinePresentation = TimelinePresentation(), -) { - val title: UiText = - presentation.titleOverride?.let { UiText.Raw(it) } ?: when (content) { - is TimelineSlotContent.Source -> content.source.title - is TimelineSlotContent.Group -> UiStrings.MixedTimeline.asText() - } - - val icon: IconType = - presentation.iconOverride ?: when (content) { - is TimelineSlotContent.Source -> content.source.icon - is TimelineSlotContent.Group -> UiIcon.Rss.asType() - } -} - -@Immutable -@Serializable -public data class TimelinePresentation internal constructor( - val titleOverride: String? = null, - val iconOverride: IconType? = null, - private val appearanceOverride: AppearanceBag? = null, - val enabled: Boolean = true, - val filterConfig: TimelineFilterConfig = TimelineFilterConfig(), -) { - public val appearance: AppearancePatch? by lazy { - appearanceOverride?.toPatch() - } - - internal fun withOverrides( - titleOverride: String?, - iconOverride: IconType?, - appearancePatch: AppearancePatch?, - enabled: Boolean, - filterConfig: TimelineFilterConfig, - ): TimelinePresentation = - TimelinePresentation( - titleOverride = titleOverride, - iconOverride = iconOverride, - appearanceOverride = appearancePatch?.takeUnless { it == AppearancePatch.EMPTY }?.toBag(), - enabled = enabled, - filterConfig = filterConfig, - ) -} - -@Immutable -@Serializable -public sealed interface TimelineSlotContent { - @Immutable - @Serializable - @SerialName("source") - public data class Source( - @SerialName("target") - public val source: TimelineSourceRef, - ) : TimelineSlotContent - - @Immutable - @Serializable - @SerialName("group") - public data class Group( - public val children: List = emptyList(), - public val source: GroupSource = GroupSource.Manual, - public val mergePolicy: TimelineMergePolicy = TimelineMergePolicy.Time, - ) : TimelineSlotContent -} - -@Immutable -@Serializable -public enum class GroupSource { - Manual, - SystemHome, -} - -@Immutable -@Serializable -public enum class TimelineMergePolicy { - Time, - TimePerPage, - Staggered, -} - -@Immutable -@Serializable -public data class TimelineSourceRef( - val id: String, - val specId: String, - val title: UiText, - val icon: IconType, - val data: String, -) { - public companion object { - @OptIn(ExperimentalSerializationApi::class) - public fun xqtDeviceFollow(accountKey: MicroBlogKey): TimelineSourceRef = - TimelineSourceRef( - id = "xqt.device_follow:$accountKey", - specId = "xqt.device_follow", - title = UiStrings.Posts.asText(), - icon = UiIcon.List.asType(), - data = - ProtoBuf.encodeToHexString( - TimelineSpec.AccountBasedData.serializer(), - TimelineSpec.AccountBasedData(accountKey), - ), - ) - } -} - -internal fun TimelineSourceRef.toSlot( - slotId: String = id, - presentation: TimelinePresentation = TimelinePresentation(), -): TimelineSlot = - TimelineSlot( - id = slotId, - content = TimelineSlotContent.Source(this), - presentation = presentation, - ) - -public data class TimelineSpec( - val id: String, - val title: UiStrings, - val icon: IconType, - val serializer: KSerializer, - val targetId: (data: T) -> String, - private val presenterFactory: (data: T) -> TimelinePresenter, -) { - @OptIn(ExperimentalSerializationApi::class) - public fun target( - data: T, - title: UiText = this.title.asText(), - icon: IconType = this.icon, - ): TimelineSourceRef = - TimelineSourceRef( - id = "$id:${targetId(data)}", - specId = id, - title = title, - icon = icon, - data = ProtoBuf.encodeToHexString(serializer, data), - ) - - public fun tabItem( - data: T, - title: UiText = this.title.asText(), - icon: IconType = this.icon, - ): SourceTimelineTabItemV2 { - val source = - target( - data = data, - title = title, - icon = icon, - ) - return SourceTimelineTabItemV2.fromSource(source) { - createPresenter(source.data) - } - } - - @OptIn(ExperimentalSerializationApi::class) - public fun createPresenter(encodedData: String): TimelinePresenter { - val data = ProtoBuf.decodeFromHexString(serializer, encodedData) - return presenterFactory(data) - } - - public interface Data - - public interface AccountData : Data { - public val accountKey: MicroBlogKey - } - - @Serializable - public open class AccountBasedData( - public override val accountKey: MicroBlogKey, - ) : AccountData - - @Serializable - public data class AccountResourceData( - public override val accountKey: MicroBlogKey, - public val resourceId: String, - ) : AccountData -} - -public data class ShortcutSpec( - val title: UiStrings, - val icon: UiIcon, - val target: Target, -) { - public sealed interface Target { - public data class Timeline( - val source: TimelineSourceRef, - ) : Target - - public data class Route( - val route: DeeplinkRoute, - ) : Target - } -} - -public class TimelineResolver internal constructor() { - private val specs: Map> by lazy { - ( - PlatformType.entries - .flatMap { it.spec.timelineSpecs } + - RssTimelineSpecs.timelineSpecs - ).distinctBy { it.id } - .associateBy { it.id } - } - - internal fun toTabItem(slot: TimelineSlot): TimelineTabItemV2 = - when (val content = slot.content) { - is TimelineSlotContent.Source -> { - SourceTimelineTabItemV2.fromSlot( - slot = slot, - source = content.source, - presenterFactory = { resolvePresenter(content.source) }, - ) - } - - is TimelineSlotContent.Group -> { - GroupTimelineTabItemV2( - id = slot.id, - children = content.children.map { toTabItem(it) }.filter { it.enabled }, - mergePolicy = content.mergePolicy, - source = content.source, - presentation = slot.presentation, - title = slot.title, - icon = slot.icon, - appearancePatch = slot.presentation.appearance, - enabled = slot.presentation.enabled, - ) - } - } - - public fun toTabItem(source: TimelineSourceRef): SourceTimelineTabItemV2 = - SourceTimelineTabItemV2.fromSource( - source = source, - presenterFactory = { resolvePresenter(source) }, - ) - - internal fun toSlot(item: TimelineTabItemV2): TimelineSlot = - when (item) { - is SourceTimelineTabItemV2 -> { - val source = - item.source - ?: throw IllegalArgumentException("Runtime timeline tab cannot be persisted: ${item.id}") - source.toSlot( - slotId = item.id, - presentation = item.presentation ?: TimelinePresentation(), - ) - } - - is GroupTimelineTabItemV2 -> { - TimelineSlot( - id = item.id, - content = - TimelineSlotContent.Group( - children = item.children.map { toSlot(it) }, - source = item.source, - mergePolicy = item.mergePolicy, - ), - presentation = item.presentation, - ) - } - } - - private fun resolvePresenter(source: TimelineSourceRef): TimelinePresenter = resolveSpec(source).createPresenter(source.data) - - @OptIn(ExperimentalSerializationApi::class) - public fun resolveAccountKey(item: TimelineTabItemV2): MicroBlogKey? = - when (item) { - is SourceTimelineTabItemV2 -> item.source?.let(::resolveAccountKey) - is GroupTimelineTabItemV2 -> null - } - - @OptIn(ExperimentalSerializationApi::class) - internal fun resolveAccountKey(slot: TimelineSlot): MicroBlogKey? = - when (val content = slot.content) { - is TimelineSlotContent.Source -> resolveAccountKey(content.source) - is TimelineSlotContent.Group -> null - } - - @OptIn(ExperimentalSerializationApi::class) - private fun resolveAccountKey(source: TimelineSourceRef): MicroBlogKey? { - val spec = resolveSpec(source) - val data = ProtoBuf.decodeFromHexString(spec.serializer, source.data) - return (data as? TimelineSpec.AccountData)?.accountKey - } - - private fun resolveSpec(source: TimelineSourceRef): TimelineSpec = - specs[source.specId] - ?: throw IllegalArgumentException("No timeline spec found for source ID: ${source.specId}") -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/PlatformDetector.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/PlatformDetector.kt deleted file mode 100644 index 58fda7dd41..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/PlatformDetector.kt +++ /dev/null @@ -1,8 +0,0 @@ -package dev.dimension.flare.data.network.nodeinfo - -internal interface PlatformDetector { - val priority: Int - get() = 0 - - suspend fun detect(host: String): NodeData? -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/BlueskyPlatformSpec.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/BlueskyPlatformSpec.kt deleted file mode 100644 index 81f98e5b4c..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/BlueskyPlatformSpec.kt +++ /dev/null @@ -1,107 +0,0 @@ -package dev.dimension.flare.data.platform - -import dev.dimension.flare.common.deeplink.DeepLinkMapping -import dev.dimension.flare.common.deeplink.DeepLinkPattern -import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource -import dev.dimension.flare.data.model.IconType -import dev.dimension.flare.data.model.tab.SourceTimelineTabItemV2 -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import dev.dimension.flare.data.network.bluesky.BlueskyPlatformDetector -import dev.dimension.flare.data.network.nodeinfo.PlatformDetector -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.model.PlatformSpec -import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.model.PlatformTypeMetadata -import dev.dimension.flare.ui.model.UiIcon -import dev.dimension.flare.ui.model.UiInstanceMetadata -import dev.dimension.flare.ui.model.UiList -import dev.dimension.flare.ui.model.UiStrings -import dev.dimension.flare.ui.model.UiText -import dev.dimension.flare.ui.model.asType -import dev.dimension.flare.ui.presenter.home.bluesky.BlueskyBookmarkTimelinePresenter -import dev.dimension.flare.ui.presenter.home.bluesky.BlueskyFeedTimelinePresenter -import io.ktor.http.Url -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList - -internal data object BlueskyPlatformSpec : PlatformSpec { - override val type = PlatformType.Bluesky - override val metadata = - PlatformTypeMetadata( - displayName = "Bluesky", - icon = UiIcon.Bluesky, - ) - override val detector: PlatformDetector = BlueskyPlatformDetector - - override fun agreementUrl(host: String): String? = "https://bsky.social/about/support/tos" - - override fun deepLinkPatterns(host: String): ImmutableList> = - buildList { - add(DeepLinkPattern(DeepLinkMapping.Type.Profile.serializer(), Url("https://$host/profile/{handle}"))) - add(DeepLinkPattern(DeepLinkMapping.Type.BlueskyPost.serializer(), Url("https://$host/profile/{handle}/post/{id}"))) - if (host == "bsky.social") { - add(DeepLinkPattern(DeepLinkMapping.Type.Profile.serializer(), Url("https://bsky.app/profile/{handle}"))) - add(DeepLinkPattern(DeepLinkMapping.Type.BlueskyPost.serializer(), Url("https://bsky.app/profile/{handle}/post/{id}"))) - } - }.toImmutableList() - - internal val bookmarkTimelineSpec = - TimelineSpec( - id = "bluesky.bookmark", - title = UiStrings.Bookmark, - icon = UiIcon.Bookmark.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - BlueskyBookmarkTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - internal val feedTimelineSpec = - TimelineSpec( - id = "bluesky.feed", - title = UiStrings.Feeds, - icon = UiIcon.Feeds.asType(), - serializer = TimelineSpec.AccountResourceData.serializer(), - targetId = { "${it.accountKey}:${it.resourceId}" }, - presenterFactory = { - BlueskyFeedTimelinePresenter( - accountType = AccountType.Specific(it.accountKey), - uri = it.resourceId, - ) - }, - ) - - override val timelineSpecs: ImmutableList> = - persistentListOf( - CommonTimelineSpecs.home, - CommonTimelineSpecs.list, - bookmarkTimelineSpec, - feedTimelineSpec, - ) - - override suspend fun instanceMetadata(host: String): UiInstanceMetadata = - throw UnsupportedOperationException("${type.name} is not supported yet") - - override fun guestDataSource( - host: String, - locale: String, - ): MicroblogDataSource = throw UnsupportedOperationException("${type.name} guest data source is not supported yet") -} - -internal fun UiList.Feed.toTimelineTabItemV2(accountKey: MicroBlogKey): TimelineTabItemV2 { - val source = - BlueskyPlatformSpec.feedTimelineSpec.target( - data = TimelineSpec.AccountResourceData(accountKey, id), - title = UiText.Raw(title), - icon = avatar?.let { IconType.Url(it) } ?: UiIcon.Feeds.asType(), - ) - return SourceTimelineTabItemV2.fromSource(source) { - BlueskyPlatformSpec.feedTimelineSpec.createPresenter(source.data) - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/CommonTimelineSpecs.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/CommonTimelineSpecs.kt deleted file mode 100644 index 91bf635d5b..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/CommonTimelineSpecs.kt +++ /dev/null @@ -1,73 +0,0 @@ -package dev.dimension.flare.data.platform - -import dev.dimension.flare.data.model.IconType -import dev.dimension.flare.data.model.tab.SourceTimelineTabItemV2 -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.UiIcon -import dev.dimension.flare.ui.model.UiList -import dev.dimension.flare.ui.model.UiStrings -import dev.dimension.flare.ui.model.UiText -import dev.dimension.flare.ui.model.asType -import dev.dimension.flare.ui.presenter.home.DiscoverStatusTimelinePresenter -import dev.dimension.flare.ui.presenter.home.HomeTimelinePresenter -import dev.dimension.flare.ui.presenter.list.ListTimelinePresenter - -internal object CommonTimelineSpecs { - val home = - TimelineSpec( - id = "common.home", - title = UiStrings.Home, - icon = UiIcon.Home.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - HomeTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - val discover = - TimelineSpec( - id = "common.discover", - title = UiStrings.Discover, - icon = UiIcon.Search.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - DiscoverStatusTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - val list = - TimelineSpec( - id = "common.list", - title = UiStrings.List, - icon = UiIcon.List.asType(), - serializer = TimelineSpec.AccountResourceData.serializer(), - targetId = { "${it.accountKey}:${it.resourceId}" }, - presenterFactory = { - ListTimelinePresenter( - accountType = AccountType.Specific(it.accountKey), - listId = it.resourceId, - ) - }, - ) -} - -internal fun UiList.List.toTimelineTabItemV2(accountKey: MicroBlogKey): TimelineTabItemV2 { - val source = - CommonTimelineSpecs.list.target( - data = TimelineSpec.AccountResourceData(accountKey, id), - title = UiText.Raw(title), - icon = avatar?.let { IconType.Url(it) } ?: UiIcon.List.asType(), - ) - return SourceTimelineTabItemV2.fromSource(source) { - CommonTimelineSpecs.list.createPresenter(source.data) - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/MastodonPlatformSpec.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/MastodonPlatformSpec.kt deleted file mode 100644 index 988df3ae37..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/MastodonPlatformSpec.kt +++ /dev/null @@ -1,128 +0,0 @@ -package dev.dimension.flare.data.platform - -import dev.dimension.flare.common.deeplink.DeepLinkMapping -import dev.dimension.flare.common.deeplink.DeepLinkPattern -import dev.dimension.flare.data.datasource.guest.mastodon.GuestMastodonDataSource -import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.network.mastodon.MastodonInstanceService -import dev.dimension.flare.data.network.mastodon.MastodonPlatformDetector -import dev.dimension.flare.data.network.nodeinfo.PlatformDetector -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.model.PlatformSpec -import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.model.PlatformTypeMetadata -import dev.dimension.flare.ui.model.UiIcon -import dev.dimension.flare.ui.model.UiInstanceMetadata -import dev.dimension.flare.ui.model.UiStrings -import dev.dimension.flare.ui.model.asType -import dev.dimension.flare.ui.model.mapper.render -import dev.dimension.flare.ui.presenter.home.mastodon.MastodonBookmarkTimelinePresenter -import dev.dimension.flare.ui.presenter.home.mastodon.MastodonFavouriteTimelinePresenter -import dev.dimension.flare.ui.presenter.home.mastodon.MastodonLocalTimelinePresenter -import dev.dimension.flare.ui.presenter.home.mastodon.MastodonPublicTimelinePresenter -import io.ktor.http.Url -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf - -internal data object MastodonPlatformSpec : PlatformSpec { - override val type = PlatformType.Mastodon - override val metadata = - PlatformTypeMetadata( - displayName = "Mastodon", - icon = UiIcon.Mastodon, - ) - override val detector: PlatformDetector = MastodonPlatformDetector - - override fun agreementUrl(host: String): String? = "https://$host/about" - - override fun deepLinkPatterns(host: String): ImmutableList> = - persistentListOf( - DeepLinkPattern( - DeepLinkMapping.Type.Profile.serializer(), - Url("https://$host/@{handle}"), - ), - DeepLinkPattern( - DeepLinkMapping.Type.Post.serializer(), - Url("https://$host/@{handle}/{id}"), - ), - ) - - internal val localTimelineSpec = - TimelineSpec( - id = "mastodon.local", - title = UiStrings.MastodonLocal, - icon = UiIcon.Local.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - MastodonLocalTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - internal val publicTimelineSpec = - TimelineSpec( - id = "mastodon.public", - title = UiStrings.MastodonPublic, - icon = UiIcon.World.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - MastodonPublicTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - internal val bookmarkTimelineSpec = - TimelineSpec( - id = "mastodon.bookmark", - title = UiStrings.Bookmark, - icon = UiIcon.Bookmark.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - MastodonBookmarkTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - internal val favouriteTimelineSpec = - TimelineSpec( - id = "mastodon.favourite", - title = UiStrings.Favourite, - icon = UiIcon.Favourite.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - MastodonFavouriteTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - override val timelineSpecs: ImmutableList> = - persistentListOf( - CommonTimelineSpecs.home, - CommonTimelineSpecs.discover, - CommonTimelineSpecs.list, - localTimelineSpec, - publicTimelineSpec, - bookmarkTimelineSpec, - favouriteTimelineSpec, - ) - - override suspend fun instanceMetadata(host: String): UiInstanceMetadata = MastodonInstanceService("https://$host/").instance().render() - - override fun guestDataSource( - host: String, - locale: String, - ): MicroblogDataSource = - GuestMastodonDataSource( - host = host, - locale = locale, - ) -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/MisskeyPlatformSpec.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/MisskeyPlatformSpec.kt deleted file mode 100644 index c109fbea16..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/MisskeyPlatformSpec.kt +++ /dev/null @@ -1,183 +0,0 @@ -package dev.dimension.flare.data.platform - -import dev.dimension.flare.common.deeplink.DeepLinkMapping -import dev.dimension.flare.common.deeplink.DeepLinkPattern -import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource -import dev.dimension.flare.data.model.IconType -import dev.dimension.flare.data.model.tab.SourceTimelineTabItemV2 -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import dev.dimension.flare.data.network.misskey.MisskeyPlatformDetector -import dev.dimension.flare.data.network.misskey.MisskeyService -import dev.dimension.flare.data.network.misskey.api.model.MetaRequest -import dev.dimension.flare.data.network.nodeinfo.PlatformDetector -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.model.PlatformSpec -import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.model.PlatformTypeMetadata -import dev.dimension.flare.ui.model.UiIcon -import dev.dimension.flare.ui.model.UiInstanceMetadata -import dev.dimension.flare.ui.model.UiList -import dev.dimension.flare.ui.model.UiStrings -import dev.dimension.flare.ui.model.UiText -import dev.dimension.flare.ui.model.asType -import dev.dimension.flare.ui.model.mapper.render -import dev.dimension.flare.ui.presenter.home.misskey.MissKeyLocalTimelinePresenter -import dev.dimension.flare.ui.presenter.home.misskey.MissKeyPublicTimelinePresenter -import dev.dimension.flare.ui.presenter.home.misskey.MisskeyFavouriteTimelinePresenter -import dev.dimension.flare.ui.presenter.home.misskey.MisskeyHybridTimelinePresenter -import dev.dimension.flare.ui.presenter.list.AntennasTimelinePresenter -import dev.dimension.flare.ui.presenter.list.ChannelTimelinePresenter -import io.ktor.http.Url -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf - -internal data object MisskeyPlatformSpec : PlatformSpec { - override val type = PlatformType.Misskey - override val metadata = - PlatformTypeMetadata( - displayName = "Misskey", - icon = UiIcon.Misskey, - ) - override val detector: PlatformDetector = MisskeyPlatformDetector - - override fun agreementUrl(host: String): String? = "https://$host/about" - - override fun deepLinkPatterns(host: String): ImmutableList> = - persistentListOf( - DeepLinkPattern(DeepLinkMapping.Type.Profile.serializer(), Url("https://$host/@{handle}")), - DeepLinkPattern(DeepLinkMapping.Type.Post.serializer(), Url("https://$host/notes/{id}")), - ) - - internal val favouriteTimelineSpec = - TimelineSpec( - id = "misskey.favourite", - title = UiStrings.Favourite, - icon = UiIcon.Favourite.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - MisskeyFavouriteTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - internal val hybridTimelineSpec = - TimelineSpec( - id = "misskey.hybrid", - title = UiStrings.Social, - icon = UiIcon.Featured.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - MisskeyHybridTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - internal val localTimelineSpec = - TimelineSpec( - id = "misskey.local", - title = UiStrings.MastodonLocal, - icon = UiIcon.Local.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - MissKeyLocalTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - internal val globalTimelineSpec = - TimelineSpec( - id = "misskey.global", - title = UiStrings.MastodonPublic, - icon = UiIcon.World.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - MissKeyPublicTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - internal val antennaTimelineSpec = - TimelineSpec( - id = "misskey.antenna", - title = UiStrings.Antenna, - icon = UiIcon.Rss.asType(), - serializer = TimelineSpec.AccountResourceData.serializer(), - targetId = { "${it.accountKey}:${it.resourceId}" }, - presenterFactory = { - AntennasTimelinePresenter( - accountType = AccountType.Specific(it.accountKey), - id = it.resourceId, - ) - }, - ) - - internal val channelTimelineSpec = - TimelineSpec( - id = "misskey.channel", - title = UiStrings.Channel, - icon = UiIcon.Channel.asType(), - serializer = TimelineSpec.AccountResourceData.serializer(), - targetId = { "${it.accountKey}:${it.resourceId}" }, - presenterFactory = { - ChannelTimelinePresenter( - accountType = AccountType.Specific(it.accountKey), - id = it.resourceId, - ) - }, - ) - - override val timelineSpecs: ImmutableList> = - persistentListOf( - CommonTimelineSpecs.home, - CommonTimelineSpecs.discover, - CommonTimelineSpecs.list, - favouriteTimelineSpec, - hybridTimelineSpec, - localTimelineSpec, - globalTimelineSpec, - antennaTimelineSpec, - channelTimelineSpec, - ) - - override suspend fun instanceMetadata(host: String): UiInstanceMetadata = - MisskeyService("https://$host/api/").meta(MetaRequest()).render() - - override fun guestDataSource( - host: String, - locale: String, - ): MicroblogDataSource = throw UnsupportedOperationException("${type.name} guest data source is not supported yet") -} - -internal fun UiList.Antenna.toTimelineTabItemV2(accountKey: MicroBlogKey): TimelineTabItemV2 { - val source = - MisskeyPlatformSpec.antennaTimelineSpec.target( - data = TimelineSpec.AccountResourceData(accountKey, id), - title = UiText.Raw(title), - icon = UiIcon.Rss.asType(), - ) - return SourceTimelineTabItemV2.fromSource(source) { - MisskeyPlatformSpec.antennaTimelineSpec.createPresenter(source.data) - } -} - -internal fun UiList.Channel.toTimelineTabItemV2(accountKey: MicroBlogKey): TimelineTabItemV2 { - val source = - MisskeyPlatformSpec.channelTimelineSpec.target( - data = TimelineSpec.AccountResourceData(accountKey, id), - title = UiText.Raw(title), - icon = banner?.let { IconType.Url(it) } ?: UiIcon.Channel.asType(), - ) - return SourceTimelineTabItemV2.fromSource(source) { - MisskeyPlatformSpec.channelTimelineSpec.createPresenter(source.data) - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/NostrPlatformSpec.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/NostrPlatformSpec.kt deleted file mode 100644 index d4bd558815..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/NostrPlatformSpec.kt +++ /dev/null @@ -1,42 +0,0 @@ -package dev.dimension.flare.data.platform - -import dev.dimension.flare.common.deeplink.DeepLinkMapping -import dev.dimension.flare.common.deeplink.DeepLinkPattern -import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.network.nodeinfo.PlatformDetector -import dev.dimension.flare.data.network.nostr.NostrPlatformDetector -import dev.dimension.flare.model.PlatformSpec -import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.model.PlatformTypeMetadata -import dev.dimension.flare.ui.model.UiIcon -import dev.dimension.flare.ui.model.UiInstanceMetadata -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf - -internal data object NostrPlatformSpec : PlatformSpec { - override val type = PlatformType.Nostr - override val metadata = - PlatformTypeMetadata( - displayName = "Nostr", - icon = UiIcon.Nostr, - ) - override val detector: PlatformDetector = NostrPlatformDetector - - override fun agreementUrl(host: String): String? = null - - override fun deepLinkPatterns(host: String): ImmutableList> = persistentListOf() - - override val timelineSpecs: ImmutableList> = - persistentListOf( - CommonTimelineSpecs.home, - ) - - override suspend fun instanceMetadata(host: String): UiInstanceMetadata = - throw UnsupportedOperationException("${type.name} is not supported yet") - - override fun guestDataSource( - host: String, - locale: String, - ): MicroblogDataSource = throw UnsupportedOperationException("${type.name} guest data source is not supported yet") -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/RssTimelineSpecs.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/RssTimelineSpecs.kt deleted file mode 100644 index 04a226b5df..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/RssTimelineSpecs.kt +++ /dev/null @@ -1,71 +0,0 @@ -package dev.dimension.flare.data.platform - -import dev.dimension.flare.data.database.app.model.SubscriptionType -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.ui.model.UiIcon -import dev.dimension.flare.ui.model.UiStrings -import dev.dimension.flare.ui.model.asType -import dev.dimension.flare.ui.presenter.home.rss.AllRssTimelinePresenter -import dev.dimension.flare.ui.presenter.home.rss.RssTimelinePresenter -import dev.dimension.flare.ui.presenter.home.rss.SubscriptionTimelinePresenter -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.serialization.Serializable - -internal data object RssTimelineSpecs { - @Serializable - data class RssData( - val feedUrl: String, - ) : TimelineSpec.Data - - @Serializable - data object AllRssData : TimelineSpec.Data - - @Serializable - data class SubscriptionData( - val subscriptionUrl: String, - val subscriptionType: SubscriptionType, - ) : TimelineSpec.Data - - val rss = - TimelineSpec( - id = "rss.feed", - title = UiStrings.Rss, - icon = UiIcon.Rss.asType(), - serializer = RssData.serializer(), - targetId = { it.feedUrl }, - presenterFactory = { RssTimelinePresenter(it.feedUrl) }, - ) - - val allRss = - TimelineSpec( - id = "rss.all", - title = UiStrings.AllRssFeeds, - icon = UiIcon.Rss.asType(), - serializer = AllRssData.serializer(), - targetId = { "all" }, - presenterFactory = { AllRssTimelinePresenter() }, - ) - - val subscription = - TimelineSpec( - id = "rss.subscription", - title = UiStrings.Rss, - icon = UiIcon.Rss.asType(), - serializer = SubscriptionData.serializer(), - targetId = { "${it.subscriptionType.name}:${it.subscriptionUrl}" }, - presenterFactory = { - SubscriptionTimelinePresenter( - type = it.subscriptionType, - url = it.subscriptionUrl, - ) - }, - ) - - val timelineSpecs: ImmutableList> = - persistentListOf( - rss, - allRss, - subscription, - ) -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/VvoPlatformSpec.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/VvoPlatformSpec.kt deleted file mode 100644 index 12d2582d28..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/VvoPlatformSpec.kt +++ /dev/null @@ -1,79 +0,0 @@ -package dev.dimension.flare.data.platform - -import dev.dimension.flare.common.deeplink.DeepLinkMapping -import dev.dimension.flare.common.deeplink.DeepLinkPattern -import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.network.nodeinfo.PlatformDetector -import dev.dimension.flare.data.network.vvo.VVOPlatformDetector -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.model.PlatformSpec -import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.model.PlatformTypeMetadata -import dev.dimension.flare.model.vvo -import dev.dimension.flare.ui.model.UiIcon -import dev.dimension.flare.ui.model.UiInstanceMetadata -import dev.dimension.flare.ui.model.UiStrings -import dev.dimension.flare.ui.model.asType -import dev.dimension.flare.ui.presenter.home.vvo.VVOFavouriteTimelinePresenter -import dev.dimension.flare.ui.presenter.home.vvo.VVOLikeTimelinePresenter -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf - -internal data object VvoPlatformSpec : PlatformSpec { - override val type = PlatformType.VVo - override val metadata = - PlatformTypeMetadata( - displayName = vvo, - icon = UiIcon.Weibo, - ) - override val detector: PlatformDetector = VVOPlatformDetector - - override fun agreementUrl(host: String): String? = null - - override fun deepLinkPatterns(host: String): ImmutableList> = persistentListOf() - - internal val favoriteTimelineSpec = - TimelineSpec( - id = "vvo.favorite", - title = UiStrings.Bookmark, - icon = UiIcon.Bookmark.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - VVOFavouriteTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - internal val likedTimelineSpec = - TimelineSpec( - id = "vvo.liked", - title = UiStrings.Liked, - icon = UiIcon.Heart.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - VVOLikeTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - override val timelineSpecs: ImmutableList> = - persistentListOf( - CommonTimelineSpecs.home, - CommonTimelineSpecs.discover, - favoriteTimelineSpec, - likedTimelineSpec, - ) - - override suspend fun instanceMetadata(host: String): UiInstanceMetadata = - throw UnsupportedOperationException("${type.name} is not supported yet") - - override fun guestDataSource( - host: String, - locale: String, - ): MicroblogDataSource = throw UnsupportedOperationException("${type.name} guest data source is not supported yet") -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/XqtPlatformSpec.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/XqtPlatformSpec.kt deleted file mode 100644 index ecd49add98..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/platform/XqtPlatformSpec.kt +++ /dev/null @@ -1,125 +0,0 @@ -package dev.dimension.flare.data.platform - -import dev.dimension.flare.common.deeplink.DeepLinkMapping -import dev.dimension.flare.common.deeplink.DeepLinkPattern -import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.network.nodeinfo.PlatformDetector -import dev.dimension.flare.data.network.xqt.XQTPlatformDetector -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.model.PlatformSpec -import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.model.PlatformTypeMetadata -import dev.dimension.flare.model.xqtHost -import dev.dimension.flare.model.xqtOldHost -import dev.dimension.flare.ui.model.UiIcon -import dev.dimension.flare.ui.model.UiInstanceMetadata -import dev.dimension.flare.ui.model.UiStrings -import dev.dimension.flare.ui.model.asType -import dev.dimension.flare.ui.presenter.home.xqt.XQTBookmarkTimelinePresenter -import dev.dimension.flare.ui.presenter.home.xqt.XQTDeviceFollowTimelinePresenter -import dev.dimension.flare.ui.presenter.home.xqt.XQTFeaturedTimelinePresenter -import io.ktor.http.Url -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList - -internal data object XqtPlatformSpec : PlatformSpec { - override val type = PlatformType.xQt - override val metadata = - PlatformTypeMetadata( - displayName = "X", - icon = UiIcon.X, - ) - override val detector: PlatformDetector = XQTPlatformDetector - - override fun agreementUrl(host: String): String = "https://help.x.com/en/rules-and-policies/x-rules" - - override fun deepLinkPatterns(host: String): ImmutableList> { - val profile = - listOf( - "https://$xqtHost/{handle}", - "https://$xqtOldHost/{handle}", - "https://www.$xqtHost/{handle}", - "https://www.$xqtOldHost/{handle}", - ) - val post = - listOf( - "https://$xqtHost/{handle}/status/{id}", - "https://$xqtOldHost/{handle}/", - "https://www.$xqtHost/{handle}/status/{id}", - "https://www.$xqtOldHost/{handle}/", - ) - val media = - listOf( - "https://$xqtHost/{handle}/status/{id}/photo/{index}", - "https://$xqtOldHost/{handle}/status/{id}/photo/{index}", - "https://www.$xqtHost/{handle}/status/{id}/photo/{index}", - "https://www.$xqtOldHost/{handle}/status/{id}/photo/{index}", - ) - return ( - profile.map { DeepLinkPattern(DeepLinkMapping.Type.Profile.serializer(), Url(it)) } + - post.map { DeepLinkPattern(DeepLinkMapping.Type.Post.serializer(), Url(it)) } + - media.map { DeepLinkPattern(DeepLinkMapping.Type.PostMedia.serializer(), Url(it)) } - ).toImmutableList() - } - - internal val featuredTimelineSpec = - TimelineSpec( - id = "xqt.featured", - title = UiStrings.Featured, - icon = UiIcon.Featured.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - XQTFeaturedTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - internal val bookmarkTimelineSpec = - TimelineSpec( - id = "xqt.bookmark", - title = UiStrings.Bookmark, - icon = UiIcon.Bookmark.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - XQTBookmarkTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - internal val deviceFollowTimelineSpec = - TimelineSpec( - id = "xqt.device_follow", - title = UiStrings.Posts, - icon = UiIcon.List.asType(), - serializer = TimelineSpec.AccountBasedData.serializer(), - targetId = { it.accountKey.toString() }, - presenterFactory = { - XQTDeviceFollowTimelinePresenter( - AccountType.Specific(it.accountKey), - ) - }, - ) - - override val timelineSpecs: ImmutableList> = - persistentListOf( - CommonTimelineSpecs.home, - CommonTimelineSpecs.list, - featuredTimelineSpec, - bookmarkTimelineSpec, - deviceFollowTimelineSpec, - ) - - override suspend fun instanceMetadata(host: String): UiInstanceMetadata = - throw UnsupportedOperationException("${type.name} is not supported yet") - - override fun guestDataSource( - host: String, - locale: String, - ): MicroblogDataSource = throw UnsupportedOperationException("${type.name} guest data source is not supported yet") -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountRepository.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountRepository.kt deleted file mode 100644 index c1c1a27f05..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountRepository.kt +++ /dev/null @@ -1,310 +0,0 @@ -package dev.dimension.flare.data.repository - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.runtime.produceState -import androidx.compose.runtime.remember -import dev.dimension.flare.common.Locale -import dev.dimension.flare.common.decodeJson -import dev.dimension.flare.common.encodeJson -import dev.dimension.flare.data.database.app.AppDatabase -import dev.dimension.flare.data.database.app.model.DbAccount -import dev.dimension.flare.data.database.cache.CacheDatabase -import dev.dimension.flare.data.database.cache.connect -import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource -import dev.dimension.flare.data.datastore.AppDataStore -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.model.spec -import dev.dimension.flare.ui.model.UiAccount -import dev.dimension.flare.ui.model.UiAccount.Companion.createDataSource -import dev.dimension.flare.ui.model.UiAccount.Companion.toUi -import dev.dimension.flare.ui.model.UiState -import dev.dimension.flare.ui.model.collectAsUiState -import dev.dimension.flare.ui.model.takeSuccess -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.distinctUntilChangedBy -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlin.time.Clock - -@Stable -internal class AccountRepository internal constructor( - private val appDatabase: AppDatabase, - private val coroutineScope: CoroutineScope, - internal val appDataStore: AppDataStore, - private val cacheDatabase: CacheDatabase, -) { - internal val activeAccount: Flow> by lazy { - appDatabase - .accountDao() - .activeAccount() - .distinctUntilChangedBy { - it?.account_key - }.map { - it?.toUi() - }.map { - if (it == null) { - UiState.Error(NoActiveAccountException) - } else { - UiState.Success(it) - } - } - } - internal val allAccounts: Flow> by lazy { - appDatabase.accountDao().sortedAccounts().map { - it.map { it.toUi() }.toImmutableList() - } - } - private val dataSourceCacheMutex = Mutex() - private val dataSourceCache by lazy { - mutableMapOf() - } - - private val addAccountFlow by lazy { - MutableStateFlow(null) - } - internal val onAdded: Flow by lazy { - addAccountFlow - .mapNotNull { it } - .distinctUntilChangedBy { it.accountKey } - } - private val removeAccountFlow by lazy { - MutableStateFlow(null) - } - internal val onRemoved: Flow by lazy { - removeAccountFlow - .mapNotNull { it } - .distinctUntilChangedBy { it } - } - - internal fun addAccount( - account: UiAccount, - credential: UiAccount.Credential, - ) = coroutineScope.launch { - val existingAccount = appDatabase.accountDao().getAccount(account.accountKey) - val dbAccount = - existingAccount?.copy( - credential_json = credential.encodeJson(), - last_active = Clock.System.now().toEpochMilliseconds(), - ) ?: DbAccount( - account_key = account.accountKey, - platform_type = account.platformType, - credential_json = credential.encodeJson(), - last_active = Clock.System.now().toEpochMilliseconds(), - sort_id = appDatabase.accountDao().getMaxSortId()?.plus(1) ?: 0L, - ) - appDatabase.accountDao().insert(dbAccount) - addAccountFlow.value = account - } - - internal fun updateCredential( - accountKey: MicroBlogKey, - credential: UiAccount.Credential, - ) = coroutineScope.launch { - appDatabase.accountDao().setCredential( - accountKey, - credential.encodeJson(), - ) - } - - internal fun updateAccountOrder(accounts: List) = - coroutineScope.launch { - appDatabase.connect { - accounts.forEachIndexed { index, accountKey -> - appDatabase.accountDao().setSortId( - accountKey, - index.toLong(), - ) - } - } - } - - internal fun setActiveAccount(accountKey: MicroBlogKey) = - coroutineScope.launch { - appDatabase.accountDao().setLastActive( - accountKey, - Clock.System.now().toEpochMilliseconds(), - ) - } - - internal fun delete(accountKey: MicroBlogKey) = - coroutineScope.launch { - appDataStore.composeConfigData.updateData { - it.copy( - lastAccounts = it.lastAccounts.filterNot { key -> key == accountKey }, - ) - } - removeAccountFlow.value = accountKey - dataSourceCacheMutex.withLock { - val datasource = dataSourceCache.remove(accountKey) - if (datasource is AutoCloseable) { - datasource.close() - } - } - cacheDatabase.pagingTimelineDao().deleteByAccountType( - AccountType.Specific(accountKey), - ) - cacheDatabase.statusDao().deleteByAccountType( - AccountType.Specific(accountKey), - ) - cacheDatabase.userDao().deleteHistoryByAccountType( - AccountType.Specific(accountKey), - ) - cacheDatabase.emojiDao().clearHistoryByAccountType( - AccountType.Specific(accountKey), - ) - cacheDatabase.messageDao().clearMessageTimeline( - AccountType.Specific(accountKey), - ) - appDatabase.accountDao().delete(accountKey) - } - - internal fun getFlow(accountKey: MicroBlogKey): Flow> = - appDatabase.accountDao().get(accountKey).map { - if (it == null) { - UiState.Error(NoActiveAccountException) - } else { - UiState.Success(it.toUi()) - } - } - - internal suspend fun find(accountKey: MicroBlogKey): UiAccount? = - appDatabase - .accountDao() - .get(accountKey) - .firstOrNull() - ?.toUi() - - internal inline fun credentialFlow(accountKey: MicroBlogKey): Flow = - appDatabase - .accountDao() - .get(accountKey) - .mapNotNull { it } - .map { - it.credential_json.decodeJson() - } - - internal suspend fun getOrCreateDataSource(account: UiAccount): MicroblogDataSource = - dataSourceCacheMutex.withLock { - dataSourceCache.getOrPut(account.accountKey) { - account.createDataSource() - } - } -} - -public data object NoActiveAccountException : Exception("No active account.") - -@Immutable -public data class LoginExpiredException( - val accountKey: MicroBlogKey, - val platformType: PlatformType, -) : Exception("Login expired.") - -@Immutable -public data class RequireReLoginException( - val accountKey: MicroBlogKey, - val platformType: PlatformType, -) : Exception("Login required.") - -@Composable -internal fun accountProvider( - accountType: AccountType, - repository: AccountRepository, -): State> = - produceState>( - initialValue = UiState.Loading(), - key1 = accountType, - ) { - when (accountType) { - AccountType.Guest, - is AccountType.GuestHost, - -> { - flowOf( - UiState.Error( - NoActiveAccountException, - ), - ) - } - - is AccountType.Specific -> { - repository.getFlow(accountType.accountKey) - } - }.collect { - value = it - } - } - -@Composable -internal fun accountServiceProvider( - accountType: AccountType, - repository: AccountRepository, -): UiState = - remember( - accountType, - ) { - accountServiceFlow( - accountType = accountType, - repository = repository, - ) - }.collectAsUiState().value - -@OptIn(ExperimentalCoroutinesApi::class) -internal fun accountServiceFlow( - accountType: AccountType, - repository: AccountRepository, -): Flow = - when (accountType) { - AccountType.Guest -> { - flowOf( - PlatformType.Mastodon.spec.guestDataSource( - host = "mastodon.social", - locale = Locale.language, - ), - ) - } - - is AccountType.GuestHost -> { - flowOf( - PlatformType.Mastodon.spec.guestDataSource( - host = accountType.host, - locale = Locale.language, - ), - ) - } - - is AccountType.Specific -> { - repository - .getFlow(accountType.accountKey) - .mapNotNull { it.takeSuccess() } - .distinctUntilChangedBy { it.accountKey } - .map { account -> repository.getOrCreateDataSource(account) } - } - } - -internal fun activeAccountFlow(repository: AccountRepository): Flow = - repository - .activeAccount - .map { it.takeSuccess() } - .distinctUntilChangedBy { it?.accountKey } - -internal fun allAccountServicesFlow(repository: AccountRepository): Flow> = - repository.allAccounts.map { accounts -> - accounts - .map { - repository.getOrCreateDataSource(it) - }.toImmutableList() - } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/ApplicationRepository.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/ApplicationRepository.kt deleted file mode 100644 index 7d1e2013ba..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/ApplicationRepository.kt +++ /dev/null @@ -1,52 +0,0 @@ -package dev.dimension.flare.data.repository - -import dev.dimension.flare.data.database.app.AppDatabase -import dev.dimension.flare.data.database.app.model.DbApplication -import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.ui.model.UiApplication -import dev.dimension.flare.ui.model.UiApplication.Companion.toUi -import kotlinx.coroutines.flow.firstOrNull - -internal class ApplicationRepository( - private val database: AppDatabase, -) { - suspend fun findByHost(host: String): UiApplication? = - database - .applicationDao() - .get(host) - .firstOrNull() - ?.toUi() - - suspend fun addApplication( - host: String, - credentialJson: String, - platformType: PlatformType, - ) { - database.applicationDao().insert( - DbApplication( - host = host, - credential_json = credentialJson, - platform_type = platformType, - ), - ) - } - - suspend fun setPendingOAuth( - host: String, - pendingOAuth: Boolean, - ) { - database.applicationDao().updatePending(host, if (pendingOAuth) 1L else 0L) - } - - suspend fun getPendingOAuth(): UiApplication? = - database - .applicationDao() - .getPending() - .firstOrNull() - ?.firstOrNull() - ?.toUi() - - suspend fun clearPendingOAuth() { - database.applicationDao().clearPending() - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/SettingsRepository.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/SettingsRepository.kt deleted file mode 100644 index 770bf765cb..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/SettingsRepository.kt +++ /dev/null @@ -1,228 +0,0 @@ -package dev.dimension.flare.data.repository - -import androidx.compose.runtime.Stable -import androidx.datastore.core.DataStore -import androidx.datastore.core.DataStoreFactory -import androidx.datastore.core.okio.OkioStorage -import dev.dimension.flare.common.protobufSerializer -import dev.dimension.flare.data.datastore.AppDataStore -import dev.dimension.flare.data.datastore.model.AppSettings -import dev.dimension.flare.data.io.PlatformPathProducer -import dev.dimension.flare.data.model.AppearanceSettings -import dev.dimension.flare.data.model.appearance.AppearanceBag -import dev.dimension.flare.data.model.appearance.AppearanceKey -import dev.dimension.flare.data.model.appearance.AppearancePatch -import dev.dimension.flare.data.model.appearance.GlobalAppearance -import dev.dimension.flare.data.model.appearance.TimelineAppearance -import dev.dimension.flare.data.model.appearance.migrateAppearanceV1ToV2 -import dev.dimension.flare.data.model.appearance.toBag -import dev.dimension.flare.data.model.appearance.toGlobalAppearance -import dev.dimension.flare.data.model.appearance.toPatch -import dev.dimension.flare.data.model.appearance.toTimelineAppearance -import dev.dimension.flare.data.model.tab.TabSettingsV2 -import dev.dimension.flare.data.model.tab.TimelineResolver -import dev.dimension.flare.data.model.tab.TimelineTabItemV2 -import dev.dimension.flare.data.model.tab.findById -import dev.dimension.flare.data.model.tab.isSystemHomeMixedTimeline -import dev.dimension.flare.data.model.tab.migrateTabSettingsV1ToV2 -import dev.dimension.flare.data.model.tab.withSystemHomeMixedTimelineEnabled -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.distinctUntilChangedBy -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import okio.FileSystem -import okio.SYSTEM - -@Stable -public class SettingsRepository internal constructor( - private val pathProducer: PlatformPathProducer, - private val appDataStore: AppDataStore, - private val timelineResolver: TimelineResolver, -) { - private val appearanceBagStore by lazy { - createDataStore( - name = "appearance_bag.pb", - serializer = protobufSerializer(AppearanceBag()), - ) - } - - private val appearanceMigrationMutex = Mutex() - private var appearanceMigrationCompleted = false - private val tabSettingsMigrationMutex = Mutex() - private var tabSettingsMigrationCompleted = false - - internal val appearanceBag: Flow by lazy { - flow { - ensureAppearanceMigrated() - emitAll( - appearanceBagStore.data - .distinctUntilChanged(), - ) - } - } - - public val appearancePatch: Flow by lazy { - appearanceBag - .map { it.toPatch() } - .distinctUntilChanged() - } - public val globalAppearance: Flow by lazy { - appearancePatch - .map { it.toGlobalAppearance() } - .distinctUntilChanged() - } - public val timelineAppearance: Flow by lazy { - appearancePatch - .map { it.toTimelineAppearance() } - .distinctUntilChanged() - } - private val appSettingsStore: DataStore by lazy { appDataStore.appSettingsStore } - public val appSettings: Flow by lazy { - appSettingsStore.data - } - - public suspend fun ensureAppearanceMigrated() { - if (appearanceMigrationCompleted) return - appearanceMigrationMutex.withLock { - if (appearanceMigrationCompleted) return - migrateAppearanceV1ToV2(pathProducer, appearanceBagStore) - appearanceMigrationCompleted = true - } - } - - public suspend fun updateAppearance( - key: AppearanceKey, - value: T, - ) { - updateAppearance { set(key, value) } - } - - public suspend fun updateAppearance(block: AppearancePatch.() -> AppearancePatch) { - ensureAppearanceMigrated() - appearanceBagStore.updateData { bag -> - bag.toPatch().block().toBag() - } - } - - public suspend fun clearAppearance(key: AppearanceKey<*>) { - updateAppearance { clear(key) } - } - - public suspend fun replaceAppearance(patch: AppearancePatch) { - ensureAppearanceMigrated() - appearanceBagStore.updateData { patch.toBag() } - } - - internal suspend fun replaceAppearance(bag: AppearanceBag) { - ensureAppearanceMigrated() - appearanceBagStore.updateData { bag } - } - - internal suspend fun replaceAppearance(settings: AppearanceSettings) { - ensureAppearanceMigrated() - appearanceBagStore.updateData { settings.toPatch().toBag() } - } - -// private val tabSettingsStore by lazy { -// createDataStore( -// name = "tab_settings.pb", -// serializer = protobufSerializer(TabSettings()), -// ) -// } - -// public val tabSettings: Flow by lazy { -// tabSettingsStore.data -// } -// -// public suspend fun updateTabSettings(block: TabSettings.() -> TabSettings) { -// tabSettingsStore.updateData(block) -// } - - private val tabSettingsV2Store by lazy { - createDataStore( - name = "tab_settings_v2.pb", - serializer = protobufSerializer(TabSettingsV2()), - ) - } - - internal val tabSettingsV2: Flow by lazy { - flow { - ensureTabSettingsMigrated() - emitAll(tabSettingsV2Store.data) - } - } - - public val homeTimelineTabs: Flow> by lazy { - tabSettingsV2 - .distinctUntilChangedBy { it.homeSlots } - .map { settings -> - val tabs = - settings.homeSlots - .map { timelineResolver.toTabItem(it) } -// .filter { it.enabled } - tabs.withSystemHomeMixedTimelineEnabled( - enabled = tabs.any { it.isSystemHomeMixedTimeline }, - ) - } - } - - internal fun homeTimelineTab(id: String): Flow = - homeTimelineTabs.map { tabs -> - tabs.findById(id) - } - - internal suspend fun updateTabSettingsV2(block: TabSettingsV2.() -> TabSettingsV2) { - ensureTabSettingsMigrated() - tabSettingsV2Store.updateData(block) - } - - internal suspend fun replaceHomeTimelineTabs(tabs: List) { - updateTabSettingsV2 { - val normalizedTabs = - tabs.withSystemHomeMixedTimelineEnabled( - enabled = tabs.any { it.isSystemHomeMixedTimeline }, - ) - copy( - homeSlots = - normalizedTabs - .distinctBy { it.id } - .map { timelineResolver.toSlot(it) }, - ) - } - } - - public suspend fun ensureTabSettingsMigrated() { - if (tabSettingsMigrationCompleted) return - tabSettingsMigrationMutex.withLock { - if (tabSettingsMigrationCompleted) return - migrateTabSettingsV1ToV2( - pathProducer = pathProducer, - tabSettingsV2Store = tabSettingsV2Store, - ) - tabSettingsMigrationCompleted = true - } - } - - public suspend fun updateAppSettings(block: AppSettings.() -> AppSettings) { - appSettingsStore.updateData(block) - } - - private inline fun createDataStore( - name: String, - serializer: androidx.datastore.core.okio.OkioSerializer, - ): DataStore = - DataStoreFactory.create( - storage = - OkioStorage( - fileSystem = FileSystem.SYSTEM, - serializer = serializer, - producePath = { - pathProducer.dataStoreFile(name) - }, - ), - ) -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationModels.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationModels.kt deleted file mode 100644 index d252bb4773..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationModels.kt +++ /dev/null @@ -1,55 +0,0 @@ -package dev.dimension.flare.data.translation - -import dev.dimension.flare.data.database.cache.model.TranslationDisplayMode -import dev.dimension.flare.data.database.cache.model.TranslationEntityType -import dev.dimension.flare.data.database.cache.model.TranslationPayload -import dev.dimension.flare.data.datastore.model.AppSettings -import dev.dimension.flare.ui.render.TranslationDocument -import kotlinx.serialization.Serializable - -@Serializable -internal data class PreTranslationBatchDocument( - val version: Int = 1, - val targetLanguage: String = "", - val items: List, -) - -@Serializable -internal data class PreTranslationBatchItem( - val entityKey: String, - val status: PreTranslationBatchItemStatus = PreTranslationBatchItemStatus.Completed, - val payload: PreTranslationBatchPayload? = null, - val reason: String? = null, -) - -@Serializable -internal enum class PreTranslationBatchItemStatus { - Completed, - Skipped, -} - -@Serializable -internal data class PreTranslationBatchPayload( - val content: TranslationDocument? = null, - val contentWarning: TranslationDocument? = null, - val title: TranslationDocument? = null, - val description: TranslationDocument? = null, -) - -internal data class ActivePreTranslationSettings( - val targetLanguage: String, - val autoTranslateExcludedLanguages: List, - val appSettings: AppSettings, - val providerCacheKey: String, -) - -internal data class PreparedTranslationCandidate( - val entityType: TranslationEntityType, - val entityKey: String, - val targetLanguage: String, - val sourceHash: String, - val sourcePayload: TranslationPayload, - val sourceDocument: PreTranslationBatchPayload, - val attemptCount: Int, - val displayMode: TranslationDisplayMode, -) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationSupport.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationSupport.kt deleted file mode 100644 index 76d798ae8b..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/TranslationSupport.kt +++ /dev/null @@ -1,49 +0,0 @@ -package dev.dimension.flare.data.translation - -import dev.dimension.flare.data.database.cache.model.TranslationDisplayOptions -import dev.dimension.flare.data.datastore.AppDataStore -import dev.dimension.flare.data.datastore.model.AiPromptDefaults -import dev.dimension.flare.data.datastore.model.AppSettings -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map - -internal object TranslationSettingsSupport { - fun displayOptionsFlow(appDataStore: AppDataStore): Flow = - appDataStore.appSettingsStore.data - .map(::displayOptions) - .distinctUntilChanged() - - fun displayOptions(settings: AppSettings): TranslationDisplayOptions = - TranslationDisplayOptions( - translationEnabled = true, - autoDisplayEnabled = settings.translateConfig.preTranslate, - providerCacheKey = settings.translationProviderCacheKey(), - ) -} - -internal object TranslationPromptFormatter { - fun buildTranslatePrompt( - settings: AppSettings, - targetLanguage: String, - sourceTemplate: String, - ): String = - settings.aiConfig.translatePrompt - .ifBlank { - AiPromptDefaults.TRANSLATE_PROMPT - }.replace("{target_language}", targetLanguage) - .replace("{source_text}", sourceTemplate) -} - -internal object TranslationResponseSanitizer { - fun clean(content: String): String = - content - .removePrefix("```json") - .removePrefix("```html") - .removePrefix("```xml") - .removePrefix("```markup") - .removePrefix("```text") - .removePrefix("```") - .removeSuffix("```") - .trim() -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/di/AppModule.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/di/AppModule.kt deleted file mode 100644 index 8d6cedbebb..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/di/AppModule.kt +++ /dev/null @@ -1,3 +0,0 @@ -package dev.dimension.flare.di - -internal fun appModule() = listOf(commonModule, platformModule) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/di/CommonModule.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/di/CommonModule.kt deleted file mode 100644 index 874396e294..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/di/CommonModule.kt +++ /dev/null @@ -1,69 +0,0 @@ -package dev.dimension.flare.di - -import dev.dimension.flare.data.database.provideAppDatabase -import dev.dimension.flare.data.database.provideCacheDatabase -import dev.dimension.flare.data.datasource.nostr.DatabaseNostrCache -import dev.dimension.flare.data.datasource.nostr.NostrCache -import dev.dimension.flare.data.model.tab.TimelineResolver -import dev.dimension.flare.data.network.ai.AiCompletionService -import dev.dimension.flare.data.network.ai.OpenAIService -import dev.dimension.flare.data.network.rss.Readability -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.AccountTabSyncCoordinator -import dev.dimension.flare.data.repository.ApplicationRepository -import dev.dimension.flare.data.repository.DraftMediaStore -import dev.dimension.flare.data.repository.DraftRepository -import dev.dimension.flare.data.repository.DraftSendingRecoveryCoordinator -import dev.dimension.flare.data.repository.LocalFilterRepository -import dev.dimension.flare.data.repository.SearchHistoryRepository -import dev.dimension.flare.data.repository.SettingsRepository -import dev.dimension.flare.data.translation.OnlinePreTranslationService -import dev.dimension.flare.data.translation.PreTranslationService -import dev.dimension.flare.ui.presenter.compose.ComposeUseCase -import dev.dimension.flare.ui.presenter.compose.RestoreDraftUseCase -import dev.dimension.flare.ui.presenter.compose.SaveDraftUseCase -import dev.dimension.flare.ui.presenter.compose.SendDraftUseCase -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -internal val commonModule = - module { - singleOf(::AccountRepository) - single(createdAtStart = true) { AccountTabSyncCoordinator(get(), get(), get(), get()) } - singleOf(::provideAppDatabase) - singleOf(::provideCacheDatabase) - single { DatabaseNostrCache(get()) } - singleOf(::ApplicationRepository) - single { - DraftMediaStore(get()) - } - single { - DraftRepository( - database = get(), - draftMediaStore = get(), - ) - } - single(createdAtStart = true) { DraftSendingRecoveryCoordinator(get(), get()) } - singleOf(::LocalFilterRepository) - single { CoroutineScope(Dispatchers.IO) } - singleOf(::SaveDraftUseCase) - singleOf(::RestoreDraftUseCase) - single { - SendDraftUseCase( - draftRepository = get(), - accountRepository = get(), - draftMediaStore = get(), - ) - } - singleOf(::ComposeUseCase) - singleOf(::SearchHistoryRepository) - singleOf(::SettingsRepository) - singleOf(::Readability) - singleOf(::OpenAIService) - singleOf(::AiCompletionService) - single { OnlinePreTranslationService(get(), get(), get(), get()) } - singleOf(::TimelineResolver) - } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/model/PlatformSpec.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/model/PlatformSpec.kt deleted file mode 100644 index 63c07e68cd..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/model/PlatformSpec.kt +++ /dev/null @@ -1,50 +0,0 @@ -package dev.dimension.flare.model - -import dev.dimension.flare.common.deeplink.DeepLinkMapping -import dev.dimension.flare.common.deeplink.DeepLinkPattern -import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.network.nodeinfo.PlatformDetector -import dev.dimension.flare.data.platform.BlueskyPlatformSpec -import dev.dimension.flare.data.platform.MastodonPlatformSpec -import dev.dimension.flare.data.platform.MisskeyPlatformSpec -import dev.dimension.flare.data.platform.NostrPlatformSpec -import dev.dimension.flare.data.platform.VvoPlatformSpec -import dev.dimension.flare.data.platform.XqtPlatformSpec -import dev.dimension.flare.ui.model.UiIcon -import dev.dimension.flare.ui.model.UiInstanceMetadata -import kotlinx.collections.immutable.ImmutableList - -internal interface PlatformSpec { - val type: PlatformType - val metadata: PlatformTypeMetadata - val detector: PlatformDetector - val timelineSpecs: ImmutableList> - - fun agreementUrl(host: String): String? - - fun deepLinkPatterns(host: String): ImmutableList> - - suspend fun instanceMetadata(host: String): UiInstanceMetadata - - fun guestDataSource( - host: String, - locale: String, - ): MicroblogDataSource -} - -public val PlatformType.icon: UiIcon - get() = spec.metadata.icon - -public fun PlatformType.agreementUrl(host: String): String? = spec.agreementUrl(host) - -internal val PlatformType.spec: PlatformSpec - get() = - when (this) { - PlatformType.Nostr -> NostrPlatformSpec - PlatformType.Mastodon -> MastodonPlatformSpec - PlatformType.Misskey -> MisskeyPlatformSpec - PlatformType.Bluesky -> BlueskyPlatformSpec - PlatformType.xQt -> XqtPlatformSpec - PlatformType.VVo -> VvoPlatformSpec - } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/humanizer/Number.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/humanizer/Number.kt deleted file mode 100644 index d53a9f58c0..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/humanizer/Number.kt +++ /dev/null @@ -1,33 +0,0 @@ -package dev.dimension.flare.ui.humanizer - -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject -import kotlin.math.round -import kotlin.time.Instant - -internal fun Float.humanizePercentage(): String { - val roundedNumber = round(this * 100 * 100).toDouble() / 100 - return "$roundedNumber%" -} - -internal object Formatter : KoinComponent { - val platformFormatter: PlatformFormatter by inject() - - internal fun Long.humanize(): String = platformFormatter.formatNumber(number = this) - - internal fun Instant.relative(): String = platformFormatter.formatRelativeInstant(this) - - internal fun Instant.full(): String = platformFormatter.formatFullInstant(this) - - internal fun Instant.absolute(): String = platformFormatter.formatAbsoluteInstant(this) -} - -internal interface PlatformFormatter { - fun formatNumber(number: Long): String - - fun formatRelativeInstant(instant: Instant): String - - fun formatFullInstant(instant: Instant): String - - fun formatAbsoluteInstant(instant: Instant): String -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/ClickContext.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/ClickContext.kt deleted file mode 100644 index e72b2e8519..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/ClickContext.kt +++ /dev/null @@ -1,70 +0,0 @@ -package dev.dimension.flare.ui.model - -import dev.dimension.flare.data.datasource.microblog.PostEvent -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.route.DeeplinkRoute -import dev.dimension.flare.ui.route.toUri -import kotlinx.serialization.Serializable - -public data class ClickContext( - val launcher: UriLauncher, -) - -@Serializable -internal sealed interface ClickEvent { - @Serializable - data object Noop : ClickEvent - - @Serializable - data class Deeplink private constructor( - val url: String, - ) : ClickEvent { - constructor(route: DeeplinkRoute) : this(route.toUri()) - constructor(route: DeeplinkEvent) : this(route.toUri()) - } - - companion object { - fun event( - accountKey: MicroBlogKey?, - postEvent: PostEvent, - ) = if (accountKey == null) { - Noop - } else { - Deeplink( - DeeplinkEvent( - accountKey = accountKey, - postEvent = postEvent, - ), - ) - } - - inline fun event( - accountKey: MicroBlogKey?, - eventCreator: (accountKey: MicroBlogKey) -> PostEvent, - ) = if (accountKey == null) { - Noop - } else { - Deeplink( - DeeplinkEvent( - accountKey = accountKey, - postEvent = eventCreator.invoke(accountKey), - ), - ) - } - } -} - -internal val ClickEvent.onClicked: ClickContext.() -> Unit - get() { - when (this) { - is ClickEvent.Deeplink -> { - return { - launcher.launch(url) - } - } - - is ClickEvent.Noop -> { - return {} - } - } - } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiAccount.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiAccount.kt deleted file mode 100644 index 72a4f09e06..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiAccount.kt +++ /dev/null @@ -1,337 +0,0 @@ -package dev.dimension.flare.ui.model - -import androidx.compose.runtime.Immutable -import dev.dimension.flare.common.decodeJson -import dev.dimension.flare.data.database.app.model.DbAccount -import dev.dimension.flare.data.datasource.bluesky.BlueskyDataSource -import dev.dimension.flare.data.datasource.mastodon.MastodonDataSource -import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource -import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource -import dev.dimension.flare.data.datasource.nostr.NostrDataSource -import dev.dimension.flare.data.datasource.pleroma.PleromaDataSource -import dev.dimension.flare.data.datasource.vvo.VVODataSource -import dev.dimension.flare.data.datasource.xqt.XQTDataSource -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.model.PlatformType -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import sh.christian.ozone.oauth.OAuthToken - -@Immutable -@Serializable -internal sealed interface NostrSignerCredential { - val stableId: String - - @Immutable - @Serializable - @SerialName("NostrSignerLocalKey") - data class LocalKey( - val nsec: String, - ) : NostrSignerCredential { - override val stableId: String - get() = "local:$nsec" - } - - @Immutable - @Serializable - @SerialName("NostrSignerBunker") - data class Bunker( - val uri: String, - val userPubkeyHex: String? = null, - val signerRelay: String? = null, - val secret: String? = null, - ) : NostrSignerCredential { - override val stableId: String - get() = "bunker:$uri" - } - - @Immutable - @Serializable - @SerialName("NostrSignerAmber") - data class Amber( - val userPubkeyHex: String, - val packageName: String? = null, - val approvedSignerPubkey: String? = null, - ) : NostrSignerCredential { - override val stableId: String - get() = "amber:$userPubkeyHex:${packageName.orEmpty()}:${approvedSignerPubkey.orEmpty()}" - } -} - -@Immutable -public sealed class UiAccount { - public abstract val accountKey: MicroBlogKey - public abstract val platformType: PlatformType - - @Immutable - @Serializable - internal sealed interface Credential - - @Immutable - internal data class Nostr( - override val accountKey: MicroBlogKey, - ) : UiAccount() { - override val platformType: PlatformType - get() = PlatformType.Nostr - - @Immutable - @Serializable - @SerialName("NostrCredential") - data class Credential( - val pubkeyHex: String = "", - val relays: List = emptyList(), - val mediaServerUrl: String = "https://blossom.nostr.build/", - val signer: NostrSignerCredential? = null, - @SerialName("nsec") - internal val legacyNsec: String? = null, - ) : UiAccount.Credential - } - - @Immutable - internal data class Mastodon( - override val accountKey: MicroBlogKey, - internal val forkType: Credential.ForkType = Credential.ForkType.Mastodon, - internal val instance: String, - val nodeType: String? = null, - ) : UiAccount() { - override val platformType: PlatformType - get() = PlatformType.Mastodon - - @Immutable - @Serializable - @SerialName("MastodonCredential") - data class Credential( - val instance: String, - val accessToken: String, - val forkType: ForkType = ForkType.Mastodon, - // to support more forks in the future - val nodeType: String? = null, - ) : UiAccount.Credential { - enum class ForkType { - Mastodon, - Pleroma, - } - } - } - - @Immutable - internal data class Misskey( - override val accountKey: MicroBlogKey, - internal val host: String, - // to support more forks in the future - val nodeType: String? = null, - ) : UiAccount() { - override val platformType: PlatformType - get() = PlatformType.Misskey - - @Immutable - @Serializable - @SerialName("MisskeyCredential") - data class Credential( - val host: String, - val accessToken: String, - val nodeType: String? = null, - ) : UiAccount.Credential - } - - @Immutable - internal data class Bluesky( - override val accountKey: MicroBlogKey, - ) : UiAccount() { - override val platformType: PlatformType - get() = PlatformType.Bluesky - - @Serializable - sealed interface Credential : UiAccount.Credential { - val baseUrl: String - val accessToken: String - val refreshToken: String - - @Immutable - @Serializable - @SerialName("BlueskyCredential") - data class BlueskyCredential( - override val baseUrl: String, - override val accessToken: String, - override val refreshToken: String, - ) : Bluesky.Credential - - @Immutable - @Serializable - @SerialName("BlueskyOAuthCredential") - data class OAuthCredential( - override val baseUrl: String, - val oAuthToken: OAuthToken, - ) : Bluesky.Credential { - override val accessToken: String - get() = oAuthToken.accessToken - - override val refreshToken: String - get() = oAuthToken.refreshToken - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is OAuthCredential) return false - - if (baseUrl != other.baseUrl) return false - if (oAuthToken.accessToken != other.oAuthToken.accessToken) return false - if (oAuthToken.refreshToken != other.oAuthToken.refreshToken) return false - if (oAuthToken.nonce != other.oAuthToken.nonce) return false - if (oAuthToken.expiresIn != other.oAuthToken.expiresIn) return false - - return true - } - - override fun hashCode(): Int { - var result = baseUrl.hashCode() - result = 31 * result + oAuthToken.hashCode() - return result - } - } - } - } - - @Immutable - internal data class XQT( - override val accountKey: MicroBlogKey, - ) : UiAccount() { - override val platformType: PlatformType - get() = PlatformType.xQt - - @Immutable - @Serializable - @SerialName("XQTCredential") - data class Credential( - val chocolate: String, - ) : UiAccount.Credential - } - - @Immutable - internal data class VVo( - override val accountKey: MicroBlogKey, - ) : UiAccount() { - override val platformType: PlatformType - get() = PlatformType.VVo - - @Immutable - @Serializable - @SerialName("VVoCredential") - data class Credential( - val chocolate: String, - ) : UiAccount.Credential - } - - internal companion object { - fun UiAccount.createDataSource(): MicroblogDataSource = - when (this) { - is Nostr -> { - NostrDataSource( - accountKey = accountKey, - ) - } - - is Mastodon -> { - when (forkType) { - Mastodon.Credential.ForkType.Mastodon -> { - MastodonDataSource( - accountKey = accountKey, - instance = instance, - ) - } - - Mastodon.Credential.ForkType.Pleroma -> { - PleromaDataSource( - accountKey = accountKey, - instance = instance, - ) - } - } - } - - is Misskey -> { - MisskeyDataSource( - accountKey = accountKey, - host = host, - ) - } - - is Bluesky -> { - BlueskyDataSource( - accountKey = accountKey, - ) - } - - is XQT -> { - XQTDataSource( - accountKey = accountKey, - ) - } - - is VVo -> { - VVODataSource( - accountKey = accountKey, - ) - } - } - - fun DbAccount.toUi(): UiAccount = - when (platform_type) { - PlatformType.Nostr -> { - Nostr( - accountKey = account_key, - ) - } - - PlatformType.Mastodon -> { - val credential = credential_json.decodeJson() - Mastodon( - accountKey = account_key, - forkType = credential.forkType, - instance = credential.instance, - nodeType = credential.nodeType, - ) - } - - PlatformType.Misskey -> { - val credential = credential_json.decodeJson() - Misskey( - accountKey = account_key, - host = credential.host, - nodeType = credential.nodeType, - ) - } - - PlatformType.Bluesky -> { - Bluesky( - accountKey = account_key, - ) - } - - PlatformType.xQt -> { - XQT( - accountKey = account_key, - ) - } - - PlatformType.VVo -> { - VVo( - accountKey = account_key, - ) - } - } - } -} - -internal val UiAccount.Nostr.Credential.effectiveSigner: NostrSignerCredential? - get() = signer ?: legacyNsec?.let(NostrSignerCredential::LocalKey) - -internal fun UiAccount.Nostr.Credential.effectivePubkeyHex(accountKey: MicroBlogKey): String = pubkeyHex.ifBlank { accountKey.id } - -internal fun UiAccount.Nostr.Credential.normalized(accountKey: MicroBlogKey): UiAccount.Nostr.Credential = - copy( - pubkeyHex = effectivePubkeyHex(accountKey), - signer = effectiveSigner, - ) - -internal fun UiAccount.Nostr.Credential.signerStableId(accountKey: MicroBlogKey): String = - effectiveSigner?.stableId ?: "readonly:${effectivePubkeyHex(accountKey)}" diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiApplication.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiApplication.kt deleted file mode 100644 index 840592241a..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiApplication.kt +++ /dev/null @@ -1,86 +0,0 @@ -package dev.dimension.flare.ui.model - -import androidx.compose.runtime.Immutable -import dev.dimension.flare.common.decodeJson -import dev.dimension.flare.data.database.app.model.DbApplication -import dev.dimension.flare.data.network.mastodon.api.model.CreateApplicationResponse -import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.model.vvoHost -import dev.dimension.flare.model.xqtHost - -@Immutable -public sealed interface UiApplication { - public val host: String - - @Immutable - public data class Nostr internal constructor( - override val host: String, - ) : UiApplication - - @Immutable - public data class Mastodon internal constructor( - override val host: String, - internal val application: CreateApplicationResponse, - ) : UiApplication - - @Immutable - public data class Misskey internal constructor( - override val host: String, - val session: String, - ) : UiApplication - - @Immutable - public data class Bluesky internal constructor( - override val host: String, - ) : UiApplication - - @Immutable - public data object XQT : UiApplication { - override val host: String = xqtHost - } - - @Immutable - public data object VVo : UiApplication { - override val host: String = vvoHost - val loginUrl: String = "https://$host/login?backURL=https://$host/" - } - - public companion object { - internal fun DbApplication.toUi(): UiApplication = - when (platform_type) { - PlatformType.Nostr -> { - Nostr( - host = host, - ) - } - - PlatformType.Mastodon -> { - Mastodon( - host = host, - application = credential_json.decodeJson(), - ) - } - - PlatformType.Misskey -> { - Misskey( - host = host, - session = credential_json, - ) - } - - PlatformType.Bluesky -> { - Bluesky( - host = host, - ) - } - - PlatformType.xQt -> { - XQT - } - - PlatformType.VVo -> { - VVo - } - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ExportSettingsPresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ExportSettingsPresenter.kt deleted file mode 100644 index ae23c0a60e..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ExportSettingsPresenter.kt +++ /dev/null @@ -1,31 +0,0 @@ -package dev.dimension.flare.ui.presenter - -import androidx.compose.runtime.Composable -import dev.dimension.flare.common.encodeJson -import dev.dimension.flare.data.model.SettingsExport -import dev.dimension.flare.data.repository.SettingsRepository -import kotlinx.coroutines.flow.first -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class ExportSettingsPresenter : - PresenterBase(), - KoinComponent { - private val settingsRepository: SettingsRepository by inject() - - @Composable - override fun body(): ExportState = - object : ExportState { - override suspend fun export(): String = this@ExportSettingsPresenter.export() - } - - public suspend fun export(): String { - val export = - SettingsExport( - appearanceBag = settingsRepository.appearanceBag.first(), - appSettings = settingsRepository.appSettings.first(), - tabSettingsV2 = settingsRepository.tabSettingsV2.first(), - ) - return export.encodeJson(SettingsExport.serializer()) - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ImportSettingsPresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ImportSettingsPresenter.kt deleted file mode 100644 index 8a5c207212..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/ImportSettingsPresenter.kt +++ /dev/null @@ -1,108 +0,0 @@ -package dev.dimension.flare.ui.presenter - -import androidx.compose.runtime.Composable -import dev.dimension.flare.common.JSON -import dev.dimension.flare.common.decodeJson -import dev.dimension.flare.data.datastore.model.AppSettings -import dev.dimension.flare.data.model.AppearanceSettings -import dev.dimension.flare.data.model.LegacyAppearanceSettingsAndTabsExport -import dev.dimension.flare.data.model.LegacyAppearanceSettingsExport -import dev.dimension.flare.data.model.LegacySettingsExport -import dev.dimension.flare.data.model.SettingsExport -import dev.dimension.flare.data.model.appearance.AppearanceBag -import dev.dimension.flare.data.model.tab.TabSettingsV2 -import dev.dimension.flare.data.model.tab.toTabSettingsV2 -import dev.dimension.flare.data.repository.SettingsRepository -import kotlinx.serialization.json.jsonObject -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class ImportSettingsPresenter( - private val jsonContent: String, -) : PresenterBase(), - KoinComponent { - private val settingsRepository: SettingsRepository by inject() - - @Composable - override fun body(): ImportState = - object : ImportState { - override suspend fun import() { - this@ImportSettingsPresenter.import() - } - } - - public suspend fun import() { - val root = JSON.parseToJsonElement(jsonContent).jsonObject - val imported = - when { - "tabSettingsV2" in root && "appearanceBag" in root -> { - val export = jsonContent.decodeJson(SettingsExport.serializer()) - ImportedSettings( - appearance = ImportedAppearance.Bag(export.appearanceBag), - appSettings = export.appSettings, - tabSettingsV2 = export.tabSettingsV2, - ) - } - - "tabSettingsV2" in root && "appearanceSettings" in root -> { - val export = jsonContent.decodeJson(LegacyAppearanceSettingsExport.serializer()) - ImportedSettings( - appearance = ImportedAppearance.Settings(export.appearanceSettings), - appSettings = export.appSettings, - tabSettingsV2 = export.tabSettingsV2, - ) - } - - "tabSettings" in root && "appearanceBag" in root -> { - val export = jsonContent.decodeJson(LegacySettingsExport.serializer()) - ImportedSettings( - appearance = ImportedAppearance.Bag(export.appearanceBag), - appSettings = export.appSettings, - tabSettingsV2 = export.tabSettings.toTabSettingsV2(), - ) - } - - "tabSettings" in root && "appearanceSettings" in root -> { - val export = jsonContent.decodeJson(LegacyAppearanceSettingsAndTabsExport.serializer()) - ImportedSettings( - appearance = ImportedAppearance.Settings(export.appearanceSettings), - appSettings = export.appSettings, - tabSettingsV2 = export.tabSettings.toTabSettingsV2(), - ) - } - - else -> { - error("Unsupported settings export format") - } - } - - when (val appearance = imported.appearance) { - is ImportedAppearance.Bag -> settingsRepository.replaceAppearance(appearance.value) - is ImportedAppearance.Settings -> settingsRepository.replaceAppearance(appearance.value) - } - - settingsRepository.updateAppSettings { - imported.appSettings - } - - settingsRepository.updateTabSettingsV2 { - imported.tabSettingsV2 - } - } - - private data class ImportedSettings( - val appearance: ImportedAppearance, - val appSettings: AppSettings, - val tabSettingsV2: TabSettingsV2, - ) - - private sealed interface ImportedAppearance { - data class Bag( - val value: AppearanceBag, - ) : ImportedAppearance - - data class Settings( - val value: AppearanceSettings, - ) : ImportedAppearance - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/RestoreDraftUseCase.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/RestoreDraftUseCase.kt deleted file mode 100644 index b35409b832..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/RestoreDraftUseCase.kt +++ /dev/null @@ -1,104 +0,0 @@ -package dev.dimension.flare.ui.presenter.compose - -import dev.dimension.flare.data.database.app.model.DraftContent -import dev.dimension.flare.data.database.app.model.DraftMediaType -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.DraftRepository -import dev.dimension.flare.ui.model.UiDraft -import dev.dimension.flare.ui.model.UiDraftAccount -import dev.dimension.flare.ui.model.UiDraftMedia -import dev.dimension.flare.ui.model.UiDraftMediaType -import dev.dimension.flare.ui.model.UiDraftStatus -import dev.dimension.flare.ui.render.toUi -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.firstOrNull -import kotlin.time.Instant - -public class RestoreDraftUseCase internal constructor( - private val draftRepository: DraftRepository, - private val accountRepository: AccountRepository, -) { - public suspend operator fun invoke(groupId: String): UiDraft? { - val draft = draftRepository.draft(groupId).firstOrNull() ?: return null - val accounts = - draft.targets.mapNotNull { target -> - accountRepository.find(target.accountKey)?.let { - UiDraftAccount(account = it) - } - } - return UiDraft( - groupId = draft.groupId, - status = draft.toUiDraftStatus(), - updatedAt = Instant.fromEpochMilliseconds(draft.updatedAt).toUi(), - accounts = accounts.toImmutableList(), - data = draft.content.toComposeData(medias = emptyList()), - medias = - draft.medias - .map { media -> - UiDraftMedia( - cachePath = media.cachePath, - fileName = media.fileName, - type = - when (media.mediaType) { - DraftMediaType.IMAGE -> UiDraftMediaType.IMAGE - DraftMediaType.VIDEO -> UiDraftMediaType.VIDEO - DraftMediaType.OTHER -> UiDraftMediaType.OTHER - }, - altText = media.altText, - ) - }.toImmutableList(), - ) - } -} - -internal fun DraftContent.toComposeData( - medias: List, -): dev.dimension.flare.data.datasource.microblog.ComposeData = - dev.dimension.flare.data.datasource.microblog.ComposeData( - content = text, - visibility = visibility, - language = language, - medias = medias, - sensitive = sensitive, - spoilerText = spoilerText, - poll = - poll?.let { - dev.dimension.flare.data.datasource.microblog.ComposeData.Poll( - options = it.options, - expiredAfter = it.expiredAfter, - multiple = it.multiple, - ) - }, - localOnly = localOnly, - referenceStatus = - reference?.let { reference -> - dev.dimension.flare.data.datasource.microblog.ComposeData.ReferenceStatus( - composeStatus = reference.toComposeStatus(), - ) - }, - ) - -internal fun DraftContent.DraftReference.toComposeStatus(): ComposeStatus = - when (type) { - dev.dimension.flare.data.database.app.model.DraftReferenceType.QUOTE -> { - ComposeStatus.Quote(statusKey) - } - - dev.dimension.flare.data.database.app.model.DraftReferenceType.REPLY -> { - ComposeStatus.Reply(statusKey) - } - - dev.dimension.flare.data.database.app.model.DraftReferenceType.VVO_COMMENT -> { - ComposeStatus.VVOComment( - statusKey = statusKey, - rootId = requireNotNull(rootId), - ) - } - } - -private fun dev.dimension.flare.data.repository.DraftGroup.toUiDraftStatus(): UiDraftStatus = - when { - targets.any { it.status == dev.dimension.flare.data.database.app.model.DraftTargetStatus.SENDING } -> UiDraftStatus.SENDING - targets.any { it.status == dev.dimension.flare.data.database.app.model.DraftTargetStatus.FAILED } -> UiDraftStatus.FAILED - else -> UiDraftStatus.DRAFT - } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DiscoverStatusTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DiscoverStatusTimelinePresenter.kt deleted file mode 100644 index b91790dcbc..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DiscoverStatusTimelinePresenter.kt +++ /dev/null @@ -1,26 +0,0 @@ -package dev.dimension.flare.ui.presenter.home - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class DiscoverStatusTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - service.discoverStatuses() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTimelinePresenter.kt deleted file mode 100644 index d6faa72859..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/HomeTimelinePresenter.kt +++ /dev/null @@ -1,27 +0,0 @@ -package dev.dimension.flare.ui.presenter.home - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class HomeTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { - it.homeTimeline() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyBookmarkTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyBookmarkTimelinePresenter.kt deleted file mode 100644 index 048e529a91..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyBookmarkTimelinePresenter.kt +++ /dev/null @@ -1,29 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.bluesky - -import dev.dimension.flare.data.datasource.bluesky.BlueskyDataSource -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class BlueskyBookmarkTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { value -> - require(value is BlueskyDataSource) - value.bookmarkTimeline() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedTimelinePresenter.kt deleted file mode 100644 index c96d4f5c75..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/bluesky/BlueskyFeedTimelinePresenter.kt +++ /dev/null @@ -1,31 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.bluesky - -import dev.dimension.flare.data.datasource.bluesky.BlueskyDataSource -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class BlueskyFeedTimelinePresenter( - private val accountType: AccountType, - private val uri: String, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is BlueskyDataSource) - service.feedTimelineLoader(uri) - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/mastodon/MastodonBookmarkTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/mastodon/MastodonBookmarkTimelinePresenter.kt deleted file mode 100644 index faea386c53..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/mastodon/MastodonBookmarkTimelinePresenter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.mastodon - -import dev.dimension.flare.data.datasource.mastodon.MastodonDataSource -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class MastodonBookmarkTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is MastodonDataSource) - service.bookmarkTimelineLoader() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/mastodon/MastodonFavouriteTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/mastodon/MastodonFavouriteTimelinePresenter.kt deleted file mode 100644 index 8c1dcdd93c..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/mastodon/MastodonFavouriteTimelinePresenter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.mastodon - -import dev.dimension.flare.data.datasource.mastodon.MastodonDataSource -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class MastodonFavouriteTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is MastodonDataSource) - service.favouriteTimelineLoader() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/mastodon/MastodonLocalTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/mastodon/MastodonLocalTimelinePresenter.kt deleted file mode 100644 index 56901d7930..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/mastodon/MastodonLocalTimelinePresenter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.mastodon - -import dev.dimension.flare.data.datasource.mastodon.MastodonDataSource -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class MastodonLocalTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is MastodonDataSource) - service.publicTimelineLoader(local = true) - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/mastodon/MastodonPublicTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/mastodon/MastodonPublicTimelinePresenter.kt deleted file mode 100644 index 87bcbe56cd..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/mastodon/MastodonPublicTimelinePresenter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.mastodon - -import dev.dimension.flare.data.datasource.mastodon.MastodonDataSource -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class MastodonPublicTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is MastodonDataSource) - service.publicTimelineLoader(local = false) - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MissKeyLocalTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MissKeyLocalTimelinePresenter.kt deleted file mode 100644 index d8b6adc6d7..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MissKeyLocalTimelinePresenter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.misskey - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class MissKeyLocalTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is MisskeyDataSource) - service.localTimelineLoader() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MissKeyPublicTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MissKeyPublicTimelinePresenter.kt deleted file mode 100644 index 9abeb3711c..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MissKeyPublicTimelinePresenter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.misskey - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class MissKeyPublicTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is MisskeyDataSource) - service.publicTimelineLoader() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelTimelinePresenter.kt deleted file mode 100644 index a57d089e25..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyChannelTimelinePresenter.kt +++ /dev/null @@ -1,31 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.misskey - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class MisskeyChannelTimelinePresenter( - private val accountType: AccountType, - private val channelId: String, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is MisskeyDataSource) - service.channelTimelineLoader(channelId) - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyFavouriteTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyFavouriteTimelinePresenter.kt deleted file mode 100644 index ab8ae55fe4..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyFavouriteTimelinePresenter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.misskey - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class MisskeyFavouriteTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is MisskeyDataSource) - service.favouriteTimelineLoader() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyHybridTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyHybridTimelinePresenter.kt deleted file mode 100644 index d312a6d9e6..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/misskey/MisskeyHybridTimelinePresenter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.misskey - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class MisskeyHybridTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is MisskeyDataSource) - service.hybridTimelineLoader() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssTimelinePresenter.kt deleted file mode 100644 index 28c11ea14e..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/rss/RssTimelinePresenter.kt +++ /dev/null @@ -1,120 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.rss - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import dev.dimension.flare.data.database.app.AppDatabase -import dev.dimension.flare.data.database.app.model.SubscriptionType -import dev.dimension.flare.data.database.cache.CacheDatabase -import dev.dimension.flare.data.datasource.microblog.MixedRemoteMediator -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.rss.RssDataSource -import dev.dimension.flare.ui.model.UiRssSource -import dev.dimension.flare.ui.model.UiState -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.model.collectAsUiState -import dev.dimension.flare.ui.model.map -import dev.dimension.flare.ui.model.mapper.render -import dev.dimension.flare.ui.presenter.PresenterBase -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import dev.dimension.flare.ui.presenter.home.TimelineState -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class RssTimelinePresenter( - private val url: String, -) : TimelinePresenter() { - override val loader: Flow> by lazy { - flowOf(RssDataSource.fetchLoader(url)) - } -} - -public class SubscriptionTimelinePresenter( - private val type: SubscriptionType, - private val url: String, -) : TimelinePresenter(), - KoinComponent { - private val appDatabase by inject() - - override val loader: Flow> by lazy { - when (type) { - SubscriptionType.RSS -> { - flowOf(RssDataSource.fetchLoader(url)) - } - - else -> { - flowOf( - RssDataSource.fetchLoader( - dev.dimension.flare.data.database.app.model.DbRssSources( - url = url, - title = null, - icon = null, - lastUpdate = 0, - type = type, - ), - ), - ) - } - } - } -} - -public class AllRssTimelinePresenter : - TimelinePresenter(), - KoinComponent { - private val appDatabase by inject() - private val database: CacheDatabase by inject() - - override val loader: Flow> by lazy { - appDatabase - .rssSourceDao() - .getAll() - .map { items -> - MixedRemoteMediator( - database = database, - mediators = - items.map { - RssDataSource.fetchLoader(it) - }, - ) - } - } -} - -public class RssSourcePresenter( - private val id: Int, -) : PresenterBase(), - KoinComponent { - private val appDatabase by inject() - - @androidx.compose.runtime.Immutable - public interface State { - public val data: UiState - public val timelineState: UiState - } - - @Composable - override fun body(): State { - val data by remember(id) { - appDatabase - .rssSourceDao() - .get(id) - .map { - it.render() - } - }.collectAsUiState() - val timelineState = - data.map { - remember(it.url, it.type) { - SubscriptionTimelinePresenter(it.type, it.url) - }.body() - } - return object : State { - override val data = data - override val timelineState = timelineState - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/vvo/VVOFavouriteTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/vvo/VVOFavouriteTimelinePresenter.kt deleted file mode 100644 index 34936b00b3..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/vvo/VVOFavouriteTimelinePresenter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.vvo - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.vvo.VVODataSource -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class VVOFavouriteTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is VVODataSource) - service.favouriteTimeline() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/vvo/VVOLikeTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/vvo/VVOLikeTimelinePresenter.kt deleted file mode 100644 index 148e87b464..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/vvo/VVOLikeTimelinePresenter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.vvo - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.vvo.VVODataSource -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class VVOLikeTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is VVODataSource) - service.likeRemoteMediator() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/XQTBookmarkTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/XQTBookmarkTimelinePresenter.kt deleted file mode 100644 index 2e3d33b6ca..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/XQTBookmarkTimelinePresenter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.xqt - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.xqt.XQTDataSource -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class XQTBookmarkTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is XQTDataSource) - service.bookmarkTimelineLoader() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/XQTDeviceFollowTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/XQTDeviceFollowTimelinePresenter.kt deleted file mode 100644 index 2f9ce38f91..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/XQTDeviceFollowTimelinePresenter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.xqt - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.xqt.XQTDataSource -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class XQTDeviceFollowTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is XQTDataSource) - service.deviceFollowTimelineLoader() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/XQTFeaturedTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/XQTFeaturedTimelinePresenter.kt deleted file mode 100644 index b158d759c9..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/xqt/XQTFeaturedTimelinePresenter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.dimension.flare.ui.presenter.home.xqt - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.xqt.XQTDataSource -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class XQTFeaturedTimelinePresenter( - private val accountType: AccountType, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is XQTDataSource) - service.featuredTimelineLoader() - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AntennasTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AntennasTimelinePresenter.kt deleted file mode 100644 index 71530f9179..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/AntennasTimelinePresenter.kt +++ /dev/null @@ -1,31 +0,0 @@ -package dev.dimension.flare.ui.presenter.list - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class AntennasTimelinePresenter( - private val accountType: AccountType, - private val id: String, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is MisskeyDataSource) - service.antennasTimelineLoader(id) - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ChannelTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ChannelTimelinePresenter.kt deleted file mode 100644 index adaf6cc3e3..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ChannelTimelinePresenter.kt +++ /dev/null @@ -1,31 +0,0 @@ -package dev.dimension.flare.ui.presenter.list - -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -public class ChannelTimelinePresenter( - private val accountType: AccountType, - private val id: String, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is MisskeyDataSource) - service.channelTimelineLoader(id) - } - } -} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListTimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListTimelinePresenter.kt deleted file mode 100644 index 68f7ea91b8..0000000000 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/list/ListTimelinePresenter.kt +++ /dev/null @@ -1,34 +0,0 @@ -package dev.dimension.flare.ui.presenter.list - -import dev.dimension.flare.data.datasource.microblog.datasource.ListDataSource -import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.accountServiceFlow -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.ui.model.UiTimelineV2 -import dev.dimension.flare.ui.presenter.home.TimelinePresenter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -/** - * Presenter for retrieving list timeline. - */ -public class ListTimelinePresenter( - private val accountType: AccountType, - private val listId: String, -) : TimelinePresenter(), - KoinComponent { - private val accountRepository: AccountRepository by inject() - - override val loader: Flow> by lazy { - accountServiceFlow( - accountType = accountType, - repository = accountRepository, - ).map { service -> - require(service is ListDataSource) - service.listTimeline(listId = listId) - } - } -} diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/DatabaseHelper.kt b/shared/src/commonTest/kotlin/dev/dimension/flare/DatabaseHelper.kt deleted file mode 100644 index f9d48c181d..0000000000 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/DatabaseHelper.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.dimension.flare - -import androidx.room3.Room -import androidx.room3.RoomDatabase -import kotlin.reflect.KClass - -internal expect fun Room.memoryDatabaseBuilder(databaseClass: KClass): RoomDatabase.Builder - -internal inline fun Room.memoryDatabaseBuilder(): RoomDatabase.Builder = memoryDatabaseBuilder(T::class) - -expect open class RobolectricTest() diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMappingTest.kt b/shared/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMappingTest.kt deleted file mode 100644 index 038ef92762..0000000000 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/common/deeplink/DeepLinkMappingTest.kt +++ /dev/null @@ -1,358 +0,0 @@ -package dev.dimension.flare.common.deeplink - -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.model.PlatformType -import dev.dimension.flare.model.spec -import dev.dimension.flare.model.xqtHost -import dev.dimension.flare.ui.model.UiAccount -import dev.dimension.flare.ui.route.DeeplinkRoute -import io.ktor.http.Url -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.ImmutableMap -import kotlinx.collections.immutable.persistentMapOf -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class DeepLinkMappingTest { - @Test - fun mastodonPatternsAreGeneratedInOrder() { - val host = "mastodon.social" - - val patterns = PlatformType.Mastodon.spec.deepLinkPatterns(host) - - assertEquals(2, patterns.size) - - val profile = patterns[0] - assertEquals(DeepLinkMapping.Type.Profile.serializer(), profile.serializer) - assertEquals(Url("https://$host/@{handle}"), profile.uriPattern) - assertEquals( - listOf("handle" to true), - profile.pathSegments - .filter { it.stringValue.isNotEmpty() } - .map { it.stringValue to it.isParamArg }, - ) - - val post = patterns[1] - assertEquals(DeepLinkMapping.Type.Post.serializer(), post.serializer) - assertEquals(Url("https://$host/@{handle}/{id}"), post.uriPattern) - assertEquals( - listOf("handle" to true, "id" to true), - post.pathSegments - .filter { it.stringValue.isNotEmpty() } - .map { it.stringValue to it.isParamArg }, - ) - } - - @Test - fun misskeyPatternsUseNotesRoute() { - val host = "misskey.example" - - val patterns = PlatformType.Misskey.spec.deepLinkPatterns(host) - - assertEquals(2, patterns.size) - - val profile = patterns[0] - assertEquals(DeepLinkMapping.Type.Profile.serializer(), profile.serializer) - assertEquals(Url("https://$host/@{handle}"), profile.uriPattern) - assertEquals( - listOf("handle" to true), - profile.pathSegments - .filter { it.stringValue.isNotEmpty() } - .map { it.stringValue to it.isParamArg }, - ) - - val post = patterns[1] - assertEquals(DeepLinkMapping.Type.Post.serializer(), post.serializer) - assertEquals(Url("https://$host/notes/{id}"), post.uriPattern) - assertEquals( - listOf("notes" to false, "id" to true), - post.pathSegments - .filter { it.stringValue.isNotEmpty() } - .map { it.stringValue to it.isParamArg }, - ) - } - - @Test - fun blueskyPatternsIncludeProfileAndPost() { - val host = "bsky.example" - - val patterns = PlatformType.Bluesky.spec.deepLinkPatterns(host) - - assertEquals(2, patterns.size) - - val profile = patterns[0] - assertEquals(DeepLinkMapping.Type.Profile.serializer(), profile.serializer) - assertEquals(Url("https://$host/profile/{handle}"), profile.uriPattern) - assertEquals( - listOf("profile" to false, "handle" to true), - profile.pathSegments - .filter { it.stringValue.isNotEmpty() } - .map { it.stringValue to it.isParamArg }, - ) - - val post = patterns[1] - assertEquals(DeepLinkMapping.Type.BlueskyPost.serializer(), post.serializer) - assertEquals(Url("https://$host/profile/{handle}/post/{id}"), post.uriPattern) - assertEquals( - listOf("profile" to false, "handle" to true, "post" to false, "id" to true), - post.pathSegments - .filter { it.stringValue.isNotEmpty() } - .map { it.stringValue to it.isParamArg }, - ) - } - - @Test - fun xqtPatternsUseStatusRoute() { - val host = xqtHost - - val patterns = PlatformType.xQt.spec.deepLinkPatterns(host) - - assertEquals(12, patterns.size) - - val profile = patterns[0] - assertEquals(DeepLinkMapping.Type.Profile.serializer(), profile.serializer) - assertEquals(Url("https://$host/{handle}"), profile.uriPattern) - assertEquals( - listOf("handle" to true), - profile.pathSegments - .filter { it.stringValue.isNotEmpty() } - .map { it.stringValue to it.isParamArg }, - ) - - val post = patterns[4] - assertEquals(DeepLinkMapping.Type.Post.serializer(), post.serializer) - assertEquals(Url("https://$host/{handle}/status/{id}"), post.uriPattern) - assertEquals( - listOf("handle" to true, "status" to false, "id" to true), - post.pathSegments - .filter { it.stringValue.isNotEmpty() } - .map { it.stringValue to it.isParamArg }, - ) - - val media = patterns[8] - assertEquals(DeepLinkMapping.Type.PostMedia.serializer(), media.serializer) - assertEquals(Url("https://$host/{handle}/status/{id}/photo/{index}"), media.uriPattern) - assertEquals( - listOf( - "handle" to true, - "status" to false, - "id" to true, - "photo" to false, - "index" to true, - ), - media.pathSegments - .filter { it.stringValue.isNotEmpty() } - .map { it.stringValue to it.isParamArg }, - ) - } - - @Test - fun vvoHasNoPatterns() { - val patterns = PlatformType.VVo.spec.deepLinkPatterns("irrelevant") - - assertTrue(patterns.isEmpty()) - } - - @Test - fun matchesReturnsAccountProfile() { - val mastodonAccount = - UiAccount.Mastodon( - accountKey = MicroBlogKey(id = "1", host = "mastodon.social"), - instance = "mastodon.social", - ) - val misskeyAccount = - UiAccount.Misskey( - accountKey = MicroBlogKey(id = "2", host = "misskey.example"), - host = "misskey.example", - ) - val mapping = - persistentMapOf( - mastodonAccount to - PlatformType.Mastodon.spec.deepLinkPatterns(mastodonAccount.accountKey.host), - misskeyAccount to - PlatformType.Misskey.spec.deepLinkPatterns(misskeyAccount.accountKey.host), - ) - - val matches = DeepLinkMapping.matches("https://mastodon.social/@alice", mapping) - - assertEquals(1, matches.size) - assertEquals(DeepLinkMapping.Type.Profile("alice"), matches[mastodonAccount]) - } - - @Test - fun matchesMultipleAccountsWithSameHost() { - val account1 = - UiAccount.Mastodon( - accountKey = MicroBlogKey(id = "1", host = "mastodon.social"), - instance = "mastodon.social", - ) - val account2 = - UiAccount.Mastodon( - accountKey = MicroBlogKey(id = "2", host = "mastodon.social"), - instance = "mastodon.social", - ) - val mapping: - ImmutableMap>> = - persistentMapOf( - account1 to - PlatformType.Mastodon.spec.deepLinkPatterns(account1.accountKey.host), - account2 to - PlatformType.Mastodon.spec.deepLinkPatterns(account2.accountKey.host), - ) - - val matches = DeepLinkMapping.matches("https://mastodon.social/@alice", mapping) - - assertEquals(2, matches.size) - assertEquals(DeepLinkMapping.Type.Profile("alice"), matches[account1]) - assertEquals(DeepLinkMapping.Type.Profile("alice"), matches[account2]) - } - - @Test - fun matchesReturnsEmptyForNonMatchingUrl() { - val account = - UiAccount.Mastodon( - accountKey = MicroBlogKey(id = "1", host = "mastodon.social"), - instance = "mastodon.social", - ) - val mapping: - ImmutableMap>> = - persistentMapOf( - account to - PlatformType.Mastodon.spec.deepLinkPatterns(account.accountKey.host), - ) - - // URL containing none of the valid hosts - assertTrue(DeepLinkMapping.matches("https://google.com", mapping).isEmpty()) - - // URL containing a valid host but invalid path - assertTrue(DeepLinkMapping.matches("https://mastodon.social/about", mapping).isEmpty()) - } - - @Test - fun matchesRealWorldLinks() { - val mastodonAccount = - UiAccount.Mastodon( - accountKey = MicroBlogKey(id = "1", host = "mastodon.example"), - instance = "mastodon.example", - ) - val misskeyAccount = - UiAccount.Misskey( - accountKey = MicroBlogKey(id = "2", host = "misskey.example"), - host = "misskey.example", - ) - val bskyAccount = - UiAccount.Bluesky( - accountKey = MicroBlogKey(id = "3", host = "bsky.example"), - ) - val xAccount = - UiAccount.XQT( - accountKey = MicroBlogKey(id = "4", host = xqtHost), - ) - - val mapping: - ImmutableMap>> = - persistentMapOf( - mastodonAccount to - PlatformType.Mastodon.spec.deepLinkPatterns(mastodonAccount.accountKey.host), - misskeyAccount to - PlatformType.Misskey.spec.deepLinkPatterns(misskeyAccount.accountKey.host), - bskyAccount to - PlatformType.Bluesky.spec.deepLinkPatterns(bskyAccount.accountKey.host), - xAccount to - PlatformType.xQt.spec.deepLinkPatterns(xAccount.accountKey.host), - ) - - // https://mastodon.example/@alice - val mastodonProfileMatch = - DeepLinkMapping.matches("https://mastodon.example/@alice", mapping) - assertEquals(DeepLinkMapping.Type.Profile("alice"), mastodonProfileMatch[mastodonAccount]) - - // https://mastodon.example/@alice/12345 - val mastodonPostMatch = - DeepLinkMapping.matches("https://mastodon.example/@alice/12345", mapping) - assertEquals( - DeepLinkMapping.Type.Post("alice", "12345"), - mastodonPostMatch[mastodonAccount], - ) - - // https://misskey.example/@bob - val misskeyProfileMatch = DeepLinkMapping.matches("https://misskey.example/@bob", mapping) - assertEquals(DeepLinkMapping.Type.Profile("bob"), misskeyProfileMatch[misskeyAccount]) - - // https://misskey.example/notes/12345 - val misskeyPostMatch = - DeepLinkMapping.matches("https://misskey.example/notes/12345", mapping) - assertEquals(DeepLinkMapping.Type.Post(null, "12345"), misskeyPostMatch[misskeyAccount]) - - // https://bsky.example/profile/alice.bsky.social - val bskyProfileMatch = - DeepLinkMapping.matches("https://bsky.example/profile/alice.bsky.social", mapping) - assertEquals( - DeepLinkMapping.Type.Profile("alice.bsky.social"), - bskyProfileMatch[bskyAccount], - ) - - // https://bsky.example/profile/alice.bsky.social/post/12345 - val bskyPostMatch = - DeepLinkMapping.matches( - "https://bsky.example/profile/alice.bsky.social/post/12345", - mapping, - ) - assertEquals( - DeepLinkMapping.Type.BlueskyPost("alice.bsky.social", "12345"), - bskyPostMatch[bskyAccount], - ) - - // https://x.example/alice - val xProfileMatch = DeepLinkMapping.matches("https://$xqtHost/alice", mapping) - assertEquals(DeepLinkMapping.Type.Profile("alice"), xProfileMatch[xAccount]) - - // https://x.example/alice/status/12345 - val xPostMatch = DeepLinkMapping.matches("https://$xqtHost/alice/status/12345", mapping) - assertEquals(DeepLinkMapping.Type.Post("alice", "12345"), xPostMatch[xAccount]) - - // https://x.example/alice/status/12345/photo/1 - val xPostPhotoMatch = - DeepLinkMapping.matches("https://$xqtHost/alice/status/12345/photo/1", mapping) - assertEquals(DeepLinkMapping.Type.PostMedia("alice", "12345", 1), xPostPhotoMatch[xAccount]) - } - - @Test - fun typeDeepLinkGeneratesCorrectUrl() { - val accountKey = MicroBlogKey(id = "1", host = "mastodon.social") - - // Profile with simple handle - val simpleProfile = DeepLinkMapping.Type.Profile("alice") - assertEquals( - DeeplinkRoute.Profile.UserNameWithHost( - accountType = AccountType.Specific(accountKey), - userName = "alice", - host = "mastodon.social", - ), - simpleProfile.deepLink(accountKey), - ) - - // Profile with full handle - val fullProfile = DeepLinkMapping.Type.Profile("bob@misskey.io") - assertEquals( - DeeplinkRoute.Profile.UserNameWithHost( - accountType = AccountType.Specific(accountKey), - userName = "bob", - host = "misskey.io", - ), - fullProfile.deepLink(accountKey), - ) - - // Post - val post = DeepLinkMapping.Type.Post(id = "12345") - assertEquals( - DeeplinkRoute.Status.Detail( - accountType = AccountType.Specific(accountKey), - statusKey = MicroBlogKey("12345", "mastodon.social"), - ), - post.deepLink(accountKey), - ) - } -} diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/repository/AccountTabSyncCoordinatorSanitizerTest.kt b/shared/src/commonTest/kotlin/dev/dimension/flare/data/repository/AccountTabSyncCoordinatorSanitizerTest.kt deleted file mode 100644 index 4beec09ec9..0000000000 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/repository/AccountTabSyncCoordinatorSanitizerTest.kt +++ /dev/null @@ -1,135 +0,0 @@ -package dev.dimension.flare.data.repository - -import dev.dimension.flare.data.model.HomeTimelineTabItem -import dev.dimension.flare.data.model.IconType -import dev.dimension.flare.data.model.ListTimelineTabItem -import dev.dimension.flare.data.model.MixedTimelineTabItem -import dev.dimension.flare.data.model.TabMetaData -import dev.dimension.flare.data.model.TabSettings -import dev.dimension.flare.data.model.TitleType -import dev.dimension.flare.data.model.tab.GroupSource -import dev.dimension.flare.data.model.tab.SYSTEM_HOME_MIXED_TIMELINE_ID -import dev.dimension.flare.data.model.tab.TimelinePresentation -import dev.dimension.flare.data.model.tab.TimelineSlot -import dev.dimension.flare.data.model.tab.TimelineSlotContent -import dev.dimension.flare.data.model.tab.toTimelineSlotOrNull -import dev.dimension.flare.model.AccountType -import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.ui.model.UiIcon -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertIs -import kotlin.test.assertNotNull - -class AccountTabSyncCoordinatorSanitizerTest { - @Test - fun normalizeSystemHomeMixedTimelineAddsSystemMixedWhenSecondDefaultTabIsAdded() { - val firstAccountKey = MicroBlogKey("1872639344760254464", "x.com") - val secondAccountKey = MicroBlogKey("1711111111111111111", "mastodon.social") - - val normalized = - listOf( - HomeTimelineTabItem(AccountType.Specific(firstAccountKey)).toTimelineSlotOrNull()!!, - HomeTimelineTabItem(AccountType.Specific(secondAccountKey)).toTimelineSlotOrNull()!!, - ).normalizeSystemHomeMixedTimeline(enabled = true) - - assertEquals( - listOf( - SYSTEM_HOME_MIXED_TIMELINE_ID, - "common.home:$firstAccountKey", - "common.home:$secondAccountKey", - ), - normalized.map { it.id }, - ) - } - - @Test - fun normalizeSystemHomeMixedTimelineDoesNotReAddSystemMixedAfterItWasRemoved() { - val firstAccountKey = MicroBlogKey("1872639344760254464", "x.com") - val secondAccountKey = MicroBlogKey("1711111111111111111", "mastodon.social") - - val normalized = - listOf( - HomeTimelineTabItem(AccountType.Specific(firstAccountKey)).toTimelineSlotOrNull()!!, - HomeTimelineTabItem(AccountType.Specific(secondAccountKey)).toTimelineSlotOrNull()!!, - ).normalizeSystemHomeMixedTimeline(enabled = true) - .filterNot { it.id == SYSTEM_HOME_MIXED_TIMELINE_ID } - .normalizeSystemHomeMixedTimeline(enabled = false) - - assertEquals( - listOf( - "common.home:$firstAccountKey", - "common.home:$secondAccountKey", - ), - normalized.map { it.id }, - ) - } - - @Test - fun normalizeSystemHomeMixedTimelinePreservesDisabledChildrenInManualGroup() { - val accountKey = MicroBlogKey("1872639344760254464", "x.com") - val secondAccountKey = MicroBlogKey("1711111111111111111", "mastodon.social") - val enabledChild = HomeTimelineTabItem(AccountType.Specific(accountKey)).toTimelineSlotOrNull()!! - val disabledChild = - HomeTimelineTabItem(AccountType.Specific(secondAccountKey)) - .toTimelineSlotOrNull()!! - .copy(presentation = TimelinePresentation(enabled = false)) - val manualGroup = - TimelineSlot( - id = "manual_group", - content = - TimelineSlotContent.Group( - children = listOf(enabledChild, disabledChild), - source = GroupSource.Manual, - ), - ) - - val normalized = - listOf( - enabledChild, - HomeTimelineTabItem(AccountType.Specific(MicroBlogKey("1999999999999999999", "mastodon.cloud"))).toTimelineSlotOrNull()!!, - manualGroup, - ).normalizeSystemHomeMixedTimeline(enabled = true) - - val preservedGroup = assertNotNull(normalized.firstOrNull { it.id == manualGroup.id }) - val groupContent = assertIs(preservedGroup.content) - assertEquals(listOf(enabledChild.id, disabledChild.id), groupContent.children.map { it.id }) - assertFalse(groupContent.children[1].presentation.enabled) - } - - @Test - fun sanitizeDuplicateTabKeysRemovesDuplicateMixedTabsAndSubTabs() { - val accountKey = MicroBlogKey("1872639344760254464", "x.com") - val accountType = AccountType.Specific(accountKey) - val homeTab = HomeTimelineTabItem(accountType) - val listTab = - ListTimelineTabItem( - account = accountType, - listId = "1681353064253640704", - metaData = - TabMetaData( - title = TitleType.Text("list"), - icon = IconType.Material(UiIcon.List), - ), - ) - val mixedTab = - MixedTimelineTabItem( - subTimelineTabItem = listOf(homeTab, homeTab, listTab), - metaData = - TabMetaData( - title = TitleType.Text("엑스"), - icon = IconType.Material(UiIcon.Rss), - ), - ) - - val sanitized = - TabSettings( - mainTabs = listOf(mixedTab, mixedTab), - ).sanitizeDuplicateTabKeys() - - assertEquals(1, sanitized.mainTabs.size) - val sanitizedMixedTab = assertIs(sanitized.mainTabs.single()) - assertEquals(listOf(homeTab.key, listTab.key), sanitizedMixedTab.subTimelineTabItem.map { it.key }) - } -} diff --git a/shared/src/jvmMain/kotlin/dev/dimension/flare/common/BuildConfig.jvm.kt b/shared/src/jvmMain/kotlin/dev/dimension/flare/common/BuildConfig.jvm.kt deleted file mode 100644 index 98767fbbe9..0000000000 --- a/shared/src/jvmMain/kotlin/dev/dimension/flare/common/BuildConfig.jvm.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.dimension.flare.common - - -internal actual object BuildConfig { - actual val debug: Boolean - get() = false -} \ No newline at end of file diff --git a/shared/src/jvmMain/kotlin/dev/dimension/flare/data/network/HttpClientEngine.jvm.kt b/shared/src/jvmMain/kotlin/dev/dimension/flare/data/network/HttpClientEngine.jvm.kt deleted file mode 100644 index 7c537a4017..0000000000 --- a/shared/src/jvmMain/kotlin/dev/dimension/flare/data/network/HttpClientEngine.jvm.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.dimension.flare.data.network - -import io.ktor.client.engine.HttpClientEngine -import io.ktor.client.engine.okhttp.OkHttp - -internal actual val httpClientEngine: HttpClientEngine = OkHttp.create { } diff --git a/shared/src/jvmMain/kotlin/dev/dimension/flare/data/repository/DraftMediaStore.jvm.kt b/shared/src/jvmMain/kotlin/dev/dimension/flare/data/repository/DraftMediaStore.jvm.kt deleted file mode 100644 index c947ccc9fd..0000000000 --- a/shared/src/jvmMain/kotlin/dev/dimension/flare/data/repository/DraftMediaStore.jvm.kt +++ /dev/null @@ -1,16 +0,0 @@ -package dev.dimension.flare.data.repository - -import dev.dimension.flare.common.FileItem -import dev.dimension.flare.common.FileType -import java.io.File - -internal actual fun draftFileItem( - path: String, - name: String?, - type: FileType, -): FileItem = - FileItem( - name = name, - type = type, - file = File(path), - ) diff --git a/social/api/build.gradle.kts b/social/api/build.gradle.kts new file mode 100644 index 0000000000..3c0bcb2647 --- /dev/null +++ b/social/api/build.gradle.kts @@ -0,0 +1,41 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.social.api" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.foundation.deeplink) + api(projects.core.model) + api(projects.modules.account.api) + api(projects.social.model) + api(projects.social.microblog) + api(libs.kotlinx.immutable) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) + } + } + } +} diff --git a/social/api/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/RecommendInstancePagingSource.kt b/social/api/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/RecommendInstancePagingSource.kt new file mode 100644 index 0000000000..dde722c5fb --- /dev/null +++ b/social/api/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/RecommendInstancePagingSource.kt @@ -0,0 +1,25 @@ +package dev.dimension.flare.data.datasource.microblog + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import dev.dimension.flare.common.DebugRepository +import dev.dimension.flare.model.SocialPlatformRegistry +import dev.dimension.flare.ui.model.UiInstance + +public class RecommendInstancePagingSource( + private val platformRegistry: SocialPlatformRegistry, +) : PagingSource() { + override fun getRefreshKey(state: PagingState): Int? = null + + override suspend fun load(params: LoadParams): LoadResult = + try { + LoadResult.Page( + data = platformRegistry.recommendedInstances(), + prevKey = null, + nextKey = null, + ) + } catch (e: Exception) { + DebugRepository.error(e) + LoadResult.Error(e) + } +} diff --git a/social/api/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/NodeData.kt b/social/api/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/NodeData.kt new file mode 100644 index 0000000000..46823e4385 --- /dev/null +++ b/social/api/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/NodeData.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.data.network.nodeinfo + +import dev.dimension.flare.model.PlatformType + +public data class NodeData( + public val host: String, + public val platformType: PlatformType, + public val software: String, + // not officially supported, but works fine for basic features + public val compatibleMode: Boolean, +) diff --git a/social/api/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/PlatformDetector.kt b/social/api/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/PlatformDetector.kt new file mode 100644 index 0000000000..b59a2a803d --- /dev/null +++ b/social/api/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/PlatformDetector.kt @@ -0,0 +1,8 @@ +package dev.dimension.flare.data.network.nodeinfo + +public interface PlatformDetector { + public val priority: Int + get() = 0 + + public suspend fun detect(host: String): NodeData? +} diff --git a/social/api/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/SocialPlatformDetection.kt b/social/api/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/SocialPlatformDetection.kt new file mode 100644 index 0000000000..8527ce7ddd --- /dev/null +++ b/social/api/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/SocialPlatformDetection.kt @@ -0,0 +1,19 @@ +package dev.dimension.flare.data.network.nodeinfo + +import dev.dimension.flare.model.SocialPlatformRegistry + +public suspend fun SocialPlatformRegistry.detectPlatformType(host: String): NodeData { + val hostCleaned = host.normalizeHost() + return specs + .map { it.detector } + .distinct() + .sortedByDescending { it.priority } + .firstNotNullOfOrNull { detector -> detector.detect(hostCleaned) } + ?: throw IllegalArgumentException("Unsupported platform: $hostCleaned") +} + +private fun String.normalizeHost(): String = + trim() + .removePrefix("https://") + .removePrefix("http://") + .removeSuffix("/") diff --git a/social/api/src/commonMain/kotlin/dev/dimension/flare/model/SocialPlatformRegistry.kt b/social/api/src/commonMain/kotlin/dev/dimension/flare/model/SocialPlatformRegistry.kt new file mode 100644 index 0000000000..ae942498dd --- /dev/null +++ b/social/api/src/commonMain/kotlin/dev/dimension/flare/model/SocialPlatformRegistry.kt @@ -0,0 +1,137 @@ +package dev.dimension.flare.model + +import androidx.compose.runtime.Immutable +import dev.dimension.flare.common.DebugRepository +import dev.dimension.flare.common.deeplink.DeepLinkMapping +import dev.dimension.flare.common.deeplink.DeepLinkPattern +import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource +import dev.dimension.flare.data.datasource.microblog.paging.CacheableRemoteLoader +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.network.nodeinfo.PlatformDetector +import dev.dimension.flare.ui.model.UiAccount +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiInstance +import dev.dimension.flare.ui.model.UiInstanceMetadata +import dev.dimension.flare.ui.model.UiTimelineV2 +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlin.jvm.JvmInline + +public interface SocialPlatformSpec { + public val type: PlatformType + public val metadata: PlatformTypeMetadata + public val detector: PlatformDetector + public val timelineSpecs: ImmutableList> + get() = persistentListOf() + + public fun agreementUrl(host: String): String? + + public fun deepLinkPatterns(host: String): ImmutableList> + + public suspend fun instanceMetadata(host: String): UiInstanceMetadata + + public fun guestDataSource( + host: String, + locale: String, + ): MicroblogDataSource + + public fun createSubscriptionLoader( + subscriptionType: SubscriptionTimelineTypeKey, + url: String, + locale: String, + ): CacheableRemoteLoader? = null +} + +@Immutable +public data class PlatformTypeMetadata( + public val displayName: String, + public val icon: UiIcon, +) + +@JvmInline +public value class SubscriptionTimelineTypeKey( + public val value: String, +) + +public object SubscriptionTimelineTypes { + public val MastodonTrends: SubscriptionTimelineTypeKey = SubscriptionTimelineTypeKey("mastodon.trends") + public val MastodonPublic: SubscriptionTimelineTypeKey = SubscriptionTimelineTypeKey("mastodon.public") + public val MastodonLocal: SubscriptionTimelineTypeKey = SubscriptionTimelineTypeKey("mastodon.local") +} + +public interface SocialPlatformPlugin { + public val spec: SocialPlatformSpec + + public fun createDataSource(account: UiAccount): MicroblogDataSource? + + public suspend fun recommendedInstances(): List = emptyList() +} + +public class SocialPlatformRegistry( + plugins: List, +) { + private val plugins = plugins.distinctBy { it.spec.type } + private val pluginsByType = this.plugins.associateBy { it.spec.type } + + public val specs: List + get() = plugins.map { it.spec } + + public val loginPlatformTypes: List + get() = specs.map { it.type } + + public suspend fun recommendedInstances(): List = + plugins + .flatMap { plugin -> + try { + plugin.recommendedInstances() + } catch (e: Exception) { + DebugRepository.error(e) + emptyList() + } + }.distinctBy { "${it.type}:${it.domain}" } + + public fun requireSpec(type: PlatformType): SocialPlatformSpec = + requireNotNull(pluginsByType[type]?.spec) { + "No social platform registered for $type" + } + + public fun metadata(type: PlatformType): PlatformTypeMetadata = requireSpec(type).metadata + + public fun agreementUrl( + type: PlatformType, + host: String, + ): String? = requireSpec(type).agreementUrl(host) + + public fun deepLinkPatterns( + type: PlatformType, + host: String, + ): ImmutableList> = requireSpec(type).deepLinkPatterns(host) + + public suspend fun instanceMetadata( + type: PlatformType, + host: String, + ): UiInstanceMetadata = requireSpec(type).instanceMetadata(host) + + public fun createDataSource(account: UiAccount): MicroblogDataSource = + requireNotNull(pluginsByType[account.platformType]) { + "No social platform registered for ${account.platformType}" + }.createDataSource(account) + ?: error("Registered social platform cannot create data source for ${account.platformType}") + + public fun guestDataSource( + type: PlatformType, + host: String, + locale: String, + ): MicroblogDataSource = + requireSpec(type).guestDataSource( + host = host, + locale = locale, + ) + + public fun createSubscriptionLoader( + type: PlatformType, + subscriptionType: SubscriptionTimelineTypeKey, + url: String, + locale: String, + ): CacheableRemoteLoader? = requireSpec(type).createSubscriptionLoader(subscriptionType, url, locale) +} diff --git a/social/api/src/commonTest/kotlin/dev/dimension/flare/model/SocialPlatformRegistryTest.kt b/social/api/src/commonTest/kotlin/dev/dimension/flare/model/SocialPlatformRegistryTest.kt new file mode 100644 index 0000000000..80c52d9bc6 --- /dev/null +++ b/social/api/src/commonTest/kotlin/dev/dimension/flare/model/SocialPlatformRegistryTest.kt @@ -0,0 +1,324 @@ +package dev.dimension.flare.model + +import dev.dimension.flare.common.deeplink.DeepLinkMapping +import dev.dimension.flare.common.deeplink.DeepLinkPattern +import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource +import dev.dimension.flare.data.datasource.microblog.ProfileTab +import dev.dimension.flare.data.datasource.microblog.paging.CacheableRemoteLoader +import dev.dimension.flare.data.datasource.microblog.paging.PagingRequest +import dev.dimension.flare.data.datasource.microblog.paging.PagingResult +import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader +import dev.dimension.flare.data.datasource.microblog.paging.notSupported +import dev.dimension.flare.data.network.nodeinfo.NodeData +import dev.dimension.flare.data.network.nodeinfo.PlatformDetector +import dev.dimension.flare.data.network.nodeinfo.detectPlatformType +import dev.dimension.flare.ui.model.UiAccount +import dev.dimension.flare.ui.model.UiHashtag +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiInstance +import dev.dimension.flare.ui.model.UiInstanceMetadata +import dev.dimension.flare.ui.model.UiProfile +import dev.dimension.flare.ui.model.UiTimelineV2 +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertSame + +class SocialPlatformRegistryTest { + @Test + fun exposesOnlyRegisteredPlatformTypes() { + val registry = + SocialPlatformRegistry( + listOf( + fakePlugin(PlatformType.Mastodon), + fakePlugin(PlatformType.Misskey), + ), + ) + + assertEquals( + listOf( + PlatformType.Mastodon, + PlatformType.Misskey, + ), + registry.loginPlatformTypes, + ) + assertFailsWith { + registry.requireSpec(PlatformType.Bluesky) + } + } + + @Test + fun keepsFirstPluginForDuplicatePlatformTypes() { + val first = fakePlugin(PlatformType.Mastodon, displayName = "first") + val second = fakePlugin(PlatformType.Mastodon, displayName = "second") + val registry = SocialPlatformRegistry(listOf(first, second)) + + assertSame(first.spec, registry.requireSpec(PlatformType.Mastodon)) + assertEquals("first", registry.requireSpec(PlatformType.Mastodon).metadata.displayName) + } + + @Test + fun createsDataSourceFromMatchingRegisteredPluginOnly() { + val registry = + SocialPlatformRegistry( + listOf( + fakePlugin( + type = PlatformType.Mastodon, + createDataSource = { FakeMicroblogDataSource }, + ), + ), + ) + + assertSame( + FakeMicroblogDataSource, + registry.createDataSource( + UiAccount.Mastodon( + accountKey = MicroBlogKey("mastodon-user", "mastodon.example"), + instance = "mastodon.example", + ), + ), + ) + assertFailsWith { + registry.createDataSource( + UiAccount.Bluesky( + accountKey = MicroBlogKey("did:plc:test", "bsky.social"), + ), + ) + } + } + + @Test + fun registeredPluginMustCreateItsOwnDataSource() { + val registry = + SocialPlatformRegistry( + listOf( + fakePlugin( + type = PlatformType.Mastodon, + createDataSource = { null }, + ), + ), + ) + + assertFailsWith { + registry.createDataSource( + UiAccount.Mastodon( + accountKey = MicroBlogKey("mastodon-user", "mastodon.example"), + instance = "mastodon.example", + ), + ) + } + } + + @Test + fun createsSubscriptionLoaderFromRegisteredSpec() { + val subscriptionType = SubscriptionTimelineTypeKey("custom.subscription") + val loader = FakeCacheableRemoteLoader("custom-loader") + val registry = + SocialPlatformRegistry( + listOf( + fakePlugin( + type = PlatformType.Mastodon, + createSubscriptionLoader = { requestedType, url, locale -> + if (requestedType == subscriptionType && url == "mastodon.example" && locale == "en") { + loader + } else { + null + } + }, + ), + ), + ) + + assertSame( + loader, + registry.createSubscriptionLoader( + type = PlatformType.Mastodon, + subscriptionType = subscriptionType, + url = "mastodon.example", + locale = "en", + ), + ) + } + + @Test + fun recommendedInstancesComeFromRegisteredFirstPlugins() = + runTest { + val registry = + SocialPlatformRegistry( + listOf( + fakePlugin( + type = PlatformType.Mastodon, + recommendedInstances = { listOf(fakeInstance(PlatformType.Mastodon, "mastodon.example")) }, + ), + fakePlugin( + type = PlatformType.Mastodon, + recommendedInstances = { listOf(fakeInstance(PlatformType.Mastodon, "duplicate.example")) }, + ), + fakePlugin( + type = PlatformType.Bluesky, + recommendedInstances = { listOf(fakeInstance(PlatformType.Bluesky, "bsky.example")) }, + ), + ), + ) + + assertEquals( + listOf( + fakeInstance(PlatformType.Mastodon, "mastodon.example"), + fakeInstance(PlatformType.Bluesky, "bsky.example"), + ), + registry.recommendedInstances(), + ) + } + + @Test + fun detectsPlatformWithRegisteredDetectorsOnly() = + runTest { + val detector = + RecordingDetector( + result = + NodeData( + host = "example.com", + platformType = PlatformType.Mastodon, + software = "mastodon", + compatibleMode = false, + ), + ) + val registry = + SocialPlatformRegistry( + listOf( + fakePlugin( + type = PlatformType.Mastodon, + detector = detector, + ), + ), + ) + + assertEquals( + NodeData( + host = "example.com", + platformType = PlatformType.Mastodon, + software = "mastodon", + compatibleMode = false, + ), + registry.detectPlatformType("https://example.com/"), + ) + assertEquals(listOf("example.com"), detector.hosts) + } + + private fun fakePlugin( + type: PlatformType, + displayName: String = type.name, + detector: PlatformDetector = RecordingDetector(null), + recommendedInstances: suspend () -> List = { emptyList() }, + createDataSource: (UiAccount) -> MicroblogDataSource? = { FakeMicroblogDataSource }, + createSubscriptionLoader: (SubscriptionTimelineTypeKey, String, String) -> CacheableRemoteLoader? = + { _, _, _ -> null }, + ): SocialPlatformPlugin = + object : SocialPlatformPlugin { + override val spec: SocialPlatformSpec = + FakeSocialPlatformSpec( + type = type, + displayName = displayName, + detector = detector, + subscriptionLoaderFactory = createSubscriptionLoader, + ) + + override fun createDataSource(account: UiAccount): MicroblogDataSource? = createDataSource(account) + + override suspend fun recommendedInstances(): List = recommendedInstances() + } + + private fun fakeInstance( + type: PlatformType, + domain: String, + ): UiInstance = + UiInstance( + name = domain, + description = domain, + iconUrl = null, + domain = domain, + type = type, + bannerUrl = null, + usersCount = 0, + ) + + private data class FakeSocialPlatformSpec( + override val type: PlatformType, + private val displayName: String, + override val detector: PlatformDetector, + private val subscriptionLoaderFactory: (SubscriptionTimelineTypeKey, String, String) -> CacheableRemoteLoader?, + ) : SocialPlatformSpec { + override val metadata: PlatformTypeMetadata = + PlatformTypeMetadata( + displayName = displayName, + icon = UiIcon.Mastodon, + ) + + override fun agreementUrl(host: String): String? = null + + override fun deepLinkPatterns(host: String): ImmutableList> = persistentListOf() + + override suspend fun instanceMetadata(host: String): UiInstanceMetadata = error("unused") + + override fun guestDataSource( + host: String, + locale: String, + ): MicroblogDataSource = FakeMicroblogDataSource + + override fun createSubscriptionLoader( + subscriptionType: SubscriptionTimelineTypeKey, + url: String, + locale: String, + ): CacheableRemoteLoader? = subscriptionLoaderFactory(subscriptionType, url, locale) + } + + private class RecordingDetector( + private val result: NodeData?, + ) : PlatformDetector { + val hosts = mutableListOf() + + override suspend fun detect(host: String): NodeData? { + hosts += host + return result + } + } + + private data object FakeMicroblogDataSource : MicroblogDataSource { + override fun homeTimeline(): RemoteLoader = notSupported() + + override fun userTimeline( + userKey: MicroBlogKey, + mediaOnly: Boolean, + ): RemoteLoader = notSupported() + + override fun context(statusKey: MicroBlogKey): RemoteLoader = notSupported() + + override fun searchStatus(query: String): RemoteLoader = notSupported() + + override fun searchUser(query: String): RemoteLoader = notSupported() + + override fun discoverUsers(): RemoteLoader = notSupported() + + override fun discoverStatuses(): RemoteLoader = notSupported() + + override fun discoverHashtags(): RemoteLoader = notSupported() + + override fun following(userKey: MicroBlogKey): RemoteLoader = notSupported() + + override fun fans(userKey: MicroBlogKey): RemoteLoader = notSupported() + + override fun profileTabs(userKey: MicroBlogKey): ImmutableList = persistentListOf() + } + + private class FakeCacheableRemoteLoader( + override val pagingKey: String, + ) : CacheableRemoteLoader { + override suspend fun load( + pageSize: Int, + request: PagingRequest, + ): PagingResult = PagingResult(endOfPaginationReached = true) + } +} diff --git a/social/bluesky/build.gradle.kts b/social/bluesky/build.gradle.kts new file mode 100644 index 0000000000..cbaa961cbd --- /dev/null +++ b/social/bluesky/build.gradle.kts @@ -0,0 +1,50 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.social.bluesky" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.model) + api(projects.modules.account.api) + api(projects.social.model) + api(projects.foundation.network) + implementation(projects.foundation.filesystem) + api(projects.social.api) + api(projects.social.microblog) + api(libs.bluesky) + api(libs.bluesky.oauth) + implementation(dependencies.platform(libs.koin.bom)) + implementation(libs.koin.core) + implementation(libs.cryptography.provider.optimal) + implementation(libs.twitter.parser) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) + implementation(libs.ktor.client.mock) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization.kotlinx.json) + } + } + } +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Bluesky.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Bluesky.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Bluesky.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Bluesky.kt index 8a11840d65..7f3333d8d2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Bluesky.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Bluesky.kt @@ -17,27 +17,27 @@ import dev.dimension.flare.ui.render.toUiPlainText import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -internal object Bluesky { - fun rooms( +public object Bluesky { + public fun rooms( accountKey: MicroBlogKey, data: List, ): List = data.map { it.toUiDMRoom(accountKey) } - fun messages( + public fun messages( accountKey: MicroBlogKey, roomKey: MicroBlogKey, data: List, users: Map = emptyMap(), ): List = data.map { it.toUiDMItem(roomKey, users, accountKey) } - fun message( + public fun message( accountKey: MicroBlogKey, roomKey: MicroBlogKey, data: DeletedMessageView, users: Map = emptyMap(), ): UiDMItem = data.toUiDMItem(roomKey, users, accountKey) - fun deletedMessageKey( + public fun deletedMessageKey( accountKey: MicroBlogKey, message: DeletedMessageView, ): MicroBlogKey = diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSource.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSource.kt similarity index 88% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSource.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSource.kt index 469e7e4ed5..f6d15e5ec7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSource.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSource.kt @@ -25,8 +25,11 @@ import com.atproto.repo.CreateRecordResponse import com.atproto.repo.DeleteRecordRequest import com.atproto.repo.StrongRef import dev.dimension.flare.common.BasePagingSource -import dev.dimension.flare.common.FileType +import dev.dimension.flare.data.io.FileType import dev.dimension.flare.common.encodeJson +import dev.dimension.flare.common.tryRun +import dev.dimension.flare.data.account.CredentialProvider +import dev.dimension.flare.data.account.credentialFlow import dev.dimension.flare.data.database.app.AppDatabase import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.datasource.microblog.ActionMenu @@ -37,15 +40,11 @@ import dev.dimension.flare.data.datasource.microblog.ComposeType import dev.dimension.flare.data.datasource.microblog.DatabaseUpdater import dev.dimension.flare.data.datasource.microblog.DirectMessageDataSource import dev.dimension.flare.data.datasource.microblog.NotificationFilter -import dev.dimension.flare.data.datasource.microblog.PostEvent import dev.dimension.flare.data.datasource.microblog.ProfileTab import dev.dimension.flare.data.datasource.microblog.datasource.ListDataSource import dev.dimension.flare.data.datasource.microblog.datasource.NotificationDataSource -import dev.dimension.flare.data.datasource.microblog.datasource.PinnableTimelineTabDataSource -import dev.dimension.flare.data.datasource.microblog.datasource.PinnableTimelineTabSection import dev.dimension.flare.data.datasource.microblog.datasource.PostDataSource import dev.dimension.flare.data.datasource.microblog.datasource.RelationDataSource -import dev.dimension.flare.data.datasource.microblog.datasource.TimelineTabConfigurationDataSource import dev.dimension.flare.data.datasource.microblog.datasource.UserDataSource import dev.dimension.flare.data.datasource.microblog.handler.DirectMessageHandler import dev.dimension.flare.data.datasource.microblog.handler.ListHandler @@ -60,20 +59,23 @@ import dev.dimension.flare.data.datasource.microblog.loader.ListMemberLoader import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader import dev.dimension.flare.data.datasource.microblog.paging.notSupported import dev.dimension.flare.data.datasource.microblog.pagingConfig +import dev.dimension.flare.data.datasource.microblog.timeline.PinnableTimelineProvider +import dev.dimension.flare.data.datasource.microblog.timeline.PinnableTimelineTabSection +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineShortcutDescriptor +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineTabProvider +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineShortcutDescriptor +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineTabDescriptor import dev.dimension.flare.data.model.IconType -import dev.dimension.flare.data.model.tab.ShortcutSpec -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.model.tab.toSlot import dev.dimension.flare.data.network.bluesky.BlueskyService import dev.dimension.flare.data.network.bluesky.model.DidDoc -import dev.dimension.flare.data.platform.BlueskyPlatformSpec -import dev.dimension.flare.data.platform.CommonTimelineSpecs -import dev.dimension.flare.data.platform.toTimelineTabItemV2 -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.tryRun +import dev.dimension.flare.data.platform.BlueskyTimelineDataSource +import dev.dimension.flare.data.platform.BlueskyTimelineSpecs +import dev.dimension.flare.data.platform.toTimelineTabDescriptor +import dev.dimension.flare.media.ImageCompressor import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.shared.image.ImageCompressor +import dev.dimension.flare.ui.model.PostEvent import dev.dimension.flare.ui.model.UiAccount import dev.dimension.flare.ui.model.UiHashtag import dev.dimension.flare.ui.model.UiIcon @@ -88,8 +90,6 @@ import dev.dimension.flare.ui.model.mapper.bskyJson import dev.dimension.flare.ui.model.mapper.parseBskyFacets import dev.dimension.flare.ui.model.mapper.render import dev.dimension.flare.ui.presenter.compose.ComposeStatus -import dev.dimension.flare.ui.presenter.status.action.BlueskyReportStatusState -import dev.dimension.flare.ui.route.DeeplinkRoute import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -113,9 +113,8 @@ import sh.christian.ozone.api.Handle import sh.christian.ozone.api.Language import sh.christian.ozone.api.Nsid import sh.christian.ozone.api.RKey -import sh.christian.ozone.api.model.JsonContent -import sh.christian.ozone.api.model.JsonContent.Companion.encodeAsJsonContent import kotlin.time.Clock +import dev.dimension.flare.data.datasource.microblog.timeline.CommonTimelineSpecs as SocialCommonTimelineSpecs private const val AT_PROTO_PERSONAL_DATA_SERVER = "AtprotoPersonalDataServer" @@ -128,18 +127,21 @@ internal class BlueskyDataSource( PostDataSource, KoinComponent, ListDataSource, - PinnableTimelineTabDataSource, - TimelineTabConfigurationDataSource, + BlueskyTimelineDataSource, + BlueskyFeedDataSource, + BlueskyReportDataSource, + PinnableTimelineProvider, + TimelineTabProvider, RelationDataSource, DirectMessageDataSource, PostEventHandler.Handler { private val database: CacheDatabase by inject() private val appDatabase: AppDatabase by inject() private val coroutineScope: CoroutineScope by inject() - private val accountRepository: AccountRepository by inject() + private val credentialProvider: CredentialProvider by inject() private val imageCompressor: ImageCompressor by inject() private val credentialFlow by lazy { - accountRepository.credentialFlow(accountKey) + credentialProvider.credentialFlow(accountKey) } private var cachedPdsService: BlueskyService? = null @@ -222,6 +224,7 @@ internal class BlueskyDataSource( PostEventHandler( accountType = AccountType.Specific(accountKey), handler = this, + optimisticActionMenu = { it.blueskyNextActionMenu() }, ) } @@ -419,9 +422,9 @@ internal class BlueskyDataSource( ).requireResponse() } - suspend fun report( + override suspend fun report( statusKey: MicroBlogKey, - reason: BlueskyReportStatusState.ReportReason, + reason: BlueskyReportReason, ) { tryRun { val service = pdsService() @@ -436,12 +439,12 @@ internal class BlueskyDataSource( CreateReportRequest( reasonType = when (reason) { - BlueskyReportStatusState.ReportReason.Spam -> Token.ReasonSpam - BlueskyReportStatusState.ReportReason.Violation -> Token.ReasonViolation - BlueskyReportStatusState.ReportReason.Misleading -> Token.ReasonMisleading - BlueskyReportStatusState.ReportReason.Sexual -> Token.ReasonSexual - BlueskyReportStatusState.ReportReason.Rude -> Token.ReasonRude - BlueskyReportStatusState.ReportReason.Other -> Token.ReasonOther + BlueskyReportReason.Spam -> Token.ReasonSpam + BlueskyReportReason.Violation -> Token.ReasonViolation + BlueskyReportReason.Misleading -> Token.ReasonMisleading + BlueskyReportReason.Sexual -> Token.ReasonSexual + BlueskyReportReason.Rude -> Token.ReasonRude + BlueskyReportReason.Other -> Token.ReasonOther }, subject = CreateReportRequestSubjectUnion.RepoStrongRef( @@ -649,7 +652,7 @@ internal class BlueskyDataSource( ) } - val feedHandler: ListHandler by lazy { + override val feedHandler: ListHandler by lazy { ListHandler( pagingKey = myFeedsKey, accountKey = accountKey, @@ -657,7 +660,7 @@ internal class BlueskyDataSource( ) } - fun popularFeeds( + override fun popularFeeds( query: String?, scope: CoroutineScope, ): Flow>> = @@ -702,25 +705,25 @@ internal class BlueskyDataSource( } }.cachedIn(scope) - fun feedTimelineLoader(uri: String) = + override fun feedTimelineLoader(uri: String) = FeedTimelineRemoteMediator( getService = this::pdsService, accountKey = accountKey, uri = uri, ) - suspend fun subscribeFeed(data: UiList.Feed) { + override suspend fun subscribeFeed(data: UiList.Feed) { tryRun { feedLoader.subscribe(data.id) feedHandler.insertToDatabase(data) } } - suspend fun unsubscribeFeed(data: UiList.Feed) { + override suspend fun unsubscribeFeed(data: UiList.Feed) { feedHandler.delete(data.id) } - suspend fun favouriteFeed(data: UiList.Feed) { + override suspend fun favouriteFeed(data: UiList.Feed) { feedHandler.withDatabase { updataCallback -> val newData = data.copy(liked = !data.liked) updataCallback(newData) @@ -773,71 +776,69 @@ internal class BlueskyDataSource( title = UiStrings.Feeds, data = feedHandler.data.map { paging -> - paging.map { it.toTimelineTabItemV2(accountKey) } + paging.map { it.toTimelineTabDescriptor(accountKey) } }, ), PinnableTimelineTabSection( title = UiStrings.List, data = listHandler.data.map { paging -> - paging.map { it.toTimelineTabItemV2(accountKey) } + paging.map { it.toTimelineTabDescriptor(accountKey) } }, ), ) } - override val defaultTabs by lazy { + override val defaultTimelineTabs by lazy { persistentListOf( - CommonTimelineSpecs.home - .target( + SocialCommonTimelineSpecs.home + .toTimelineTabDescriptor( data = TimelineSpec.AccountBasedData(accountKey), icon = IconType.FavIcon(accountKey.host), - ).toSlot(), + ), ) } override val builtInTimelineTabs by lazy { persistentListOf( - CommonTimelineSpecs.home.tabItem( + SocialCommonTimelineSpecs.home.toTimelineTabDescriptor( data = TimelineSpec.AccountBasedData(accountKey), icon = IconType.FavIcon(accountKey.host), ), - BlueskyPlatformSpec.bookmarkTimelineSpec.tabItem(TimelineSpec.AccountBasedData(accountKey)), + BlueskyTimelineSpecs.bookmark.toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)), ) } - override val shortcuts by lazy { + override val timelineShortcuts by lazy { persistentListOf( - ShortcutSpec( + TimelineShortcutDescriptor( title = UiStrings.List, icon = UiIcon.List, target = - ShortcutSpec.Target.Route( - DeeplinkRoute.AllLists(accountKey), + TimelineShortcutDescriptor.Target.Route( + id = TimelineShortcutDescriptor.RouteIds.ALL_LISTS, + accountKey = accountKey, ), ), - ShortcutSpec( + TimelineShortcutDescriptor( title = UiStrings.Feeds, icon = UiIcon.Feeds, target = - ShortcutSpec.Target.Route( - DeeplinkRoute.Bluesky.AllFeeds(accountKey), - ), - ), - ShortcutSpec( - title = UiStrings.Bookmark, - icon = UiIcon.Bookmark, - target = - ShortcutSpec.Target.Timeline( - BlueskyPlatformSpec.bookmarkTimelineSpec.target(TimelineSpec.AccountBasedData(accountKey)), + TimelineShortcutDescriptor.Target.Route( + id = TimelineShortcutDescriptor.RouteIds.BLUESKY_ALL_FEEDS, + accountKey = accountKey, ), ), - ShortcutSpec( + BlueskyTimelineSpecs.bookmark + .toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)) + .toTimelineShortcutDescriptor(UiStrings.Bookmark, UiIcon.Bookmark), + TimelineShortcutDescriptor( title = UiStrings.DirectMessage, icon = UiIcon.Messages, target = - ShortcutSpec.Target.Route( - DeeplinkRoute.AllDirectMessages(accountKey), + TimelineShortcutDescriptor.Target.Route( + id = TimelineShortcutDescriptor.RouteIds.ALL_DIRECT_MESSAGES, + accountKey = accountKey, ), ), ) @@ -918,11 +919,9 @@ internal class BlueskyDataSource( }, ).toPersistentList() - fun bookmarkTimeline(): RemoteLoader = + override fun bookmarkTimeline(): RemoteLoader = BookmarkTimelineRemoteMediator( getService = this::pdsService, accountKey = accountKey, ) } - -internal inline fun T.bskyJson(): JsonContent = bskyJson.encodeAsJsonContent(this) diff --git a/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSourceCapabilities.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSourceCapabilities.kt new file mode 100644 index 0000000000..1ba6cf656e --- /dev/null +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSourceCapabilities.kt @@ -0,0 +1,40 @@ +package dev.dimension.flare.data.datasource.bluesky + +import androidx.paging.PagingData +import dev.dimension.flare.data.datasource.microblog.handler.ListHandler +import dev.dimension.flare.data.platform.BlueskyTimelineDataSource +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow + +public interface BlueskyFeedDataSource : BlueskyTimelineDataSource { + public val feedHandler: ListHandler + + public fun popularFeeds( + query: String?, + scope: CoroutineScope, + ): Flow>> + + public suspend fun subscribeFeed(data: UiList.Feed) + + public suspend fun unsubscribeFeed(data: UiList.Feed) + + public suspend fun favouriteFeed(data: UiList.Feed) +} + +public interface BlueskyReportDataSource { + public suspend fun report( + statusKey: MicroBlogKey, + reason: BlueskyReportReason, + ) +} + +public enum class BlueskyReportReason { + Spam, + Violation, + Misleading, + Sexual, + Rude, + Other, +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDirectMessageLoader.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDirectMessageLoader.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDirectMessageLoader.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDirectMessageLoader.kt index 11e849bde5..d6bce44bf2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDirectMessageLoader.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDirectMessageLoader.kt @@ -14,13 +14,13 @@ import chat.bsky.convo.LogDeleteMessageMessageUnion import chat.bsky.convo.MessageInput import chat.bsky.convo.SendMessageRequest import chat.bsky.convo.UpdateReadRequest +import dev.dimension.flare.common.tryRun import dev.dimension.flare.data.database.cache.mapper.Bluesky import dev.dimension.flare.data.datasource.microblog.loader.DirectMessageDelta import dev.dimension.flare.data.datasource.microblog.loader.DirectMessageLoader import dev.dimension.flare.data.datasource.microblog.paging.PagingRequest import dev.dimension.flare.data.datasource.microblog.paging.PagingResult import dev.dimension.flare.data.network.bluesky.BlueskyService -import dev.dimension.flare.data.repository.tryRun import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType import dev.dimension.flare.ui.model.UiDMItem @@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import sh.christian.ozone.api.Did -internal class BlueskyDirectMessageLoader( +public class BlueskyDirectMessageLoader( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, ) : DirectMessageLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyFeedLoader.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyFeedLoader.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyFeedLoader.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyFeedLoader.kt index b77fcc1a7e..ba9295763b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyFeedLoader.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyFeedLoader.kt @@ -27,9 +27,10 @@ import sh.christian.ozone.api.Did import sh.christian.ozone.api.Nsid import sh.christian.ozone.api.RKey import kotlin.time.Clock +import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid -internal class BlueskyFeedLoader( +public class BlueskyFeedLoader( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, ) : ListLoader { @@ -158,7 +159,8 @@ internal class BlueskyFeedLoader( ) } - suspend fun subscribe(feedUri: String) { + @OptIn(ExperimentalUuidApi::class) + public suspend fun subscribe(feedUri: String) { val service = getService() val currentPreferences = service.getPreferencesForActor().requireResponse() val feedInfo = @@ -204,7 +206,7 @@ internal class BlueskyFeedLoader( ) } - suspend fun favourite(feedUri: String) { + public suspend fun favourite(feedUri: String) { val service = getService() val feedInfo = service @@ -222,7 +224,7 @@ internal class BlueskyFeedLoader( } } - suspend fun unfavourite(feedUri: String) { + public suspend fun unfavourite(feedUri: String) { val service = getService() val feedInfo = service diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyListLoader.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyListLoader.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyListLoader.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyListLoader.kt index 539623018e..6019319a51 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyListLoader.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyListLoader.kt @@ -7,7 +7,7 @@ import com.atproto.repo.ApplyWritesRequest import com.atproto.repo.ApplyWritesRequestWriteUnion import com.atproto.repo.CreateRecordRequest import com.atproto.repo.PutRecordRequest -import dev.dimension.flare.common.FileItem +import dev.dimension.flare.data.io.FileItem import dev.dimension.flare.data.datasource.microblog.list.ListMetaData import dev.dimension.flare.data.datasource.microblog.list.ListMetaDataType import dev.dimension.flare.data.datasource.microblog.loader.ListLoader @@ -26,7 +26,7 @@ import sh.christian.ozone.api.Nsid import sh.christian.ozone.api.RKey import kotlin.time.Clock -internal class BlueskyListLoader( +public class BlueskyListLoader( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, ) : ListLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyListMemberLoader.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyListMemberLoader.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyListMemberLoader.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyListMemberLoader.kt index 13dde7a599..a163a890a0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyListMemberLoader.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyListMemberLoader.kt @@ -21,7 +21,7 @@ import sh.christian.ozone.api.Nsid import sh.christian.ozone.api.RKey import kotlin.time.Clock -internal class BlueskyListMemberLoader( +public class BlueskyListMemberLoader( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, ) : ListMemberLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyLoader.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyLoader.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyLoader.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyLoader.kt index 9426a13cae..61f3c285e2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyLoader.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyLoader.kt @@ -28,8 +28,8 @@ import sh.christian.ozone.api.Nsid import sh.christian.ozone.api.RKey import kotlin.time.Clock -internal class BlueskyLoader( - val accountKey: MicroBlogKey, +public class BlueskyLoader( + public val accountKey: MicroBlogKey, private val getService: suspend () -> BlueskyService, ) : NotificationLoader, UserLoader, diff --git a/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyPostEventActionMenu.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyPostEventActionMenu.kt new file mode 100644 index 0000000000..1c1d544fd3 --- /dev/null +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyPostEventActionMenu.kt @@ -0,0 +1,47 @@ +package dev.dimension.flare.data.datasource.bluesky + +import dev.dimension.flare.data.datasource.microblog.ActionMenu +import dev.dimension.flare.ui.model.PostEvent +import dev.dimension.flare.ui.model.mapper.blueskyBookmark +import dev.dimension.flare.ui.model.mapper.blueskyLike +import dev.dimension.flare.ui.model.mapper.blueskyReblog + +internal fun PostEvent.blueskyNextActionMenu(): ActionMenu.Item? = + when (this) { + is PostEvent.Bluesky.Reblog -> { + ActionMenu.blueskyReblog( + accountKey = accountKey, + postKey = postKey, + cid = cid, + uri = uri, + count = count + if (repostUri == null) 1 else -1, + repostUri = if (repostUri == null) "" else null, + ) + } + + is PostEvent.Bluesky.Like -> { + ActionMenu.blueskyLike( + accountKey = accountKey, + postKey = postKey, + cid = cid, + uri = uri, + count = count + if (likedUri == null) 1 else -1, + likedUri = if (likedUri == null) "" else null, + ) + } + + is PostEvent.Bluesky.Bookmark -> { + ActionMenu.blueskyBookmark( + accountKey = accountKey, + postKey = postKey, + cid = cid, + uri = uri, + bookmarked = !bookmarked, + count = count + if (!bookmarked) 1 else -1, + ) + } + + else -> { + null + } + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BookmarkTimelineRemoteMediator.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BookmarkTimelineRemoteMediator.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BookmarkTimelineRemoteMediator.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BookmarkTimelineRemoteMediator.kt index 2695dc6de6..11ae066995 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BookmarkTimelineRemoteMediator.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BookmarkTimelineRemoteMediator.kt @@ -11,7 +11,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class BookmarkTimelineRemoteMediator( +public class BookmarkTimelineRemoteMediator( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, ) : CacheableRemoteLoader { diff --git a/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BskyJsonContent.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BskyJsonContent.kt new file mode 100644 index 0000000000..f70cf629eb --- /dev/null +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BskyJsonContent.kt @@ -0,0 +1,7 @@ +package dev.dimension.flare.data.datasource.bluesky + +import dev.dimension.flare.ui.model.mapper.bskyJson +import sh.christian.ozone.api.model.JsonContent +import sh.christian.ozone.api.model.JsonContent.Companion.encodeAsJsonContent + +public inline fun T.bskyJson(): JsonContent = bskyJson.encodeAsJsonContent(this) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FansPagingSource.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FansPagingSource.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FansPagingSource.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FansPagingSource.kt index e3a83e7b05..b298df9a22 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FansPagingSource.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FansPagingSource.kt @@ -10,7 +10,7 @@ import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render import sh.christian.ozone.api.Did -internal class FansPagingSource( +public class FansPagingSource( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, private val userKey: MicroBlogKey, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FeedTimelineRemoteMediator.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FeedTimelineRemoteMediator.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FeedTimelineRemoteMediator.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FeedTimelineRemoteMediator.kt index 1fd27349ac..f45a4c30ce 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FeedTimelineRemoteMediator.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FeedTimelineRemoteMediator.kt @@ -12,12 +12,12 @@ import dev.dimension.flare.ui.model.mapper.render import sh.christian.ozone.api.AtUri @OptIn(ExperimentalPagingApi::class) -internal class FeedTimelineRemoteMediator( +public class FeedTimelineRemoteMediator( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, private val uri: String, ) : CacheableRemoteLoader { - override val pagingKey = "feed_timeline_$uri" + override val pagingKey: String = "feed_timeline_$uri" override suspend fun load( pageSize: Int, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FollowingPagingSource.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FollowingPagingSource.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FollowingPagingSource.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FollowingPagingSource.kt index c45261c669..2bd94ba1a3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FollowingPagingSource.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/FollowingPagingSource.kt @@ -10,7 +10,7 @@ import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render import sh.christian.ozone.api.Did -internal class FollowingPagingSource( +public class FollowingPagingSource( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, private val userKey: MicroBlogKey, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/HomeTimelineRemoteMediator.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/HomeTimelineRemoteMediator.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/HomeTimelineRemoteMediator.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/HomeTimelineRemoteMediator.kt index eb922d607e..512968afaf 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/HomeTimelineRemoteMediator.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/HomeTimelineRemoteMediator.kt @@ -11,7 +11,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class HomeTimelineRemoteMediator( +public class HomeTimelineRemoteMediator( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, ) : CacheableRemoteLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/ListTimelineRemoteMediator.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/ListTimelineRemoteMediator.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/ListTimelineRemoteMediator.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/ListTimelineRemoteMediator.kt index f4a70d7357..e35028c059 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/ListTimelineRemoteMediator.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/ListTimelineRemoteMediator.kt @@ -12,12 +12,12 @@ import dev.dimension.flare.ui.model.mapper.render import sh.christian.ozone.api.AtUri @OptIn(ExperimentalPagingApi::class) -internal class ListTimelineRemoteMediator( +public class ListTimelineRemoteMediator( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, private val uri: String, ) : CacheableRemoteLoader { - override val pagingKey = "list_timeline_${uri}_$accountKey" + override val pagingKey: String = "list_timeline_${uri}_$accountKey" override suspend fun load( pageSize: Int, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/NotificationRemoteMediator.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/NotificationRemoteMediator.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/NotificationRemoteMediator.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/NotificationRemoteMediator.kt index 41c72e3d0d..516218978a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/NotificationRemoteMediator.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/NotificationRemoteMediator.kt @@ -19,7 +19,7 @@ import kotlinx.collections.immutable.toImmutableMap import kotlin.time.Clock @OptIn(ExperimentalPagingApi::class) -internal class NotificationRemoteMediator( +public class NotificationRemoteMediator( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, private val onClearMarker: () -> Unit, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchStatusRemoteMediator.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchStatusRemoteMediator.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchStatusRemoteMediator.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchStatusRemoteMediator.kt index f69c3ab564..02401ae4bc 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchStatusRemoteMediator.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchStatusRemoteMediator.kt @@ -11,7 +11,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class SearchStatusRemoteMediator( +public class SearchStatusRemoteMediator( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, private val query: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchUserPagingSource.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchUserPagingSource.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchUserPagingSource.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchUserPagingSource.kt index 8d91daae09..975c14c78d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchUserPagingSource.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/SearchUserPagingSource.kt @@ -9,7 +9,7 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render -internal class SearchUserPagingSource( +public class SearchUserPagingSource( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, private val query: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/StatusDetailRemoteMediator.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/StatusDetailRemoteMediator.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/StatusDetailRemoteMediator.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/StatusDetailRemoteMediator.kt index 9d376b88f9..20679d592f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/StatusDetailRemoteMediator.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/StatusDetailRemoteMediator.kt @@ -19,7 +19,7 @@ import kotlinx.collections.immutable.toPersistentList import sh.christian.ozone.api.AtUri @OptIn(ExperimentalPagingApi::class) -internal class StatusDetailRemoteMediator( +public class StatusDetailRemoteMediator( private val statusKey: MicroBlogKey, private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/TrendsUserPagingSource.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/TrendsUserPagingSource.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/TrendsUserPagingSource.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/TrendsUserPagingSource.kt index 4b866f3025..05dfd0b79c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/TrendsUserPagingSource.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/TrendsUserPagingSource.kt @@ -9,7 +9,7 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render -internal class TrendsUserPagingSource( +public class TrendsUserPagingSource( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, ) : RemoteLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserLikesTimelineRemoteMediator.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserLikesTimelineRemoteMediator.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserLikesTimelineRemoteMediator.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserLikesTimelineRemoteMediator.kt index 02a9a453e8..f39d58d30b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserLikesTimelineRemoteMediator.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserLikesTimelineRemoteMediator.kt @@ -12,11 +12,11 @@ import dev.dimension.flare.ui.model.mapper.render import sh.christian.ozone.api.Did @OptIn(ExperimentalPagingApi::class) -internal class UserLikesTimelineRemoteMediator( +public class UserLikesTimelineRemoteMediator( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, ) : CacheableRemoteLoader { - override val pagingKey = "user_timeline_likes_$accountKey" + override val pagingKey: String = "user_timeline_likes_$accountKey" override suspend fun load( pageSize: Int, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserTimelineRemoteMediator.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserTimelineRemoteMediator.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserTimelineRemoteMediator.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserTimelineRemoteMediator.kt index 039994a549..30f2c1c8bc 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserTimelineRemoteMediator.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/UserTimelineRemoteMediator.kt @@ -13,14 +13,14 @@ import dev.dimension.flare.ui.model.mapper.render import sh.christian.ozone.api.Did @OptIn(ExperimentalPagingApi::class) -internal class UserTimelineRemoteMediator( +public class UserTimelineRemoteMediator( private val getService: suspend () -> BlueskyService, private val accountKey: MicroBlogKey, private val userKey: MicroBlogKey, private val onlyMedia: Boolean = false, private val withReplies: Boolean = false, ) : CacheableRemoteLoader { - override val pagingKey = + override val pagingKey: String = buildString { append("user_timeline") if (onlyMedia) { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyAuthPlugin.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyAuthPlugin.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyAuthPlugin.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyAuthPlugin.kt index 037234dbbd..a02541ba9a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyAuthPlugin.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyAuthPlugin.kt @@ -1,7 +1,7 @@ package dev.dimension.flare.data.network.bluesky import com.atproto.server.RefreshSessionResponse -import dev.dimension.flare.data.repository.LoginExpiredException +import dev.dimension.flare.model.LoginExpiredException import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType import dev.dimension.flare.ui.model.UiAccount diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyPlatformDetector.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyPlatformDetector.kt similarity index 84% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyPlatformDetector.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyPlatformDetector.kt index 8268eec1a4..c3eb59709f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyPlatformDetector.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyPlatformDetector.kt @@ -1,11 +1,11 @@ package dev.dimension.flare.data.network.bluesky +import dev.dimension.flare.common.tryRun import dev.dimension.flare.data.network.nodeinfo.NodeData import dev.dimension.flare.data.network.nodeinfo.PlatformDetector -import dev.dimension.flare.data.repository.tryRun import dev.dimension.flare.model.PlatformType -internal data object BlueskyPlatformDetector : PlatformDetector { +public data object BlueskyPlatformDetector : PlatformDetector { override val priority: Int = 80 override suspend fun detect(host: String): NodeData? = diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyService.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyService.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyService.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyService.kt index 633072ab05..0052d341b0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyService.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyService.kt @@ -23,7 +23,7 @@ import kotlin.io.encoding.Base64 // compatibility support for darwin (iOS, macOS) since Ktor's SHA-256 implementation is not available there // see: https://github.com/ktorio/ktor/blob/477d76409fec6c2d71683817c6060f1b2afdcbb2/ktor-utils/posix/src/io/ktor/util/CryptoNative.kt#L25C57-L25C92 -internal data object OAuthCodeChallengeMethodS256 : OAuthCodeChallengeMethod("S256") { +public data object OAuthCodeChallengeMethodS256 : OAuthCodeChallengeMethod("S256") { override suspend fun provideCodeChallenge(codeVerifier: String): String { val hasher = CryptographyProvider @@ -43,7 +43,7 @@ internal data object OAuthCodeChallengeMethodS256 : OAuthCodeChallengeMethod("S2 .replace('/', '_') } -internal data class BlueskyService private constructor( +public data class BlueskyService private constructor( private val baseUrlFlow: Flow, private val accountKey: MicroBlogKey? = null, private val authTokenFlow: Flow? = null, @@ -71,7 +71,7 @@ internal data class BlueskyService private constructor( expectSuccess = false }, ) { - constructor( + public constructor( accountKey: MicroBlogKey, credentialFlow: Flow, onCredentialRefreshed: suspend (UiAccount.Bluesky.Credential) -> Unit, @@ -82,13 +82,13 @@ internal data class BlueskyService private constructor( onCredentialRefreshed = onCredentialRefreshed, ) - constructor( + public constructor( baseUrl: String, ) : this( baseUrlFlow = flowOf(baseUrl), ) - fun newBaseUrlService(baseUrl: String): BlueskyService = copy(baseUrlFlow = flowOf(baseUrl)) + public fun newBaseUrlService(baseUrl: String): BlueskyService = copy(baseUrlFlow = flowOf(baseUrl)) } private class AtprotoProxyPlugin { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/model/DidDoc.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/model/DidDoc.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/model/DidDoc.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/model/DidDoc.kt index b95ed2c5ab..91aa03d822 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/model/DidDoc.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/network/bluesky/model/DidDoc.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class DidDoc( +public data class DidDoc( @SerialName("@context") val context: List? = null, val id: String? = null, @@ -14,14 +14,14 @@ internal data class DidDoc( ) @Serializable -internal data class Service( +public data class Service( val id: String? = null, val type: String? = null, val serviceEndpoint: String? = null, ) @Serializable -internal data class VerificationMethod( +public data class VerificationMethod( val id: String? = null, val type: String? = null, val controller: String? = null, diff --git a/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/platform/BlueskySocialPlatformPlugin.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/platform/BlueskySocialPlatformPlugin.kt new file mode 100644 index 0000000000..5e91c80748 --- /dev/null +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/platform/BlueskySocialPlatformPlugin.kt @@ -0,0 +1,78 @@ +package dev.dimension.flare.data.platform + +import dev.dimension.flare.common.deeplink.DeepLinkMapping +import dev.dimension.flare.common.deeplink.DeepLinkPattern +import dev.dimension.flare.data.datasource.bluesky.BlueskyDataSource +import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.network.bluesky.BlueskyPlatformDetector +import dev.dimension.flare.data.network.nodeinfo.PlatformDetector +import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.model.PlatformTypeMetadata +import dev.dimension.flare.model.SocialPlatformPlugin +import dev.dimension.flare.model.SocialPlatformSpec +import dev.dimension.flare.ui.model.UiAccount +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiInstance +import dev.dimension.flare.ui.model.UiInstanceMetadata +import io.ktor.http.Url +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList + +public data object BlueskySocialPlatformPlugin : SocialPlatformPlugin { + public override val spec: SocialPlatformSpec = BlueskySocialPlatformSpec + + public override fun createDataSource(account: UiAccount): MicroblogDataSource? = + (account as? UiAccount.Bluesky)?.let { + BlueskyDataSource( + accountKey = it.accountKey, + ) + } + + public override suspend fun recommendedInstances(): List = + listOf( + UiInstance( + name = "Bluesky", + description = + "The web. Email. RSS feeds. XMPP chats. " + + "What all these technologies had in common is they allowed people to freely interact " + + "and create content, without a single intermediary.", + iconUrl = null, + domain = "bsky.social", + type = PlatformType.Bluesky, + bannerUrl = null, + usersCount = 0, + ), + ) +} + +public data object BlueskySocialPlatformSpec : SocialPlatformSpec { + public override val type: PlatformType = PlatformType.Bluesky + public override val timelineSpecs: ImmutableList> = BlueskyTimelineSpecs.timelineSpecs + public override val metadata: PlatformTypeMetadata = + PlatformTypeMetadata( + displayName = "Bluesky", + icon = UiIcon.Bluesky, + ) + public override val detector: PlatformDetector = BlueskyPlatformDetector + + public override fun agreementUrl(host: String): String = "https://bsky.social/about/support/tos" + + public override fun deepLinkPatterns(host: String): ImmutableList> = + buildList { + add(DeepLinkPattern(DeepLinkMapping.Type.Profile.serializer(), Url("https://$host/profile/{handle}"))) + add(DeepLinkPattern(DeepLinkMapping.Type.BlueskyPost.serializer(), Url("https://$host/profile/{handle}/post/{id}"))) + if (host == "bsky.social") { + add(DeepLinkPattern(DeepLinkMapping.Type.Profile.serializer(), Url("https://bsky.app/profile/{handle}"))) + add(DeepLinkPattern(DeepLinkMapping.Type.BlueskyPost.serializer(), Url("https://bsky.app/profile/{handle}/post/{id}"))) + } + }.toImmutableList() + + public override suspend fun instanceMetadata(host: String): UiInstanceMetadata = + throw UnsupportedOperationException("${type.name} is not supported yet") + + public override fun guestDataSource( + host: String, + locale: String, + ): MicroblogDataSource = throw UnsupportedOperationException("${type.name} guest data source is not supported yet") +} diff --git a/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/platform/BlueskyTimelineDescriptorFactories.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/platform/BlueskyTimelineDescriptorFactories.kt new file mode 100644 index 0000000000..8b39ef437a --- /dev/null +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/platform/BlueskyTimelineDescriptorFactories.kt @@ -0,0 +1,18 @@ +package dev.dimension.flare.data.platform + +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineTabDescriptor +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineTabDescriptor +import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiList +import dev.dimension.flare.ui.model.UiText +import dev.dimension.flare.ui.model.asType + +public fun UiList.Feed.toTimelineTabDescriptor(accountKey: MicroBlogKey): TimelineTabDescriptor.Source = + BlueskyTimelineSpecs.feed.toTimelineTabDescriptor( + data = TimelineSpec.AccountResourceData(accountKey, id), + title = UiText.Raw(title), + icon = avatar?.let { IconType.Url(it) } ?: UiIcon.Feeds.asType(), + ) diff --git a/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/platform/BlueskyTimelineSpecs.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/platform/BlueskyTimelineSpecs.kt new file mode 100644 index 0000000000..17c02ca17f --- /dev/null +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/data/platform/BlueskyTimelineSpecs.kt @@ -0,0 +1,54 @@ +package dev.dimension.flare.data.platform + +import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader +import dev.dimension.flare.data.datasource.microblog.timeline.AccountTimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.CommonTimelineSpecs +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.UiTimelineV2 +import dev.dimension.flare.ui.model.asType +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +public interface BlueskyTimelineDataSource { + public fun bookmarkTimeline(): RemoteLoader + + public fun feedTimelineLoader(uri: String): RemoteLoader +} + +public object BlueskyTimelineSpecs { + public val bookmark: AccountTimelineSpec = + AccountTimelineSpec( + id = "bluesky.bookmark", + title = UiStrings.Bookmark, + icon = UiIcon.Bookmark.asType(), + serializer = TimelineSpec.AccountBasedData.serializer(), + stableKeyFactory = { it.accountKey.toString() }, + loaderFactory = { service, _ -> + require(service is BlueskyTimelineDataSource) + service.bookmarkTimeline() + }, + ) + + public val feed: AccountTimelineSpec = + AccountTimelineSpec( + id = "bluesky.feed", + title = UiStrings.Feeds, + icon = UiIcon.Feeds.asType(), + serializer = TimelineSpec.AccountResourceData.serializer(), + stableKeyFactory = { "${it.accountKey}:${it.resourceId}" }, + loaderFactory = { service, data -> + require(service is BlueskyTimelineDataSource) + service.feedTimelineLoader(data.resourceId) + }, + ) + + public val timelineSpecs: ImmutableList> = + persistentListOf( + CommonTimelineSpecs.home, + CommonTimelineSpecs.list, + bookmark, + feed, + ) +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Bluesky.kt b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Bluesky.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Bluesky.kt rename to social/bluesky/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Bluesky.kt index cd1e15ee5c..648febde94 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Bluesky.kt +++ b/social/bluesky/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Bluesky.kt @@ -31,13 +31,13 @@ import chat.bsky.convo.DeletedMessageView import chat.bsky.convo.MessageView import dev.dimension.flare.common.SerializableImmutableList import dev.dimension.flare.data.datasource.microblog.ActionMenu -import dev.dimension.flare.data.datasource.microblog.PostEvent import dev.dimension.flare.data.datasource.microblog.userActionsMenu import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType import dev.dimension.flare.model.toAccountType import dev.dimension.flare.ui.model.ClickEvent +import dev.dimension.flare.ui.model.PostEvent import dev.dimension.flare.ui.model.UiCard import dev.dimension.flare.ui.model.UiDMItem import dev.dimension.flare.ui.model.UiHandle @@ -100,7 +100,7 @@ private val parser = enableDomainDetection = true, ) -internal val bskyJson by lazy { +public val bskyJson: Json by lazy { Json { ignoreUnknownKeys = true classDiscriminator = "${'$'}type" @@ -109,7 +109,7 @@ internal val bskyJson by lazy { private fun List.stringify(): String = this.toByteArray().decodeToString() -internal fun BookmarkView.render(accountKey: MicroBlogKey): UiTimelineV2? = +public fun BookmarkView.render(accountKey: MicroBlogKey): UiTimelineV2? = when (val content = item) { is BookmarkViewItemUnion.BlockedPost -> null is BookmarkViewItemUnion.NotFoundPost -> null @@ -117,7 +117,7 @@ internal fun BookmarkView.render(accountKey: MicroBlogKey): UiTimelineV2? = is BookmarkViewItemUnion.Unknown -> null } -internal fun parseBlueskyJson( +public fun parseBlueskyJson( json: JsonContent, accountKey: MicroBlogKey, sourceLanguages: List = emptyList(), @@ -157,7 +157,7 @@ private fun JsonContent.sourceLanguages(): PersistentList { ?: persistentListOf() } -internal suspend fun parseBskyFacets( +public suspend fun parseBskyFacets( content: String, resolveMentionDid: suspend (handle: String) -> String, ): List { @@ -520,9 +520,9 @@ private val ListNotificationsNotificationReason.type: UiTimelineV2.Message.Type } } -internal fun List.render(accountKey: MicroBlogKey): List = this.map { it.render(accountKey) } +public fun List.render(accountKey: MicroBlogKey): List = this.map { it.render(accountKey) } -internal fun List.render( +public fun List.render( accountKey: MicroBlogKey, references: ImmutableMap, ): List { @@ -765,7 +765,7 @@ private fun FeedViewPost.render(accountKey: MicroBlogKey): UiTimelineV2 { ) } -internal fun PostView.render(accountKey: MicroBlogKey): UiTimelineV2.Post { +public fun PostView.render(accountKey: MicroBlogKey): UiTimelineV2.Post { val user = author.render(accountKey) val isFromMe = user.key == accountKey val statusKey = @@ -971,7 +971,7 @@ internal fun PostView.render(accountKey: MicroBlogKey): UiTimelineV2.Post { ) } -internal fun ActionMenu.Companion.blueskyReblog( +public fun ActionMenu.Companion.blueskyReblog( accountKey: MicroBlogKey, postKey: MicroBlogKey, count: Long, @@ -1007,7 +1007,7 @@ internal fun ActionMenu.Companion.blueskyReblog( }, ) -internal fun ActionMenu.Companion.blueskyLike( +public fun ActionMenu.Companion.blueskyLike( accountKey: MicroBlogKey, postKey: MicroBlogKey, count: Long, @@ -1043,7 +1043,7 @@ internal fun ActionMenu.Companion.blueskyLike( }, ) -internal fun ActionMenu.Companion.blueskyBookmark( +public fun ActionMenu.Companion.blueskyBookmark( accountKey: MicroBlogKey, postKey: MicroBlogKey, uri: String, @@ -1078,7 +1078,7 @@ internal fun ActionMenu.Companion.blueskyBookmark( }, ) -internal fun chat.bsky.actor.ProfileViewBasic.render(accountKey: MicroBlogKey): UiProfile { +public fun chat.bsky.actor.ProfileViewBasic.render(accountKey: MicroBlogKey): UiProfile { val userKey = MicroBlogKey( id = did.did, @@ -1115,7 +1115,7 @@ internal fun chat.bsky.actor.ProfileViewBasic.render(accountKey: MicroBlogKey): ) } -internal fun ProfileViewBasic.render(accountKey: MicroBlogKey): UiProfile { +public fun ProfileViewBasic.render(accountKey: MicroBlogKey): UiProfile { val userKey = MicroBlogKey( id = did.did, @@ -1152,7 +1152,7 @@ internal fun ProfileViewBasic.render(accountKey: MicroBlogKey): UiProfile { ) } -internal fun ProfileView.render(accountKey: MicroBlogKey): UiProfile { +public fun ProfileView.render(accountKey: MicroBlogKey): UiProfile { val userKey = MicroBlogKey( id = did.did, @@ -1189,7 +1189,7 @@ internal fun ProfileView.render(accountKey: MicroBlogKey): UiProfile { ) } -internal fun ProfileViewDetailed.render(accountKey: MicroBlogKey): UiProfile { +public fun ProfileViewDetailed.render(accountKey: MicroBlogKey): UiProfile { val userKey = MicroBlogKey( id = did.did, @@ -1683,7 +1683,7 @@ private fun render( } } -internal fun GeneratorView.render(accountKey: MicroBlogKey) = +public fun GeneratorView.render(accountKey: MicroBlogKey): UiList.Feed = UiList.Feed( id = uri.atUri, title = displayName, @@ -1694,7 +1694,7 @@ internal fun GeneratorView.render(accountKey: MicroBlogKey) = liked = viewer?.like?.atUri != null, ) -internal fun ListView.render(accountKey: MicroBlogKey) = +public fun ListView.render(accountKey: MicroBlogKey): UiList.List = UiList.List( id = uri.atUri, title = name, @@ -1703,9 +1703,9 @@ internal fun ListView.render(accountKey: MicroBlogKey) = creator = creator.render(accountKey), ) -internal fun DeletedMessageView.render(): UiDMItem.Message = UiDMItem.Message.Deleted +public fun DeletedMessageView.render(): UiDMItem.Message = UiDMItem.Message.Deleted -internal fun MessageView.render(accountKey: MicroBlogKey) = +public fun MessageView.render(accountKey: MicroBlogKey): UiDMItem.Message.Text = UiDMItem.Message.Text( text = parseBluesky(text, facets.orEmpty(), accountKey), ) diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyAuthPluginTest.kt b/social/bluesky/src/commonTest/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyAuthPluginTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyAuthPluginTest.kt rename to social/bluesky/src/commonTest/kotlin/dev/dimension/flare/data/network/bluesky/BlueskyAuthPluginTest.kt diff --git a/social/mastodon/build.gradle.kts b/social/mastodon/build.gradle.kts new file mode 100644 index 0000000000..cf172f80b7 --- /dev/null +++ b/social/mastodon/build.gradle.kts @@ -0,0 +1,64 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ksp) + alias(libs.plugins.ktorfit) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.social.mastodon" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + ksp( + libs.ktorfit.ksp, + ) + } + + sourceSets { + all { + languageSettings { + optIn("kotlin.uuid.ExperimentalUuidApi") + } + } + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.core.model) + api(projects.modules.account.api) + api(projects.social.model) + api(projects.foundation.network) + implementation(projects.foundation.filesystem) + api(projects.social.api) + api(projects.social.microblog) + implementation(projects.social.nodeinfo) + api(libs.ktorfit.converters.response) + implementation(dependencies.platform(libs.koin.bom)) + implementation(libs.koin.core) + implementation(libs.ksoup) + implementation(libs.twitter.parser) + implementation(libs.kotlinx.datetime) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) + implementation(libs.ktor.client.mock) + } + } + } +} + +ktorfit { + compilerPluginVersion.set("2.3.3") +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestDiscoverStatusPagingSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestDiscoverStatusPagingSource.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestDiscoverStatusPagingSource.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestDiscoverStatusPagingSource.kt index 4d3fc70008..9982c5893b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestDiscoverStatusPagingSource.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestDiscoverStatusPagingSource.kt @@ -7,7 +7,7 @@ import dev.dimension.flare.data.network.mastodon.api.TrendsResources import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render -internal class GuestDiscoverStatusPagingSource( +public class GuestDiscoverStatusPagingSource( private val service: TrendsResources, private val host: String, ) : RemoteLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestMastodonDataSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestMastodonDataSource.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestMastodonDataSource.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestMastodonDataSource.kt index f6799195ef..f2b29def9b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestMastodonDataSource.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestMastodonDataSource.kt @@ -4,9 +4,9 @@ import dev.dimension.flare.data.datasource.mastodon.MastodonFansPagingSource import dev.dimension.flare.data.datasource.mastodon.MastodonFollowingPagingSource import dev.dimension.flare.data.datasource.mastodon.SearchUserPagingSource import dev.dimension.flare.data.datasource.mastodon.TrendHashtagPagingSource +import dev.dimension.flare.data.datasource.mastodon.mastodonNextActionMenu import dev.dimension.flare.data.datasource.microblog.DatabaseUpdater import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource -import dev.dimension.flare.data.datasource.microblog.PostEvent import dev.dimension.flare.data.datasource.microblog.ProfileTab import dev.dimension.flare.data.datasource.microblog.datasource.PostDataSource import dev.dimension.flare.data.datasource.microblog.datasource.RelationDataSource @@ -22,6 +22,7 @@ import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader import dev.dimension.flare.data.network.mastodon.GuestMastodonService import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.PostEvent import dev.dimension.flare.ui.model.UiHashtag import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.UiTimelineV2 @@ -74,6 +75,7 @@ internal class GuestMastodonDataSource( PostEventHandler( accountType = AccountType.GuestHost(host), handler = this, + optimisticActionMenu = { it.mastodonNextActionMenu() }, ) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestMastodonLoader.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestMastodonLoader.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestMastodonLoader.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestMastodonLoader.kt index 7a46ef71a8..6911b88b77 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestMastodonLoader.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestMastodonLoader.kt @@ -12,7 +12,7 @@ import dev.dimension.flare.ui.model.UiRelation import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render -internal class GuestMastodonLoader( +public class GuestMastodonLoader( private val host: String, private val service: GuestMastodonService, ) : UserLoader, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestPublicTimelineRemoteMediator.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestPublicTimelineRemoteMediator.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestPublicTimelineRemoteMediator.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestPublicTimelineRemoteMediator.kt index 0022e17869..a5d8364c4b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestPublicTimelineRemoteMediator.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestPublicTimelineRemoteMediator.kt @@ -7,7 +7,7 @@ import dev.dimension.flare.data.network.mastodon.GuestMastodonService import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render -internal class GuestPublicTimelineRemoteMediator( +public class GuestPublicTimelineRemoteMediator( private val host: String, private val locale: String, private val local: Boolean, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestSearchStatusPagingSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestSearchStatusPagingSource.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestSearchStatusPagingSource.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestSearchStatusPagingSource.kt index 145a3b67fc..28542d5202 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestSearchStatusPagingSource.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestSearchStatusPagingSource.kt @@ -7,7 +7,7 @@ import dev.dimension.flare.data.network.mastodon.GuestMastodonService import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render -internal class GuestSearchStatusPagingSource( +public class GuestSearchStatusPagingSource( private val service: GuestMastodonService, private val host: String, private val query: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestStatusDetailPagingSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestStatusDetailPagingSource.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestStatusDetailPagingSource.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestStatusDetailPagingSource.kt index aadcf5bfea..89d8232ab8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestStatusDetailPagingSource.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestStatusDetailPagingSource.kt @@ -8,7 +8,7 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render -internal class GuestStatusDetailPagingSource( +public class GuestStatusDetailPagingSource( private val service: GuestMastodonService, private val host: String, private val statusKey: MicroBlogKey, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestTimelinePagingSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestTimelinePagingSource.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestTimelinePagingSource.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestTimelinePagingSource.kt index c5f959916e..3a30569ed4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestTimelinePagingSource.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestTimelinePagingSource.kt @@ -7,7 +7,7 @@ import dev.dimension.flare.data.network.mastodon.api.TrendsResources import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render -internal class GuestTimelinePagingSource( +public class GuestTimelinePagingSource( private val service: TrendsResources, private val host: String, ) : RemoteLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestTrendsRemoteMediator.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestTrendsRemoteMediator.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestTrendsRemoteMediator.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestTrendsRemoteMediator.kt index 87a0c3f678..6978e27922 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestTrendsRemoteMediator.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestTrendsRemoteMediator.kt @@ -7,7 +7,7 @@ import dev.dimension.flare.data.network.mastodon.GuestMastodonService import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render -internal class GuestTrendsRemoteMediator( +public class GuestTrendsRemoteMediator( private val host: String, private val locale: String, ) : CacheableRemoteLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestUserTimelinePagingSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestUserTimelinePagingSource.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestUserTimelinePagingSource.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestUserTimelinePagingSource.kt index 3f108bb899..32b75f6ee0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestUserTimelinePagingSource.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/guest/mastodon/GuestUserTimelinePagingSource.kt @@ -7,7 +7,7 @@ import dev.dimension.flare.data.network.mastodon.api.TimelineResources import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render -internal class GuestUserTimelinePagingSource( +public class GuestUserTimelinePagingSource( private val service: TimelineResources, private val host: String, private val userId: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/BookmarkTimelineRemoteMediator.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/BookmarkTimelineRemoteMediator.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/BookmarkTimelineRemoteMediator.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/BookmarkTimelineRemoteMediator.kt index d15cd6a8b0..6d4d1e7e95 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/BookmarkTimelineRemoteMediator.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/BookmarkTimelineRemoteMediator.kt @@ -10,7 +10,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class BookmarkTimelineRemoteMediator( +public class BookmarkTimelineRemoteMediator( private val service: MastodonService, private val accountKey: MicroBlogKey, ) : CacheableRemoteLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/DiscoverStatusRemoteMediator.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/DiscoverStatusRemoteMediator.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/DiscoverStatusRemoteMediator.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/DiscoverStatusRemoteMediator.kt index fe5200a007..83aaf3416e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/DiscoverStatusRemoteMediator.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/DiscoverStatusRemoteMediator.kt @@ -10,7 +10,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class DiscoverStatusRemoteMediator( +public class DiscoverStatusRemoteMediator( private val service: MastodonService, private val accountKey: MicroBlogKey, ) : CacheableRemoteLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/FavouriteTimelineRemoteMediator.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/FavouriteTimelineRemoteMediator.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/FavouriteTimelineRemoteMediator.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/FavouriteTimelineRemoteMediator.kt index 19081f3f06..dd3326f7f8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/FavouriteTimelineRemoteMediator.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/FavouriteTimelineRemoteMediator.kt @@ -10,7 +10,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class FavouriteTimelineRemoteMediator( +public class FavouriteTimelineRemoteMediator( private val service: MastodonService, private val accountKey: MicroBlogKey, ) : CacheableRemoteLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/HomeTimelineRemoteMediator.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/HomeTimelineRemoteMediator.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/HomeTimelineRemoteMediator.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/HomeTimelineRemoteMediator.kt index 206af51022..b709e4c97f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/HomeTimelineRemoteMediator.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/HomeTimelineRemoteMediator.kt @@ -10,7 +10,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class HomeTimelineRemoteMediator( +public class HomeTimelineRemoteMediator( private val service: MastodonService, private val accountKey: MicroBlogKey, ) : CacheableRemoteLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/ListTimelineRemoteMediator.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/ListTimelineRemoteMediator.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/ListTimelineRemoteMediator.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/ListTimelineRemoteMediator.kt index c15b69dc85..6c49cdaa02 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/ListTimelineRemoteMediator.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/ListTimelineRemoteMediator.kt @@ -10,12 +10,12 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class ListTimelineRemoteMediator( +public class ListTimelineRemoteMediator( private val listId: String, private val service: MastodonService, private val accountKey: MicroBlogKey, ) : CacheableRemoteLoader { - override val pagingKey = "list_${accountKey}_$listId" + override val pagingKey: String = "list_${accountKey}_$listId" override val supportPrepend: Boolean get() = true diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonDataSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonDataSource.kt similarity index 84% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonDataSource.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonDataSource.kt index 5c73dbf96f..09111d50be 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonDataSource.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonDataSource.kt @@ -2,22 +2,21 @@ package dev.dimension.flare.data.datasource.mastodon import androidx.paging.ExperimentalPagingApi import androidx.paging.map -import dev.dimension.flare.common.FileType +import dev.dimension.flare.data.io.FileType +import dev.dimension.flare.common.tryRun +import dev.dimension.flare.data.account.CredentialProvider +import dev.dimension.flare.data.account.credentialFlow import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource import dev.dimension.flare.data.datasource.microblog.ComposeConfig import dev.dimension.flare.data.datasource.microblog.ComposeData import dev.dimension.flare.data.datasource.microblog.ComposeType import dev.dimension.flare.data.datasource.microblog.DatabaseUpdater import dev.dimension.flare.data.datasource.microblog.NotificationFilter -import dev.dimension.flare.data.datasource.microblog.PostEvent import dev.dimension.flare.data.datasource.microblog.ProfileTab import dev.dimension.flare.data.datasource.microblog.datasource.ListDataSource import dev.dimension.flare.data.datasource.microblog.datasource.NotificationDataSource -import dev.dimension.flare.data.datasource.microblog.datasource.PinnableTimelineTabDataSource -import dev.dimension.flare.data.datasource.microblog.datasource.PinnableTimelineTabSection import dev.dimension.flare.data.datasource.microblog.datasource.PostDataSource import dev.dimension.flare.data.datasource.microblog.datasource.RelationDataSource -import dev.dimension.flare.data.datasource.microblog.datasource.TimelineTabConfigurationDataSource import dev.dimension.flare.data.datasource.microblog.datasource.UserDataSource import dev.dimension.flare.data.datasource.microblog.handler.EmojiHandler import dev.dimension.flare.data.datasource.microblog.handler.ListHandler @@ -31,25 +30,27 @@ import dev.dimension.flare.data.datasource.microblog.loader.ListLoader import dev.dimension.flare.data.datasource.microblog.loader.ListMemberLoader import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader import dev.dimension.flare.data.datasource.microblog.paging.notSupported +import dev.dimension.flare.data.datasource.microblog.timeline.PinnableTimelineProvider +import dev.dimension.flare.data.datasource.microblog.timeline.PinnableTimelineTabSection +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineShortcutDescriptor +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineTabProvider +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineShortcutDescriptor +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineTabDescriptor import dev.dimension.flare.data.datasource.pleroma.PleromaDataSource import dev.dimension.flare.data.model.IconType -import dev.dimension.flare.data.model.tab.ShortcutSpec -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.model.tab.toSlot import dev.dimension.flare.data.network.mastodon.MastodonService import dev.dimension.flare.data.network.mastodon.api.model.PostPoll import dev.dimension.flare.data.network.mastodon.api.model.PostReport import dev.dimension.flare.data.network.mastodon.api.model.PostStatus import dev.dimension.flare.data.network.mastodon.api.model.PostVote import dev.dimension.flare.data.network.mastodon.api.model.Visibility -import dev.dimension.flare.data.platform.CommonTimelineSpecs -import dev.dimension.flare.data.platform.MastodonPlatformSpec -import dev.dimension.flare.data.platform.toTimelineTabItemV2 -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.tryRun +import dev.dimension.flare.data.platform.MastodonTimelineDataSource +import dev.dimension.flare.data.platform.MastodonTimelineSpecs +import dev.dimension.flare.media.ImageCompressor import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.shared.image.ImageCompressor +import dev.dimension.flare.ui.model.PostEvent import dev.dimension.flare.ui.model.UiAccount import dev.dimension.flare.ui.model.UiHashtag import dev.dimension.flare.ui.model.UiIcon @@ -58,7 +59,6 @@ import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.UiStrings import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.presenter.compose.ComposeStatus -import dev.dimension.flare.ui.route.DeeplinkRoute import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @@ -66,6 +66,7 @@ import kotlinx.coroutines.flow.map import org.koin.core.component.KoinComponent import org.koin.core.component.inject import kotlin.uuid.Uuid +import dev.dimension.flare.data.datasource.microblog.timeline.CommonTimelineSpecs as SocialCommonTimelineSpecs @OptIn(ExperimentalPagingApi::class) internal open class MastodonDataSource( @@ -77,17 +78,19 @@ internal open class MastodonDataSource( PostDataSource, KoinComponent, ListDataSource, - PinnableTimelineTabDataSource, - TimelineTabConfigurationDataSource, + MastodonTimelineDataSource, + MastodonReportDataSource, + PinnableTimelineProvider, + TimelineTabProvider, RelationDataSource, PostEventHandler.Handler { - private val accountRepository: AccountRepository by inject() + private val credentialProvider: CredentialProvider by inject() private val imageCompressor: ImageCompressor by inject() private val service by lazy { MastodonService( baseUrl = "https://$instance/", accessTokenFlow = - accountRepository + credentialProvider .credentialFlow(accountKey) .map { it.accessToken }, ) @@ -142,6 +145,7 @@ internal open class MastodonDataSource( PostEventHandler( accountType = AccountType.Specific(accountKey), handler = this, + optimisticActionMenu = { it.mastodonNextActionMenu() }, ) } @@ -183,13 +187,13 @@ internal open class MastodonDataSource( accountKey, ) - fun bookmarkTimelineLoader() = + override fun bookmarkTimelineLoader() = BookmarkTimelineRemoteMediator( service, accountKey, ) - fun favouriteTimelineLoader() = + override fun favouriteTimelineLoader() = FavouriteTimelineRemoteMediator( service, accountKey, @@ -202,7 +206,7 @@ internal open class MastodonDataSource( accountKey, ) - fun publicTimelineLoader(local: Boolean) = + override fun publicTimelineLoader(local: Boolean) = PublicTimelineRemoteMediator( service, accountKey, @@ -385,7 +389,7 @@ internal open class MastodonDataSource( } } - suspend fun report( + override suspend fun report( userKey: MicroBlogKey, statusKey: MicroBlogKey?, ) { @@ -491,79 +495,60 @@ internal open class MastodonDataSource( title = UiStrings.List, data = listHandler.data.map { paging -> - paging.map { it.toTimelineTabItemV2(accountKey) } + paging.map { it.toTimelineTabDescriptor(accountKey) } }, ), ) } - override val defaultTabs by lazy { + override val defaultTimelineTabs by lazy { persistentListOf( - CommonTimelineSpecs.home - .target( + SocialCommonTimelineSpecs.home + .toTimelineTabDescriptor( data = TimelineSpec.AccountBasedData(accountKey), icon = IconType.FavIcon(accountKey.host), - ).toSlot(), + ), ) } override val builtInTimelineTabs by lazy { persistentListOf( - CommonTimelineSpecs.home.tabItem( + SocialCommonTimelineSpecs.home.toTimelineTabDescriptor( data = TimelineSpec.AccountBasedData(accountKey), icon = IconType.FavIcon(accountKey.host), ), - CommonTimelineSpecs.discover.tabItem( + SocialCommonTimelineSpecs.discover.toTimelineTabDescriptor( data = TimelineSpec.AccountBasedData(accountKey), icon = IconType.FavIcon(accountKey.host), ), - MastodonPlatformSpec.localTimelineSpec.tabItem(TimelineSpec.AccountBasedData(accountKey)), - MastodonPlatformSpec.publicTimelineSpec.tabItem(TimelineSpec.AccountBasedData(accountKey)), - MastodonPlatformSpec.bookmarkTimelineSpec.tabItem(TimelineSpec.AccountBasedData(accountKey)), - MastodonPlatformSpec.favouriteTimelineSpec.tabItem(TimelineSpec.AccountBasedData(accountKey)), + MastodonTimelineSpecs.local.toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)), + MastodonTimelineSpecs.federated.toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)), + MastodonTimelineSpecs.bookmark.toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)), + MastodonTimelineSpecs.favourite.toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)), ) } - override val shortcuts by lazy { + override val timelineShortcuts by lazy { persistentListOf( - ShortcutSpec( - title = UiStrings.MastodonLocal, - icon = UiIcon.Local, - target = - ShortcutSpec.Target.Timeline( - MastodonPlatformSpec.localTimelineSpec.target(TimelineSpec.AccountBasedData(accountKey)), - ), - ), - ShortcutSpec( - title = UiStrings.MastodonPublic, - icon = UiIcon.World, - target = - ShortcutSpec.Target.Timeline( - MastodonPlatformSpec.publicTimelineSpec.target(TimelineSpec.AccountBasedData(accountKey)), - ), - ), - ShortcutSpec( - title = UiStrings.Bookmark, - icon = UiIcon.Bookmark, - target = - ShortcutSpec.Target.Timeline( - MastodonPlatformSpec.bookmarkTimelineSpec.target(TimelineSpec.AccountBasedData(accountKey)), - ), - ), - ShortcutSpec( - title = UiStrings.Favourite, - icon = UiIcon.Favourite, - target = - ShortcutSpec.Target.Timeline( - MastodonPlatformSpec.favouriteTimelineSpec.target(TimelineSpec.AccountBasedData(accountKey)), - ), - ), - ShortcutSpec( + MastodonTimelineSpecs.local + .toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)) + .toTimelineShortcutDescriptor(UiStrings.MastodonLocal, UiIcon.Local), + MastodonTimelineSpecs.federated + .toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)) + .toTimelineShortcutDescriptor(UiStrings.MastodonPublic, UiIcon.World), + MastodonTimelineSpecs.bookmark + .toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)) + .toTimelineShortcutDescriptor(UiStrings.Bookmark, UiIcon.Bookmark), + MastodonTimelineSpecs.favourite + .toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)) + .toTimelineShortcutDescriptor(UiStrings.Favourite, UiIcon.Favourite), + TimelineShortcutDescriptor( title = UiStrings.List, icon = UiIcon.List, target = - ShortcutSpec.Target.Route( - DeeplinkRoute.AllLists(accountKey), + TimelineShortcutDescriptor.Target.Route( + id = TimelineShortcutDescriptor.RouteIds.ALL_LISTS, + accountKey = accountKey, ), ), ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonFansPagingSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonFansPagingSource.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonFansPagingSource.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonFansPagingSource.kt index b392fc9da8..e84774f3a6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonFansPagingSource.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonFansPagingSource.kt @@ -8,7 +8,7 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render -internal class MastodonFansPagingSource( +public class MastodonFansPagingSource( private val service: AccountResources, private val accountKey: MicroBlogKey?, private val host: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonFollowingPagingSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonFollowingPagingSource.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonFollowingPagingSource.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonFollowingPagingSource.kt index aa30046b93..64f638d25c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonFollowingPagingSource.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonFollowingPagingSource.kt @@ -8,7 +8,7 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render -internal class MastodonFollowingPagingSource( +public class MastodonFollowingPagingSource( private val service: AccountResources, private val accountKey: MicroBlogKey?, private val host: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonListLoader.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonListLoader.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonListLoader.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonListLoader.kt index 4c2c0f6c32..5947955c1c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonListLoader.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonListLoader.kt @@ -13,7 +13,7 @@ import dev.dimension.flare.ui.model.UiList import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -internal class MastodonListLoader( +public class MastodonListLoader( private val service: MastodonService, private val accountKey: MicroBlogKey, ) : ListLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonListMemberLoader.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonListMemberLoader.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonListMemberLoader.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonListMemberLoader.kt index a4c2bf80f3..245b68c612 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonListMemberLoader.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonListMemberLoader.kt @@ -11,7 +11,7 @@ import dev.dimension.flare.ui.model.UiList import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render -internal class MastodonListMemberLoader( +public class MastodonListMemberLoader( private val service: MastodonService, private val accountKey: MicroBlogKey, ) : ListMemberLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonLoader.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonLoader.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonLoader.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonLoader.kt index 899bc0d99d..3ba564eba7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonLoader.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonLoader.kt @@ -20,8 +20,8 @@ import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap -internal class MastodonLoader( - val accountKey: MicroBlogKey, +public class MastodonLoader( + public val accountKey: MicroBlogKey, private val service: MastodonService, ) : NotificationLoader, UserLoader, diff --git a/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonPostEventActionMenu.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonPostEventActionMenu.kt new file mode 100644 index 0000000000..a0001a28e6 --- /dev/null +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonPostEventActionMenu.kt @@ -0,0 +1,40 @@ +package dev.dimension.flare.data.datasource.mastodon + +import dev.dimension.flare.data.datasource.microblog.ActionMenu +import dev.dimension.flare.ui.model.PostEvent +import dev.dimension.flare.ui.model.mapper.mastodonBookmark +import dev.dimension.flare.ui.model.mapper.mastodonLike +import dev.dimension.flare.ui.model.mapper.mastodonRepost + +internal fun PostEvent.mastodonNextActionMenu(): ActionMenu.Item? = + when (this) { + is PostEvent.Mastodon.Reblog -> { + ActionMenu.mastodonRepost( + reblogged = !reblogged, + reblogsCount = count + if (!reblogged) 1 else -1, + accountKey = accountKey, + statusKey = postKey, + ) + } + + is PostEvent.Mastodon.Like -> { + ActionMenu.mastodonLike( + favourited = !liked, + favouritesCount = count + if (!liked) 1 else -1, + accountKey = accountKey, + statusKey = postKey, + ) + } + + is PostEvent.Mastodon.Bookmark -> { + ActionMenu.mastodonBookmark( + bookmarked = !bookmarked, + accountKey = accountKey, + statusKey = postKey, + ) + } + + else -> { + null + } + } diff --git a/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonReportDataSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonReportDataSource.kt new file mode 100644 index 0000000000..06adec278b --- /dev/null +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MastodonReportDataSource.kt @@ -0,0 +1,10 @@ +package dev.dimension.flare.data.datasource.mastodon + +import dev.dimension.flare.model.MicroBlogKey + +public interface MastodonReportDataSource { + public suspend fun report( + userKey: MicroBlogKey, + statusKey: MicroBlogKey?, + ) +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MentionRemoteMediator.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MentionRemoteMediator.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MentionRemoteMediator.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MentionRemoteMediator.kt index 03fd15e288..9ec0ad0118 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MentionRemoteMediator.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/MentionRemoteMediator.kt @@ -11,7 +11,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class MentionRemoteMediator( +public class MentionRemoteMediator( private val service: MastodonService, private val accountKey: MicroBlogKey, ) : CacheableRemoteLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/NotificationRemoteMediator.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/NotificationRemoteMediator.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/NotificationRemoteMediator.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/NotificationRemoteMediator.kt index abec223673..4aa5b441da 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/NotificationRemoteMediator.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/NotificationRemoteMediator.kt @@ -12,7 +12,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class NotificationRemoteMediator( +public class NotificationRemoteMediator( private val service: MastodonService, private val accountKey: MicroBlogKey, private val onClearMarker: () -> Unit, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/PublicTimelineRemoteMediator.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/PublicTimelineRemoteMediator.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/PublicTimelineRemoteMediator.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/PublicTimelineRemoteMediator.kt index 7512df474a..dffad3f72d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/PublicTimelineRemoteMediator.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/PublicTimelineRemoteMediator.kt @@ -10,7 +10,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class PublicTimelineRemoteMediator( +public class PublicTimelineRemoteMediator( private val service: MastodonService, private val accountKey: MicroBlogKey, private val local: Boolean, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchStatusPagingSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchStatusPagingSource.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchStatusPagingSource.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchStatusPagingSource.kt index fd5023ad60..4b167a3fae 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchStatusPagingSource.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchStatusPagingSource.kt @@ -10,7 +10,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class SearchStatusPagingSource( +public class SearchStatusPagingSource( private val service: MastodonService, private val accountKey: MicroBlogKey, private val query: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchUserPagingSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchUserPagingSource.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchUserPagingSource.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchUserPagingSource.kt index 2be760878b..ca3486445f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchUserPagingSource.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/SearchUserPagingSource.kt @@ -8,7 +8,7 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render -internal class SearchUserPagingSource( +public class SearchUserPagingSource( private val service: SearchResources, private val host: String, private val accountKey: MicroBlogKey?, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/StatusDetailRemoteMediator.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/StatusDetailRemoteMediator.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/StatusDetailRemoteMediator.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/StatusDetailRemoteMediator.kt index aea07a22c1..56d260297d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/StatusDetailRemoteMediator.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/StatusDetailRemoteMediator.kt @@ -8,16 +8,14 @@ import dev.dimension.flare.data.network.mastodon.MastodonService import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render -import org.koin.core.component.KoinComponent @OptIn(ExperimentalPagingApi::class) -internal class StatusDetailRemoteMediator( +public class StatusDetailRemoteMediator( private val statusKey: MicroBlogKey, private val service: MastodonService, private val accountKey: MicroBlogKey, private val statusOnly: Boolean, -) : CacheableRemoteLoader, - KoinComponent { +) : CacheableRemoteLoader { override val pagingKey: String = buildString { append("status_detail_") diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/TrendHashtagPagingSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/TrendHashtagPagingSource.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/TrendHashtagPagingSource.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/TrendHashtagPagingSource.kt index 72c18f96f5..d145fe5f73 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/TrendHashtagPagingSource.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/TrendHashtagPagingSource.kt @@ -6,7 +6,7 @@ import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader import dev.dimension.flare.data.network.mastodon.api.TrendsResources import dev.dimension.flare.ui.model.UiHashtag -internal class TrendHashtagPagingSource( +public class TrendHashtagPagingSource( private val service: TrendsResources, ) : RemoteLoader { override suspend fun load( diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/TrendsUserLoader.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/TrendsUserLoader.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/TrendsUserLoader.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/TrendsUserLoader.kt index ffdcb6d5da..641ee6e9d8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/TrendsUserLoader.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/TrendsUserLoader.kt @@ -8,7 +8,7 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render -internal class TrendsUserLoader( +public class TrendsUserLoader( private val service: TrendsResources, private val accountKey: MicroBlogKey?, private val host: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/UserTimelineRemoteMediator.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/UserTimelineRemoteMediator.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/UserTimelineRemoteMediator.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/UserTimelineRemoteMediator.kt index 043112bdaa..a735fa1d25 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/UserTimelineRemoteMediator.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/mastodon/UserTimelineRemoteMediator.kt @@ -10,7 +10,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class UserTimelineRemoteMediator( +public class UserTimelineRemoteMediator( private val service: MastodonService, private val accountKey: MicroBlogKey, private val userKey: MicroBlogKey, @@ -18,7 +18,7 @@ internal class UserTimelineRemoteMediator( private val withReplies: Boolean = false, private val withPinned: Boolean = false, ) : CacheableRemoteLoader { - override val pagingKey = + override val pagingKey: String = buildString { append("user_timeline") if (onlyMedia) { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/pleroma/PleromaDataSource.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/pleroma/PleromaDataSource.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/pleroma/PleromaDataSource.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/datasource/pleroma/PleromaDataSource.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/GuestMastodonService.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/GuestMastodonService.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/GuestMastodonService.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/GuestMastodonService.kt index e3dd657fc8..939b0a577d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/GuestMastodonService.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/GuestMastodonService.kt @@ -11,6 +11,7 @@ import dev.dimension.flare.data.network.mastodon.api.createLookupResources import dev.dimension.flare.data.network.mastodon.api.createSearchResources import dev.dimension.flare.data.network.mastodon.api.createTimelineResources import dev.dimension.flare.data.network.mastodon.api.createTrendsResources +import dev.dimension.flare.data.network.mastodon.api.model.MastodonPagingConverterFactory import io.ktor.client.plugins.HttpResponseValidator import io.ktor.client.plugins.ResponseException import io.ktor.client.plugins.defaultRequest @@ -20,7 +21,10 @@ import io.ktor.client.statement.bodyAsText private fun config( baseUrl: String, locale: String, -) = ktorfit(baseUrl) { +) = ktorfit( + baseUrl = baseUrl, + converterFactories = listOf(MastodonPagingConverterFactory()), +) { expectSuccess = true HttpResponseValidator { handleResponseExceptionWithRequest { exception, _ -> @@ -41,7 +45,7 @@ private fun config( } } -internal class GuestMastodonService( +public class GuestMastodonService( private val baseUrl: String, private val locale: String, ) : TrendsResources by config(baseUrl, locale).createTrendsResources(), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/JoinMastodonService.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/JoinMastodonService.kt similarity index 84% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/JoinMastodonService.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/JoinMastodonService.kt index 6bedfc5b9d..ea216b087b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/JoinMastodonService.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/JoinMastodonService.kt @@ -6,9 +6,9 @@ import dev.dimension.flare.data.network.mastodon.api.JoinMastodonResources import dev.dimension.flare.data.network.mastodon.api.createInstanceResources import dev.dimension.flare.data.network.mastodon.api.createJoinMastodonResources -internal object JoinMastodonService : +public object JoinMastodonService : JoinMastodonResources by ktorfit("https://api.joinmastodon.org/").createJoinMastodonResources() -internal class MastodonInstanceService( - val baseUrl: String, +public class MastodonInstanceService( + public val baseUrl: String, ) : InstanceResources by ktorfit(baseUrl).createInstanceResources() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonException.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonException.kt similarity index 78% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonException.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonException.kt index e84cbb4fea..a79a5f046f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonException.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonException.kt @@ -5,12 +5,12 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class MastodonException( +public data class MastodonException( @SerialName("error") val error: String? = null, ) : Exception(error) -internal fun String.toMastodonExceptionOrNull(): MastodonException? = +public fun String.toMastodonExceptionOrNull(): MastodonException? = runCatching { decodeJson() }.getOrNull() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonOAuthService.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonOAuthService.kt similarity index 65% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonOAuthService.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonOAuthService.kt index 875a035477..302ae8ab60 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonOAuthService.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonOAuthService.kt @@ -3,11 +3,13 @@ package dev.dimension.flare.data.network.mastodon import dev.dimension.flare.data.network.ktorfit import dev.dimension.flare.data.network.mastodon.api.MastodonOAuthResources import dev.dimension.flare.data.network.mastodon.api.createMastodonOAuthResources +import dev.dimension.flare.data.network.mastodon.api.model.Account import dev.dimension.flare.data.network.mastodon.api.model.CreateApplicationResponse import dev.dimension.flare.data.network.mastodon.api.model.MastodonAuthScope +import dev.dimension.flare.data.network.mastodon.api.model.RequestTokenResponse import io.ktor.http.encodeURLParameter -internal class MastodonOAuthService( +public class MastodonOAuthService( private val baseUrl: String, private val client_name: String, private val website: String? = null, @@ -20,7 +22,7 @@ internal class MastodonOAuthService( MastodonAuthScope.Push, ), ) : MastodonOAuthResources by ktorfit(baseUrl).createMastodonOAuthResources() { - suspend fun createApplication() = + public suspend fun createApplication(): CreateApplicationResponse = createApplication( client_name = client_name, redirect_uris = redirect_uri, @@ -28,7 +30,7 @@ internal class MastodonOAuthService( website = website, ) - fun getWebOAuthUrl(response: CreateApplicationResponse) = + public fun getWebOAuthUrl(response: CreateApplicationResponse): String = "${baseUrl}oauth/authorize?" + "client_id=${response.clientID}" + "&response_type=code" + @@ -39,17 +41,18 @@ internal class MastodonOAuthService( ) { it.value }.encodeURLParameter() }" - suspend fun getAccessToken( + public suspend fun getAccessToken( code: String, response: CreateApplicationResponse, - ) = requestToken( - client_id = response.clientID, - client_secret = response.clientSecret, - redirect_uri = response.redirectURI, - scope = scopes.joinToString(" ") { it.value }, - code = code, - grant_type = "authorization_code", - ) + ): RequestTokenResponse = + requestToken( + client_id = response.clientID, + client_secret = response.clientSecret, + redirect_uri = response.redirectURI, + scope = scopes.joinToString(" ") { it.value }, + code = code, + grant_type = "authorization_code", + ) - suspend fun verify(accessToken: String) = verifyCredentials(accessToken = "Bearer $accessToken") + public suspend fun verify(accessToken: String): Account = verifyCredentials(accessToken = "Bearer $accessToken") } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonPlatformDetector.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonPlatformDetector.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonPlatformDetector.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonPlatformDetector.kt index 2988330e21..0d96b3e337 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonPlatformDetector.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonPlatformDetector.kt @@ -1,12 +1,12 @@ package dev.dimension.flare.data.network.mastodon +import dev.dimension.flare.common.tryRun import dev.dimension.flare.data.network.nodeinfo.NodeData -import dev.dimension.flare.data.network.nodeinfo.NodeInfoService import dev.dimension.flare.data.network.nodeinfo.PlatformDetector -import dev.dimension.flare.data.repository.tryRun +import dev.dimension.flare.data.nodeinfo.NodeInfoService import dev.dimension.flare.model.PlatformType -internal data object MastodonPlatformDetector : PlatformDetector { +public data object MastodonPlatformDetector : PlatformDetector { override val priority: Int = 60 override suspend fun detect(host: String): NodeData? { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonService.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonService.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonService.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonService.kt index 0e0e80f234..e9bbece83b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonService.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/MastodonService.kt @@ -1,6 +1,6 @@ package dev.dimension.flare.data.network.mastodon -import dev.dimension.flare.common.MimeTypes +import dev.dimension.flare.data.io.MimeTypes import dev.dimension.flare.data.network.ktorfit import dev.dimension.flare.data.network.mastodon.api.AccountResources import dev.dimension.flare.data.network.mastodon.api.FriendshipResources @@ -20,6 +20,7 @@ import dev.dimension.flare.data.network.mastodon.api.createSearchResources import dev.dimension.flare.data.network.mastodon.api.createStatusResources import dev.dimension.flare.data.network.mastodon.api.createTimelineResources import dev.dimension.flare.data.network.mastodon.api.createTrendsResources +import dev.dimension.flare.data.network.mastodon.api.model.MastodonPagingConverterFactory import dev.dimension.flare.data.network.mastodon.api.model.UploadResponse import io.ktor.client.plugins.HttpResponseValidator import io.ktor.client.plugins.ResponseException @@ -55,7 +56,10 @@ private val MastodonHeaderPlugin = private fun config( baseUrl: String, accessTokenFlow: Flow, -) = ktorfit(baseUrl) { +) = ktorfit( + baseUrl = baseUrl, + converterFactories = listOf(MastodonPagingConverterFactory()), +) { expectSuccess = true install(MastodonHeaderPlugin) { this.accessTokenFlow = accessTokenFlow @@ -74,7 +78,7 @@ private fun config( } } -internal class MastodonService( +public class MastodonService( baseUrl: String, accessTokenFlow: Flow, ) : TimelineResources by config(baseUrl, accessTokenFlow).createTimelineResources(), @@ -86,7 +90,7 @@ internal class MastodonService( ListsResources by config(baseUrl, accessTokenFlow).createListsResources(), TrendsResources by config(baseUrl, accessTokenFlow).createTrendsResources(), MastodonResources by config(baseUrl, accessTokenFlow).createMastodonResources() { - suspend fun upload( + public suspend fun upload( data: ByteArray, name: String, description: String?, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/AccountResources.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/AccountResources.kt similarity index 88% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/AccountResources.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/AccountResources.kt index 79975fd6ea..949fa49852 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/AccountResources.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/AccountResources.kt @@ -7,9 +7,9 @@ import dev.dimension.flare.data.network.mastodon.api.model.Account import dev.dimension.flare.data.network.mastodon.api.model.MastodonList import dev.dimension.flare.data.network.mastodon.api.model.MastodonPaging -internal interface AccountResources { +public interface AccountResources { @GET("api/v1/accounts/{id}/followers") - suspend fun followers( + public suspend fun followers( @Path(value = "id") id: String, @Query("max_id") max_id: String? = null, @Query("since_id") since_id: String? = null, @@ -17,7 +17,7 @@ internal interface AccountResources { ): MastodonPaging @GET("api/v1/accounts/{id}/following") - suspend fun following( + public suspend fun following( @Path(value = "id") id: String, @Query("max_id") max_id: String? = null, @Query("since_id") since_id: String? = null, @@ -25,7 +25,7 @@ internal interface AccountResources { ): MastodonPaging @GET("api/v1/accounts/{id}/lists") - suspend fun accountLists( + public suspend fun accountLists( @Path(value = "id") id: String, ): MastodonPaging } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/FriendshipResources.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/FriendshipResources.kt similarity index 79% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/FriendshipResources.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/FriendshipResources.kt index b33a46b58a..4fb14b1fcf 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/FriendshipResources.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/FriendshipResources.kt @@ -9,54 +9,54 @@ import dev.dimension.flare.data.network.mastodon.api.model.Account import dev.dimension.flare.data.network.mastodon.api.model.PostReport import dev.dimension.flare.data.network.mastodon.api.model.RelationshipResponse -internal interface FriendshipResources { +public interface FriendshipResources { @POST("api/v1/accounts/{id}/follow") - suspend fun follow( + public suspend fun follow( @Path(value = "id") id: String, ): Account @POST("api/v1/accounts/{id}/unfollow") - suspend fun unfollow( + public suspend fun unfollow( @Path(value = "id") id: String, ): Account @GET("api/v1/accounts/relationships") - suspend fun showFriendships( + public suspend fun showFriendships( @Query("id[]") id: List, ): List @POST("api/v1/accounts/{id}/block") - suspend fun block( + public suspend fun block( @Path(value = "id") id: String, ): RelationshipResponse @POST("api/v1/accounts/{id}/unblock") - suspend fun unblock( + public suspend fun unblock( @Path(value = "id") id: String, ): RelationshipResponse @POST("api/v1/accounts/{id}/mute") - suspend fun muteUser( + public suspend fun muteUser( @Path(value = "id") id: String, ): RelationshipResponse @POST("api/v1/accounts/{id}/unmute") - suspend fun unmuteUser( + public suspend fun unmuteUser( @Path(value = "id") id: String, ): RelationshipResponse @POST("api/v1/reports") - suspend fun report( + public suspend fun report( @Body data: PostReport, ) @POST("api/v1/follow_requests/{id}/authorize") - suspend fun authorizeFollowRequest( + public suspend fun authorizeFollowRequest( @Path(value = "id") id: String, ): RelationshipResponse @POST("api/v1/follow_requests/{id}/reject") - suspend fun rejectFollowRequest( + public suspend fun rejectFollowRequest( @Path(value = "id") id: String, ): RelationshipResponse } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/InstanceResources.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/InstanceResources.kt similarity index 68% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/InstanceResources.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/InstanceResources.kt index 2e12496dae..b078ca0149 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/InstanceResources.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/InstanceResources.kt @@ -4,10 +4,10 @@ import de.jensklingenberg.ktorfit.http.GET import dev.dimension.flare.data.network.mastodon.api.model.InstanceData import dev.dimension.flare.data.network.mastodon.api.model.InstanceInfoV1 -internal interface InstanceResources { +public interface InstanceResources { @GET("api/v2/instance") - suspend fun instance(): InstanceData + public suspend fun instance(): InstanceData @GET("api/v1/instance") - suspend fun instanceV1(): InstanceInfoV1 + public suspend fun instanceV1(): InstanceInfoV1 } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/JoinMastodonResources.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/JoinMastodonResources.kt similarity index 66% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/JoinMastodonResources.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/JoinMastodonResources.kt index 9859888609..ce85cf0039 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/JoinMastodonResources.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/JoinMastodonResources.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.network.mastodon.api import de.jensklingenberg.ktorfit.http.GET import dev.dimension.flare.data.network.mastodon.api.model.MastodonInstanceElement -internal interface JoinMastodonResources { +public interface JoinMastodonResources { @GET("servers") - suspend fun servers(): List + public suspend fun servers(): List } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/ListsResources.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/ListsResources.kt similarity index 86% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/ListsResources.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/ListsResources.kt index 6601637f66..23d1561882 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/ListsResources.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/ListsResources.kt @@ -16,35 +16,35 @@ import dev.dimension.flare.data.network.mastodon.api.model.MastodonPaging import dev.dimension.flare.data.network.mastodon.api.model.PostAccounts import dev.dimension.flare.data.network.mastodon.api.model.PostList -internal interface ListsResources { +public interface ListsResources { @GET("api/v1/lists") - suspend fun lists(): List + public suspend fun lists(): List @POST("api/v1/lists") - suspend fun createList( + public suspend fun createList( @Body postList: PostList, @Header("Content-Type") contentType: String = "application/json", ): MastodonList @GET("api/v1/lists/{id}") - suspend fun getList( + public suspend fun getList( @Path("id") id: String, ): MastodonList @PUT("api/v1/lists/{id}") - suspend fun updateList( + public suspend fun updateList( @Path("id") id: String, @Body postList: PostList, @Header("Content-Type") contentType: String = "application/json", ): MastodonList @DELETE("api/v1/lists/{id}") - suspend fun deleteList( + public suspend fun deleteList( @Path("id") id: String, ): Response @GET("api/v1/lists/{id}/accounts") - suspend fun listMembers( + public suspend fun listMembers( @Path("id") listId: String, @Query("max_id") max_id: String? = null, @Query("since_id") since_id: String? = null, @@ -52,14 +52,14 @@ internal interface ListsResources { ): MastodonPaging @POST("api/v1/lists/{id}/accounts") - suspend fun addMember( + public suspend fun addMember( @Path("id") listId: String, @Body accounts: PostAccounts, @Header("Content-Type") contentType: String = "application/json", ): Response @HTTP(method = "DELETE", path = "/api/v1/lists/{id}/accounts", hasBody = true) - suspend fun removeMember( + public suspend fun removeMember( @Path("id") listId: String, @Body accounts: PostAccounts, @Header("Content-Type") contentType: String = "application/json", diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/LookupResources.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/LookupResources.kt similarity index 79% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/LookupResources.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/LookupResources.kt index b34dc58771..3492b7bd64 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/LookupResources.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/LookupResources.kt @@ -6,19 +6,19 @@ import de.jensklingenberg.ktorfit.http.Query import dev.dimension.flare.data.network.mastodon.api.model.Account import dev.dimension.flare.data.network.mastodon.api.model.Status -internal interface LookupResources { +public interface LookupResources { @GET("api/v1/accounts/{id}") - suspend fun lookupUser( + public suspend fun lookupUser( @Path(value = "id") id: String, ): Account @GET("api/v1/statuses/{id}") - suspend fun lookupStatus( + public suspend fun lookupStatus( @Path("id") id: String, ): Status @GET("api/v1/accounts/lookup") - suspend fun lookupUserByAcct( + public suspend fun lookupUserByAcct( @Query("acct") acct: String, ): Account? } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/MastodonOAuthResources.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/MastodonOAuthResources.kt similarity index 88% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/MastodonOAuthResources.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/MastodonOAuthResources.kt index ff3cac5971..27878bf8ea 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/MastodonOAuthResources.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/MastodonOAuthResources.kt @@ -9,10 +9,10 @@ import dev.dimension.flare.data.network.mastodon.api.model.Account import dev.dimension.flare.data.network.mastodon.api.model.CreateApplicationResponse import dev.dimension.flare.data.network.mastodon.api.model.RequestTokenResponse -internal interface MastodonOAuthResources { +public interface MastodonOAuthResources { @POST("api/v1/apps") @FormUrlEncoded - suspend fun createApplication( + public suspend fun createApplication( @Field("client_name") client_name: String, @Field("redirect_uris") redirect_uris: String, @Field("scopes") scopes: String, @@ -20,13 +20,13 @@ internal interface MastodonOAuthResources { ): CreateApplicationResponse @GET("api/v1/accounts/verify_credentials") - suspend fun verifyCredentials( + public suspend fun verifyCredentials( @Header("Authorization") accessToken: String, ): Account @POST("oauth/token") @FormUrlEncoded - suspend fun requestToken( + public suspend fun requestToken( @Field("client_id") client_id: String, @Field("client_secret") client_secret: String, @Field("redirect_uri") redirect_uri: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/MastodonResources.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/MastodonResources.kt similarity index 79% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/MastodonResources.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/MastodonResources.kt index 475890fe4b..dce106b37a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/MastodonResources.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/MastodonResources.kt @@ -8,15 +8,15 @@ import dev.dimension.flare.data.network.mastodon.api.model.Emoji import dev.dimension.flare.data.network.mastodon.api.model.Marker import dev.dimension.flare.data.network.mastodon.api.model.MarkerUpdate -internal interface MastodonResources { +public interface MastodonResources { @GET("api/v1/custom_emojis") - suspend fun emojis(): List + public suspend fun emojis(): List @GET("api/v1/markers?timeline[]=notifications") - suspend fun notificationMarkers(): Marker + public suspend fun notificationMarkers(): Marker @POST("api/v1/markers") - suspend fun updateMarker( + public suspend fun updateMarker( @Body data: MarkerUpdate, @Header("Content-Type") contentType: String = "application/json", ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/SearchResources.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/SearchResources.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/SearchResources.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/SearchResources.kt index abc525ffa1..85481f3f0c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/SearchResources.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/SearchResources.kt @@ -4,9 +4,9 @@ import de.jensklingenberg.ktorfit.http.GET import de.jensklingenberg.ktorfit.http.Query import dev.dimension.flare.data.network.mastodon.api.model.SearchResult -internal interface SearchResources { +public interface SearchResources { @GET("api/v2/search") - suspend fun searchV2( + public suspend fun searchV2( @Query("q") query: String, @Query("account_id") account_id: String? = null, @Query("max_id") max_id: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/StatusResources.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/StatusResources.kt similarity index 82% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/StatusResources.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/StatusResources.kt index b0ca9d08c1..fe468f19a4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/StatusResources.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/StatusResources.kt @@ -13,64 +13,64 @@ import dev.dimension.flare.data.network.mastodon.api.model.Status import dev.dimension.flare.data.network.mastodon.api.model.UploadResponse import io.ktor.client.request.forms.MultiPartFormDataContent -internal interface StatusResources { +public interface StatusResources { @POST("api/v1/statuses/{id}/favourite") - suspend fun favourite( + public suspend fun favourite( @Path("id") id: String, ): Status @POST("api/v1/statuses/{id}/unfavourite") - suspend fun unfavourite( + public suspend fun unfavourite( @Path("id") id: String, ): Status @POST("api/v1/statuses/{id}/reblog") - suspend fun reblog( + public suspend fun reblog( @Path("id") id: String, ): Status @POST("api/v1/statuses/{id}/unreblog") - suspend fun unreblog( + public suspend fun unreblog( @Path("id") id: String, ): Status @POST("api/v1/statuses/{id}/bookmark") - suspend fun bookmark( + public suspend fun bookmark( @Path("id") id: String, ): Status @POST("api/v1/statuses/{id}/unbookmark") - suspend fun unbookmark( + public suspend fun unbookmark( @Path("id") id: String, ): Status @POST("api/v1/statuses/{id}/mute") - suspend fun mute( + public suspend fun mute( @Path("id") id: String, ): Status @POST("api/v1/statuses/{id}/unmute") - suspend fun unmute( + public suspend fun unmute( @Path("id") id: String, ): Status @POST("api/v1/statuses/{id}/pin") - suspend fun pin( + public suspend fun pin( @Path("id") id: String, ): Status @POST("api/v1/statuses/{id}/unpin") - suspend fun unpin( + public suspend fun unpin( @Path("id") id: String, ): Status @DELETE("api/v1/statuses/{id}") - suspend fun delete( + public suspend fun delete( @Path("id") id: String, ): Status @POST("api/v1/statuses") - suspend fun post( + public suspend fun post( @Header("Idempotency-Key") idempotencyKey: String, @Body data: PostStatus, @Header("Content-Type") contentType: String = "application/json", @@ -78,12 +78,12 @@ internal interface StatusResources { @Multipart @POST("api/v1/media") - suspend fun upload( + public suspend fun upload( @Body map: MultiPartFormDataContent, ): UploadResponse @POST("api/v1/polls/{id}/votes") - suspend fun vote( + public suspend fun vote( @Path("id") id: String, @Body data: PostVote, @Header("Content-Type") contentType: String = "application/json", diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/TimelineResources.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/TimelineResources.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/TimelineResources.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/TimelineResources.kt index e496f938ac..f48dbe5029 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/TimelineResources.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/TimelineResources.kt @@ -9,9 +9,9 @@ import dev.dimension.flare.data.network.mastodon.api.model.Notification import dev.dimension.flare.data.network.mastodon.api.model.NotificationTypes import dev.dimension.flare.data.network.mastodon.api.model.Status -internal interface TimelineResources { +public interface TimelineResources { @GET("api/v1/timelines/home") - suspend fun homeTimeline( + public suspend fun homeTimeline( @Query("max_id") max_id: String? = null, @Query("since_id") since_id: String? = null, @Query("min_id") min_id: String? = null, @@ -20,7 +20,7 @@ internal interface TimelineResources { ): MastodonPaging @GET("api/v1/timelines/public") - suspend fun publicTimeline( + public suspend fun publicTimeline( @Query("max_id") max_id: String? = null, @Query("since_id") since_id: String? = null, @Query("min_id") min_id: String? = null, @@ -31,7 +31,7 @@ internal interface TimelineResources { ): MastodonPaging @GET("api/v1/accounts/{id}/statuses") - suspend fun userTimeline( + public suspend fun userTimeline( @Path("id") user_id: String, @Query("max_id") max_id: String? = null, @Query("since_id") since_id: String? = null, @@ -43,7 +43,7 @@ internal interface TimelineResources { ): MastodonPaging @GET("api/v1/favourites") - suspend fun favoritesList( + public suspend fun favoritesList( @Query("max_id") max_id: String? = null, @Query("since_id") since_id: String? = null, @Query("min_id") min_id: String? = null, @@ -52,7 +52,7 @@ internal interface TimelineResources { ): MastodonPaging @GET("api/v1/notifications") - suspend fun notification( + public suspend fun notification( @Query("max_id") max_id: String? = null, @Query("since_id") since_id: String? = null, @Query("min_id") min_id: String? = null, @@ -62,12 +62,12 @@ internal interface TimelineResources { ): MastodonPaging @GET("api/v1/statuses/{id}/context") - suspend fun context( + public suspend fun context( @Path("id") id: String, ): Context @GET("api/v1/timelines/tag/{hashtag}") - suspend fun hashtagTimeline( + public suspend fun hashtagTimeline( @Path("hashtag") hashtag: String, @Query("max_id") max_id: String? = null, @Query("since_id") since_id: String? = null, @@ -78,7 +78,7 @@ internal interface TimelineResources { ): MastodonPaging @GET("api/v1//timelines/list/{id}") - suspend fun listTimeline( + public suspend fun listTimeline( @Path("id") listId: String, @Query("max_id") max_id: String? = null, @Query("since_id") since_id: String? = null, @@ -87,7 +87,7 @@ internal interface TimelineResources { ): MastodonPaging @GET("api/v1/bookmarks") - suspend fun bookmarks( + public suspend fun bookmarks( @Query("max_id") max_id: String? = null, @Query("since_id") since_id: String? = null, @Query("min_id") min_id: String? = null, @@ -95,7 +95,7 @@ internal interface TimelineResources { ): MastodonPaging @GET("api/v1/favourites") - suspend fun favorites( + public suspend fun favorites( @Query("max_id") max_id: String? = null, @Query("since_id") since_id: String? = null, @Query("min_id") min_id: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/TrendsResources.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/TrendsResources.kt similarity index 79% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/TrendsResources.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/TrendsResources.kt index 32e033841b..96194bdd8e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/TrendsResources.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/TrendsResources.kt @@ -6,18 +6,18 @@ import dev.dimension.flare.data.network.mastodon.api.model.Status import dev.dimension.flare.data.network.mastodon.api.model.Suggestions import dev.dimension.flare.data.network.mastodon.api.model.Trend -internal interface TrendsResources { +public interface TrendsResources { @GET("api/v1/trends/tags") - suspend fun trendsTags(): List + public suspend fun trendsTags(): List @GET("api/v1/trends/statuses") - suspend fun trendsStatuses( + public suspend fun trendsStatuses( @Query("limit") limit: Int? = null, @Query("offset") offset: Int? = null, ): List @GET("api/v2/suggestions") - suspend fun suggestionsUsers( + public suspend fun suggestionsUsers( @Query("limit") limit: Int? = null, ): List } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Account.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Account.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Account.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Account.kt index e653901164..e9cf9c4479 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Account.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Account.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class Account( +public data class Account( val id: String? = null, val username: String? = null, val acct: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Application.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Application.kt similarity index 84% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Application.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Application.kt index a56e5a25ee..042e756909 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Application.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Application.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.network.mastodon.api.model import kotlinx.serialization.Serializable @Serializable -internal data class Application( +public data class Application( val name: String? = null, val website: String? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Attachment.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Attachment.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Attachment.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Attachment.kt index 2fd7cbf76e..b2300812ad 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Attachment.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Attachment.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class Attachment( +public data class Attachment( val id: String? = null, val type: MediaType? = null, val url: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Card.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Card.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Card.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Card.kt index cf4969afbe..635ecd46e7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Card.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Card.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class Card( +public data class Card( val url: String? = null, val title: String? = null, val description: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Context.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Context.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Context.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Context.kt index 04ec2a95b4..cb40acb35d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Context.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Context.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.network.mastodon.api.model import kotlinx.serialization.Serializable @Serializable -internal data class Context( +public data class Context( val ancestors: List? = null, val descendants: List? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/CreateApplicationResponse.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/CreateApplicationResponse.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/CreateApplicationResponse.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/CreateApplicationResponse.kt index e38b01cd94..74e39d2a01 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/CreateApplicationResponse.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/CreateApplicationResponse.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class CreateApplicationResponse( +public data class CreateApplicationResponse( val id: String? = null, val name: String? = null, val website: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Emoji.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Emoji.kt similarity index 83% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Emoji.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Emoji.kt index c31a509b47..f066fe9d04 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Emoji.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Emoji.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class Emoji( +public data class Emoji( val shortcode: String? = null, val url: String? = null, @SerialName("static_url") @@ -15,12 +15,12 @@ internal data class Emoji( ) @Serializable -internal data class Marker( +public data class Marker( val notifications: Notifications? = null, ) @Serializable -internal data class Notifications( +public data class Notifications( @SerialName("last_read_id") val lastReadID: String? = null, val version: Long? = null, @@ -29,13 +29,13 @@ internal data class Notifications( ) @Serializable -internal data class MarkerUpdate( +public data class MarkerUpdate( val home: UpdateContent? = null, val notifications: UpdateContent? = null, ) @Serializable -internal data class UpdateContent( +public data class UpdateContent( @SerialName("last_read_id") val lastReadID: String? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/EmojiReaction.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/EmojiReaction.kt similarity index 88% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/EmojiReaction.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/EmojiReaction.kt index 778a68422e..1e172e12b1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/EmojiReaction.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/EmojiReaction.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable // compatibility layer for Pleroma/Akkoma @Serializable -internal data class EmojiReaction( +public data class EmojiReaction( val name: String? = null, val count: Long? = null, val me: Boolean? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Field.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Field.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Field.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Field.kt index d356254ea2..60553050b7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Field.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Field.kt @@ -11,7 +11,7 @@ import kotlinx.serialization.encoding.Encoder import kotlin.time.Instant @Serializable -internal data class Field( +public data class Field( val name: String? = null, val value: String? = null, @SerialName("verified_at") @@ -19,7 +19,7 @@ internal data class Field( val verifiedAt: Instant? = null, ) -internal object DateSerializer : KSerializer { +public object DateSerializer : KSerializer { override val descriptor: SerialDescriptor get() = PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Hashtag.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Hashtag.kt similarity index 88% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Hashtag.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Hashtag.kt index e007ef88b5..b9675b74d6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Hashtag.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Hashtag.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.network.mastodon.api.model import kotlinx.serialization.Serializable @Serializable -internal data class Hashtag( +public data class Hashtag( val name: String? = null, val url: String? = null, val history: List? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/History.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/History.kt similarity index 88% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/History.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/History.kt index 7d6b1d08a5..560253720f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/History.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/History.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.network.mastodon.api.model import kotlinx.serialization.Serializable @Serializable -internal data class History( +public data class History( val day: String? = null, val uses: String? = null, val accounts: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/InstanceInfoV1.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/InstanceInfoV1.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/InstanceInfoV1.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/InstanceInfoV1.kt index 3f6ceeecca..4818f789b2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/InstanceInfoV1.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/InstanceInfoV1.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class InstanceInfoV1( +public data class InstanceInfoV1( val uri: String? = null, val title: String? = null, @SerialName("short_description") diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonAuthScope.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonAuthScope.kt similarity index 68% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonAuthScope.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonAuthScope.kt index aafad674ef..119ea0905e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonAuthScope.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonAuthScope.kt @@ -1,7 +1,7 @@ package dev.dimension.flare.data.network.mastodon.api.model -internal enum class MastodonAuthScope( - val value: String, +public enum class MastodonAuthScope( + public val value: String, ) { Read("read"), Write("write"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonInstanceElement.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonInstanceElement.kt similarity index 88% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonInstanceElement.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonInstanceElement.kt index fa977ed0f6..eb7ae48ad5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonInstanceElement.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonInstanceElement.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class MastodonInstanceElement( +public data class MastodonInstanceElement( val domain: String, val version: String, val description: String, @@ -25,7 +25,7 @@ internal data class MastodonInstanceElement( ) @Serializable -internal data class InstanceData( +public data class InstanceData( val domain: String? = null, val title: String? = null, val version: String? = null, @@ -45,25 +45,25 @@ internal data class InstanceData( ) @Serializable -internal data class Rule( +public data class Rule( val id: String? = null, val text: String? = null, val hint: String? = null, ) @Serializable -internal data class Icon( +public data class Icon( val src: String? = null, val size: String? = null, ) @Serializable -internal data class APIVersions( +public data class APIVersions( val mastodon: Long? = null, ) @Serializable -internal data class Configuration( +public data class Configuration( val urls: Urls? = null, val vapid: Vapid? = null, val accounts: Accounts? = null, @@ -75,7 +75,7 @@ internal data class Configuration( ) @Serializable -internal data class MediaAttachments( +public data class MediaAttachments( @SerialName("description_limit") val descriptionLimit: Long? = null, @SerialName("image_matrix_limit") @@ -93,7 +93,7 @@ internal data class MediaAttachments( ) @Serializable -internal data class Polls( +public data class Polls( @SerialName("max_options") val maxOptions: Long? = null, @SerialName("max_characters_per_option") @@ -105,13 +105,13 @@ internal data class Polls( ) @Serializable -internal data class Vapid( +public data class Vapid( @SerialName("public_key") val publicKey: String? = null, ) @Serializable -internal data class Accounts( +public data class Accounts( @SerialName("max_featured_tags") val maxFeaturedTags: Long? = null, @SerialName("max_pinned_statuses") @@ -119,25 +119,25 @@ internal data class Accounts( ) @Serializable -internal data class Thumbnail( +public data class Thumbnail( val url: String? = null, val blurhash: String? = null, val versions: Map? = null, ) @Serializable -internal data class Users( +public data class Users( @SerialName("active_month") val activeMonth: Long? = null, ) @Serializable -internal data class Translation( +public data class Translation( val enabled: Boolean? = null, ) @Serializable -internal data class Urls( +public data class Urls( val streaming: String? = null, val status: String? = null, val about: String? = null, @@ -148,7 +148,7 @@ internal data class Urls( ) @Serializable -internal data class Statuses( +public data class Statuses( @SerialName("max_characters") val maxCharacters: Long? = null, @SerialName("max_media_attachments") @@ -158,13 +158,13 @@ internal data class Statuses( ) @Serializable -internal data class Contact( +public data class Contact( val email: String? = null, val account: Account? = null, ) @Serializable -internal data class Registrations( +public data class Registrations( val enabled: Boolean? = null, @SerialName("approval_required") val approvalRequired: Boolean? = null, @@ -177,6 +177,6 @@ internal data class Registrations( ) @Serializable -internal data class Usage( +public data class Usage( val users: Users? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonList.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonList.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonList.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonList.kt index dfdc5d704d..3efc775c51 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonList.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonList.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class MastodonList( +public data class MastodonList( @SerialName("id") val id: String? = null, @SerialName("replies_policy") diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonPaging.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonPaging.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonPaging.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonPaging.kt index d3f560165a..726ebdf3dc 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonPaging.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonPaging.kt @@ -11,12 +11,12 @@ import io.ktor.util.reflect.serializer import io.ktor.utils.io.InternalAPI import kotlinx.serialization.builtins.ListSerializer -internal class MastodonPaging( +public class MastodonPaging( private val data: List, - val next: String? = null, - val prev: String? = null, + public val next: String? = null, + public val prev: String? = null, ) : List by data { - operator fun plus(other: List): MastodonPaging = + public operator fun plus(other: List): MastodonPaging = MastodonPaging( data = this.data.plus(other), next = this.next, @@ -24,15 +24,15 @@ internal class MastodonPaging( ) } -internal class MastodonPagingConverterFactory : Converter.Factory { +public class MastodonPagingConverterFactory : Converter.Factory { @OptIn(InternalAPI::class) - override fun suspendResponseConverter( + public override fun suspendResponseConverter( typeData: TypeData, ktorfit: Ktorfit, ): Converter.SuspendResponseConverter>? { if (typeData.typeInfo.type == MastodonPaging::class) { return object : Converter.SuspendResponseConverter> { - override suspend fun convert(result: KtorfitResult): MastodonPaging<*> = + public override suspend fun convert(result: KtorfitResult): MastodonPaging<*> = when (result) { is KtorfitResult.Failure -> { throw result.throwable diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MediaType.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MediaType.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MediaType.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MediaType.kt index 013a5d32a7..379710b6cb 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MediaType.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MediaType.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal enum class MediaType { +public enum class MediaType { @SerialName("unknown") Unknown, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Mention.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Mention.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Mention.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Mention.kt index 6f460eac66..aa5cbdba4c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Mention.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Mention.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.network.mastodon.api.model import kotlinx.serialization.Serializable @Serializable -internal data class Mention( +public data class Mention( val id: String? = null, val username: String? = null, val url: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Meta.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Meta.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Meta.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Meta.kt index 6e54400125..b2ca86a941 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Meta.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Meta.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class Meta( +public data class Meta( val length: String? = null, val duration: Double? = null, val fps: Long? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Notification.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Notification.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Notification.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Notification.kt index d55c0c1d8a..c765497896 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Notification.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Notification.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable import kotlin.time.Instant @Serializable -internal data class Notification( +public data class Notification( val id: String? = null, val type: NotificationTypes? = null, @SerialName("created_at") @@ -16,7 +16,7 @@ internal data class Notification( ) @Serializable -internal enum class NotificationTypes { +public enum class NotificationTypes { @SerialName("follow") Follow, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Option.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Option.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Option.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Option.kt index 40188b4082..233b11b361 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Option.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Option.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class Option( +public data class Option( val title: String? = null, @SerialName("votes_count") val votesCount: Long? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Original.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Original.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Original.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Original.kt index a87da56c48..b7182a71fe 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Original.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Original.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class Original( +public data class Original( val width: Long? = null, val height: Long? = null, @SerialName("frame_rate") diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Poll.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Poll.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Poll.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Poll.kt index 4a2b44358b..7c788bb32b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Poll.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Poll.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable import kotlin.time.Instant @Serializable -internal data class Poll( +public data class Poll( val id: String? = null, @SerialName("expires_at") @Serializable(with = DateSerializer::class) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostAccounts.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostAccounts.kt similarity index 82% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostAccounts.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostAccounts.kt index f8b6d54562..907ec34692 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostAccounts.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostAccounts.kt @@ -3,6 +3,6 @@ package dev.dimension.flare.data.network.mastodon.api.model import kotlinx.serialization.Serializable @Serializable -internal data class PostAccounts( +public data class PostAccounts( val account_ids: List, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostList.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostList.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostList.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostList.kt index 9ea1d91173..051f0c7273 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostList.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostList.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.network.mastodon.api.model import kotlinx.serialization.Serializable @Serializable -internal data class PostList( +public data class PostList( val title: String? = null, // Enumerable oneOf followed list none. Defaults to list. val replies_policy: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostPoll.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostPoll.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostPoll.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostPoll.kt index d6449cf3ee..b9c33b6c3f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostPoll.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostPoll.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class PostPoll( +public data class PostPoll( val options: List? = null, @SerialName("expires_in") val expiresIn: Long? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostReport.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostReport.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostReport.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostReport.kt index c8ddc1c202..59117e7167 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostReport.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostReport.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class PostReport( +public data class PostReport( @SerialName("account_id") val accountId: String, @SerialName("status_ids") diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostStatus.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostStatus.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostStatus.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostStatus.kt index 2055ee5b6d..519d156188 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostStatus.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostStatus.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class PostStatus( +public data class PostStatus( val status: String? = null, @SerialName("in_reply_to_id") val inReplyToID: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostVote.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostVote.kt similarity index 83% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostVote.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostVote.kt index 31145d7dfa..79ea6f6089 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostVote.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/PostVote.kt @@ -3,6 +3,6 @@ package dev.dimension.flare.data.network.mastodon.api.model import kotlinx.serialization.Serializable @Serializable -internal data class PostVote( +public data class PostVote( val choices: List, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/RelationshipResponse.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/RelationshipResponse.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/RelationshipResponse.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/RelationshipResponse.kt index e4f046a7c0..67bbeb77e9 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/RelationshipResponse.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/RelationshipResponse.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class RelationshipResponse( +public data class RelationshipResponse( val id: String? = null, val following: Boolean? = null, @SerialName("showing_reblogs") diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/RequestTokenResponse.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/RequestTokenResponse.kt similarity index 88% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/RequestTokenResponse.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/RequestTokenResponse.kt index 49952d4043..f1c5f6df95 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/RequestTokenResponse.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/RequestTokenResponse.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class RequestTokenResponse( +public data class RequestTokenResponse( @SerialName("access_token") val accessToken: String? = null, @SerialName("token_type") diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/SearchResult.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/SearchResult.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/SearchResult.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/SearchResult.kt index e031643546..c751d74e35 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/SearchResult.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/SearchResult.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.network.mastodon.api.model import kotlinx.serialization.Serializable @Serializable -internal data class SearchResult( +public data class SearchResult( val accounts: List? = null, val statuses: List? = null, val hashtags: List? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/SearchType.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/SearchType.kt similarity index 70% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/SearchType.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/SearchType.kt index 8d6fe52a05..ee8ec2e5a1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/SearchType.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/SearchType.kt @@ -1,7 +1,7 @@ package dev.dimension.flare.data.network.mastodon.api.model -internal enum class SearchType( - val value: String, +public enum class SearchType( + public val value: String, ) { Accounts("accounts"), HashTags("hashtags"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Small.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Small.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Small.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Small.kt index 593f4b157e..bc39e9f41c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Small.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Small.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.network.mastodon.api.model import kotlinx.serialization.Serializable @Serializable -internal data class Small( +public data class Small( val width: Long? = null, val height: Long? = null, val size: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Source.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Source.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Source.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Source.kt index 324a76b4d3..6ebe45aedd 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Source.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Source.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonArray @Serializable -internal data class Source( +public data class Source( val privacy: String? = null, val sensitive: Boolean? = null, val language: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Status.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Status.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Status.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Status.kt index 5f9b96bbf1..cdfaf2ba48 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Status.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Status.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.json.JsonObject import kotlin.time.Instant @Serializable -internal data class Status( +public data class Status( val id: String? = null, @SerialName("created_at") @Serializable(with = DateSerializer::class) @@ -74,14 +74,14 @@ internal data class Status( } @Serializable -internal data class MastodonQuote( +public data class MastodonQuote( val state: String? = null, @SerialName("quoted_status") val quoted_status: Status? = null, ) @Serializable -internal data class QuoteApproval( +public data class QuoteApproval( @SerialName("automatic") val automatic: List? = null, @SerialName("manual") @@ -90,7 +90,7 @@ internal data class QuoteApproval( val currentUser: CurrentUser? = null, ) { @Serializable - enum class CurrentUser { + public enum class CurrentUser { @SerialName("automatic") Automatic, @@ -111,7 +111,7 @@ internal data class QuoteApproval( // following = People followed by the author are expected to be able to quote this status and have the quote be automatically accepted. // unsupported_policy = The underlying quote policy is not supported by Mastodon. Some accounts that do not fit in the above categories may be able to quote and have the quote be automatically accepted. @Serializable - enum class Approval { + public enum class Approval { @SerialName("public") Public, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Tag.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Tag.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Tag.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Tag.kt index 2140916f5e..7d807f497d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Tag.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Tag.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.network.mastodon.api.model import kotlinx.serialization.Serializable @Serializable -internal data class Tag( +public data class Tag( val name: String? = null, val url: String? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Trend.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Trend.kt similarity index 86% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Trend.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Trend.kt index 69b4076963..fe56b27040 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Trend.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Trend.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class Trend( +public data class Trend( @SerialName("history") val history: List? = null, @SerialName("name") @@ -14,7 +14,7 @@ internal data class Trend( ) @Serializable -internal data class TrendHistory( +public data class TrendHistory( @SerialName("accounts") val accounts: String? = null, @SerialName("day") @@ -24,7 +24,7 @@ internal data class TrendHistory( ) @Serializable -internal data class Suggestions( +public data class Suggestions( val source: String? = null, val account: Account? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/UploadResponse.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/UploadResponse.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/UploadResponse.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/UploadResponse.kt index ff38dc9ff8..f844a0a815 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/UploadResponse.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/UploadResponse.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class UploadResponse( +public data class UploadResponse( val id: String? = null, val type: String? = null, val url: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/VerifyCredentialsResponse.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/VerifyCredentialsResponse.kt similarity index 85% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/VerifyCredentialsResponse.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/VerifyCredentialsResponse.kt index f24fbc6a5c..124d21c452 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/VerifyCredentialsResponse.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/VerifyCredentialsResponse.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class VerifyCredentialsResponse( +public data class VerifyCredentialsResponse( val name: String? = null, val website: String? = null, @SerialName("vapid_key") diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Visibility.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Visibility.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Visibility.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Visibility.kt index 69f97ca445..c51ac95dff 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Visibility.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/network/mastodon/api/model/Visibility.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal enum class Visibility { +public enum class Visibility { @SerialName("public") Public, diff --git a/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/platform/MastodonSocialPlatformPlugin.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/platform/MastodonSocialPlatformPlugin.kt new file mode 100644 index 0000000000..ffd722d72b --- /dev/null +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/platform/MastodonSocialPlatformPlugin.kt @@ -0,0 +1,174 @@ +package dev.dimension.flare.data.platform + +import dev.dimension.flare.common.deeplink.DeepLinkMapping +import dev.dimension.flare.common.deeplink.DeepLinkPattern +import dev.dimension.flare.common.tryRun +import dev.dimension.flare.data.datasource.guest.mastodon.GuestMastodonDataSource +import dev.dimension.flare.data.datasource.guest.mastodon.GuestPublicTimelineRemoteMediator +import dev.dimension.flare.data.datasource.guest.mastodon.GuestTrendsRemoteMediator +import dev.dimension.flare.data.datasource.mastodon.MastodonDataSource +import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource +import dev.dimension.flare.data.datasource.microblog.paging.CacheableRemoteLoader +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.pleroma.PleromaDataSource +import dev.dimension.flare.data.network.mastodon.JoinMastodonService +import dev.dimension.flare.data.network.mastodon.MastodonInstanceService +import dev.dimension.flare.data.network.mastodon.MastodonPlatformDetector +import dev.dimension.flare.data.network.nodeinfo.PlatformDetector +import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.model.PlatformTypeMetadata +import dev.dimension.flare.model.SocialPlatformPlugin +import dev.dimension.flare.model.SocialPlatformSpec +import dev.dimension.flare.model.SubscriptionTimelineTypeKey +import dev.dimension.flare.model.SubscriptionTimelineTypes +import dev.dimension.flare.ui.model.UiAccount +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiInstance +import dev.dimension.flare.ui.model.UiInstanceMetadata +import dev.dimension.flare.ui.model.UiTimelineV2 +import dev.dimension.flare.ui.model.mapper.render +import io.ktor.http.Url +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +public data object MastodonSocialPlatformPlugin : SocialPlatformPlugin { + public override val spec: SocialPlatformSpec = MastodonSocialPlatformSpec + + public override fun createDataSource(account: UiAccount): MicroblogDataSource? = + (account as? UiAccount.Mastodon)?.let { + when (it.forkType) { + UiAccount.Mastodon.Credential.ForkType.Mastodon -> { + MastodonDataSource( + accountKey = it.accountKey, + instance = it.instance, + ) + } + + UiAccount.Mastodon.Credential.ForkType.Pleroma -> { + PleromaDataSource( + accountKey = it.accountKey, + instance = it.instance, + ) + } + } + } + + public override suspend fun recommendedInstances(): List { + val instances = + tryRun { + JoinMastodonService.servers().map { + UiInstance( + name = it.domain, + description = it.description, + iconUrl = null, + domain = it.domain, + type = PlatformType.Mastodon, + bannerUrl = it.proxiedThumbnail, + usersCount = it.totalUsers, + ) + } + }.getOrDefault(emptyList()) + val pawoo = + tryRun { + MastodonInstanceService("https://pawoo.net/").instance().let { + UiInstance( + name = it.domain ?: "pawoo.net", + description = it.title, + iconUrl = it.thumbnail?.url, + domain = it.domain ?: "pawoo.net", + type = PlatformType.Mastodon, + bannerUrl = it.thumbnail?.url, + usersCount = it.usage?.users?.activeMonth ?: 0, + ) + } + }.getOrNull() + val pinned = + listOf( + instances.firstOrNull { it.domain == "mstdn.jp" } ?: mastodonFallback("mstdn.jp"), + instances.firstOrNull { it.domain == "pawoo.net" } ?: pawoo ?: mastodonFallback("pawoo.net"), + ) + return pinned + instances.sortedByDescending { it.usersCount }.filter { it !in pinned } + } +} + +public data object MastodonSocialPlatformSpec : SocialPlatformSpec { + public override val type: PlatformType = PlatformType.Mastodon + public override val timelineSpecs: ImmutableList> = MastodonTimelineSpecs.timelineSpecs + public override val metadata: PlatformTypeMetadata = + PlatformTypeMetadata( + displayName = "Mastodon", + icon = UiIcon.Mastodon, + ) + public override val detector: PlatformDetector = MastodonPlatformDetector + + public override fun agreementUrl(host: String): String = "https://$host/about" + + public override fun deepLinkPatterns(host: String): ImmutableList> = + persistentListOf( + DeepLinkPattern( + DeepLinkMapping.Type.Profile.serializer(), + Url("https://$host/@{handle}"), + ), + DeepLinkPattern( + DeepLinkMapping.Type.Post.serializer(), + Url("https://$host/@{handle}/{id}"), + ), + ) + + public override suspend fun instanceMetadata(host: String): UiInstanceMetadata = + MastodonInstanceService("https://$host/").instance().render() + + public override fun guestDataSource( + host: String, + locale: String, + ): MicroblogDataSource = + GuestMastodonDataSource( + host = host, + locale = locale, + ) + + public override fun createSubscriptionLoader( + subscriptionType: SubscriptionTimelineTypeKey, + url: String, + locale: String, + ): CacheableRemoteLoader? = + when (subscriptionType) { + SubscriptionTimelineTypes.MastodonTrends -> { + GuestTrendsRemoteMediator( + host = url, + locale = locale, + ) + } + + SubscriptionTimelineTypes.MastodonPublic -> { + GuestPublicTimelineRemoteMediator( + host = url, + locale = locale, + local = false, + ) + } + + SubscriptionTimelineTypes.MastodonLocal -> { + GuestPublicTimelineRemoteMediator( + host = url, + locale = locale, + local = true, + ) + } + + else -> { + null + } + } +} + +private fun mastodonFallback(domain: String): UiInstance = + UiInstance( + name = domain, + description = domain, + iconUrl = null, + domain = domain, + type = PlatformType.Mastodon, + bannerUrl = null, + usersCount = 0, + ) diff --git a/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/platform/MastodonTimelineSpecs.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/platform/MastodonTimelineSpecs.kt new file mode 100644 index 0000000000..f39104f942 --- /dev/null +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/data/platform/MastodonTimelineSpecs.kt @@ -0,0 +1,85 @@ +package dev.dimension.flare.data.platform + +import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader +import dev.dimension.flare.data.datasource.microblog.timeline.AccountTimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.CommonTimelineSpecs +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.UiTimelineV2 +import dev.dimension.flare.ui.model.asType +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +public interface MastodonTimelineDataSource { + public fun publicTimelineLoader(local: Boolean): RemoteLoader + + public fun bookmarkTimelineLoader(): RemoteLoader + + public fun favouriteTimelineLoader(): RemoteLoader +} + +public object MastodonTimelineSpecs { + public val local: AccountTimelineSpec = + AccountTimelineSpec( + id = "mastodon.local", + title = UiStrings.MastodonLocal, + icon = UiIcon.Local.asType(), + serializer = TimelineSpec.AccountBasedData.serializer(), + stableKeyFactory = { it.accountKey.toString() }, + loaderFactory = { service, _ -> + require(service is MastodonTimelineDataSource) + service.publicTimelineLoader(local = true) + }, + ) + + public val federated: AccountTimelineSpec = + AccountTimelineSpec( + id = "mastodon.public", + title = UiStrings.MastodonPublic, + icon = UiIcon.World.asType(), + serializer = TimelineSpec.AccountBasedData.serializer(), + stableKeyFactory = { it.accountKey.toString() }, + loaderFactory = { service, _ -> + require(service is MastodonTimelineDataSource) + service.publicTimelineLoader(local = false) + }, + ) + + public val bookmark: AccountTimelineSpec = + AccountTimelineSpec( + id = "mastodon.bookmark", + title = UiStrings.Bookmark, + icon = UiIcon.Bookmark.asType(), + serializer = TimelineSpec.AccountBasedData.serializer(), + stableKeyFactory = { it.accountKey.toString() }, + loaderFactory = { service, _ -> + require(service is MastodonTimelineDataSource) + service.bookmarkTimelineLoader() + }, + ) + + public val favourite: AccountTimelineSpec = + AccountTimelineSpec( + id = "mastodon.favourite", + title = UiStrings.Favourite, + icon = UiIcon.Favourite.asType(), + serializer = TimelineSpec.AccountBasedData.serializer(), + stableKeyFactory = { it.accountKey.toString() }, + loaderFactory = { service, _ -> + require(service is MastodonTimelineDataSource) + service.favouriteTimelineLoader() + }, + ) + + public val timelineSpecs: ImmutableList> = + persistentListOf( + CommonTimelineSpecs.home, + CommonTimelineSpecs.discover, + CommonTimelineSpecs.list, + local, + federated, + bookmark, + favourite, + ) +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Mastodon.kt b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Mastodon.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Mastodon.kt rename to social/mastodon/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Mastodon.kt index 73499648ab..bb5b019fd2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Mastodon.kt +++ b/social/mastodon/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Mastodon.kt @@ -3,7 +3,6 @@ package dev.dimension.flare.ui.model.mapper import com.fleeksoft.ksoup.nodes.Element import com.fleeksoft.ksoup.nodes.Node import dev.dimension.flare.data.datasource.microblog.ActionMenu -import dev.dimension.flare.data.datasource.microblog.PostEvent import dev.dimension.flare.data.datasource.microblog.userActionsMenu import dev.dimension.flare.data.network.mastodon.api.model.Account import dev.dimension.flare.data.network.mastodon.api.model.Attachment @@ -23,6 +22,7 @@ import dev.dimension.flare.model.PlatformType import dev.dimension.flare.model.ReferenceType import dev.dimension.flare.model.toAccountType import dev.dimension.flare.ui.model.ClickEvent +import dev.dimension.flare.ui.model.PostEvent import dev.dimension.flare.ui.model.UiCard import dev.dimension.flare.ui.model.UiHandle import dev.dimension.flare.ui.model.UiIcon @@ -58,14 +58,15 @@ private val mastodonParser by lazy { ) } -internal fun Notification.render(accountKey: MicroBlogKey): UiTimelineV2 { - requireNotNull(account) { "account is null" } - val user = account.render(accountKey, host = accountKey.host) +public fun Notification.render(accountKey: MicroBlogKey): UiTimelineV2 { + val notificationAccount = requireNotNull(account) { "account is null" } + val notificationType = type + val user = notificationAccount.render(accountKey, host = accountKey.host) val messageType = - if (type == null) { + if (notificationType == null) { UiTimelineV2.Message.Type.Unknown(rawType = "") } else { - when (type) { + when (notificationType) { NotificationTypes.Follow -> { UiTimelineV2.Message.Type.Localized.MessageId.Follow } @@ -112,7 +113,7 @@ internal fun Notification.render(accountKey: MicroBlogKey): UiTimelineV2 { UiTimelineV2.Message( user = user, icon = - when (type) { + when (notificationType) { NotificationTypes.Follow -> UiIcon.Follow NotificationTypes.Favourite -> UiIcon.Favourite NotificationTypes.Reblog -> UiIcon.Retweet @@ -136,7 +137,8 @@ internal fun Notification.render(accountKey: MicroBlogKey): UiTimelineV2 { ), accountType = accountKey.toAccountType(), ) - if (type in listOf(NotificationTypes.FollowRequest)) { + val notificationStatus = status + if (notificationType in listOf(NotificationTypes.FollowRequest)) { return UiTimelineV2.User( value = user, message = message, @@ -180,8 +182,8 @@ internal fun Notification.render(accountKey: MicroBlogKey): UiTimelineV2 { statusKey = MicroBlogKey(id ?: "", accountKey.host), accountType = accountKey.toAccountType(), ) - } else if (status != null) { - return status + } else if (notificationStatus != null) { + return notificationStatus .renderStatus( host = accountKey.host, accountKey = accountKey, @@ -199,18 +201,19 @@ internal fun Notification.render(accountKey: MicroBlogKey): UiTimelineV2 { } } -internal fun List.render(accountKey: MicroBlogKey): List = +public fun List.render(accountKey: MicroBlogKey): List = this .map { it.render(host = accountKey.host, accountKey = accountKey) } .resolveParents() .collapseStandaloneParents() -internal fun Status.render( +public fun Status.render( host: String, accountKey: MicroBlogKey?, ): UiTimelineV2 { requireNotNull(account) { "account is null" } val currentStatus = this.renderStatus(host, accountKey) + val boostedStatus = reblog val topMessage = if (pinned == true) { UiTimelineV2.Message( @@ -225,7 +228,7 @@ internal fun Status.render( clickEvent = ClickEvent.Noop, accountType = accountKey.toAccountType(), ) - } else if (reblog != null) { + } else if (boostedStatus != null) { val userKey = currentStatus.user?.key UiTimelineV2.Message( user = currentStatus.user, @@ -253,11 +256,11 @@ internal fun Status.render( } else { null } - return if (reblog != null) { + return if (boostedStatus != null) { currentStatus.copy( message = topMessage, internalRepost = - reblog.renderStatus( + boostedStatus.renderStatus( host = host, accountKey = accountKey, ), @@ -271,8 +274,8 @@ private fun Status.renderStatus( host: String, accountKey: MicroBlogKey?, ): UiTimelineV2.Post { - requireNotNull(account) { "actualStatus.account is null" } - val actualUser = account.render(accountKey, host) + val statusAccount = requireNotNull(account) { "actualStatus.account is null" } + val actualUser = statusAccount.render(accountKey, host) val isFromMe = actualUser.key == accountKey val canReblog = visibility in @@ -284,19 +287,22 @@ private fun Status.renderStatus( val canQuote = if (emojiReactions != null) { // assuming that is pleroma canReblog - } else if (quoteApproval != null && quoteApproval.currentUser != null) { - when (quoteApproval.currentUser) { + } else { + val approval = quoteApproval + when (approval?.currentUser) { QuoteApproval.CurrentUser.Automatic -> { - if (!quoteApproval.automatic.isNullOrEmpty()) { - quoteApproval.automatic.contains(QuoteApproval.Approval.Public) + val automatic = approval.automatic.orEmpty() + if (automatic.isNotEmpty()) { + automatic.contains(QuoteApproval.Approval.Public) } else { isFromMe } } QuoteApproval.CurrentUser.Manual -> { - if (!quoteApproval.manual.isNullOrEmpty()) { - quoteApproval.manual.contains(QuoteApproval.Approval.Public) + val manual = approval.manual.orEmpty() + if (manual.isNotEmpty()) { + manual.contains(QuoteApproval.Approval.Public) } else { isFromMe } @@ -309,9 +315,11 @@ private fun Status.renderStatus( QuoteApproval.CurrentUser.Unknown -> { false } + + null -> { + false + } } - } else { - false } // val canReact = dataSource is StatusEvent.Pleroma // TODO: there are too many actions for Pleroma, disable for now @@ -359,7 +367,7 @@ private fun Status.renderStatus( } else if (!uri.isNullOrEmpty()) { append(uri) } else { - append("https://$host/@${account.acct}/$id") + append("https://$host/@${statusAccount.acct}/$id") } } val quoteStatus = quote?.renderStatus(host, accountKey) @@ -379,19 +387,21 @@ private fun Status.renderStatus( quote = listOfNotNull(quoteStatus).toImmutableList(), content = parseMastodonContent(this, accountKey, host, sourceLanguages), card = - card?.url?.let { url -> + card?.let { statusCard -> + val cardUrl = statusCard.url ?: return@let null + val cardImage = statusCard.image UiCard( - url = url, - title = card.title.orEmpty(), - description = card.description?.takeIf { it.isNotEmpty() && it.isNotBlank() }, + url = cardUrl, + title = statusCard.title.orEmpty(), + description = statusCard.description?.takeIf { it.isNotEmpty() && it.isNotBlank() }, media = - card.image?.let { + cardImage?.let { UiMedia.Image( - url = card.image, - previewUrl = card.image, - description = card.description, - width = card.width?.toFloat() ?: 0f, - height = card.height?.toFloat() ?: 0f, + url = it, + previewUrl = it, + description = statusCard.description, + width = statusCard.width?.toFloat() ?: 0f, + height = statusCard.height?.toFloat() ?: 0f, sensitive = false, ) }, @@ -761,7 +771,7 @@ private fun List.collapseStandaloneParents(): List { } } -internal fun ActionMenu.Companion.mastodonLike( +public fun ActionMenu.Companion.mastodonLike( favourited: Boolean, favouritesCount: Long, accountKey: MicroBlogKey?, @@ -793,7 +803,7 @@ internal fun ActionMenu.Companion.mastodonLike( }, ) -internal fun ActionMenu.Companion.mastodonRepost( +public fun ActionMenu.Companion.mastodonRepost( reblogged: Boolean, reblogsCount: Long, accountKey: MicroBlogKey?, @@ -825,7 +835,7 @@ internal fun ActionMenu.Companion.mastodonRepost( }, ) -internal fun ActionMenu.Companion.mastodonBookmark( +public fun ActionMenu.Companion.mastodonBookmark( bookmarked: Boolean, accountKey: MicroBlogKey?, statusKey: MicroBlogKey, @@ -912,13 +922,14 @@ private fun Attachment.toUi(sensitive: Boolean): UiMedia? = } } -internal fun Account.render( +public fun Account.render( accountKey: MicroBlogKey?, host: String, ): UiProfile { + val accountAcct = acct val remoteHost = - if (acct != null && acct.contains('@')) { - acct.substring(acct.indexOf('@') + 1) + if (accountAcct != null && accountAcct.contains('@')) { + accountAcct.substring(accountAcct.indexOf('@') + 1) } else { host } @@ -1074,7 +1085,7 @@ private fun updateHtmlTagToken( } } -internal fun RelationshipResponse.toUi(): UiRelation = +public fun RelationshipResponse.toUi(): UiRelation = UiRelation( following = following ?: false, isFans = followedBy ?: false, @@ -1096,7 +1107,7 @@ private fun parseName(status: Account): UiRichText { return parseHtml(content).toUi() } -internal fun parseMastodonContent( +public fun parseMastodonContent( status: Status, // text: String, accountKey: MicroBlogKey?, @@ -1180,7 +1191,7 @@ private fun replaceMentionAndHashtag( } } -internal fun InstanceData.render(): UiInstanceMetadata { +public fun InstanceData.render(): UiInstanceMetadata { val configuration = UiInstanceMetadata.Configuration( registration = @@ -1239,7 +1250,7 @@ internal fun InstanceData.render(): UiInstanceMetadata { ) } -internal fun MastodonList.render(): UiList.List = +public fun MastodonList.render(): UiList.List = UiList.List( id = id.toString(), title = title.orEmpty(), diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/network/mastodon/MastodonExceptionTest.kt b/social/mastodon/src/commonTest/kotlin/dev/dimension/flare/data/network/mastodon/MastodonExceptionTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/network/mastodon/MastodonExceptionTest.kt rename to social/mastodon/src/commonTest/kotlin/dev/dimension/flare/data/network/mastodon/MastodonExceptionTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonPagingConverterFactoryTest.kt b/social/mastodon/src/commonTest/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonPagingConverterFactoryTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonPagingConverterFactoryTest.kt rename to social/mastodon/src/commonTest/kotlin/dev/dimension/flare/data/network/mastodon/api/model/MastodonPagingConverterFactoryTest.kt diff --git a/social/microblog/build.gradle.kts b/social/microblog/build.gradle.kts new file mode 100644 index 0000000000..1b4a9278ba --- /dev/null +++ b/social/microblog/build.gradle.kts @@ -0,0 +1,49 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.social.microblog" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + sourceSets { + all { + languageSettings { + optIn("kotlin.uuid.ExperimentalUuidApi") + } + } + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.core.model) + api(projects.modules.account.api) + api(projects.social.model) + api(projects.foundation.database) + api(projects.foundation.filesystem) + implementation(projects.modules.translation.api) + api(libs.paging.common) + implementation(dependencies.platform(libs.koin.bom)) + implementation(libs.koin.core) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) + } + } + } +} diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/common/BasePagingSource.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/common/BasePagingSource.kt new file mode 100644 index 0000000000..a6ed5ff8df --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/common/BasePagingSource.kt @@ -0,0 +1,20 @@ +package dev.dimension.flare.common + +import androidx.paging.PagingSource +import dev.dimension.flare.common.DebugRepository + +public abstract class BasePagingSource : PagingSource() { + override suspend fun load(params: LoadParams): LoadResult = + try { + doLoad(params) + } catch (e: Exception) { + onError(e) + DebugRepository.error(e) + LoadResult.Error(e) + } + + public abstract suspend fun doLoad(params: LoadParams): LoadResult + + protected open fun onError(e: Throwable) { + } +} diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/common/BaseRemoteMediator.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/common/BaseRemoteMediator.kt new file mode 100644 index 0000000000..3432ad1e80 --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/common/BaseRemoteMediator.kt @@ -0,0 +1,30 @@ +package dev.dimension.flare.common + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import dev.dimension.flare.common.DebugRepository + +@OptIn(ExperimentalPagingApi::class) +public abstract class BaseRemoteMediator : RemoteMediator() { + final override suspend fun load( + loadType: LoadType, + state: PagingState, + ): MediatorResult = + try { + doLoad(loadType, state) + } catch (e: Exception) { + onError(e) + DebugRepository.error(e) + MediatorResult.Error(e) + } + + public abstract suspend fun doLoad( + loadType: LoadType, + state: PagingState, + ): MediatorResult + + protected open fun onError(e: Throwable) { + } +} diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/AuthenticatedMicroblogDataSource.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/AuthenticatedMicroblogDataSource.kt new file mode 100644 index 0000000000..aaa66a3996 --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/AuthenticatedMicroblogDataSource.kt @@ -0,0 +1,20 @@ +package dev.dimension.flare.data.datasource.microblog + +import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiTimelineV2 + +public interface AuthenticatedMicroblogDataSource : MicroblogDataSource { + public val accountKey: MicroBlogKey + + public fun notification(type: NotificationFilter = NotificationFilter.All): RemoteLoader + + public val supportedNotificationFilter: List + + public suspend fun compose( + data: ComposeData, + progress: () -> Unit, + ) + + public fun composeConfig(type: ComposeType): ComposeConfig +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeConfig.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeConfig.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeConfig.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeConfig.kt index fadc60fac9..936f74dd95 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeConfig.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeConfig.kt @@ -8,7 +8,7 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableMap @Immutable -public data class ComposeConfig internal constructor( +public data class ComposeConfig( val text: Text? = null, val media: Media? = null, val poll: Poll? = null, @@ -18,10 +18,10 @@ public data class ComposeConfig internal constructor( val language: Language? = null, ) { @Immutable - public data class Text internal constructor( + public data class Text( val maxLength: Int, ) { - internal fun merge(other: Text): Text = + public fun merge(other: Text): Text = Text( maxLength = minOf(maxLength, other.maxLength), ) @@ -29,7 +29,7 @@ public data class ComposeConfig internal constructor( @Immutable // in ISO 639-1 format - public data class Language internal constructor( + public data class Language( val maxCount: Int, ) { private val popularCodes = @@ -246,13 +246,13 @@ public data class ComposeConfig internal constructor( } @Immutable - public data class Media internal constructor( + public data class Media( val maxCount: Int, val canSensitive: Boolean, val altTextMaxLength: Int, val allowMediaOnly: Boolean, ) { - internal fun merge(other: Media): Media = + public fun merge(other: Media): Media = Media( maxCount = minOf(maxCount, other.maxCount), canSensitive = canSensitive && other.canSensitive, @@ -262,23 +262,23 @@ public data class ComposeConfig internal constructor( } @Immutable - public data class Poll internal constructor( + public data class Poll( val maxOptions: Int, ) { - internal fun merge(other: Poll): Poll = + public fun merge(other: Poll): Poll = Poll( maxOptions = minOf(maxOptions, other.maxOptions), ) } @Immutable - public data class Emoji internal constructor( - internal val emoji: CacheData>>, + public data class Emoji( + val emoji: CacheData>>, // Emojis picker can be merged only if their mergeTag is the same. val mergeTag: String, val accountKey: MicroBlogKey, ) { - internal fun merge(other: Emoji): Emoji? = + public fun merge(other: Emoji): Emoji? = if (mergeTag == other.mergeTag) { Emoji( emoji = emoji, @@ -296,7 +296,7 @@ public data class ComposeConfig internal constructor( @Immutable public data object Visibility - internal fun merge(other: ComposeConfig): ComposeConfig { + public fun merge(other: ComposeConfig): ComposeConfig { val text = if (text != null && other.text != null) { text.merge(other.text) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeData.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeData.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeData.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeData.kt index 8c050fc8f2..6281021cb5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeData.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeData.kt @@ -1,6 +1,6 @@ package dev.dimension.flare.data.datasource.microblog -import dev.dimension.flare.common.FileItem +import dev.dimension.flare.data.io.FileItem import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.presenter.compose.ComposeStatus diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeType.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeType.kt similarity index 72% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeType.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeType.kt index 7cdde36152..59fbf88d9b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeType.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ComposeType.kt @@ -1,6 +1,6 @@ package dev.dimension.flare.data.datasource.microblog -internal enum class ComposeType { +public enum class ComposeType { New, Quote, Reply, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/DatabaseUpdater.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/DatabaseUpdater.kt similarity index 65% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/DatabaseUpdater.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/DatabaseUpdater.kt index 3bbb57ab48..63ace4d495 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/DatabaseUpdater.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/DatabaseUpdater.kt @@ -3,15 +3,15 @@ package dev.dimension.flare.data.datasource.microblog import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiTimelineV2 -internal interface DatabaseUpdater { - suspend fun updateCache( +public interface DatabaseUpdater { + public suspend fun updateCache( postKey: MicroBlogKey, update: suspend (UiTimelineV2) -> UiTimelineV2, ) - suspend fun deleteFromCache(postKey: MicroBlogKey) + public suspend fun deleteFromCache(postKey: MicroBlogKey) - suspend fun updateActionMenu( + public suspend fun updateActionMenu( postKey: MicroBlogKey, newActionMenu: ActionMenu.Item, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/DirectMessageDataSource.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/DirectMessageDataSource.kt similarity index 75% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/DirectMessageDataSource.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/DirectMessageDataSource.kt index c5f9ecdbf1..5f789067cf 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/DirectMessageDataSource.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/DirectMessageDataSource.kt @@ -20,50 +20,50 @@ import kotlinx.coroutines.flow.Flow import kotlin.time.Clock import kotlin.uuid.Uuid -internal interface DirectMessageDataSource : AuthenticatedMicroblogDataSource { - val directMessageHandler: DirectMessageHandler +public interface DirectMessageDataSource : AuthenticatedMicroblogDataSource { + public val directMessageHandler: DirectMessageHandler - fun directMessageList(scope: CoroutineScope): Flow> = directMessageHandler.list(scope) + public fun directMessageList(scope: CoroutineScope): Flow> = directMessageHandler.list(scope) - fun directMessageConversation( + public fun directMessageConversation( roomKey: MicroBlogKey, scope: CoroutineScope, ): Flow> = directMessageHandler.conversation(roomKey, scope) - fun sendDirectMessage( + public fun sendDirectMessage( roomKey: MicroBlogKey, message: String, ) { directMessageHandler.send(roomKey, message) } - fun retrySendDirectMessage(messageKey: MicroBlogKey) { + public fun retrySendDirectMessage(messageKey: MicroBlogKey) { directMessageHandler.retry(messageKey) } - fun deleteDirectMessage( + public fun deleteDirectMessage( roomKey: MicroBlogKey, messageKey: MicroBlogKey, ) { directMessageHandler.delete(roomKey, messageKey) } - fun getDirectMessageConversationInfo(roomKey: MicroBlogKey): CacheData = directMessageHandler.roomInfo(roomKey) + public fun getDirectMessageConversationInfo(roomKey: MicroBlogKey): CacheData = directMessageHandler.roomInfo(roomKey) - suspend fun fetchNewDirectMessageForConversation(roomKey: MicroBlogKey) { + public suspend fun fetchNewDirectMessageForConversation(roomKey: MicroBlogKey) { directMessageHandler.fetchNew(roomKey) } - val directMessageBadgeCount: CacheData + public val directMessageBadgeCount: CacheData get() = directMessageHandler.badgeCount - fun leaveDirectMessage(roomKey: MicroBlogKey) { + public fun leaveDirectMessage(roomKey: MicroBlogKey) { directMessageHandler.leave(roomKey) } - fun createDirectMessageRoom(userKey: MicroBlogKey): Flow> = directMessageHandler.createRoom(userKey) + public fun createDirectMessageRoom(userKey: MicroBlogKey): Flow> = directMessageHandler.createRoom(userKey) - suspend fun canSendDirectMessage(userKey: MicroBlogKey): Boolean = directMessageHandler.canSend(userKey) + public suspend fun canSendDirectMessage(userKey: MicroBlogKey): Boolean = directMessageHandler.canSend(userKey) } internal fun createSendingDirectMessage( diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MicroblogDataSource.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MicroblogDataSource.kt new file mode 100644 index 0000000000..3c66b4b8a9 --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MicroblogDataSource.kt @@ -0,0 +1,35 @@ +package dev.dimension.flare.data.datasource.microblog + +import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiHashtag +import dev.dimension.flare.ui.model.UiProfile +import dev.dimension.flare.ui.model.UiTimelineV2 +import kotlinx.collections.immutable.ImmutableList + +public interface MicroblogDataSource { + public fun homeTimeline(): RemoteLoader + + public fun userTimeline( + userKey: MicroBlogKey, + mediaOnly: Boolean = false, + ): RemoteLoader + + public fun context(statusKey: MicroBlogKey): RemoteLoader + + public fun searchStatus(query: String): RemoteLoader + + public fun searchUser(query: String): RemoteLoader + + public fun discoverUsers(): RemoteLoader + + public fun discoverStatuses(): RemoteLoader + + public fun discoverHashtags(): RemoteLoader + + public fun following(userKey: MicroBlogKey): RemoteLoader + + public fun fans(userKey: MicroBlogKey): RemoteLoader + + public fun profileTabs(userKey: MicroBlogKey): ImmutableList +} diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MicroblogTimelineMergePolicy.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MicroblogTimelineMergePolicy.kt new file mode 100644 index 0000000000..7ab03bbf0a --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MicroblogTimelineMergePolicy.kt @@ -0,0 +1,7 @@ +package dev.dimension.flare.data.datasource.microblog + +public enum class MicroblogTimelineMergePolicy { + Time, + TimePerPage, + Staggered, +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MixedRemoteMediator.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MixedRemoteMediator.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MixedRemoteMediator.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MixedRemoteMediator.kt index c63d507675..228e4a9eea 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MixedRemoteMediator.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/MixedRemoteMediator.kt @@ -8,20 +8,19 @@ import dev.dimension.flare.data.datasource.microblog.paging.PagingRequest import dev.dimension.flare.data.datasource.microblog.paging.PagingResult import dev.dimension.flare.data.datasource.microblog.paging.ReportableRemoteLoader import dev.dimension.flare.data.datasource.microblog.paging.SortIdProvider -import dev.dimension.flare.data.model.tab.TimelineMergePolicy import dev.dimension.flare.ui.model.UiTimelineV2 import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope -internal class MixedRemoteMediator( +public class MixedRemoteMediator( private val database: CacheDatabase, private val mediators: List>, - private val mergePolicy: TimelineMergePolicy = TimelineMergePolicy.TimePerPage, + private val mergePolicy: MicroblogTimelineMergePolicy = MicroblogTimelineMergePolicy.TimePerPage, ) : CacheableRemoteLoader, ReportableRemoteLoader, SortIdProvider { - override val pagingKey = + public override val pagingKey: String = buildString { append("mixed_timeline") mediators.forEach { mediator -> @@ -88,7 +87,7 @@ internal class MixedRemoteMediator( } override suspend fun sortId(data: UiTimelineV2): Long? = - if (mergePolicy == TimelineMergePolicy.Time) { + if (mergePolicy == MicroblogTimelineMergePolicy.Time) { val createdAt = data.createdAt.value.toEpochMilliseconds() val tieBreaker = (itemIdentity(data).hashCode().toLong() - Int.MIN_VALUE.toLong()) % SORT_ID_TIE_BUCKET @@ -99,8 +98,8 @@ internal class MixedRemoteMediator( private fun merge(response: List): List = when (mergePolicy) { - TimelineMergePolicy.Time, - TimelineMergePolicy.TimePerPage, + MicroblogTimelineMergePolicy.Time, + MicroblogTimelineMergePolicy.TimePerPage, -> { response .flatMap { it.result.data } @@ -109,7 +108,7 @@ internal class MixedRemoteMediator( } } - TimelineMergePolicy.Staggered -> { + MicroblogTimelineMergePolicy.Staggered -> { response .map { it.result.data } .interleave() @@ -162,15 +161,17 @@ internal class MixedRemoteMediator( subResponse: SubResponse, ) { val (mediator, result) = subResponse - if (request is PagingRequest.Prepend && result.previousKey != null) { + val previousKey = result.previousKey + val nextKey = result.nextKey + if (request is PagingRequest.Prepend && previousKey != null) { database.pagingTimelineDao().updatePagingKeyPrevKey( pagingKey = subKey(mediator), - prevKey = result.previousKey, + prevKey = previousKey, ) - } else if (request is PagingRequest.Append && result.nextKey != null) { + } else if (request is PagingRequest.Append && nextKey != null) { database.pagingTimelineDao().updatePagingKeyNextKey( pagingKey = subKey(mediator), - nextKey = result.nextKey, + nextKey = nextKey, ) } else if (request is PagingRequest.Refresh) { database.pagingTimelineDao().deletePagingKey(subKey(mediator)) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/NotificationFilter.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/NotificationFilter.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/NotificationFilter.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/NotificationFilter.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/Paging.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/Paging.kt similarity index 82% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/Paging.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/Paging.kt index 8ce0b32c37..474640ca2b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/Paging.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/Paging.kt @@ -2,7 +2,7 @@ package dev.dimension.flare.data.datasource.microblog import androidx.paging.PagingConfig -internal val pagingConfig: PagingConfig = +public val pagingConfig: PagingConfig = PagingConfig( pageSize = 20, prefetchDistance = 2, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ProfileTab.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ProfileTab.kt similarity index 77% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ProfileTab.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ProfileTab.kt index ebf772d050..e90767bdab 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ProfileTab.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ProfileTab.kt @@ -7,9 +7,9 @@ import dev.dimension.flare.ui.model.UiTimelineV2 @Immutable public sealed interface ProfileTab { @Immutable - public data class Timeline internal constructor( - internal val type: Type, - internal val loader: RemoteLoader, + public data class Timeline( + val type: Type, + val loader: RemoteLoader, ) : ProfileTab { @Immutable public enum class Type { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ReactionDataSource.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ReactionDataSource.kt similarity index 70% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ReactionDataSource.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ReactionDataSource.kt index 61bf4bd583..c88b5723eb 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ReactionDataSource.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ReactionDataSource.kt @@ -1,6 +1,6 @@ package dev.dimension.flare.data.datasource.microblog -internal interface ReactionDataSource : AuthenticatedMicroblogDataSource { +public interface ReactionDataSource : AuthenticatedMicroblogDataSource { // fun react( // statusKey: MicroBlogKey, // hasReacted: Boolean, diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/ListDataSource.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/ListDataSource.kt new file mode 100644 index 0000000000..2bfb1fdc2c --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/ListDataSource.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.data.datasource.microblog.datasource + +import dev.dimension.flare.data.datasource.microblog.handler.ListHandler +import dev.dimension.flare.data.datasource.microblog.handler.ListMemberHandler +import dev.dimension.flare.data.datasource.microblog.timeline.ListTimelineDataSource +import dev.dimension.flare.ui.model.UiList + +public interface ListDataSource : ListTimelineDataSource { + public val listHandler: ListHandler + public val listMemberHandler: ListMemberHandler +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/NotificationDataSource.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/NotificationDataSource.kt similarity index 60% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/NotificationDataSource.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/NotificationDataSource.kt index 5be5b9972b..2de799f8e7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/NotificationDataSource.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/NotificationDataSource.kt @@ -2,6 +2,6 @@ package dev.dimension.flare.data.datasource.microblog.datasource import dev.dimension.flare.data.datasource.microblog.handler.NotificationHandler -internal interface NotificationDataSource { - val notificationHandler: NotificationHandler +public interface NotificationDataSource { + public val notificationHandler: NotificationHandler } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/PostDataSource.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/PostDataSource.kt similarity index 63% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/PostDataSource.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/PostDataSource.kt index c228b446da..d6dcadfcab 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/PostDataSource.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/PostDataSource.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.datasource.microblog.datasource import dev.dimension.flare.data.datasource.microblog.handler.PostEventHandler import dev.dimension.flare.data.datasource.microblog.handler.PostHandler -internal interface PostDataSource { - val postHandler: PostHandler - val postEventHandler: PostEventHandler +public interface PostDataSource { + public val postHandler: PostHandler + public val postEventHandler: PostEventHandler } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/RelationDataSource.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/RelationDataSource.kt similarity index 60% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/RelationDataSource.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/RelationDataSource.kt index b5804372d1..2c39e471f6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/RelationDataSource.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/RelationDataSource.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.datasource.microblog.datasource import dev.dimension.flare.data.datasource.microblog.handler.RelationHandler import dev.dimension.flare.data.datasource.microblog.loader.RelationActionType -internal interface RelationDataSource { - val relationHandler: RelationHandler - val supportedRelationTypes: Set +public interface RelationDataSource { + public val relationHandler: RelationHandler + public val supportedRelationTypes: Set } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/UserDataSource.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/UserDataSource.kt similarity index 65% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/UserDataSource.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/UserDataSource.kt index 90d9be9ebe..97932c153d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/UserDataSource.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/datasource/UserDataSource.kt @@ -2,6 +2,6 @@ package dev.dimension.flare.data.datasource.microblog.datasource import dev.dimension.flare.data.datasource.microblog.handler.UserHandler -internal interface UserDataSource { - val userHandler: UserHandler +public interface UserDataSource { + public val userHandler: UserHandler } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/DirectMessageHandler.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/DirectMessageHandler.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/DirectMessageHandler.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/DirectMessageHandler.kt index f1f2d6ae18..b329428297 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/DirectMessageHandler.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/DirectMessageHandler.kt @@ -7,6 +7,7 @@ import androidx.paging.cachedIn import androidx.paging.map import dev.dimension.flare.common.CacheData import dev.dimension.flare.common.Cacheable +import dev.dimension.flare.common.tryRun import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.connect import dev.dimension.flare.data.database.cache.mapper.toDbUser @@ -21,7 +22,6 @@ import dev.dimension.flare.data.datasource.microblog.loader.DirectMessageLoader import dev.dimension.flare.data.datasource.microblog.paging.PagingRequest import dev.dimension.flare.data.datasource.microblog.paging.createPagingRemoteMediator import dev.dimension.flare.data.datasource.microblog.pagingConfig -import dev.dimension.flare.data.repository.tryRun import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiDMItem @@ -40,7 +40,7 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.inject @OptIn(ExperimentalPagingApi::class) -internal class DirectMessageHandler( +public class DirectMessageHandler( private val accountKey: MicroBlogKey, private val loader: DirectMessageLoader, private val coroutineScope: CoroutineScope, @@ -49,7 +49,7 @@ internal class DirectMessageHandler( private val accountType = AccountType.Specific(accountKey) private val inMemoryBadgeCount = MutableStateFlow(null) - fun list(scope: CoroutineScope): Flow> = + public fun list(scope: CoroutineScope): Flow> = Pager( config = pagingConfig, remoteMediator = @@ -76,7 +76,7 @@ internal class DirectMessageHandler( paging.map(transformer.room) }.cachedIn(scope) - fun conversation( + public fun conversation( roomKey: MicroBlogKey, scope: CoroutineScope, ): Flow> = @@ -110,7 +110,7 @@ internal class DirectMessageHandler( paging.map(transformer.item) }.cachedIn(scope) - fun roomInfo(roomKey: MicroBlogKey): CacheData = + public fun roomInfo(roomKey: MicroBlogKey): CacheData = Cacheable( fetchSource = { saveRooms(listOf(loader.fetchRoomInfo(roomKey))) @@ -130,7 +130,7 @@ internal class DirectMessageHandler( }, ) - fun send( + public fun send( roomKey: MicroBlogKey, message: String, ) { @@ -157,7 +157,7 @@ internal class DirectMessageHandler( } } - fun retry(messageKey: MicroBlogKey) { + public fun retry(messageKey: MicroBlogKey) { coroutineScope.launch { val current = database.messageDao().getMessage(messageKey) val text = (current?.content?.content as? UiDMItem.Message.Text)?.text?.raw @@ -190,7 +190,7 @@ internal class DirectMessageHandler( } } - fun delete( + public fun delete( roomKey: MicroBlogKey, messageKey: MicroBlogKey, ) { @@ -212,7 +212,7 @@ internal class DirectMessageHandler( } } - suspend fun fetchNew(roomKey: MicroBlogKey) { + public suspend fun fetchNew(roomKey: MicroBlogKey) { val cursor = database.messageDao().getLatestMessage(roomKey)?.remoteCursor saveDelta( roomKey = roomKey, @@ -222,7 +222,7 @@ internal class DirectMessageHandler( updateRoomTimelineFromMessages(roomKey, unreadCount = 0) } - val badgeCount: CacheData by lazy { + public val badgeCount: CacheData by lazy { Cacheable( fetchSource = { inMemoryBadgeCount.value = loader.loadBadgeCount() @@ -233,7 +233,7 @@ internal class DirectMessageHandler( ) } - fun leave(roomKey: MicroBlogKey) { + public fun leave(roomKey: MicroBlogKey) { coroutineScope.launch { tryRun { loader.leaveRoom(roomKey) @@ -246,7 +246,7 @@ internal class DirectMessageHandler( } } - fun createRoom(userKey: MicroBlogKey): Flow> = + public fun createRoom(userKey: MicroBlogKey): Flow> = loader.createRoom(userKey).map { state -> when (state) { is UiState.Success -> { @@ -264,7 +264,7 @@ internal class DirectMessageHandler( } } - suspend fun canSend(userKey: MicroBlogKey): Boolean = loader.canSend(userKey) + public suspend fun canSend(userKey: MicroBlogKey): Boolean = loader.canSend(userKey) private suspend fun saveDelta( roomKey: MicroBlogKey, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/EmojiHandler.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/EmojiHandler.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/EmojiHandler.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/EmojiHandler.kt index d3b5497559..a7feeb0b6d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/EmojiHandler.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/EmojiHandler.kt @@ -15,12 +15,12 @@ import kotlinx.serialization.SerializationException import org.koin.core.component.KoinComponent import org.koin.core.component.inject -internal class EmojiHandler( +public class EmojiHandler( private val host: String, private val loader: EmojiLoader, ) : KoinComponent { private val database: CacheDatabase by inject() - val emoji: Cacheable>> = + public val emoji: Cacheable>> = Cacheable( fetchSource = { val emojis = loader.emojis() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListHandler.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListHandler.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListHandler.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListHandler.kt index 9c47841f0a..0fa0fa2406 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListHandler.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListHandler.kt @@ -6,6 +6,7 @@ import androidx.paging.PagingData import androidx.paging.map import dev.dimension.flare.common.CacheData import dev.dimension.flare.common.Cacheable +import dev.dimension.flare.common.tryRun import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.connect import dev.dimension.flare.data.database.cache.model.DbList @@ -16,7 +17,6 @@ import dev.dimension.flare.data.datasource.microblog.loader.ListLoader import dev.dimension.flare.data.datasource.microblog.paging.PagingRequest import dev.dimension.flare.data.datasource.microblog.paging.createPagingRemoteMediator import dev.dimension.flare.data.datasource.microblog.pagingConfig -import dev.dimension.flare.data.repository.tryRun import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.DbAccountType import dev.dimension.flare.model.MicroBlogKey @@ -29,7 +29,7 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.inject @OptIn(ExperimentalPagingApi::class) -internal class ListHandler( +public class ListHandler( private val pagingKey: String, private val accountKey: MicroBlogKey, private val loader: ListLoader, @@ -37,10 +37,10 @@ internal class ListHandler( private val accountType: DbAccountType = AccountType.Specific(accountKey) private val database: CacheDatabase by inject() - val supportedMetaData: ImmutableList by lazy { + public val supportedMetaData: ImmutableList by lazy { loader.supportedMetaData } - val data: Flow> by lazy { + public val data: Flow> by lazy { Pager( config = pagingConfig, remoteMediator = @@ -88,7 +88,7 @@ internal class ListHandler( } } - val cacheData: Flow> by lazy { + public val cacheData: Flow> by lazy { database.listDao().getListKeysFlow(pagingKey).map { dbItems -> dbItems.map { @Suppress("UNCHECKED_CAST") @@ -97,7 +97,7 @@ internal class ListHandler( } } - fun listInfo(listId: String): CacheData { + public fun listInfo(listId: String): CacheData { val listKey = MicroBlogKey(listId, accountKey.host) return Cacheable( fetchSource = { @@ -128,7 +128,7 @@ internal class ListHandler( ) } - suspend fun create(metaData: ListMetaData) { + public suspend fun create(metaData: ListMetaData) { tryRun { loader.create(metaData) }.onSuccess { result -> @@ -155,7 +155,7 @@ internal class ListHandler( } } - suspend fun update( + public suspend fun update( listId: String, metaData: ListMetaData, ) { @@ -173,7 +173,7 @@ internal class ListHandler( } } - suspend fun delete(listId: String) { + public suspend fun delete(listId: String) { val listKey = MicroBlogKey(listId, accountKey.host) tryRun { loader.delete(listId) @@ -191,7 +191,7 @@ internal class ListHandler( } } - suspend fun insertToDatabase(data: UiList) { + public suspend fun insertToDatabase(data: UiList) { val listKey = MicroBlogKey(data.id, accountKey.host) database.connect { database.listDao().insertAllList( @@ -216,7 +216,7 @@ internal class ListHandler( } } - suspend fun withDatabase(block: suspend (update: suspend (UiList) -> Unit) -> Unit) { + public suspend fun withDatabase(block: suspend (update: suspend (UiList) -> Unit) -> Unit) { block.invoke { data -> val listKey = MicroBlogKey(data.id, accountKey.host) database.connect { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListMemberHandler.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListMemberHandler.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListMemberHandler.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListMemberHandler.kt index 13fe62344c..4f2864d64f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListMemberHandler.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/ListMemberHandler.kt @@ -5,6 +5,7 @@ import androidx.paging.Pager import androidx.paging.PagingData import androidx.paging.map import dev.dimension.flare.common.Cacheable +import dev.dimension.flare.common.tryRun import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.connect import dev.dimension.flare.data.database.cache.mapper.toDbUser @@ -15,10 +16,10 @@ import dev.dimension.flare.data.datasource.microblog.loader.ListMemberLoader import dev.dimension.flare.data.datasource.microblog.paging.PagingRequest import dev.dimension.flare.data.datasource.microblog.paging.createPagingRemoteMediator import dev.dimension.flare.data.datasource.microblog.pagingConfig -import dev.dimension.flare.data.repository.tryRun import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.DbAccountType import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiList import dev.dimension.flare.ui.model.UiProfile import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -26,7 +27,7 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.inject @OptIn(ExperimentalPagingApi::class) -internal class ListMemberHandler( +public class ListMemberHandler( private val pagingKey: String, private val accountKey: MicroBlogKey, private val loader: ListMemberLoader, @@ -36,7 +37,7 @@ internal class ListMemberHandler( private val memberPagingKey: String get() = "${pagingKey}_members" - fun listMembers(listId: String): Flow> { + public fun listMembers(listId: String): Flow> { val listKey = MicroBlogKey(listId, accountKey.host) return Pager( config = pagingConfig, @@ -80,7 +81,7 @@ internal class ListMemberHandler( } } - fun listMembersListFlow(listId: String) = + public fun listMembersListFlow(listId: String): Flow> = database .listDao() .getListMembersFlow( @@ -91,7 +92,7 @@ internal class ListMemberHandler( } } - suspend fun addMember( + public suspend fun addMember( listId: String, userKey: MicroBlogKey, ) { @@ -124,7 +125,7 @@ internal class ListMemberHandler( } } - suspend fun removeMember( + public suspend fun removeMember( listId: String, userKey: MicroBlogKey, ) { @@ -154,7 +155,7 @@ internal class ListMemberHandler( private val userListsPagingKey: String get() = "${pagingKey}_user_lists" - fun userLists(userKey: MicroBlogKey) = + public fun userLists(userKey: MicroBlogKey): Cacheable> = Cacheable( fetchSource = { tryRun { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/NotificationHandler.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/NotificationHandler.kt similarity index 71% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/NotificationHandler.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/NotificationHandler.kt index 8b70434503..49b871aceb 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/NotificationHandler.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/NotificationHandler.kt @@ -4,16 +4,16 @@ import dev.dimension.flare.common.MemCacheable import dev.dimension.flare.data.datasource.microblog.loader.NotificationLoader import dev.dimension.flare.model.MicroBlogKey -internal class NotificationHandler( - val accountKey: MicroBlogKey, - val loader: NotificationLoader, +public class NotificationHandler( + public val accountKey: MicroBlogKey, + public val loader: NotificationLoader, private val fetchBadgeCount: (suspend () -> Int) = { loader.notificationBadgeCount() }, ) { private val key by lazy { "notificationHandler_$accountKey" } - val notificationBadgeCount by lazy { + public val notificationBadgeCount: MemCacheable by lazy { MemCacheable( key = key, fetchSource = { @@ -22,11 +22,11 @@ internal class NotificationHandler( ) } - fun update(count: Int) { + public fun update(count: Int) { MemCacheable.update(key, count) } - fun clear() { + public fun clear() { update(0) } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostEventHandler.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostEventHandler.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostEventHandler.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostEventHandler.kt index dc3765befb..fe141b3714 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostEventHandler.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostEventHandler.kt @@ -1,16 +1,15 @@ package dev.dimension.flare.data.datasource.microblog.handler +import dev.dimension.flare.common.tryRun import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.connect import dev.dimension.flare.data.database.cache.model.DbStatus import dev.dimension.flare.data.datasource.microblog.ActionMenu import dev.dimension.flare.data.datasource.microblog.DatabaseUpdater -import dev.dimension.flare.data.datasource.microblog.PostEvent -import dev.dimension.flare.data.datasource.microblog.UpdatePostActionMenuEvent -import dev.dimension.flare.data.repository.tryRun import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.DbAccountType import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.PostEvent import dev.dimension.flare.ui.model.UiTimelineV2 import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -20,23 +19,24 @@ import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject -internal class PostEventHandler( +public class PostEventHandler( private val accountType: AccountType, private val handler: Handler, + private val optimisticActionMenu: (PostEvent) -> ActionMenu.Item? = { null }, ) : KoinComponent, DatabaseUpdater { private val coroutineScope: CoroutineScope by inject() private val database: CacheDatabase by inject() private val dbAccountType = accountType as DbAccountType - interface Handler { - suspend fun handle( + public interface Handler { + public suspend fun handle( event: PostEvent, updater: DatabaseUpdater, ) } - fun handleEvent(event: PostEvent) { + public fun handleEvent(event: PostEvent) { coroutineScope.launch { val originalData = database @@ -46,13 +46,14 @@ internal class PostEventHandler( accountType = dbAccountType, ).firstOrNull() ?.content - if (event is UpdatePostActionMenuEvent && originalData is UiTimelineV2.Post) { + val nextActionMenu = optimisticActionMenu(event) + if (nextActionMenu != null && originalData is UiTimelineV2.Post) { val updatedData = originalData.copy( actions = findAndReplaceActionMenu( actions = originalData.actions, - newActionMenu = event.nextActionMenu(), + newActionMenu = nextActionMenu, ), ) database.statusDao().update( @@ -62,13 +63,14 @@ internal class PostEventHandler( ) } if (event is PostEvent.PollEvent && originalData is UiTimelineV2.Post) { + val originalPoll = originalData.poll val updatedData = originalData.copy( poll = - originalData.poll?.copy( + originalPoll?.copy( ownVotes = event.options, options = - originalData.poll.options + originalPoll.options .mapIndexed { index, option -> if (event.options.contains(index)) { option.copy( diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandler.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandler.kt similarity index 86% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandler.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandler.kt index 967bd9ca15..6704b3b1e4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandler.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandler.kt @@ -1,16 +1,15 @@ package dev.dimension.flare.data.datasource.microblog.handler import dev.dimension.flare.common.Cacheable +import dev.dimension.flare.common.tryRun import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.connect +import dev.dimension.flare.data.database.cache.mapper.TimelinePagingMapper import dev.dimension.flare.data.database.cache.mapper.saveToDatabase import dev.dimension.flare.data.database.cache.model.DbStatus import dev.dimension.flare.data.datasource.microblog.loader.PostLoader -import dev.dimension.flare.data.datasource.microblog.paging.TimelinePagingMapper -import dev.dimension.flare.data.datastore.AppDataStore -import dev.dimension.flare.data.repository.tryRun import dev.dimension.flare.data.translation.PreTranslationService -import dev.dimension.flare.data.translation.TranslationSettingsSupport +import dev.dimension.flare.data.translation.TranslationSettingsProvider import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.DbAccountType import dev.dimension.flare.model.MicroBlogKey @@ -24,20 +23,20 @@ import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject -internal class PostHandler( - val accountType: AccountType, - val loader: PostLoader, +public class PostHandler( + public val accountType: AccountType, + public val loader: PostLoader, ) : KoinComponent { private val database: CacheDatabase by inject() private val coroutineScope: CoroutineScope by inject() - private val appDataStore: AppDataStore by inject() + private val translationSettingsProvider: TranslationSettingsProvider by inject() private val preTranslationService: PreTranslationService by inject() private val translationDisplayFlow by lazy { - TranslationSettingsSupport.displayOptionsFlow(appDataStore) + translationSettingsProvider.displayOptionsFlow } - fun post(postKey: MicroBlogKey): Cacheable { + public fun post(postKey: MicroBlogKey): Cacheable { val pagingKey = "post_only_$postKey" return Cacheable( fetchSource = { @@ -80,7 +79,7 @@ internal class PostHandler( ) } - fun delete(postKey: MicroBlogKey) { + public fun delete(postKey: MicroBlogKey) { coroutineScope.launch { tryRun { loader.deleteStatus(postKey) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/RelationHandler.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/RelationHandler.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/RelationHandler.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/RelationHandler.kt index cca502a8b7..8e07cd4403 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/RelationHandler.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/RelationHandler.kt @@ -1,29 +1,30 @@ package dev.dimension.flare.data.datasource.microblog.handler import dev.dimension.flare.common.Cacheable +import dev.dimension.flare.common.tryRun import dev.dimension.flare.data.database.cache.CacheDatabase import dev.dimension.flare.data.database.cache.model.DbUserRelation import dev.dimension.flare.data.datasource.microblog.loader.RelationLoader -import dev.dimension.flare.data.repository.tryRun import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.DbAccountType import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiRelation import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject -internal class RelationHandler( - val accountType: AccountType, - val dataSource: RelationLoader, +public class RelationHandler( + public val accountType: AccountType, + public val dataSource: RelationLoader, ) : KoinComponent { private val database: CacheDatabase by inject() private val coroutineScope: CoroutineScope by inject() - fun relation(userKey: MicroBlogKey) = + public fun relation(userKey: MicroBlogKey): Cacheable = Cacheable( fetchSource = { val result = dataSource.relation(userKey) @@ -45,7 +46,7 @@ internal class RelationHandler( }, ) - fun follow(userKey: MicroBlogKey) = + public fun follow(userKey: MicroBlogKey): Job = coroutineScope.launch { tryRun { updateRelation( @@ -69,7 +70,7 @@ internal class RelationHandler( } } - fun unfollow(userKey: MicroBlogKey) = + public fun unfollow(userKey: MicroBlogKey): Job = coroutineScope.launch { tryRun { updateRelation( @@ -93,7 +94,7 @@ internal class RelationHandler( } } - fun block(userKey: MicroBlogKey) = + public fun block(userKey: MicroBlogKey): Job = coroutineScope.launch { tryRun { updateRelation( @@ -117,7 +118,7 @@ internal class RelationHandler( } } - fun unblock(userKey: MicroBlogKey) = + public fun unblock(userKey: MicroBlogKey): Job = coroutineScope.launch { tryRun { updateRelation( @@ -141,7 +142,7 @@ internal class RelationHandler( } } - fun mute(userKey: MicroBlogKey) = + public fun mute(userKey: MicroBlogKey): Job = coroutineScope.launch { tryRun { updateRelation( @@ -165,7 +166,7 @@ internal class RelationHandler( } } - fun unmute(userKey: MicroBlogKey) = + public fun unmute(userKey: MicroBlogKey): Job = coroutineScope.launch { tryRun { updateRelation( @@ -189,7 +190,7 @@ internal class RelationHandler( } } - suspend fun approveFollowRequest(userKey: MicroBlogKey) { + public suspend fun approveFollowRequest(userKey: MicroBlogKey) { updateRelation( userKey = userKey, update = { relation -> @@ -201,7 +202,7 @@ internal class RelationHandler( ) } - suspend fun rejectFollowRequest(userKey: MicroBlogKey) { + public suspend fun rejectFollowRequest(userKey: MicroBlogKey) { updateRelation( userKey = userKey, update = { relation -> diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandler.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandler.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandler.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandler.kt index 709b1623e4..f5f8b64e87 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandler.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandler.kt @@ -3,17 +3,17 @@ package dev.dimension.flare.data.datasource.microblog.handler import dev.dimension.flare.common.Cacheable import dev.dimension.flare.common.Locale import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.database.cache.mapper.applyTranslation import dev.dimension.flare.data.database.cache.mapper.toDbUser import dev.dimension.flare.data.database.cache.mapper.upsertUser -import dev.dimension.flare.data.database.cache.model.TranslationEntityType -import dev.dimension.flare.data.database.cache.model.applyTranslation +import dev.dimension.flare.data.translation.TranslationEntityType import dev.dimension.flare.data.database.cache.model.translationEntityKey import dev.dimension.flare.data.datasource.microblog.loader.UserLoader -import dev.dimension.flare.data.datastore.AppDataStore import dev.dimension.flare.data.translation.PreTranslationService -import dev.dimension.flare.data.translation.TranslationSettingsSupport +import dev.dimension.flare.data.translation.TranslationSettingsProvider import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiHandle +import dev.dimension.flare.ui.model.UiProfile import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -23,19 +23,19 @@ import kotlinx.coroutines.flow.mapNotNull import org.koin.core.component.KoinComponent import org.koin.core.component.inject -internal class UserHandler( +public class UserHandler( private val host: String, private val loader: UserLoader, ) : KoinComponent { private val database: CacheDatabase by inject() - private val appDataStore: AppDataStore by inject() + private val translationSettingsProvider: TranslationSettingsProvider by inject() private val preTranslationService: PreTranslationService by inject() private val translationDisplayFlow by lazy { - TranslationSettingsSupport.displayOptionsFlow(appDataStore) + translationSettingsProvider.displayOptionsFlow } - fun userByHandleAndHost(uiHandle: UiHandle) = + public fun userByHandleAndHost(uiHandle: UiHandle): Cacheable = Cacheable( fetchSource = { val user = loader.userByHandleAndHost(uiHandle) @@ -57,7 +57,7 @@ internal class UserHandler( }, ) - fun userById(id: String) = + public fun userById(id: String): Cacheable = Cacheable( fetchSource = { val user = loader.userById(id) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/list/ListMetaData.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/list/ListMetaData.kt similarity index 81% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/list/ListMetaData.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/list/ListMetaData.kt index ea22cc3ad3..78bb193a9e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/list/ListMetaData.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/list/ListMetaData.kt @@ -1,6 +1,6 @@ package dev.dimension.flare.data.datasource.microblog.list -import dev.dimension.flare.common.FileItem +import dev.dimension.flare.data.io.FileItem public data class ListMetaData( val title: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/list/ListMetaDataType.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/list/ListMetaDataType.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/list/ListMetaDataType.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/list/ListMetaDataType.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/DirectMessageLoader.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/DirectMessageLoader.kt similarity index 62% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/DirectMessageLoader.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/DirectMessageLoader.kt index 2ace637a9d..944cb5db62 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/DirectMessageLoader.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/DirectMessageLoader.kt @@ -10,55 +10,55 @@ import dev.dimension.flare.ui.model.UiState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf -internal interface DirectMessageLoader { - val platformType: PlatformType +public interface DirectMessageLoader { + public val platformType: PlatformType - val runtimeTransformer: Flow + public val runtimeTransformer: Flow get() = flowOf(DirectMessageRuntimeTransformer()) - suspend fun loadRooms( + public suspend fun loadRooms( pageSize: Int, request: PagingRequest, ): PagingResult - suspend fun loadMessages( + public suspend fun loadMessages( roomKey: MicroBlogKey, pageSize: Int, request: PagingRequest, ): PagingResult - suspend fun fetchRoomInfo(roomKey: MicroBlogKey): UiDMRoom + public suspend fun fetchRoomInfo(roomKey: MicroBlogKey): UiDMRoom - suspend fun sendMessage( + public suspend fun sendMessage( roomKey: MicroBlogKey, message: String, ): UiDMItem - suspend fun deleteMessage( + public suspend fun deleteMessage( roomKey: MicroBlogKey, messageKey: MicroBlogKey, ) - suspend fun fetchNewMessages( + public suspend fun fetchNewMessages( roomKey: MicroBlogKey, cursor: String?, ): DirectMessageDelta - suspend fun leaveRoom(roomKey: MicroBlogKey) + public suspend fun leaveRoom(roomKey: MicroBlogKey) - fun createRoom(userKey: MicroBlogKey): Flow> + public fun createRoom(userKey: MicroBlogKey): Flow> - suspend fun canSend(userKey: MicroBlogKey): Boolean + public suspend fun canSend(userKey: MicroBlogKey): Boolean - suspend fun loadBadgeCount(): Int + public suspend fun loadBadgeCount(): Int } -internal data class DirectMessageDelta( +public data class DirectMessageDelta( val messages: List = emptyList(), val deletedMessageKeys: List = emptyList(), ) -internal data class DirectMessageRuntimeTransformer( +public data class DirectMessageRuntimeTransformer( val room: (UiDMRoom) -> UiDMRoom = { it }, val item: (UiDMItem) -> UiDMItem = { it }, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/EmojiLoader.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/EmojiLoader.kt similarity index 65% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/EmojiLoader.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/EmojiLoader.kt index e1e0efd06b..e594895b8c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/EmojiLoader.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/EmojiLoader.kt @@ -4,6 +4,6 @@ import dev.dimension.flare.ui.model.UiEmoji import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableMap -internal interface EmojiLoader { - suspend fun emojis(): ImmutableMap> +public interface EmojiLoader { + public suspend fun emojis(): ImmutableMap> } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/ListLoader.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/ListLoader.kt similarity index 65% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/ListLoader.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/ListLoader.kt index a7d3a765bf..5ff4284748 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/ListLoader.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/ListLoader.kt @@ -7,22 +7,22 @@ import dev.dimension.flare.data.datasource.microblog.paging.PagingResult import dev.dimension.flare.ui.model.UiList import kotlinx.collections.immutable.ImmutableList -internal interface ListLoader { - suspend fun load( +public interface ListLoader { + public suspend fun load( pageSize: Int, request: PagingRequest, ): PagingResult - suspend fun info(listId: String): T + public suspend fun info(listId: String): T - suspend fun create(metaData: ListMetaData): T + public suspend fun create(metaData: ListMetaData): T - suspend fun update( + public suspend fun update( listId: String, metaData: ListMetaData, ): T - suspend fun delete(listId: String) + public suspend fun delete(listId: String) - val supportedMetaData: ImmutableList + public val supportedMetaData: ImmutableList } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/ListMemberLoader.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/ListMemberLoader.kt similarity index 79% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/ListMemberLoader.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/ListMemberLoader.kt index d364f1cdde..0fb83142f1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/ListMemberLoader.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/ListMemberLoader.kt @@ -6,24 +6,24 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiList import dev.dimension.flare.ui.model.UiProfile -internal interface ListMemberLoader { - suspend fun loadMembers( +public interface ListMemberLoader { + public suspend fun loadMembers( pageSize: Int, request: PagingRequest, listId: String, ): PagingResult - suspend fun addMember( + public suspend fun addMember( listId: String, userKey: MicroBlogKey, ): UiProfile - suspend fun removeMember( + public suspend fun removeMember( listId: String, userKey: MicroBlogKey, ) - suspend fun loadUserLists( + public suspend fun loadUserLists( pageSize: Int, request: PagingRequest, userKey: MicroBlogKey, diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/NotificationLoader.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/NotificationLoader.kt new file mode 100644 index 0000000000..ca0636b312 --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/NotificationLoader.kt @@ -0,0 +1,5 @@ +package dev.dimension.flare.data.datasource.microblog.loader + +public interface NotificationLoader { + public suspend fun notificationBadgeCount(): Int +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/PostLoader.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/PostLoader.kt similarity index 50% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/PostLoader.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/PostLoader.kt index 9ac4794fb6..ae1365772f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/PostLoader.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/PostLoader.kt @@ -3,8 +3,8 @@ package dev.dimension.flare.data.datasource.microblog.loader import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiTimelineV2 -internal interface PostLoader { - suspend fun status(statusKey: MicroBlogKey): UiTimelineV2 +public interface PostLoader { + public suspend fun status(statusKey: MicroBlogKey): UiTimelineV2 - suspend fun deleteStatus(statusKey: MicroBlogKey) + public suspend fun deleteStatus(statusKey: MicroBlogKey) } diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/RelationLoader.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/RelationLoader.kt new file mode 100644 index 0000000000..8c1610d8a4 --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/RelationLoader.kt @@ -0,0 +1,28 @@ +package dev.dimension.flare.data.datasource.microblog.loader + +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiRelation + +public enum class RelationActionType { + Follow, + Block, + Mute, +} + +public interface RelationLoader { + public val supportedTypes: Set + + public suspend fun relation(userKey: MicroBlogKey): UiRelation + + public suspend fun follow(userKey: MicroBlogKey) + + public suspend fun unfollow(userKey: MicroBlogKey) + + public suspend fun block(userKey: MicroBlogKey) + + public suspend fun unblock(userKey: MicroBlogKey) + + public suspend fun mute(userKey: MicroBlogKey) + + public suspend fun unmute(userKey: MicroBlogKey) +} diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/UserLoader.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/UserLoader.kt new file mode 100644 index 0000000000..775879b303 --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/loader/UserLoader.kt @@ -0,0 +1,10 @@ +package dev.dimension.flare.data.datasource.microblog.loader + +import dev.dimension.flare.ui.model.UiHandle +import dev.dimension.flare.ui.model.UiProfile + +public interface UserLoader { + public suspend fun userByHandleAndHost(uiHandle: UiHandle): UiProfile + + public suspend fun userById(id: String): UiProfile +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/BasePagingRemoteMediator.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/BasePagingRemoteMediator.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/BasePagingRemoteMediator.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/BasePagingRemoteMediator.kt index cf3cbac72e..075b149699 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/BasePagingRemoteMediator.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/BasePagingRemoteMediator.kt @@ -30,10 +30,10 @@ internal fun createPagingRemoteMediator( } } -internal abstract class BasePagingRemoteMediator( +public abstract class BasePagingRemoteMediator( private val database: CacheDatabase, ) : BaseRemoteMediator() { - abstract val pagingKey: String + public abstract val pagingKey: String @OptIn(ExperimentalPagingApi::class) override suspend fun doLoad( @@ -67,6 +67,8 @@ internal abstract class BasePagingRemoteMediator( request = request, ) database.connect { + val previousKey = result.previousKey + val nextKey = result.nextKey if (loadType == LoadType.REFRESH) { database.pagingTimelineDao().deletePagingKey(pagingKey) database.pagingTimelineDao().insertPagingKey( @@ -76,15 +78,15 @@ internal abstract class BasePagingRemoteMediator( prevKey = result.previousKey, ), ) - } else if (loadType == LoadType.PREPEND && result.previousKey != null) { + } else if (loadType == LoadType.PREPEND && previousKey != null) { database.pagingTimelineDao().updatePagingKeyPrevKey( pagingKey = pagingKey, - prevKey = result.previousKey, + prevKey = previousKey, ) - } else if (loadType == LoadType.APPEND && result.nextKey != null) { + } else if (loadType == LoadType.APPEND && nextKey != null) { database.pagingTimelineDao().updatePagingKeyNextKey( pagingKey = pagingKey, - nextKey = result.nextKey, + nextKey = nextKey, ) } onSaveCache(request, result.data) diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/CacheableRemoteLoader.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/CacheableRemoteLoader.kt new file mode 100644 index 0000000000..e3dbb8a942 --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/CacheableRemoteLoader.kt @@ -0,0 +1,11 @@ +package dev.dimension.flare.data.datasource.microblog.paging + +public interface CacheableRemoteLoader : RemoteLoader { + public val pagingKey: String + public val supportPrepend: Boolean + get() = false +} + +public interface ReportableRemoteLoader { + public var reportError: ((Throwable) -> Unit)? +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/PagingRequest.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/PagingRequest.kt similarity index 53% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/PagingRequest.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/PagingRequest.kt index 3c855bd167..2eaac0bddb 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/PagingRequest.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/PagingRequest.kt @@ -1,13 +1,13 @@ package dev.dimension.flare.data.datasource.microblog.paging -internal sealed interface PagingRequest { - data object Refresh : PagingRequest +public sealed interface PagingRequest { + public data object Refresh : PagingRequest - data class Prepend( + public data class Prepend( val previousKey: String, ) : PagingRequest - data class Append( + public data class Append( val nextKey: String, ) : PagingRequest } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/PagingResult.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/PagingResult.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/PagingResult.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/PagingResult.kt index e8db47deae..6203e78bdb 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/PagingResult.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/PagingResult.kt @@ -1,11 +1,11 @@ package dev.dimension.flare.data.datasource.microblog.paging -internal data class PagingResult( +public data class PagingResult( val data: List = emptyList(), val nextKey: String? = null, val previousKey: String? = null, ) { - constructor( + public constructor( endOfPaginationReached: Boolean, data: List = emptyList(), nextKey: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/RemoteLoader.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/RemoteLoader.kt similarity index 82% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/RemoteLoader.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/RemoteLoader.kt index 10af34db56..85ebef339c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/RemoteLoader.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/RemoteLoader.kt @@ -3,23 +3,23 @@ package dev.dimension.flare.data.datasource.microblog.paging import androidx.paging.PagingSource import androidx.paging.PagingState -internal interface RemoteLoader { - suspend fun load( +public interface RemoteLoader { + public suspend fun load( pageSize: Int, request: PagingRequest, ): PagingResult } -internal fun notSupported(): RemoteLoader = NotSupportRemoteLoader() +public fun notSupported(): RemoteLoader = NotSupportRemoteLoader() -internal class NotSupportRemoteLoader : RemoteLoader { +public class NotSupportRemoteLoader : RemoteLoader { override suspend fun load( pageSize: Int, request: PagingRequest, ): PagingResult = PagingResult(endOfPaginationReached = true) } -internal fun RemoteLoader.toPagingSource() = +public fun RemoteLoader.toPagingSource(): PagingSource = object : PagingSource() { override suspend fun load(params: LoadParams): LoadResult { val request = diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/SortIdProvider.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/SortIdProvider.kt similarity index 55% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/SortIdProvider.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/SortIdProvider.kt index 87455fa30a..d99c883182 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/SortIdProvider.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/SortIdProvider.kt @@ -2,6 +2,6 @@ package dev.dimension.flare.data.datasource.microblog.paging import dev.dimension.flare.ui.model.UiTimelineV2 -internal interface SortIdProvider { - suspend fun sortId(data: UiTimelineV2): Long? +public interface SortIdProvider { + public suspend fun sortId(data: UiTimelineV2): Long? } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/TimelineRemoteMediator.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/TimelineRemoteMediator.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/TimelineRemoteMediator.kt rename to social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/TimelineRemoteMediator.kt index c8c42d7821..453db40331 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/TimelineRemoteMediator.kt +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/TimelineRemoteMediator.kt @@ -3,6 +3,7 @@ package dev.dimension.flare.data.datasource.microblog.paging import androidx.paging.ExperimentalPagingApi import dev.dimension.flare.common.SnowflakeIdGenerator import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.database.cache.mapper.TimelinePagingMapper import dev.dimension.flare.data.database.cache.mapper.saveToDatabase import dev.dimension.flare.data.database.cache.model.DbPagingTimelineWithStatus import dev.dimension.flare.data.database.cache.model.DbStatusWithReference @@ -14,7 +15,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import kotlinx.collections.immutable.toImmutableList @OptIn(ExperimentalPagingApi::class) -internal class TimelineRemoteMediator( +public class TimelineRemoteMediator( private val loader: CacheableRemoteLoader, private val database: CacheDatabase, private val allowLongText: Boolean, @@ -72,7 +73,7 @@ internal class TimelineRemoteMediator( ) } - suspend fun timeline( + public suspend fun timeline( pageSize: Int, request: PagingRequest, ): PagingResult = diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/CommonTimelineSpecs.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/CommonTimelineSpecs.kt new file mode 100644 index 0000000000..b221b3ccd2 --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/CommonTimelineSpecs.kt @@ -0,0 +1,59 @@ +package dev.dimension.flare.data.datasource.microblog.timeline + +import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.UiTimelineV2 +import dev.dimension.flare.ui.model.asType +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +public interface ListTimelineDataSource { + public fun listTimeline(listId: String): RemoteLoader +} + +public object CommonTimelineSpecs { + public val home: AccountTimelineSpec = + AccountTimelineSpec( + id = "common.home", + title = UiStrings.Home, + icon = UiIcon.Home.asType(), + serializer = TimelineSpec.AccountBasedData.serializer(), + stableKeyFactory = { it.accountKey.toString() }, + loaderFactory = { service, _ -> + service.homeTimeline() + }, + ) + + public val discover: AccountTimelineSpec = + AccountTimelineSpec( + id = "common.discover", + title = UiStrings.Discover, + icon = UiIcon.Search.asType(), + serializer = TimelineSpec.AccountBasedData.serializer(), + stableKeyFactory = { it.accountKey.toString() }, + loaderFactory = { service, _ -> + service.discoverStatuses() + }, + ) + + public val list: AccountTimelineSpec = + AccountTimelineSpec( + id = "common.list", + title = UiStrings.List, + icon = UiIcon.List.asType(), + serializer = TimelineSpec.AccountResourceData.serializer(), + stableKeyFactory = { "${it.accountKey}:${it.resourceId}" }, + loaderFactory = { service, data -> + require(service is ListTimelineDataSource) + service.listTimeline(listId = data.resourceId) + }, + ) + + public val timelineSpecs: ImmutableList> = + persistentListOf( + home, + discover, + list, + ) +} diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineCatalog.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineCatalog.kt new file mode 100644 index 0000000000..de8808e014 --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineCatalog.kt @@ -0,0 +1,47 @@ +package dev.dimension.flare.data.datasource.microblog.timeline + +public class TimelineCatalog( + specs: Iterable>, +) { + private val specsById: Map> = + specs + .distinctBy { it.id } + .associateBy { it.id } + + public val specs: List> + get() = specsById.values.toList() + + public fun requireSpec(id: String): TimelineSpec = + requireNotNull(specsById[id]) { + "No timeline spec registered for $id" + } + + public fun encode(ref: TimelineRef): EncodedTimelineRef = encodeTyped(ref) + + public fun decode( + specId: String, + encodedData: String, + ): TimelineRef = decodeTyped(requireSpec(specId), encodedData) + + private fun encodeTyped(ref: TimelineRef): EncodedTimelineRef = + EncodedTimelineRef( + specId = ref.spec.id, + stableKey = ref.spec.stableKey(ref.data), + data = ref.spec.encode(ref.data), + ) + + @Suppress("UNCHECKED_CAST") + private fun decodeTyped( + spec: TimelineSpec, + encodedData: String, + ): TimelineRef { + val typedSpec = spec as TimelineSpec + return typedSpec.ref(typedSpec.decode(encodedData)) + } +} + +public data class EncodedTimelineRef( + public val specId: String, + public val stableKey: String, + public val data: String, +) diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineDescriptorFactories.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineDescriptorFactories.kt new file mode 100644 index 0000000000..42073105d7 --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineDescriptorFactories.kt @@ -0,0 +1,54 @@ +package dev.dimension.flare.data.datasource.microblog.timeline + +import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiList +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.UiText +import dev.dimension.flare.ui.model.asText +import dev.dimension.flare.ui.model.asType + +public fun TimelineRef.toTimelineTabDescriptor( + title: UiText = spec.title.asText(), + icon: IconType = spec.icon, +): TimelineTabDescriptor.Source = + TimelineTabDescriptor.Source( + ref = this, + display = + TimelineDisplay( + title = title, + icon = icon, + ), + ) + +public fun TimelineSpec.toTimelineTabDescriptor( + data: T, + title: UiText = this.title.asText(), + icon: IconType = this.icon, +): TimelineTabDescriptor.Source = + ref(data).toTimelineTabDescriptor( + title = title, + icon = icon, + ) + +public fun TimelineTabDescriptor.Source.toTimelineShortcutDescriptor( + title: UiStrings, + icon: UiIcon, +): TimelineShortcutDescriptor = + TimelineShortcutDescriptor( + title = title, + icon = icon, + target = + TimelineShortcutDescriptor.Target.Timeline( + ref = ref, + display = display, + ), + ) + +public fun UiList.List.toTimelineTabDescriptor(accountKey: MicroBlogKey): TimelineTabDescriptor.Source = + CommonTimelineSpecs.list.toTimelineTabDescriptor( + data = TimelineSpec.AccountResourceData(accountKey, id), + title = UiText.Raw(title), + icon = avatar?.let { IconType.Url(it) } ?: UiIcon.List.asType(), + ) diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineRef.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineRef.kt new file mode 100644 index 0000000000..1f3aff0936 --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineRef.kt @@ -0,0 +1,9 @@ +package dev.dimension.flare.data.datasource.microblog.timeline + +public data class TimelineRef( + public val spec: TimelineSpec, + public val data: T, +) { + public val semanticId: String + get() = "${spec.id}:${spec.stableKey(data)}" +} diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineSpec.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineSpec.kt new file mode 100644 index 0000000000..46906c6bbc --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineSpec.kt @@ -0,0 +1,100 @@ +package dev.dimension.flare.data.datasource.microblog.timeline + +import dev.dimension.flare.data.database.app.AppDatabase +import dev.dimension.flare.data.database.cache.CacheDatabase +import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource +import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader +import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.UiTimelineV2 +import kotlinx.coroutines.flow.Flow +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromHexString +import kotlinx.serialization.encodeToHexString +import kotlinx.serialization.protobuf.ProtoBuf + +public interface TimelineSpec { + public val id: String + public val title: UiStrings + public val icon: IconType + public val serializer: KSerializer + + public fun stableKey(data: T): String + + public fun ref(data: T): TimelineRef = + TimelineRef( + spec = this, + data = data, + ) + + @OptIn(ExperimentalSerializationApi::class) + public fun encode(data: T): String = ProtoBuf.encodeToHexString(serializer, data) + + @OptIn(ExperimentalSerializationApi::class) + public fun decode(encodedData: String): T = ProtoBuf.decodeFromHexString(serializer, encodedData) + + public interface Data + + public interface AccountData : Data { + public val accountKey: MicroBlogKey + } + + @Serializable + public open class AccountBasedData( + public override val accountKey: MicroBlogKey, + ) : AccountData + + @Serializable + public data class AccountResourceData( + public override val accountKey: MicroBlogKey, + public val resourceId: String, + ) : AccountData +} + +public class AccountTimelineSpec( + override val id: String, + override val title: UiStrings, + override val icon: IconType, + override val serializer: KSerializer, + private val stableKeyFactory: (data: T) -> String, + private val loaderFactory: (service: MicroblogDataSource, data: T) -> RemoteLoader, +) : TimelineSpec { + override fun stableKey(data: T): String = stableKeyFactory(data) + + public fun accountKey(data: TimelineSpec.Data): MicroBlogKey = typedData(data).accountKey + + public fun createLoader( + service: MicroblogDataSource, + data: TimelineSpec.Data, + ): RemoteLoader = loaderFactory(service, typedData(data)) + + @Suppress("UNCHECKED_CAST") + private fun typedData(data: TimelineSpec.Data): T = data as T +} + +public class StandaloneTimelineSpec( + override val id: String, + override val title: UiStrings, + override val icon: IconType, + override val serializer: KSerializer, + private val stableKeyFactory: (data: T) -> String, + private val loaderFactory: (context: TimelineLoaderContext, data: T) -> Flow>, +) : TimelineSpec { + override fun stableKey(data: T): String = stableKeyFactory(data) + + public fun createLoader( + context: TimelineLoaderContext, + data: TimelineSpec.Data, + ): Flow> = loaderFactory(context, typedData(data)) + + @Suppress("UNCHECKED_CAST") + private fun typedData(data: TimelineSpec.Data): T = data as T +} + +public class TimelineLoaderContext( + public val appDatabase: AppDatabase, + public val cacheDatabase: CacheDatabase, +) diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineTabDescriptor.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineTabDescriptor.kt new file mode 100644 index 0000000000..3203127a0a --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineTabDescriptor.kt @@ -0,0 +1,70 @@ +package dev.dimension.flare.data.datasource.microblog.timeline + +import androidx.paging.PagingData +import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.UiText +import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.flow.Flow + +public data class TimelineDisplay( + public val title: UiText, + public val icon: IconType, +) + +public sealed interface TimelineTabDescriptor { + public val id: String + public val display: TimelineDisplay + + public data class Source( + public val ref: TimelineRef, + override val display: TimelineDisplay, + ) : TimelineTabDescriptor { + override val id: String + get() = ref.semanticId + } +} + +public interface TimelineTabProvider { + public val defaultTimelineTabs: ImmutableList + public val builtInTimelineTabs: ImmutableList + public val timelineShortcuts: ImmutableList +} + +public interface PinnableTimelineProvider { + public val pinnableTimelineTabs: List +} + +public data class PinnableTimelineTabSection( + public val title: UiStrings, + public val data: Flow>, +) + +public data class TimelineShortcutDescriptor( + public val title: UiStrings, + public val icon: UiIcon, + public val target: Target, +) { + public object RouteIds { + public const val ALL_LISTS: String = "all_lists" + public const val ALL_DIRECT_MESSAGES: String = "all_direct_messages" + public const val BLUESKY_ALL_FEEDS: String = "bluesky_all_feeds" + public const val MISSKEY_ALL_ANTENNAS: String = "misskey_all_antennas" + public const val MISSKEY_ALL_CHANNELS: String = "misskey_all_channels" + } + + public sealed interface Target { + public data class Timeline( + public val ref: TimelineRef, + public val display: TimelineDisplay, + ) : Target + + public data class Route( + public val id: String, + public val accountKey: MicroBlogKey? = null, + public val parameters: Map = emptyMap(), + ) : Target + } +} diff --git a/social/microblog/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposeStatus.kt b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposeStatus.kt new file mode 100644 index 0000000000..7d571f5fa2 --- /dev/null +++ b/social/microblog/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/compose/ComposeStatus.kt @@ -0,0 +1,22 @@ +package dev.dimension.flare.ui.presenter.compose + +import androidx.compose.runtime.Immutable +import dev.dimension.flare.model.MicroBlogKey + +@Immutable +public sealed class ComposeStatus { + public abstract val statusKey: MicroBlogKey + + public data class Quote( + override val statusKey: MicroBlogKey, + ) : ComposeStatus() + + public open class Reply( + override val statusKey: MicroBlogKey, + ) : ComposeStatus() + + public data class VVOComment( + override val statusKey: MicroBlogKey, + val rootId: String, + ) : Reply(statusKey) +} diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/NotificationHandlerTest.kt b/social/microblog/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/NotificationHandlerTest.kt similarity index 96% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/NotificationHandlerTest.kt rename to social/microblog/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/NotificationHandlerTest.kt index 1c9d365ff5..5c377368b5 100644 --- a/shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/NotificationHandlerTest.kt +++ b/social/microblog/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/NotificationHandlerTest.kt @@ -1,7 +1,6 @@ package dev.dimension.flare.data.datasource.microblog.handler import androidx.paging.LoadState -import dev.dimension.flare.RobolectricTest import dev.dimension.flare.common.CacheState import dev.dimension.flare.data.datasource.microblog.loader.NotificationLoader import dev.dimension.flare.model.MicroBlogKey @@ -16,7 +15,7 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue @OptIn(ExperimentalCoroutinesApi::class) -class NotificationHandlerTest : RobolectricTest() { +class NotificationHandlerTest { @Test fun refreshLoadsBadgeCountIntoCache() = runTest { @@ -87,7 +86,6 @@ class NotificationHandlerTest : RobolectricTest() { loader = loader, ) - // Warm up cache with a successful refresh. val initialValueDeferred = async { handler.notificationBadgeCount.data diff --git a/social/microblog/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineCatalogTest.kt b/social/microblog/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineCatalogTest.kt new file mode 100644 index 0000000000..2f505a6349 --- /dev/null +++ b/social/microblog/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/timeline/TimelineCatalogTest.kt @@ -0,0 +1,62 @@ +package dev.dimension.flare.data.datasource.microblog.timeline + +import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.asType +import kotlinx.serialization.Serializable +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertSame + +class TimelineCatalogTest { + @Test + fun encodeAndDecodeRoundTripTypedData() { + val spec = FakeTimelineSpec(id = "fake.timeline") + val catalog = TimelineCatalog(listOf(spec)) + val ref = spec.ref(FakeData("home")) + + val encoded = catalog.encode(ref) + val decoded = catalog.decode(encoded.specId, encoded.data) + + assertEquals("fake.timeline", encoded.specId) + assertEquals("home", encoded.stableKey) + assertSame(spec, decoded.spec) + assertEquals(FakeData("home"), decoded.data) + } + + @Test + fun duplicateSpecIdsKeepFirstRegistration() { + val first = FakeTimelineSpec(id = "duplicate") + val second = FakeTimelineSpec(id = "duplicate") + val catalog = TimelineCatalog(listOf(first, second)) + + assertSame(first, catalog.requireSpec("duplicate")) + assertEquals(1, catalog.specs.size) + } + + @Test + fun unknownSpecIdFailsClearly() { + val catalog = TimelineCatalog(emptyList()) + + assertFailsWith { + catalog.requireSpec("missing") + } + } + + private class FakeTimelineSpec( + override val id: String, + ) : TimelineSpec { + override val title: UiStrings = UiStrings.Home + override val icon: IconType = UiIcon.Home.asType() + override val serializer = FakeData.serializer() + + override fun stableKey(data: FakeData): String = data.value + } + + @Serializable + private data class FakeData( + val value: String, + ) : TimelineSpec.Data +} diff --git a/social/misskey/build.gradle.kts b/social/misskey/build.gradle.kts new file mode 100644 index 0000000000..1ccc80b5f1 --- /dev/null +++ b/social/misskey/build.gradle.kts @@ -0,0 +1,60 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ksp) + alias(libs.plugins.ktorfit) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.social.misskey" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + ksp( + libs.ktorfit.ksp, + ) + } + + sourceSets { + all { + languageSettings { + optIn("kotlin.uuid.ExperimentalUuidApi") + } + } + + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.core.model) + api(projects.modules.account.api) + api(projects.social.model) + api(projects.foundation.network) + implementation(projects.foundation.filesystem) + api(projects.social.api) + api(projects.social.microblog) + implementation(projects.social.nodeinfo) + implementation(dependencies.platform(libs.koin.bom)) + implementation(libs.koin.core) + implementation(libs.mfm.multiplatform) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + } +} + +ktorfit { + compilerPluginVersion.set("2.3.3") +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/AntennasListPagingSource.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/AntennasListPagingSource.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/AntennasListPagingSource.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/AntennasListPagingSource.kt index 9d0af46d7d..7ed90640bc 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/AntennasListPagingSource.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/AntennasListPagingSource.kt @@ -1,14 +1,14 @@ package dev.dimension.flare.data.datasource.misskey +import dev.dimension.flare.common.tryRun import dev.dimension.flare.data.datasource.microblog.paging.PagingRequest import dev.dimension.flare.data.datasource.microblog.paging.PagingResult import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader import dev.dimension.flare.data.network.misskey.MisskeyService -import dev.dimension.flare.data.repository.tryRun import dev.dimension.flare.ui.model.UiList import dev.dimension.flare.ui.model.mapper.render -internal class AntennasListPagingSource( +public class AntennasListPagingSource( private val service: MisskeyService, ) : RemoteLoader { override suspend fun load( diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/AntennasTimelineRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/AntennasTimelineRemoteMediator.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/AntennasTimelineRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/AntennasTimelineRemoteMediator.kt index 26779526e1..d8edbee0d2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/AntennasTimelineRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/AntennasTimelineRemoteMediator.kt @@ -11,12 +11,12 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class AntennasTimelineRemoteMediator( +public class AntennasTimelineRemoteMediator( private val service: MisskeyService, private val accountKey: MicroBlogKey, private val id: String, ) : CacheableRemoteLoader { - override val pagingKey = "antennas_${id}_$accountKey" + override val pagingKey: String = "antennas_${id}_$accountKey" override suspend fun load( pageSize: Int, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/ChannelTimelineRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/ChannelTimelineRemoteMediator.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/ChannelTimelineRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/ChannelTimelineRemoteMediator.kt index 5adef84dbe..3c2622e56b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/ChannelTimelineRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/ChannelTimelineRemoteMediator.kt @@ -9,12 +9,12 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render -internal class ChannelTimelineRemoteMediator( +public class ChannelTimelineRemoteMediator( private val service: MisskeyService, private val accountKey: MicroBlogKey, private val id: String, ) : CacheableRemoteLoader { - override val pagingKey = "channel_${id}_$accountKey" + override val pagingKey: String = "channel_${id}_$accountKey" override suspend fun load( pageSize: Int, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/DiscoverStatusRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/DiscoverStatusRemoteMediator.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/DiscoverStatusRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/DiscoverStatusRemoteMediator.kt index 450f716905..960c4c125c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/DiscoverStatusRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/DiscoverStatusRemoteMediator.kt @@ -11,7 +11,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class DiscoverStatusRemoteMediator( +public class DiscoverStatusRemoteMediator( private val service: MisskeyService, private val accountKey: MicroBlogKey, ) : CacheableRemoteLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FansPagingSource.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FansPagingSource.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FansPagingSource.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FansPagingSource.kt index ceaeebf85f..1dd5669f89 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FansPagingSource.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FansPagingSource.kt @@ -9,7 +9,7 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render -internal class FansPagingSource( +public class FansPagingSource( private val service: MisskeyService, private val accountKey: MicroBlogKey, private val userKey: MicroBlogKey, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FavouriteTimelineRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FavouriteTimelineRemoteMediator.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FavouriteTimelineRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FavouriteTimelineRemoteMediator.kt index 5559c97e06..c717a41f96 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FavouriteTimelineRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FavouriteTimelineRemoteMediator.kt @@ -11,7 +11,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class FavouriteTimelineRemoteMediator( +public class FavouriteTimelineRemoteMediator( private val accountKey: MicroBlogKey, private val service: MisskeyService, ) : CacheableRemoteLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FollowingPagingSource.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FollowingPagingSource.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FollowingPagingSource.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FollowingPagingSource.kt index b819ca0d17..c8743844c4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FollowingPagingSource.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/FollowingPagingSource.kt @@ -9,7 +9,7 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render -internal class FollowingPagingSource( +public class FollowingPagingSource( private val service: MisskeyService, private val accountKey: MicroBlogKey, private val userKey: MicroBlogKey, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HomeTimelineRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HomeTimelineRemoteMediator.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HomeTimelineRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HomeTimelineRemoteMediator.kt index f7995f7f24..45aa2fce9c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HomeTimelineRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HomeTimelineRemoteMediator.kt @@ -11,11 +11,11 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class HomeTimelineRemoteMediator( +public class HomeTimelineRemoteMediator( private val accountKey: MicroBlogKey, private val service: MisskeyService, ) : CacheableRemoteLoader { - override val pagingKey = "home_$accountKey" + override val pagingKey: String = "home_$accountKey" override suspend fun load( pageSize: Int, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HybridTimelineRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HybridTimelineRemoteMediator.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HybridTimelineRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HybridTimelineRemoteMediator.kt index 1b467a38b7..1e22e629e3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HybridTimelineRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/HybridTimelineRemoteMediator.kt @@ -9,11 +9,11 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render -internal class HybridTimelineRemoteMediator( +public class HybridTimelineRemoteMediator( private val accountKey: MicroBlogKey, private val service: MisskeyService, ) : CacheableRemoteLoader { - override val pagingKey = "hybrid_timeline_$accountKey" + override val pagingKey: String = "hybrid_timeline_$accountKey" override suspend fun load( pageSize: Int, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/ListTimelineRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/ListTimelineRemoteMediator.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/ListTimelineRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/ListTimelineRemoteMediator.kt index 093c894a3a..739db8085f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/ListTimelineRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/ListTimelineRemoteMediator.kt @@ -11,12 +11,12 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class ListTimelineRemoteMediator( +public class ListTimelineRemoteMediator( private val listId: String, private val service: MisskeyService, private val accountKey: MicroBlogKey, ) : CacheableRemoteLoader { - override val pagingKey = "list_${accountKey}_$listId" + override val pagingKey: String = "list_${accountKey}_$listId" override suspend fun load( pageSize: Int, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/LocalTimelineRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/LocalTimelineRemoteMediator.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/LocalTimelineRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/LocalTimelineRemoteMediator.kt index 63a4877e9f..72c15677d7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/LocalTimelineRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/LocalTimelineRemoteMediator.kt @@ -11,11 +11,11 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class LocalTimelineRemoteMediator( +public class LocalTimelineRemoteMediator( private val accountKey: MicroBlogKey, private val service: MisskeyService, ) : CacheableRemoteLoader { - override val pagingKey = "local_$accountKey" + override val pagingKey: String = "local_$accountKey" override suspend fun load( pageSize: Int, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MentionTimelineRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MentionTimelineRemoteMediator.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MentionTimelineRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MentionTimelineRemoteMediator.kt index 6de846c9fe..af103b87d7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MentionTimelineRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MentionTimelineRemoteMediator.kt @@ -11,11 +11,11 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class MentionTimelineRemoteMediator( +public class MentionTimelineRemoteMediator( private val accountKey: MicroBlogKey, private val service: MisskeyService, ) : CacheableRemoteLoader { - override val pagingKey = "mention_$accountKey" + override val pagingKey: String = "mention_$accountKey" override suspend fun load( pageSize: Int, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyChannelLoader.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyChannelLoader.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyChannelLoader.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyChannelLoader.kt index 22aef7b1f0..bc1e68a7e6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyChannelLoader.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyChannelLoader.kt @@ -17,12 +17,12 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -internal class MisskeyChannelLoader( +public class MisskeyChannelLoader( private val service: MisskeyService, private val accountKey: MicroBlogKey, private val source: Source, ) : ListLoader { - enum class Source { + public enum class Source { Followed, MyFavorites, Owned, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyDataSource.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyDataSource.kt similarity index 85% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyDataSource.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyDataSource.kt index baea52961d..3d43d6a294 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyDataSource.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyDataSource.kt @@ -5,22 +5,21 @@ import androidx.paging.Pager import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.map -import dev.dimension.flare.common.FileType +import dev.dimension.flare.data.io.FileType +import dev.dimension.flare.common.tryRun +import dev.dimension.flare.data.account.CredentialProvider +import dev.dimension.flare.data.account.credentialFlow import dev.dimension.flare.data.datasource.microblog.AuthenticatedMicroblogDataSource import dev.dimension.flare.data.datasource.microblog.ComposeConfig import dev.dimension.flare.data.datasource.microblog.ComposeData import dev.dimension.flare.data.datasource.microblog.ComposeType import dev.dimension.flare.data.datasource.microblog.DatabaseUpdater import dev.dimension.flare.data.datasource.microblog.NotificationFilter -import dev.dimension.flare.data.datasource.microblog.PostEvent import dev.dimension.flare.data.datasource.microblog.ProfileTab import dev.dimension.flare.data.datasource.microblog.ReactionDataSource import dev.dimension.flare.data.datasource.microblog.datasource.ListDataSource -import dev.dimension.flare.data.datasource.microblog.datasource.PinnableTimelineTabDataSource -import dev.dimension.flare.data.datasource.microblog.datasource.PinnableTimelineTabSection import dev.dimension.flare.data.datasource.microblog.datasource.PostDataSource import dev.dimension.flare.data.datasource.microblog.datasource.RelationDataSource -import dev.dimension.flare.data.datasource.microblog.datasource.TimelineTabConfigurationDataSource import dev.dimension.flare.data.datasource.microblog.datasource.UserDataSource import dev.dimension.flare.data.datasource.microblog.handler.EmojiHandler import dev.dimension.flare.data.datasource.microblog.handler.ListHandler @@ -37,10 +36,14 @@ import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader import dev.dimension.flare.data.datasource.microblog.paging.notSupported import dev.dimension.flare.data.datasource.microblog.paging.toPagingSource import dev.dimension.flare.data.datasource.microblog.pagingConfig +import dev.dimension.flare.data.datasource.microblog.timeline.PinnableTimelineProvider +import dev.dimension.flare.data.datasource.microblog.timeline.PinnableTimelineTabSection +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineShortcutDescriptor +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineTabProvider +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineShortcutDescriptor +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineTabDescriptor import dev.dimension.flare.data.model.IconType -import dev.dimension.flare.data.model.tab.ShortcutSpec -import dev.dimension.flare.data.model.tab.TimelineSpec -import dev.dimension.flare.data.model.tab.toSlot import dev.dimension.flare.data.network.misskey.api.model.AdminAccountsDeleteRequest import dev.dimension.flare.data.network.misskey.api.model.ChannelsFeaturedRequest import dev.dimension.flare.data.network.misskey.api.model.ChannelsFollowRequest @@ -49,15 +52,14 @@ import dev.dimension.flare.data.network.misskey.api.model.NotesCreateRequest import dev.dimension.flare.data.network.misskey.api.model.NotesCreateRequestPoll import dev.dimension.flare.data.network.misskey.api.model.NotesPollsVoteRequest import dev.dimension.flare.data.network.misskey.api.model.NotesReactionsCreateRequest -import dev.dimension.flare.data.platform.CommonTimelineSpecs -import dev.dimension.flare.data.platform.MisskeyPlatformSpec -import dev.dimension.flare.data.platform.toTimelineTabItemV2 -import dev.dimension.flare.data.repository.AccountRepository -import dev.dimension.flare.data.repository.tryRun +import dev.dimension.flare.data.platform.MisskeyTimelineDataSource +import dev.dimension.flare.data.platform.MisskeyTimelineSpecs +import dev.dimension.flare.data.platform.toTimelineTabDescriptor +import dev.dimension.flare.media.ImageCompressor import dev.dimension.flare.model.AccountType import dev.dimension.flare.model.MicroBlogKey -import dev.dimension.flare.shared.image.ImageCompressor import dev.dimension.flare.ui.model.ClickEvent +import dev.dimension.flare.ui.model.PostEvent import dev.dimension.flare.ui.model.UiAccount import dev.dimension.flare.ui.model.UiHashtag import dev.dimension.flare.ui.model.UiIcon @@ -68,7 +70,6 @@ import dev.dimension.flare.ui.model.UiStrings import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render import dev.dimension.flare.ui.presenter.compose.ComposeStatus -import dev.dimension.flare.ui.route.DeeplinkRoute import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -80,6 +81,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import org.koin.core.component.KoinComponent import org.koin.core.component.inject +import dev.dimension.flare.data.datasource.microblog.timeline.CommonTimelineSpecs as SocialCommonTimelineSpecs @OptIn(ExperimentalPagingApi::class) internal class MisskeyDataSource( @@ -90,19 +92,23 @@ internal class MisskeyDataSource( PostDataSource, KoinComponent, ListDataSource, - PinnableTimelineTabDataSource, - TimelineTabConfigurationDataSource, + MisskeyTimelineDataSource, + MisskeyAntennaDataSource, + MisskeyChannelDataSource, + MisskeyReportDataSource, + PinnableTimelineProvider, + TimelineTabProvider, ReactionDataSource, RelationDataSource, PostEventHandler.Handler { - private val accountRepository: AccountRepository by inject() + private val credentialProvider: CredentialProvider by inject() private val imageCompressor: ImageCompressor by inject() private val service by lazy { dev.dimension.flare.data.network.misskey.MisskeyService( baseUrl = "https://$host/api/", accountKey = accountKey, accessTokenFlow = - accountRepository + credentialProvider .credentialFlow(accountKey) .map { it.accessToken }, ) @@ -150,6 +156,7 @@ internal class MisskeyDataSource( PostEventHandler( accountType = AccountType.Specific(accountKey), handler = this, + optimisticActionMenu = { it.misskeyNextActionMenu() }, ) } @@ -311,25 +318,25 @@ internal class MisskeyDataSource( service, ) - fun localTimelineLoader() = + override fun localTimelineLoader() = LocalTimelineRemoteMediator( accountKey, service, ) - fun hybridTimelineLoader() = + override fun hybridTimelineLoader() = HybridTimelineRemoteMediator( accountKey, service, ) - fun publicTimelineLoader() = + override fun publicTimelineLoader() = PublicTimelineRemoteMediator( accountKey, service, ) - fun featuredChannels(scope: CoroutineScope): Flow> = + override fun featuredChannels(scope: CoroutineScope): Flow> = Pager( config = pagingConfig, ) { @@ -500,7 +507,7 @@ internal class MisskeyDataSource( // progress(ComposeProgress(maxProgress, maxProgress)) } - suspend fun report( + override suspend fun report( userKey: MicroBlogKey, statusKey: MicroBlogKey?, comment: String, @@ -647,7 +654,7 @@ internal class MisskeyDataSource( ProfileTab.Media, ).toPersistentList() - fun favouriteTimelineLoader() = + override fun favouriteTimelineLoader() = FavouriteTimelineRemoteMediator( service = service, accountKey = accountKey, @@ -691,109 +698,92 @@ internal class MisskeyDataSource( title = UiStrings.List, data = listHandler.data.map { paging -> - paging.map { it.toTimelineTabItemV2(accountKey) } + paging.map { it.toTimelineTabDescriptor(accountKey) } }, ), PinnableTimelineTabSection( title = UiStrings.Antenna, data = antennasList().map { paging -> - paging.map { it.toTimelineTabItemV2(accountKey) } + paging.map { it.toTimelineTabDescriptor(accountKey) } }, ), PinnableTimelineTabSection( title = UiStrings.Channel, data = channelHandler.data.map { paging -> - paging.map { it.toTimelineTabItemV2(accountKey) } + paging.map { it.toTimelineTabDescriptor(accountKey) } }, ), ) } - override val defaultTabs by lazy { + override val defaultTimelineTabs by lazy { persistentListOf( - CommonTimelineSpecs.home - .target( + SocialCommonTimelineSpecs.home + .toTimelineTabDescriptor( data = TimelineSpec.AccountBasedData(accountKey), icon = IconType.FavIcon(accountKey.host), - ).toSlot(), + ), ) } override val builtInTimelineTabs by lazy { persistentListOf( - CommonTimelineSpecs.home.tabItem( + SocialCommonTimelineSpecs.home.toTimelineTabDescriptor( data = TimelineSpec.AccountBasedData(accountKey), icon = IconType.FavIcon(accountKey.host), ), - CommonTimelineSpecs.discover.tabItem( + SocialCommonTimelineSpecs.discover.toTimelineTabDescriptor( data = TimelineSpec.AccountBasedData(accountKey), icon = IconType.FavIcon(accountKey.host), ), - MisskeyPlatformSpec.favouriteTimelineSpec.tabItem(TimelineSpec.AccountBasedData(accountKey)), - MisskeyPlatformSpec.hybridTimelineSpec.tabItem(TimelineSpec.AccountBasedData(accountKey)), - MisskeyPlatformSpec.localTimelineSpec.tabItem(TimelineSpec.AccountBasedData(accountKey)), - MisskeyPlatformSpec.globalTimelineSpec.tabItem(TimelineSpec.AccountBasedData(accountKey)), + MisskeyTimelineSpecs.favourite.toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)), + MisskeyTimelineSpecs.hybrid.toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)), + MisskeyTimelineSpecs.local.toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)), + MisskeyTimelineSpecs.global.toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)), ) } - override val shortcuts by lazy { + override val timelineShortcuts by lazy { persistentListOf( - ShortcutSpec( - title = UiStrings.Favourite, - icon = UiIcon.Favourite, - target = - ShortcutSpec.Target.Timeline( - MisskeyPlatformSpec.favouriteTimelineSpec.target(TimelineSpec.AccountBasedData(accountKey)), - ), - ), - ShortcutSpec( + MisskeyTimelineSpecs.favourite + .toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)) + .toTimelineShortcutDescriptor(UiStrings.Favourite, UiIcon.Favourite), + TimelineShortcutDescriptor( title = UiStrings.List, icon = UiIcon.List, target = - ShortcutSpec.Target.Route( - DeeplinkRoute.AllLists(accountKey), - ), - ), - ShortcutSpec( - title = UiStrings.Social, - icon = UiIcon.Featured, - target = - ShortcutSpec.Target.Timeline( - MisskeyPlatformSpec.hybridTimelineSpec.target(TimelineSpec.AccountBasedData(accountKey)), - ), - ), - ShortcutSpec( - title = UiStrings.MastodonLocal, - icon = UiIcon.Local, - target = - ShortcutSpec.Target.Timeline( - MisskeyPlatformSpec.localTimelineSpec.target(TimelineSpec.AccountBasedData(accountKey)), - ), - ), - ShortcutSpec( - title = UiStrings.MastodonPublic, - icon = UiIcon.World, - target = - ShortcutSpec.Target.Timeline( - MisskeyPlatformSpec.globalTimelineSpec.target(TimelineSpec.AccountBasedData(accountKey)), + TimelineShortcutDescriptor.Target.Route( + id = TimelineShortcutDescriptor.RouteIds.ALL_LISTS, + accountKey = accountKey, ), ), - ShortcutSpec( + MisskeyTimelineSpecs.hybrid + .toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)) + .toTimelineShortcutDescriptor(UiStrings.Social, UiIcon.Featured), + MisskeyTimelineSpecs.local + .toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)) + .toTimelineShortcutDescriptor(UiStrings.MastodonLocal, UiIcon.Local), + MisskeyTimelineSpecs.global + .toTimelineTabDescriptor(TimelineSpec.AccountBasedData(accountKey)) + .toTimelineShortcutDescriptor(UiStrings.MastodonPublic, UiIcon.World), + TimelineShortcutDescriptor( title = UiStrings.Antenna, icon = UiIcon.Rss, target = - ShortcutSpec.Target.Route( - DeeplinkRoute.Misskey.AllAntennas(accountKey), + TimelineShortcutDescriptor.Target.Route( + id = TimelineShortcutDescriptor.RouteIds.MISSKEY_ALL_ANTENNAS, + accountKey = accountKey, ), ), - ShortcutSpec( + TimelineShortcutDescriptor( title = UiStrings.Channel, icon = UiIcon.Channel, target = - ShortcutSpec.Target.Route( - DeeplinkRoute.Misskey.AllChannels(accountKey), + TimelineShortcutDescriptor.Target.Route( + id = TimelineShortcutDescriptor.RouteIds.MISSKEY_ALL_CHANNELS, + accountKey = accountKey, ), ), ) @@ -807,7 +797,7 @@ internal class MisskeyDataSource( ) } - val channelHandler: ListHandler by lazy { + override val channelHandler: ListHandler by lazy { ListHandler( pagingKey = "followedChannels_$accountKey", accountKey = accountKey, @@ -820,7 +810,7 @@ internal class MisskeyDataSource( ) } - val myFavoriteChannelHandler: ListHandler by lazy { + override val myFavoriteChannelHandler: ListHandler by lazy { ListHandler( pagingKey = "myFavoriteChannels_$accountKey", accountKey = accountKey, @@ -833,7 +823,7 @@ internal class MisskeyDataSource( ) } - val ownedChannelHandler: ListHandler by lazy { + override val ownedChannelHandler: ListHandler by lazy { ListHandler( pagingKey = "ownedChannels_$accountKey", accountKey = accountKey, @@ -846,7 +836,7 @@ internal class MisskeyDataSource( ) } - suspend fun followChannel(data: UiList) { + override suspend fun followChannel(data: UiList) { tryRun { service.channelsFollow( ChannelsFollowRequest(channelId = data.id), @@ -855,7 +845,7 @@ internal class MisskeyDataSource( } } - suspend fun unfollowChannel(data: UiList) { + override suspend fun unfollowChannel(data: UiList) { tryRun { service.channelsUnfollow( ChannelsFollowRequest(channelId = data.id), @@ -864,7 +854,7 @@ internal class MisskeyDataSource( } } - suspend fun favoriteChannel(data: UiList) { + override suspend fun favoriteChannel(data: UiList) { tryRun { service.channelsFavorite( ChannelsFollowRequest(channelId = data.id), @@ -873,7 +863,7 @@ internal class MisskeyDataSource( } } - suspend fun unfavoriteChannel(data: UiList) { + override suspend fun unfavoriteChannel(data: UiList) { tryRun { service.channelsUnfavorite( ChannelsFollowRequest(channelId = data.id), @@ -882,7 +872,7 @@ internal class MisskeyDataSource( } } - fun antennasList(): Flow> = + override fun antennasList(): Flow> = Pager( config = pagingConfig, ) { @@ -891,14 +881,14 @@ internal class MisskeyDataSource( ).toPagingSource() }.flow - fun antennasTimelineLoader(id: String) = + override fun antennasTimelineLoader(id: String) = AntennasTimelineRemoteMediator( service = service, accountKey = accountKey, id = id, ) - fun channelTimelineLoader(id: String) = + override fun channelTimelineLoader(id: String) = ChannelTimelineRemoteMediator( service = service, accountKey = accountKey, diff --git a/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyDataSourceCapabilities.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyDataSourceCapabilities.kt new file mode 100644 index 0000000000..f7bd95cc3c --- /dev/null +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyDataSourceCapabilities.kt @@ -0,0 +1,39 @@ +package dev.dimension.flare.data.datasource.misskey + +import androidx.paging.PagingData +import dev.dimension.flare.data.datasource.microblog.handler.ListHandler +import dev.dimension.flare.data.platform.MisskeyTimelineDataSource +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow + +public interface MisskeyAntennaDataSource : MisskeyTimelineDataSource { + public fun antennasList(): Flow> +} + +public interface MisskeyChannelDataSource : MisskeyTimelineDataSource { + public val channelHandler: ListHandler + + public val myFavoriteChannelHandler: ListHandler + + public val ownedChannelHandler: ListHandler + + public fun featuredChannels(scope: CoroutineScope): Flow> + + public suspend fun followChannel(data: UiList) + + public suspend fun unfollowChannel(data: UiList) + + public suspend fun favoriteChannel(data: UiList) + + public suspend fun unfavoriteChannel(data: UiList) +} + +public interface MisskeyReportDataSource { + public suspend fun report( + userKey: MicroBlogKey, + statusKey: MicroBlogKey?, + comment: String, + ) +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyListLoader.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyListLoader.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyListLoader.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyListLoader.kt index a0c856eebd..f58bc8a209 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyListLoader.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyListLoader.kt @@ -18,7 +18,7 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -internal class MisskeyListLoader( +public class MisskeyListLoader( private val service: MisskeyService, private val accountKey: MicroBlogKey, ) : ListLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyListMemberLoader.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyListMemberLoader.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyListMemberLoader.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyListMemberLoader.kt index 4ffc8fad5d..09f72bfde2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyListMemberLoader.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyListMemberLoader.kt @@ -14,7 +14,7 @@ import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render import kotlinx.collections.immutable.toImmutableList -internal class MisskeyListMemberLoader( +public class MisskeyListMemberLoader( private val service: MisskeyService, private val accountKey: MicroBlogKey, ) : ListMemberLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyLoader.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyLoader.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyLoader.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyLoader.kt index b8b6122439..5b8f80e39f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyLoader.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyLoader.kt @@ -23,8 +23,8 @@ import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap -internal class MisskeyLoader( - val accountKey: MicroBlogKey, +public class MisskeyLoader( + public val accountKey: MicroBlogKey, private val service: MisskeyService, ) : UserLoader, PostLoader, diff --git a/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyPostEventActionMenu.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyPostEventActionMenu.kt new file mode 100644 index 0000000000..24addf47d1 --- /dev/null +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/MisskeyPostEventActionMenu.kt @@ -0,0 +1,40 @@ +package dev.dimension.flare.data.datasource.misskey + +import dev.dimension.flare.data.datasource.microblog.ActionMenu +import dev.dimension.flare.ui.model.PostEvent +import dev.dimension.flare.ui.model.mapper.misskeyFavourite +import dev.dimension.flare.ui.model.mapper.misskeyReact +import dev.dimension.flare.ui.model.mapper.misskeyRenote + +internal fun PostEvent.misskeyNextActionMenu(): ActionMenu.Item? = + when (this) { + is PostEvent.Misskey.React -> { + ActionMenu.misskeyReact( + postKey = postKey, + hasReacted = !hasReacted, + reaction = reaction, + count = (count + if (!hasReacted) 1 else -1).coerceAtLeast(0), + accountKey = accountKey, + ) + } + + is PostEvent.Misskey.Renote -> { + ActionMenu.misskeyRenote( + postKey = postKey, + count = count + 1, + accountKey = accountKey, + ) + } + + is PostEvent.Misskey.Favourite -> { + ActionMenu.misskeyFavourite( + postKey = postKey, + favourited = !favourited, + accountKey = accountKey, + ) + } + + else -> { + null + } + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/NotificationRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/NotificationRemoteMediator.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/NotificationRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/NotificationRemoteMediator.kt index c000f02501..fac6294ff1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/NotificationRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/NotificationRemoteMediator.kt @@ -11,11 +11,11 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class NotificationRemoteMediator( +public class NotificationRemoteMediator( private val accountKey: MicroBlogKey, private val service: MisskeyService, ) : CacheableRemoteLoader { - override val pagingKey = "notification_$accountKey" + override val pagingKey: String = "notification_$accountKey" override suspend fun load( pageSize: Int, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/PublicTimelineRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/PublicTimelineRemoteMediator.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/PublicTimelineRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/PublicTimelineRemoteMediator.kt index a45b44e872..680d70f1f6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/PublicTimelineRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/PublicTimelineRemoteMediator.kt @@ -11,11 +11,11 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class PublicTimelineRemoteMediator( +public class PublicTimelineRemoteMediator( private val accountKey: MicroBlogKey, private val service: MisskeyService, ) : CacheableRemoteLoader { - override val pagingKey = "public_$accountKey" + override val pagingKey: String = "public_$accountKey" override suspend fun load( pageSize: Int, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchStatusRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchStatusRemoteMediator.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchStatusRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchStatusRemoteMediator.kt index 85686a7f3f..ba6634462c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchStatusRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchStatusRemoteMediator.kt @@ -11,7 +11,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class SearchStatusRemoteMediator( +public class SearchStatusRemoteMediator( private val service: MisskeyService, private val accountKey: MicroBlogKey, private val query: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchUserPagingSource.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchUserPagingSource.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchUserPagingSource.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchUserPagingSource.kt index ffb2fe7fee..93c41e3952 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchUserPagingSource.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/SearchUserPagingSource.kt @@ -9,7 +9,7 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render -internal class SearchUserPagingSource( +public class SearchUserPagingSource( private val service: MisskeyService, private val accountKey: MicroBlogKey, private val query: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/StatusDetailRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/StatusDetailRemoteMediator.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/StatusDetailRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/StatusDetailRemoteMediator.kt index 6344372e01..61007d0750 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/StatusDetailRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/StatusDetailRemoteMediator.kt @@ -10,16 +10,14 @@ import dev.dimension.flare.data.network.misskey.api.model.NotesChildrenRequest import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render -import org.koin.core.component.KoinComponent @OptIn(ExperimentalPagingApi::class) -internal class StatusDetailRemoteMediator( +public class StatusDetailRemoteMediator( private val statusKey: MicroBlogKey, private val accountKey: MicroBlogKey, private val service: MisskeyService, private val statusOnly: Boolean, -) : CacheableRemoteLoader, - KoinComponent { +) : CacheableRemoteLoader { override val pagingKey: String = buildString { append("status_detail_") diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/TrendHashtagPagingSource.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/TrendHashtagPagingSource.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/TrendHashtagPagingSource.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/TrendHashtagPagingSource.kt index 73fd00ce47..6ce9d76e28 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/TrendHashtagPagingSource.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/TrendHashtagPagingSource.kt @@ -6,7 +6,7 @@ import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader import dev.dimension.flare.data.network.misskey.MisskeyService import dev.dimension.flare.ui.model.UiHashtag -internal class TrendHashtagPagingSource( +public class TrendHashtagPagingSource( private val service: MisskeyService, ) : RemoteLoader { override suspend fun load( diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/TrendsUserPagingSource.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/TrendsUserPagingSource.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/TrendsUserPagingSource.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/TrendsUserPagingSource.kt index 10deabd2c9..bb19832a5e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/TrendsUserPagingSource.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/TrendsUserPagingSource.kt @@ -9,7 +9,7 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.UiProfile import dev.dimension.flare.ui.model.mapper.render -internal class TrendsUserPagingSource( +public class TrendsUserPagingSource( private val service: MisskeyService, private val accountKey: MicroBlogKey, ) : RemoteLoader { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/UserTimelineRemoteMediator.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/UserTimelineRemoteMediator.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/UserTimelineRemoteMediator.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/UserTimelineRemoteMediator.kt index e91f6a4241..86f34c798a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/UserTimelineRemoteMediator.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/datasource/misskey/UserTimelineRemoteMediator.kt @@ -12,7 +12,7 @@ import dev.dimension.flare.ui.model.UiTimelineV2 import dev.dimension.flare.ui.model.mapper.render @OptIn(ExperimentalPagingApi::class) -internal class UserTimelineRemoteMediator( +public class UserTimelineRemoteMediator( private val accountKey: MicroBlogKey, private val service: MisskeyService, private val userKey: MicroBlogKey, @@ -20,7 +20,7 @@ internal class UserTimelineRemoteMediator( private val withReplies: Boolean = false, private val withPinned: Boolean = false, ) : CacheableRemoteLoader { - var pinnedIds = emptyList() + private var pinnedIds: List = emptyList() override val pagingKey: String get() = diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/JoinMisskeyService.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/JoinMisskeyService.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/JoinMisskeyService.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/JoinMisskeyService.kt index c2ef6cda82..b4a29b748e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/JoinMisskeyService.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/JoinMisskeyService.kt @@ -4,5 +4,5 @@ import dev.dimension.flare.data.network.ktorfit import dev.dimension.flare.data.network.misskey.api.MisskeyInstanceAppApi import dev.dimension.flare.data.network.misskey.api.createMisskeyInstanceAppApi -internal object JoinMisskeyService : +public object JoinMisskeyService : MisskeyInstanceAppApi by ktorfit("https://instanceapp.misskey.page/").createMisskeyInstanceAppApi() diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyAuthorizationPlugin.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyAuthorizationPlugin.kt similarity index 86% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyAuthorizationPlugin.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyAuthorizationPlugin.kt index 5e8b9d6a8f..6ae9c70d1b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyAuthorizationPlugin.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyAuthorizationPlugin.kt @@ -16,20 +16,20 @@ import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.jsonObject -internal class MisskeyAuthorizationPlugin( +public class MisskeyAuthorizationPlugin( private val accessTokenFlow: Flow?, ) { @KtorDsl - class Config( + public class Config( internal var accessTokenFlow: Flow? = null, ) @KtorDsl - companion object Plugin : HttpClientPlugin { - override val key: AttributeKey + public companion object Plugin : HttpClientPlugin { + public override val key: AttributeKey get() = AttributeKey("MisskeyAuthorizationPlugin") - override fun install( + public override fun install( plugin: MisskeyAuthorizationPlugin, scope: HttpClient, ) { @@ -60,7 +60,7 @@ internal class MisskeyAuthorizationPlugin( } } - override fun prepare(block: Config.() -> Unit): MisskeyAuthorizationPlugin { + public override fun prepare(block: Config.() -> Unit): MisskeyAuthorizationPlugin { val config = Config().apply(block) return MisskeyAuthorizationPlugin(config.accessTokenFlow) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyOauthService.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyOauthService.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyOauthService.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyOauthService.kt index 9402af7dd6..14bd65ac2a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyOauthService.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyOauthService.kt @@ -7,6 +7,7 @@ import dev.dimension.flare.data.network.misskey.api.model.response.MiAuthCheckRe import io.ktor.http.URLBuilder import io.ktor.http.URLProtocol import io.ktor.http.appendPathSegments +import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid private val defaultPermission = @@ -40,7 +41,8 @@ private val defaultPermission = "write:channels", ) -internal class MisskeyOauthService( +@OptIn(ExperimentalUuidApi::class) +public class MisskeyOauthService( private val host: String, private val name: String? = null, private val icon: String? = null, @@ -48,7 +50,7 @@ internal class MisskeyOauthService( private val permission: List = defaultPermission, private val session: String = Uuid.random().toString(), ) : AuthResources by ktorfit("https://$host/").createAuthResources() { - fun getAuthorizeUrl(): String { + public fun getAuthorizeUrl(): String { val url = URLBuilder().apply { protocol = URLProtocol.HTTPS @@ -68,5 +70,5 @@ internal class MisskeyOauthService( return url.buildString() } - suspend fun check(): MiAuthCheckResponse = check(session) + public suspend fun check(): MiAuthCheckResponse = check(session) } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyPlatformDetector.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyPlatformDetector.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyPlatformDetector.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyPlatformDetector.kt index c458eccf2e..14dedb960d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyPlatformDetector.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyPlatformDetector.kt @@ -1,13 +1,13 @@ package dev.dimension.flare.data.network.misskey +import dev.dimension.flare.common.tryRun import dev.dimension.flare.data.network.misskey.api.model.MetaRequest import dev.dimension.flare.data.network.nodeinfo.NodeData -import dev.dimension.flare.data.network.nodeinfo.NodeInfoService import dev.dimension.flare.data.network.nodeinfo.PlatformDetector -import dev.dimension.flare.data.repository.tryRun +import dev.dimension.flare.data.nodeinfo.NodeInfoService import dev.dimension.flare.model.PlatformType -internal data object MisskeyPlatformDetector : PlatformDetector { +public data object MisskeyPlatformDetector : PlatformDetector { override val priority: Int = 70 override suspend fun detect(host: String): NodeData? { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyService.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyService.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyService.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyService.kt index 8788a238c0..0d00d81f10 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyService.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/MisskeyService.kt @@ -26,9 +26,9 @@ import dev.dimension.flare.data.network.misskey.api.createReactionsApi import dev.dimension.flare.data.network.misskey.api.createUsersApi import dev.dimension.flare.data.network.misskey.api.model.DriveFile import dev.dimension.flare.data.network.misskey.api.model.MisskeyException -import dev.dimension.flare.data.repository.RequireReLoginException import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.model.RequireReLoginException import io.ktor.client.plugins.DefaultRequest import io.ktor.client.plugins.HttpResponseValidator import io.ktor.client.request.forms.MultiPartFormDataContent @@ -82,7 +82,7 @@ private fun config( }, ) -internal class MisskeyService( +public class MisskeyService( baseUrl: String, accountKey: MicroBlogKey? = null, private val accessTokenFlow: Flow? = null, @@ -97,7 +97,7 @@ internal class MisskeyService( ListsApi by config(baseUrl, accountKey, accessTokenFlow).createListsApi(), AntennasApi by config(baseUrl, accountKey, accessTokenFlow).createAntennasApi(), ChannelsApi by config(baseUrl, accountKey, accessTokenFlow).createChannelsApi() { - suspend fun upload( + public suspend fun upload( data: ByteArray, name: String, sensitive: Boolean = false, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AccountApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AccountApi.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AccountApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AccountApi.kt index 4b099392d2..5691c6d4c3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AccountApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AccountApi.kt @@ -38,7 +38,7 @@ import dev.dimension.flare.data.network.misskey.api.model.SwUpdateRegistrationRe import dev.dimension.flare.data.network.misskey.api.model.UserDetailedNotMe import dev.dimension.flare.data.network.misskey.api.model.UsersUpdateMemoRequest -internal interface AccountApi { +public interface AccountApi { /** * blocking/create * No description provided. **Credential required**: *Yes* / **Permission**: *write:blocks* @@ -54,7 +54,7 @@ internal interface AccountApi { * @param adminAccountsDeleteRequest * @return [UserDetailedNotMe] */ @POST("blocking/create") - suspend fun blockingCreate( + public suspend fun blockingCreate( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): UserDetailedNotMe @@ -73,7 +73,7 @@ internal interface AccountApi { * @param adminAccountsDeleteRequest * @return [UserDetailedNotMe] */ @POST("blocking/delete") - suspend fun blockingDelete( + public suspend fun blockingDelete( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): UserDetailedNotMe @@ -91,7 +91,7 @@ internal interface AccountApi { * @param blockingListRequest * @return [kotlin.collections.List] */ @POST("blocking/list") - suspend fun blockingList( + public suspend fun blockingList( @Body blockingListRequest: BlockingListRequest, ): kotlin.collections.List @@ -110,7 +110,7 @@ internal interface AccountApi { * @param clipsAddNoteRequest * @return [Unit] */ @POST("clips/add-note") - suspend fun clipsAddNote( + public suspend fun clipsAddNote( @Body clipsAddNoteRequest: ClipsAddNoteRequest, ): Unit @@ -128,7 +128,7 @@ internal interface AccountApi { * @param body * @return [kotlin.collections.List] */ @POST("clips/my-favorites") - suspend fun clipsMyFavorites( + public suspend fun clipsMyFavorites( @Body body: kotlin.Any, ): kotlin.collections.List @@ -146,7 +146,7 @@ internal interface AccountApi { * @param clipsNotesRequest * @return [kotlin.collections.List] */ @POST("clips/notes") - suspend fun clipsNotes( + public suspend fun clipsNotes( @Body clipsNotesRequest: ClipsNotesRequest, ): kotlin.collections.List @@ -164,7 +164,7 @@ internal interface AccountApi { * @param clipsAddNoteRequest * @return [Unit] */ @POST("clips/remove-note") - suspend fun clipsRemoveNote( + public suspend fun clipsRemoveNote( @Body clipsAddNoteRequest: ClipsAddNoteRequest, ): Unit @@ -182,7 +182,7 @@ internal interface AccountApi { * @param adminAdListRequest * @return [kotlin.collections.List] */ @POST("flash/my") - suspend fun flashMy( + public suspend fun flashMy( @Body adminAdListRequest: AdminAdListRequest, ): kotlin.collections.List @@ -200,7 +200,7 @@ internal interface AccountApi { * @param adminAdListRequest * @return [kotlin.collections.List] */ @POST("flash/my-likes") - suspend fun flashMyLikes( + public suspend fun flashMyLikes( @Body adminAdListRequest: AdminAdListRequest, ): kotlin.collections.List @@ -218,7 +218,7 @@ internal interface AccountApi { * @param body * @return [MeDetailed] */ @POST("i") - suspend fun i( + public suspend fun i( @Body body: kotlin.Any, ): MeDetailed @@ -236,7 +236,7 @@ internal interface AccountApi { * @param adminAdListRequest * @return [kotlin.collections.List] */ @POST("i/favorites") - suspend fun iFavorites( + public suspend fun iFavorites( @Body adminAdListRequest: AdminAdListRequest, ): kotlin.collections.List @@ -254,7 +254,7 @@ internal interface AccountApi { * @param adminAdListRequest * @return [kotlin.collections.List] */ @POST("i/gallery/likes") - suspend fun iGalleryLikes( + public suspend fun iGalleryLikes( @Body adminAdListRequest: AdminAdListRequest, ): kotlin.collections.List @@ -272,7 +272,7 @@ internal interface AccountApi { * @param adminAdListRequest * @return [kotlin.collections.List] */ @POST("i/gallery/posts") - suspend fun iGalleryPosts( + public suspend fun iGalleryPosts( @Body adminAdListRequest: AdminAdListRequest, ): kotlin.collections.List @@ -290,7 +290,7 @@ internal interface AccountApi { * @param body * @return [IGetWordMutedNotesCount200Response] */ @POST("i/get-word-muted-notes-count") - suspend fun iGetWordMutedNotesCount( + public suspend fun iGetWordMutedNotesCount( @Body body: kotlin.Any, ): IGetWordMutedNotesCount200Response @@ -309,7 +309,7 @@ internal interface AccountApi { * @param inotificationsRequest * @return [kotlin.collections.List] */ @POST("i/notifications") - suspend fun iNotifications( + public suspend fun iNotifications( @Body inotificationsRequest: INotificationsRequest, ): kotlin.collections.List @@ -327,7 +327,7 @@ internal interface AccountApi { * @param adminAdListRequest * @return [kotlin.collections.List] */ @POST("i/page-likes") - suspend fun iPageLikes( + public suspend fun iPageLikes( @Body adminAdListRequest: AdminAdListRequest, ): kotlin.collections.List @@ -345,7 +345,7 @@ internal interface AccountApi { * @param adminAdListRequest * @return [kotlin.collections.List] */ @POST("i/pages") - suspend fun iPages( + public suspend fun iPages( @Body adminAdListRequest: AdminAdListRequest, ): kotlin.collections.List @@ -363,7 +363,7 @@ internal interface AccountApi { * @param ipinRequest * @return [MeDetailed] */ @POST("i/pin") - suspend fun iPin( + public suspend fun iPin( @Body ipinRequest: IPinRequest, ): MeDetailed @@ -381,7 +381,7 @@ internal interface AccountApi { * @param body * @return [Unit] */ @POST("i/read-all-unread-notes") - suspend fun iReadAllUnreadNotes( + public suspend fun iReadAllUnreadNotes( @Body body: kotlin.Any, ): Unit @@ -399,7 +399,7 @@ internal interface AccountApi { * @param ireadAnnouncementRequest * @return [Unit] */ @POST("i/read-announcement") - suspend fun iReadAnnouncement( + public suspend fun iReadAnnouncement( @Body ireadAnnouncementRequest: IReadAnnouncementRequest, ): Unit @@ -417,7 +417,7 @@ internal interface AccountApi { * @param ipinRequest * @return [MeDetailed] */ @POST("i/unpin") - suspend fun iUnpin( + public suspend fun iUnpin( @Body ipinRequest: IPinRequest, ): MeDetailed @@ -435,7 +435,7 @@ internal interface AccountApi { * @param iupdateRequest * @return [MeDetailed] */ @POST("i/update") - suspend fun iUpdate( + public suspend fun iUpdate( @Body iupdateRequest: IUpdateRequest, ): MeDetailed @@ -454,7 +454,7 @@ internal interface AccountApi { * @param muteCreateRequest * @return [Unit] */ @POST("mute/create") - suspend fun muteCreate( + public suspend fun muteCreate( @Body muteCreateRequest: MuteCreateRequest, ): Unit @@ -472,7 +472,7 @@ internal interface AccountApi { * @param adminAccountsDeleteRequest * @return [Unit] */ @POST("mute/delete") - suspend fun muteDelete( + public suspend fun muteDelete( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): Unit @@ -490,7 +490,7 @@ internal interface AccountApi { * @param blockingListRequest * @return [kotlin.collections.List] */ @POST("mute/list") - suspend fun muteList( + public suspend fun muteList( @Body blockingListRequest: BlockingListRequest, ): kotlin.collections.List @@ -508,7 +508,7 @@ internal interface AccountApi { * @param myAppsRequest * @return [kotlin.collections.List] */ @POST("my/apps") - suspend fun myApps( + public suspend fun myApps( @Body myAppsRequest: MyAppsRequest, ): kotlin.collections.List @@ -527,7 +527,7 @@ internal interface AccountApi { * @param adminAccountsDeleteRequest * @return [Unit] */ @POST("renote-mute/create") - suspend fun renoteMuteCreate( + public suspend fun renoteMuteCreate( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): Unit @@ -545,7 +545,7 @@ internal interface AccountApi { * @param adminAccountsDeleteRequest * @return [Unit] */ @POST("renote-mute/delete") - suspend fun renoteMuteDelete( + public suspend fun renoteMuteDelete( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): Unit @@ -563,7 +563,7 @@ internal interface AccountApi { * @param blockingListRequest * @return [kotlin.collections.List] */ @POST("renote-mute/list") - suspend fun renoteMuteList( + public suspend fun renoteMuteList( @Body blockingListRequest: BlockingListRequest, ): kotlin.collections.List @@ -581,7 +581,7 @@ internal interface AccountApi { * @param swRegisterRequest * @return [SwRegister200Response] */ @POST("sw/register") - suspend fun swRegister( + public suspend fun swRegister( @Body swRegisterRequest: SwRegisterRequest, ): SwRegister200Response @@ -599,7 +599,7 @@ internal interface AccountApi { * @param endpointRequest * @return [SwShowRegistration200Response] */ @POST("sw/show-registration") - suspend fun swShowRegistration( + public suspend fun swShowRegistration( @Body endpointRequest: EndpointRequest, ): SwShowRegistration200Response @@ -617,7 +617,7 @@ internal interface AccountApi { * @param endpointRequest * @return [Unit] */ @POST("sw/unregister") - suspend fun swUnregister( + public suspend fun swUnregister( @Body endpointRequest: EndpointRequest, ): Unit @@ -635,7 +635,7 @@ internal interface AccountApi { * @param swUpdateRegistrationRequest * @return [SwUpdateRegistration200Response] */ @POST("sw/update-registration") - suspend fun swUpdateRegistration( + public suspend fun swUpdateRegistration( @Body swUpdateRegistrationRequest: SwUpdateRegistrationRequest, ): SwUpdateRegistration200Response @@ -653,7 +653,7 @@ internal interface AccountApi { * @param usersUpdateMemoRequest * @return [Unit] */ @POST("users/update-memo") - suspend fun usersUpdateMemo( + public suspend fun usersUpdateMemo( @Body usersUpdateMemoRequest: UsersUpdateMemoRequest, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AdminApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AdminApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AdminApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AdminApi.kt index cf523417f1..56764ea1eb 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AdminApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AdminApi.kt @@ -62,7 +62,7 @@ import dev.dimension.flare.data.network.misskey.api.model.DriveFile import dev.dimension.flare.data.network.misskey.api.model.User import dev.dimension.flare.data.network.misskey.api.model.UserDetailed -internal interface AdminApi { +public interface AdminApi { /** * admin/abuse-report-resolver/create * No description provided. **Credential required**: *Yes* @@ -77,7 +77,7 @@ internal interface AdminApi { * @param adminAbuseReportResolverCreateRequest * @return [AdminAbuseReportResolverCreate200Response] */ @POST("admin/abuse-report-resolver/create") - suspend fun adminAbuseReportResolverCreate( + public suspend fun adminAbuseReportResolverCreate( @Body adminAbuseReportResolverCreateRequest: AdminAbuseReportResolverCreateRequest, ): AdminAbuseReportResolverCreate200Response @@ -95,7 +95,7 @@ internal interface AdminApi { * @param adminAbuseUserReportsRequest * @return [kotlin.collections.List] */ @POST("admin/abuse-user-reports") - suspend fun adminAbuseUserReports( + public suspend fun adminAbuseUserReports( @Body adminAbuseUserReportsRequest: AdminAbuseUserReportsRequest, ): kotlin.collections.List @@ -113,7 +113,7 @@ internal interface AdminApi { * @param adminAccountsCreateRequest * @return [User] */ @POST("admin/accounts/create") - suspend fun adminAccountsCreate( + public suspend fun adminAccountsCreate( @Body adminAccountsCreateRequest: AdminAccountsCreateRequest, ): User @@ -131,7 +131,7 @@ internal interface AdminApi { * @param adminAccountsDeleteRequest * @return [Unit] */ @POST("admin/accounts/delete") - suspend fun adminAccountsDelete( + public suspend fun adminAccountsDelete( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): Unit @@ -149,7 +149,7 @@ internal interface AdminApi { * @param adminAdCreateRequest * @return [Unit] */ @POST("admin/ad/create") - suspend fun adminAdCreate( + public suspend fun adminAdCreate( @Body adminAdCreateRequest: AdminAdCreateRequest, ): Unit @@ -167,7 +167,7 @@ internal interface AdminApi { * @param adminAdDeleteRequest * @return [Unit] */ @POST("admin/ad/delete") - suspend fun adminAdDelete( + public suspend fun adminAdDelete( @Body adminAdDeleteRequest: AdminAdDeleteRequest, ): Unit @@ -185,7 +185,7 @@ internal interface AdminApi { * @param adminAdListRequest * @return [Unit] */ @POST("admin/ad/list") - suspend fun adminAdList( + public suspend fun adminAdList( @Body adminAdListRequest: AdminAdListRequest, ): Unit @@ -203,7 +203,7 @@ internal interface AdminApi { * @param adminAdUpdateRequest * @return [Unit] */ @POST("admin/ad/update") - suspend fun adminAdUpdate( + public suspend fun adminAdUpdate( @Body adminAdUpdateRequest: AdminAdUpdateRequest, ): Unit @@ -221,7 +221,7 @@ internal interface AdminApi { * @param adminAnnouncementsCreateRequest * @return [AdminAnnouncementsCreate200Response] */ @POST("admin/announcements/create") - suspend fun adminAnnouncementsCreate( + public suspend fun adminAnnouncementsCreate( @Body adminAnnouncementsCreateRequest: AdminAnnouncementsCreateRequest, ): AdminAnnouncementsCreate200Response @@ -239,7 +239,7 @@ internal interface AdminApi { * @param adminAdDeleteRequest * @return [Unit] */ @POST("admin/announcements/delete") - suspend fun adminAnnouncementsDelete( + public suspend fun adminAnnouncementsDelete( @Body adminAdDeleteRequest: AdminAdDeleteRequest, ): Unit @@ -257,7 +257,7 @@ internal interface AdminApi { * @param adminAnnouncementsListRequest * @return [kotlin.collections.List] */ @POST("admin/announcements/list") - suspend fun adminAnnouncementsList( + public suspend fun adminAnnouncementsList( @Body adminAnnouncementsListRequest: AdminAnnouncementsListRequest, ): kotlin.collections.List @@ -275,7 +275,7 @@ internal interface AdminApi { * @param adminAnnouncementsUpdateRequest * @return [Unit] */ @POST("admin/announcements/update") - suspend fun adminAnnouncementsUpdate( + public suspend fun adminAnnouncementsUpdate( @Body adminAnnouncementsUpdateRequest: AdminAnnouncementsUpdateRequest, ): Unit @@ -293,7 +293,7 @@ internal interface AdminApi { * @param adminAccountsDeleteRequest * @return [kotlin.Any] */ @POST("admin/delete-account") - suspend fun adminDeleteAccount( + public suspend fun adminDeleteAccount( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): kotlin.Any @@ -311,7 +311,7 @@ internal interface AdminApi { * @param adminAccountsDeleteRequest * @return [Unit] */ @POST("admin/delete-all-files-of-a-user") - suspend fun adminDeleteAllFilesOfAUser( + public suspend fun adminDeleteAllFilesOfAUser( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): Unit @@ -329,7 +329,7 @@ internal interface AdminApi { * @param body * @return [Unit] */ @POST("admin/drive/clean-remote-files") - suspend fun adminDriveCleanRemoteFiles( + public suspend fun adminDriveCleanRemoteFiles( @Body body: kotlin.Any, ): Unit @@ -347,7 +347,7 @@ internal interface AdminApi { * @param body * @return [Unit] */ @POST("admin/drive/cleanup") - suspend fun adminDriveCleanup( + public suspend fun adminDriveCleanup( @Body body: kotlin.Any, ): Unit @@ -365,7 +365,7 @@ internal interface AdminApi { * @param adminDriveFilesRequest * @return [kotlin.collections.List] */ @POST("admin/drive/files") - suspend fun adminDriveFiles( + public suspend fun adminDriveFiles( @Body adminDriveFilesRequest: AdminDriveFilesRequest, ): kotlin.collections.List @@ -383,7 +383,7 @@ internal interface AdminApi { * @param adminDriveShowFileRequest * @return [AdminDriveShowFile200Response] */ @POST("admin/drive/show-file") - suspend fun adminDriveShowFile( + public suspend fun adminDriveShowFile( @Body adminDriveShowFileRequest: AdminDriveShowFileRequest, ): AdminDriveShowFile200Response @@ -401,7 +401,7 @@ internal interface AdminApi { * @param adminEmojiAddRequest * @return [Unit] */ @POST("admin/emoji/add") - suspend fun adminEmojiAdd( + public suspend fun adminEmojiAdd( @Body adminEmojiAddRequest: AdminEmojiAddRequest, ): Unit @@ -419,7 +419,7 @@ internal interface AdminApi { * @param adminEmojiAddAliasesBulkRequest * @return [Unit] */ @POST("admin/emoji/add-aliases-bulk") - suspend fun adminEmojiAddAliasesBulk( + public suspend fun adminEmojiAddAliasesBulk( @Body adminEmojiAddAliasesBulkRequest: AdminEmojiAddAliasesBulkRequest, ): Unit @@ -437,7 +437,7 @@ internal interface AdminApi { * @param adminEmojiCopyRequest * @return [AdminEmojiCopy200Response] */ @POST("admin/emoji/copy") - suspend fun adminEmojiCopy( + public suspend fun adminEmojiCopy( @Body adminEmojiCopyRequest: AdminEmojiCopyRequest, ): AdminEmojiCopy200Response @@ -455,7 +455,7 @@ internal interface AdminApi { * @param adminAdDeleteRequest * @return [Unit] */ @POST("admin/emoji/delete") - suspend fun adminEmojiDelete( + public suspend fun adminEmojiDelete( @Body adminAdDeleteRequest: AdminAdDeleteRequest, ): Unit @@ -473,7 +473,7 @@ internal interface AdminApi { * @param adminEmojiDeleteBulkRequest * @return [Unit] */ @POST("admin/emoji/delete-bulk") - suspend fun adminEmojiDeleteBulk( + public suspend fun adminEmojiDeleteBulk( @Body adminEmojiDeleteBulkRequest: AdminEmojiDeleteBulkRequest, ): Unit @@ -491,7 +491,7 @@ internal interface AdminApi { * @param adminEmojiListRequest * @return [kotlin.collections.List] */ @POST("admin/emoji/list") - suspend fun adminEmojiList( + public suspend fun adminEmojiList( @Body adminEmojiListRequest: AdminEmojiListRequest, ): kotlin.collections.List @@ -509,7 +509,7 @@ internal interface AdminApi { * @param adminEmojiListRemoteRequest * @return [kotlin.collections.List] */ @POST("admin/emoji/list-remote") - suspend fun adminEmojiListRemote( + public suspend fun adminEmojiListRemote( @Body adminEmojiListRemoteRequest: AdminEmojiListRemoteRequest, ): kotlin.collections.List @@ -527,7 +527,7 @@ internal interface AdminApi { * @param adminEmojiAddAliasesBulkRequest * @return [Unit] */ @POST("admin/emoji/remove-aliases-bulk") - suspend fun adminEmojiRemoveAliasesBulk( + public suspend fun adminEmojiRemoveAliasesBulk( @Body adminEmojiAddAliasesBulkRequest: AdminEmojiAddAliasesBulkRequest, ): Unit @@ -545,7 +545,7 @@ internal interface AdminApi { * @param adminEmojiAddAliasesBulkRequest * @return [Unit] */ @POST("admin/emoji/set-aliases-bulk") - suspend fun adminEmojiSetAliasesBulk( + public suspend fun adminEmojiSetAliasesBulk( @Body adminEmojiAddAliasesBulkRequest: AdminEmojiAddAliasesBulkRequest, ): Unit @@ -563,7 +563,7 @@ internal interface AdminApi { * @param adminEmojiSetCategoryBulkRequest * @return [Unit] */ @POST("admin/emoji/set-category-bulk") - suspend fun adminEmojiSetCategoryBulk( + public suspend fun adminEmojiSetCategoryBulk( @Body adminEmojiSetCategoryBulkRequest: AdminEmojiSetCategoryBulkRequest, ): Unit @@ -581,7 +581,7 @@ internal interface AdminApi { * @param adminEmojiSetLicenseBulkRequest * @return [Unit] */ @POST("admin/emoji/set-license-bulk") - suspend fun adminEmojiSetLicenseBulk( + public suspend fun adminEmojiSetLicenseBulk( @Body adminEmojiSetLicenseBulkRequest: AdminEmojiSetLicenseBulkRequest, ): Unit @@ -599,7 +599,7 @@ internal interface AdminApi { * @param adminEmojiUpdateRequest * @return [Unit] */ @POST("admin/emoji/update") - suspend fun adminEmojiUpdate( + public suspend fun adminEmojiUpdate( @Body adminEmojiUpdateRequest: AdminEmojiUpdateRequest, ): Unit @@ -617,7 +617,7 @@ internal interface AdminApi { * @param adminFederationDeleteAllFilesRequest * @return [Unit] */ @POST("admin/federation/delete-all-files") - suspend fun adminFederationDeleteAllFiles( + public suspend fun adminFederationDeleteAllFiles( @Body adminFederationDeleteAllFilesRequest: AdminFederationDeleteAllFilesRequest, ): Unit @@ -635,7 +635,7 @@ internal interface AdminApi { * @param adminFederationDeleteAllFilesRequest * @return [Unit] */ @POST("admin/federation/refresh-remote-instance-metadata") - suspend fun adminFederationRefreshRemoteInstanceMetadata( + public suspend fun adminFederationRefreshRemoteInstanceMetadata( @Body adminFederationDeleteAllFilesRequest: AdminFederationDeleteAllFilesRequest, ): Unit @@ -653,7 +653,7 @@ internal interface AdminApi { * @param adminFederationDeleteAllFilesRequest * @return [Unit] */ @POST("admin/federation/remove-all-following") - suspend fun adminFederationRemoveAllFollowing( + public suspend fun adminFederationRemoveAllFollowing( @Body adminFederationDeleteAllFilesRequest: AdminFederationDeleteAllFilesRequest, ): Unit @@ -671,7 +671,7 @@ internal interface AdminApi { * @param adminFederationUpdateInstanceRequest * @return [Unit] */ @POST("admin/federation/update-instance") - suspend fun adminFederationUpdateInstance( + public suspend fun adminFederationUpdateInstance( @Body adminFederationUpdateInstanceRequest: AdminFederationUpdateInstanceRequest, ): Unit @@ -689,7 +689,7 @@ internal interface AdminApi { * @param body * @return [Unit] */ @POST("admin/get-index-stats") - suspend fun adminGetIndexStats( + public suspend fun adminGetIndexStats( @Body body: kotlin.Any, ): Unit @@ -707,7 +707,7 @@ internal interface AdminApi { * @param body * @return [kotlin.Any] */ @POST("admin/get-table-stats") - suspend fun adminGetTableStats( + public suspend fun adminGetTableStats( @Body body: kotlin.Any, ): kotlin.Any @@ -725,7 +725,7 @@ internal interface AdminApi { * @param adminAccountsDeleteRequest * @return [Unit] */ @POST("admin/get-user-ips") - suspend fun adminGetUserIps( + public suspend fun adminGetUserIps( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): Unit @@ -743,7 +743,7 @@ internal interface AdminApi { * @param adminInviteCreateRequest * @return [kotlin.collections.List] */ @POST("admin/invite/create") - suspend fun adminInviteCreate( + public suspend fun adminInviteCreate( @Body adminInviteCreateRequest: AdminInviteCreateRequest, ): kotlin.collections.List @@ -761,7 +761,7 @@ internal interface AdminApi { * @param adminInviteListRequest * @return [kotlin.collections.List] */ @POST("admin/invite/list") - suspend fun adminInviteList( + public suspend fun adminInviteList( @Body adminInviteListRequest: AdminInviteListRequest, ): kotlin.collections.List @@ -779,7 +779,7 @@ internal interface AdminApi { * @param adminPromoCreateRequest * @return [Unit] */ @POST("admin/promo/create") - suspend fun adminPromoCreate( + public suspend fun adminPromoCreate( @Body adminPromoCreateRequest: AdminPromoCreateRequest, ): Unit @@ -797,7 +797,7 @@ internal interface AdminApi { * @param body * @return [Unit] */ @POST("admin/queue/clear") - suspend fun adminQueueClear( + public suspend fun adminQueueClear( @Body body: kotlin.Any, ): Unit @@ -815,7 +815,7 @@ internal interface AdminApi { * @param body * @return [kotlin.collections.List>] */ @POST("admin/queue/deliver-delayed") - suspend fun adminQueueDeliverDelayed( + public suspend fun adminQueueDeliverDelayed( @Body body: kotlin.Any, ): kotlin.collections.List> @@ -833,7 +833,7 @@ internal interface AdminApi { * @param body * @return [kotlin.collections.List>] */ @POST("admin/queue/inbox-delayed") - suspend fun adminQueueInboxDelayed( + public suspend fun adminQueueInboxDelayed( @Body body: kotlin.Any, ): kotlin.collections.List> @@ -851,7 +851,7 @@ internal interface AdminApi { * @param adminQueuePromoteRequest * @return [Unit] */ @POST("admin/queue/promote") - suspend fun adminQueuePromote( + public suspend fun adminQueuePromote( @Body adminQueuePromoteRequest: AdminQueuePromoteRequest, ): Unit @@ -869,7 +869,7 @@ internal interface AdminApi { * @param body * @return [AdminQueueStats200Response] */ @POST("admin/queue/stats") - suspend fun adminQueueStats( + public suspend fun adminQueueStats( @Body body: kotlin.Any, ): AdminQueueStats200Response @@ -887,7 +887,7 @@ internal interface AdminApi { * @param adminRelaysAddRequest * @return [AdminRelaysAdd200Response] */ @POST("admin/relays/add") - suspend fun adminRelaysAdd( + public suspend fun adminRelaysAdd( @Body adminRelaysAddRequest: AdminRelaysAddRequest, ): AdminRelaysAdd200Response @@ -905,7 +905,7 @@ internal interface AdminApi { * @param body * @return [kotlin.collections.List] */ @POST("admin/relays/list") - suspend fun adminRelaysList( + public suspend fun adminRelaysList( @Body body: kotlin.Any, ): kotlin.collections.List @@ -923,7 +923,7 @@ internal interface AdminApi { * @param adminRelaysAddRequest * @return [Unit] */ @POST("admin/relays/remove") - suspend fun adminRelaysRemove( + public suspend fun adminRelaysRemove( @Body adminRelaysAddRequest: AdminRelaysAddRequest, ): Unit @@ -941,7 +941,7 @@ internal interface AdminApi { * @param adminAccountsDeleteRequest * @return [AdminResetPassword200Response] */ @POST("admin/reset-password") - suspend fun adminResetPassword( + public suspend fun adminResetPassword( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): AdminResetPassword200Response @@ -959,7 +959,7 @@ internal interface AdminApi { * @param adminResolveAbuseUserReportRequest * @return [Unit] */ @POST("admin/resolve-abuse-user-report") - suspend fun adminResolveAbuseUserReport( + public suspend fun adminResolveAbuseUserReport( @Body adminResolveAbuseUserReportRequest: AdminResolveAbuseUserReportRequest, ): Unit @@ -977,7 +977,7 @@ internal interface AdminApi { * @param adminRolesAssignRequest * @return [Unit] */ @POST("admin/roles/assign") - suspend fun adminRolesAssign( + public suspend fun adminRolesAssign( @Body adminRolesAssignRequest: AdminRolesAssignRequest, ): Unit @@ -995,7 +995,7 @@ internal interface AdminApi { * @param adminRolesCreateRequest * @return [Unit] */ @POST("admin/roles/create") - suspend fun adminRolesCreate( + public suspend fun adminRolesCreate( @Body adminRolesCreateRequest: AdminRolesCreateRequest, ): Unit @@ -1013,7 +1013,7 @@ internal interface AdminApi { * @param adminRolesDeleteRequest * @return [Unit] */ @POST("admin/roles/delete") - suspend fun adminRolesDelete( + public suspend fun adminRolesDelete( @Body adminRolesDeleteRequest: AdminRolesDeleteRequest, ): Unit @@ -1031,7 +1031,7 @@ internal interface AdminApi { * @param body * @return [Unit] */ @POST("admin/roles/list") - suspend fun adminRolesList( + public suspend fun adminRolesList( @Body body: kotlin.Any, ): Unit @@ -1049,7 +1049,7 @@ internal interface AdminApi { * @param adminRolesDeleteRequest * @return [Unit] */ @POST("admin/roles/show") - suspend fun adminRolesShow( + public suspend fun adminRolesShow( @Body adminRolesDeleteRequest: AdminRolesDeleteRequest, ): Unit @@ -1067,7 +1067,7 @@ internal interface AdminApi { * @param adminRolesUnassignRequest * @return [Unit] */ @POST("admin/roles/unassign") - suspend fun adminRolesUnassign( + public suspend fun adminRolesUnassign( @Body adminRolesUnassignRequest: AdminRolesUnassignRequest, ): Unit @@ -1085,7 +1085,7 @@ internal interface AdminApi { * @param adminRolesUpdateRequest * @return [Unit] */ @POST("admin/roles/update") - suspend fun adminRolesUpdate( + public suspend fun adminRolesUpdate( @Body adminRolesUpdateRequest: AdminRolesUpdateRequest, ): Unit @@ -1103,7 +1103,7 @@ internal interface AdminApi { * @param adminRolesUpdateDefaultPoliciesRequest * @return [Unit] */ @POST("admin/roles/update-default-policies") - suspend fun adminRolesUpdateDefaultPolicies( + public suspend fun adminRolesUpdateDefaultPolicies( @Body adminRolesUpdateDefaultPoliciesRequest: AdminRolesUpdateDefaultPoliciesRequest, ): Unit @@ -1121,7 +1121,7 @@ internal interface AdminApi { * @param adminRolesUsersRequest * @return [Unit] */ @POST("admin/roles/users") - suspend fun adminRolesUsers( + public suspend fun adminRolesUsers( @Body adminRolesUsersRequest: AdminRolesUsersRequest, ): Unit @@ -1139,7 +1139,7 @@ internal interface AdminApi { * @param adminSendEmailRequest * @return [Unit] */ @POST("admin/send-email") - suspend fun adminSendEmail( + public suspend fun adminSendEmail( @Body adminSendEmailRequest: AdminSendEmailRequest, ): Unit @@ -1157,7 +1157,7 @@ internal interface AdminApi { * @param body * @return [AdminServerInfo200Response] */ @POST("admin/server-info") - suspend fun adminServerInfo( + public suspend fun adminServerInfo( @Body body: kotlin.Any, ): AdminServerInfo200Response @@ -1175,7 +1175,7 @@ internal interface AdminApi { * @param adminAdListRequest * @return [kotlin.collections.List] */ @POST("admin/show-moderation-logs") - suspend fun adminShowModerationLogs( + public suspend fun adminShowModerationLogs( @Body adminAdListRequest: AdminAdListRequest, ): kotlin.collections.List @@ -1193,7 +1193,7 @@ internal interface AdminApi { * @param adminAccountsDeleteRequest * @return [kotlin.Any] */ @POST("admin/show-user") - suspend fun adminShowUser( + public suspend fun adminShowUser( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): kotlin.Any @@ -1211,7 +1211,7 @@ internal interface AdminApi { * @param adminShowUsersRequest * @return [kotlin.collections.List] */ @POST("admin/show-users") - suspend fun adminShowUsers( + public suspend fun adminShowUsers( @Body adminShowUsersRequest: AdminShowUsersRequest, ): kotlin.collections.List @@ -1229,7 +1229,7 @@ internal interface AdminApi { * @param adminAccountsDeleteRequest * @return [Unit] */ @POST("admin/suspend-user") - suspend fun adminSuspendUser( + public suspend fun adminSuspendUser( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): Unit @@ -1247,7 +1247,7 @@ internal interface AdminApi { * @param adminAccountsDeleteRequest * @return [Unit] */ @POST("admin/unsuspend-user") - suspend fun adminUnsuspendUser( + public suspend fun adminUnsuspendUser( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): Unit @@ -1265,7 +1265,7 @@ internal interface AdminApi { * @param adminUpdateMetaRequest * @return [Unit] */ @POST("admin/update-meta") - suspend fun adminUpdateMeta( + public suspend fun adminUpdateMeta( @Body adminUpdateMetaRequest: AdminUpdateMetaRequest, ): Unit @@ -1283,7 +1283,7 @@ internal interface AdminApi { * @param adminUpdateUserNoteRequest * @return [Unit] */ @POST("admin/update-user-note") - suspend fun adminUpdateUserNote( + public suspend fun adminUpdateUserNote( @Body adminUpdateUserNoteRequest: AdminUpdateUserNoteRequest, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AntennasApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AntennasApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AntennasApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AntennasApi.kt index b48bd5e68d..14e0d4ffc6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AntennasApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AntennasApi.kt @@ -11,7 +11,7 @@ import dev.dimension.flare.data.network.misskey.api.model.Note import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject -internal interface AntennasApi { +public interface AntennasApi { /** * antennas/create * No description provided. **Credential required**: *Yes* / **Permission**: *write:account* @@ -26,7 +26,7 @@ internal interface AntennasApi { * @param antennasCreateRequest * @return [Antenna] */ @POST("antennas/create") - suspend fun antennasCreate( + public suspend fun antennasCreate( @Body antennasCreateRequest: AntennasCreateRequest, ): Antenna @@ -44,7 +44,7 @@ internal interface AntennasApi { * @param antennasDeleteRequest * @return [Unit] */ @POST("antennas/delete") - suspend fun antennasDelete( + public suspend fun antennasDelete( @Body antennasDeleteRequest: AntennasDeleteRequest, ): Unit @@ -62,7 +62,7 @@ internal interface AntennasApi { * @param body * @return [kotlin.collections.List] */ @POST("antennas/list") - suspend fun antennasList( + public suspend fun antennasList( @Body body: JsonObject = buildJsonObject { }, ): kotlin.collections.List @@ -80,7 +80,7 @@ internal interface AntennasApi { * @param antennasNotesRequest * @return [kotlin.collections.List] */ @POST("antennas/notes") - suspend fun antennasNotes( + public suspend fun antennasNotes( @Body antennasNotesRequest: AntennasNotesRequest, ): kotlin.collections.List @@ -98,7 +98,7 @@ internal interface AntennasApi { * @param antennasDeleteRequest * @return [Antenna] */ @POST("antennas/show") - suspend fun antennasShow( + public suspend fun antennasShow( @Body antennasDeleteRequest: AntennasDeleteRequest, ): Antenna @@ -116,7 +116,7 @@ internal interface AntennasApi { * @param antennasUpdateRequest * @return [Antenna] */ @POST("antennas/update") - suspend fun antennasUpdate( + public suspend fun antennasUpdate( @Body antennasUpdateRequest: AntennasUpdateRequest, ): Antenna } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AppApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AppApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AppApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AppApi.kt index 04f0f8af8b..859f05ae3c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AppApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AppApi.kt @@ -6,7 +6,7 @@ import dev.dimension.flare.data.network.misskey.api.model.App import dev.dimension.flare.data.network.misskey.api.model.AppCreateRequest import dev.dimension.flare.data.network.misskey.api.model.AppShowRequest -internal interface AppApi { +public interface AppApi { /** * app/create * No description provided. **Credential required**: *No* @@ -21,7 +21,7 @@ internal interface AppApi { * @param appCreateRequest * @return [App] */ @POST("app/create") - suspend fun appCreate( + public suspend fun appCreate( @Body appCreateRequest: AppCreateRequest, ): App @@ -39,7 +39,7 @@ internal interface AppApi { * @param appShowRequest * @return [App] */ @POST("app/show") - suspend fun appShow( + public suspend fun appShow( @Body appShowRequest: AppShowRequest, ): App } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AuthApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AuthApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AuthApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AuthApi.kt index ccbe32d5d6..74289f437d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AuthApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AuthApi.kt @@ -9,7 +9,7 @@ import dev.dimension.flare.data.network.misskey.api.model.AuthSessionShowRequest import dev.dimension.flare.data.network.misskey.api.model.AuthSessionUserkey200Response import dev.dimension.flare.data.network.misskey.api.model.AuthSessionUserkeyRequest -internal interface AuthApi { +public interface AuthApi { /** * auth/session/generate * No description provided. **Credential required**: *No* @@ -24,7 +24,7 @@ internal interface AuthApi { * @param authSessionGenerateRequest * @return [AuthSessionGenerate200Response] */ @POST("auth/session/generate") - suspend fun authSessionGenerate( + public suspend fun authSessionGenerate( @Body authSessionGenerateRequest: AuthSessionGenerateRequest, ): AuthSessionGenerate200Response @@ -42,7 +42,7 @@ internal interface AuthApi { * @param authSessionShowRequest * @return [AuthSessionShow200Response] */ @POST("auth/session/show") - suspend fun authSessionShow( + public suspend fun authSessionShow( @Body authSessionShowRequest: AuthSessionShowRequest, ): AuthSessionShow200Response @@ -60,7 +60,7 @@ internal interface AuthApi { * @param authSessionUserkeyRequest * @return [AuthSessionUserkey200Response] */ @POST("auth/session/userkey") - suspend fun authSessionUserkey( + public suspend fun authSessionUserkey( @Body authSessionUserkeyRequest: AuthSessionUserkeyRequest, ): AuthSessionUserkey200Response } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AuthResources.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AuthResources.kt similarity index 83% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AuthResources.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AuthResources.kt index 655b404d03..fc02d70ed9 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AuthResources.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/AuthResources.kt @@ -4,9 +4,9 @@ import de.jensklingenberg.ktorfit.http.POST import de.jensklingenberg.ktorfit.http.Path import dev.dimension.flare.data.network.misskey.api.model.response.MiAuthCheckResponse -internal interface AuthResources { +public interface AuthResources { @POST("api/miauth/{id}/check") - suspend fun check( + public suspend fun check( @Path("id") id: String, ): MiAuthCheckResponse } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ChannelsApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ChannelsApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ChannelsApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ChannelsApi.kt index 4e74fbe7a0..2ec28f191c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ChannelsApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ChannelsApi.kt @@ -11,7 +11,7 @@ import dev.dimension.flare.data.network.misskey.api.model.ChannelsFollowedReques import dev.dimension.flare.data.network.misskey.api.model.ChannelsSearchRequest import dev.dimension.flare.data.network.misskey.api.model.ChannelsUpdateRequest -internal interface ChannelsApi { +public interface ChannelsApi { /** * channels/create * No description provided. **Credential required**: *Yes* / **Permission**: *write:channels* @@ -27,7 +27,7 @@ internal interface ChannelsApi { * @param channelsCreateRequest * @return [Channel] */ @POST("channels/create") - suspend fun channelsCreate( + public suspend fun channelsCreate( @Body channelsCreateRequest: ChannelsCreateRequest, @Header("Content-Type") contentType: kotlin.String = "application/json", ): Channel @@ -46,7 +46,7 @@ internal interface ChannelsApi { * @param channelsFollowRequest * @return [Unit] */ @POST("channels/favorite") - suspend fun channelsFavorite( + public suspend fun channelsFavorite( @Body channelsFollowRequest: ChannelsFollowRequest, @Header("Content-Type") contentType: kotlin.String = "application/json", ): Unit @@ -65,7 +65,7 @@ internal interface ChannelsApi { * @param request * @return [kotlin.collections.List] */ @POST("channels/featured") - suspend fun channelsFeatured( + public suspend fun channelsFeatured( @Body request: ChannelsFeaturedRequest, @Header("Content-Type") contentType: kotlin.String = "application/json", ): kotlin.collections.List @@ -84,7 +84,7 @@ internal interface ChannelsApi { * @param channelsFollowRequest * @return [Unit] */ @POST("channels/follow") - suspend fun channelsFollow( + public suspend fun channelsFollow( @Body channelsFollowRequest: ChannelsFollowRequest, @Header("Content-Type") contentType: kotlin.String = "application/json", ): Unit @@ -103,7 +103,7 @@ internal interface ChannelsApi { * @param channelsFollowedRequest * @return [kotlin.collections.List] */ @POST("channels/followed") - suspend fun channelsFollowed( + public suspend fun channelsFollowed( @Body channelsFollowedRequest: ChannelsFollowedRequest, @Header("Content-Type") contentType: kotlin.String = "application/json", ): kotlin.collections.List @@ -122,7 +122,7 @@ internal interface ChannelsApi { * @param body * @return [kotlin.collections.List] */ @POST("channels/my-favorites") - suspend fun channelsMyFavorites( + public suspend fun channelsMyFavorites( @Body channelsFollowedRequest: ChannelsFollowedRequest, @Header("Content-Type") contentType: kotlin.String = "application/json", ): kotlin.collections.List @@ -141,7 +141,7 @@ internal interface ChannelsApi { * @param channelsFollowedRequest * @return [kotlin.collections.List] */ @POST("channels/owned") - suspend fun channelsOwned( + public suspend fun channelsOwned( @Body channelsFollowedRequest: ChannelsFollowedRequest, @Header("Content-Type") contentType: kotlin.String = "application/json", ): kotlin.collections.List @@ -160,7 +160,7 @@ internal interface ChannelsApi { * @param channelsSearchRequest * @return [kotlin.collections.List] */ @POST("channels/search") - suspend fun channelsSearch( + public suspend fun channelsSearch( @Body channelsSearchRequest: ChannelsSearchRequest, @Header("Content-Type") contentType: kotlin.String = "application/json", ): kotlin.collections.List @@ -179,7 +179,7 @@ internal interface ChannelsApi { * @param channelsFollowRequest * @return [Channel] */ @POST("channels/show") - suspend fun channelsShow( + public suspend fun channelsShow( @Body channelsFollowRequest: ChannelsFollowRequest, @Header("Content-Type") contentType: kotlin.String = "application/json", ): Channel @@ -198,7 +198,7 @@ internal interface ChannelsApi { * @param channelsFollowRequest * @return [Unit] */ @POST("channels/unfavorite") - suspend fun channelsUnfavorite( + public suspend fun channelsUnfavorite( @Body channelsFollowRequest: ChannelsFollowRequest, @Header("Content-Type") contentType: kotlin.String = "application/json", ): Unit @@ -217,7 +217,7 @@ internal interface ChannelsApi { * @param channelsFollowRequest * @return [Unit] */ @POST("channels/unfollow") - suspend fun channelsUnfollow( + public suspend fun channelsUnfollow( @Body channelsFollowRequest: ChannelsFollowRequest, @Header("Content-Type") contentType: kotlin.String = "application/json", ): Unit @@ -236,7 +236,7 @@ internal interface ChannelsApi { * @param channelsUpdateRequest * @return [Channel] */ @POST("channels/update") - suspend fun channelsUpdate( + public suspend fun channelsUpdate( @Body channelsUpdateRequest: ChannelsUpdateRequest, @Header("Content-Type") contentType: kotlin.String = "application/json", ): Channel diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ChartsApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ChartsApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ChartsApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ChartsApi.kt index 061332fca6..120821590e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ChartsApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ChartsApi.kt @@ -18,7 +18,7 @@ import dev.dimension.flare.data.network.misskey.api.model.ChartsUserPv200Respons import dev.dimension.flare.data.network.misskey.api.model.ChartsUserReactions200Response import dev.dimension.flare.data.network.misskey.api.model.ChartsUsers200Response -internal interface ChartsApi { +public interface ChartsApi { /** * charts/active-users * No description provided. **Credential required**: *No* @@ -33,7 +33,7 @@ internal interface ChartsApi { * @param chartsActiveUsersRequest * @return [ChartsActiveUsers200Response] */ @POST("charts/active-users") - suspend fun chartsActiveUsers( + public suspend fun chartsActiveUsers( @Body chartsActiveUsersRequest: ChartsActiveUsersRequest, ): ChartsActiveUsers200Response @@ -51,7 +51,7 @@ internal interface ChartsApi { * @param chartsActiveUsersRequest * @return [ChartsApRequest200Response] */ @POST("charts/ap-request") - suspend fun chartsApRequest( + public suspend fun chartsApRequest( @Body chartsActiveUsersRequest: ChartsActiveUsersRequest, ): ChartsApRequest200Response @@ -69,7 +69,7 @@ internal interface ChartsApi { * @param chartsActiveUsersRequest * @return [ChartsDrive200Response] */ @POST("charts/drive") - suspend fun chartsDrive( + public suspend fun chartsDrive( @Body chartsActiveUsersRequest: ChartsActiveUsersRequest, ): ChartsDrive200Response @@ -87,7 +87,7 @@ internal interface ChartsApi { * @param chartsActiveUsersRequest * @return [ChartsFederation200Response] */ @POST("charts/federation") - suspend fun chartsFederation( + public suspend fun chartsFederation( @Body chartsActiveUsersRequest: ChartsActiveUsersRequest, ): ChartsFederation200Response @@ -105,7 +105,7 @@ internal interface ChartsApi { * @param chartsInstanceRequest * @return [ChartsInstance200Response] */ @POST("charts/instance") - suspend fun chartsInstance( + public suspend fun chartsInstance( @Body chartsInstanceRequest: ChartsInstanceRequest, ): ChartsInstance200Response @@ -123,7 +123,7 @@ internal interface ChartsApi { * @param chartsActiveUsersRequest * @return [ChartsNotes200Response] */ @POST("charts/notes") - suspend fun chartsNotes( + public suspend fun chartsNotes( @Body chartsActiveUsersRequest: ChartsActiveUsersRequest, ): ChartsNotes200Response @@ -141,7 +141,7 @@ internal interface ChartsApi { * @param chartsUserDriveRequest * @return [ChartsUserDrive200Response] */ @POST("charts/user/drive") - suspend fun chartsUserDrive( + public suspend fun chartsUserDrive( @Body chartsUserDriveRequest: ChartsUserDriveRequest, ): ChartsUserDrive200Response @@ -159,7 +159,7 @@ internal interface ChartsApi { * @param chartsUserDriveRequest * @return [ChartsUserFollowing200Response] */ @POST("charts/user/following") - suspend fun chartsUserFollowing( + public suspend fun chartsUserFollowing( @Body chartsUserDriveRequest: ChartsUserDriveRequest, ): ChartsUserFollowing200Response @@ -177,7 +177,7 @@ internal interface ChartsApi { * @param chartsUserDriveRequest * @return [ChartsUserNotes200Response] */ @POST("charts/user/notes") - suspend fun chartsUserNotes( + public suspend fun chartsUserNotes( @Body chartsUserDriveRequest: ChartsUserDriveRequest, ): ChartsUserNotes200Response @@ -195,7 +195,7 @@ internal interface ChartsApi { * @param chartsUserDriveRequest * @return [ChartsUserPv200Response] */ @POST("charts/user/pv") - suspend fun chartsUserPv( + public suspend fun chartsUserPv( @Body chartsUserDriveRequest: ChartsUserDriveRequest, ): ChartsUserPv200Response @@ -213,7 +213,7 @@ internal interface ChartsApi { * @param chartsUserDriveRequest * @return [ChartsUserReactions200Response] */ @POST("charts/user/reactions") - suspend fun chartsUserReactions( + public suspend fun chartsUserReactions( @Body chartsUserDriveRequest: ChartsUserDriveRequest, ): ChartsUserReactions200Response @@ -231,7 +231,7 @@ internal interface ChartsApi { * @param chartsActiveUsersRequest * @return [ChartsUsers200Response] */ @POST("charts/users") - suspend fun chartsUsers( + public suspend fun chartsUsers( @Body chartsActiveUsersRequest: ChartsActiveUsersRequest, ): ChartsUsers200Response } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ClipApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ClipApi.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ClipApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ClipApi.kt index 129ec58657..636bdf246e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ClipApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ClipApi.kt @@ -4,7 +4,7 @@ import de.jensklingenberg.ktorfit.http.Body import de.jensklingenberg.ktorfit.http.POST import dev.dimension.flare.data.network.misskey.api.model.ClipsDeleteRequest -internal interface ClipApi { +public interface ClipApi { /** * clips/favorite * No description provided. **Credential required**: *Yes* / **Permission**: *write:clip-favorite* @@ -19,7 +19,7 @@ internal interface ClipApi { * @param clipsDeleteRequest * @return [Unit] */ @POST("clips/favorite") - suspend fun clipsFavorite( + public suspend fun clipsFavorite( @Body clipsDeleteRequest: ClipsDeleteRequest, ): Unit @@ -37,7 +37,7 @@ internal interface ClipApi { * @param clipsDeleteRequest * @return [Unit] */ @POST("clips/unfavorite") - suspend fun clipsUnfavorite( + public suspend fun clipsUnfavorite( @Body clipsDeleteRequest: ClipsDeleteRequest, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ClipsApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ClipsApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ClipsApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ClipsApi.kt index 94d0869541..97cb2950eb 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ClipsApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ClipsApi.kt @@ -8,7 +8,7 @@ import dev.dimension.flare.data.network.misskey.api.model.ClipsDeleteRequest import dev.dimension.flare.data.network.misskey.api.model.ClipsUpdateRequest import dev.dimension.flare.data.network.misskey.api.model.IPinRequest -internal interface ClipsApi { +public interface ClipsApi { /** * clips/create * No description provided. **Credential required**: *Yes* / **Permission**: *write:account* @@ -23,7 +23,7 @@ internal interface ClipsApi { * @param clipsCreateRequest * @return [Clip] */ @POST("clips/create") - suspend fun clipsCreate( + public suspend fun clipsCreate( @Body clipsCreateRequest: ClipsCreateRequest, ): Clip @@ -41,7 +41,7 @@ internal interface ClipsApi { * @param clipsDeleteRequest * @return [Unit] */ @POST("clips/delete") - suspend fun clipsDelete( + public suspend fun clipsDelete( @Body clipsDeleteRequest: ClipsDeleteRequest, ): Unit @@ -59,7 +59,7 @@ internal interface ClipsApi { * @param body * @return [kotlin.collections.List] */ @POST("clips/list") - suspend fun clipsList( + public suspend fun clipsList( @Body body: kotlin.Any, ): kotlin.collections.List @@ -77,7 +77,7 @@ internal interface ClipsApi { * @param clipsDeleteRequest * @return [Clip] */ @POST("clips/show") - suspend fun clipsShow( + public suspend fun clipsShow( @Body clipsDeleteRequest: ClipsDeleteRequest, ): Clip @@ -95,7 +95,7 @@ internal interface ClipsApi { * @param clipsUpdateRequest * @return [Clip] */ @POST("clips/update") - suspend fun clipsUpdate( + public suspend fun clipsUpdate( @Body clipsUpdateRequest: ClipsUpdateRequest, ): Clip @@ -113,7 +113,7 @@ internal interface ClipsApi { * @param ipinRequest * @return [kotlin.collections.List] */ @POST("notes/clips") - suspend fun notesClips( + public suspend fun notesClips( @Body ipinRequest: IPinRequest, ): kotlin.collections.List } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/DefaultApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/DefaultApi.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/DefaultApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/DefaultApi.kt index 5961a17235..f878b573ee 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/DefaultApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/DefaultApi.kt @@ -12,7 +12,7 @@ import dev.dimension.flare.data.network.misskey.api.model.UserList import dev.dimension.flare.data.network.misskey.api.model.UsersListsCreateFromPublicRequest import dev.dimension.flare.data.network.misskey.api.model.UsersListsDeleteRequest -internal interface DefaultApi { +public interface DefaultApi { /** * admin/abuse-report-resolver/delete * No description provided. **Credential required**: *No* @@ -27,7 +27,7 @@ internal interface DefaultApi { * @param adminAbuseReportResolverDeleteRequest * @return [Unit] */ @POST("admin/abuse-report-resolver/delete") - suspend fun adminAbuseReportResolverDelete( + public suspend fun adminAbuseReportResolverDelete( @Body adminAbuseReportResolverDeleteRequest: AdminAbuseReportResolverDeleteRequest, ): Unit @@ -45,7 +45,7 @@ internal interface DefaultApi { * @param adminAbuseReportResolverListRequest * @return [kotlin.collections.List] */ @POST("admin/abuse-report-resolver/list") - suspend fun adminAbuseReportResolverList( + public suspend fun adminAbuseReportResolverList( @Body adminAbuseReportResolverListRequest: AdminAbuseReportResolverListRequest, ): kotlin.collections.List @@ -63,7 +63,7 @@ internal interface DefaultApi { * @param adminAbuseReportResolverUpdateRequest * @return [Unit] */ @POST("admin/abuse-report-resolver/update") - suspend fun adminAbuseReportResolverUpdate( + public suspend fun adminAbuseReportResolverUpdate( @Body adminAbuseReportResolverUpdateRequest: AdminAbuseReportResolverUpdateRequest, ): Unit @@ -81,7 +81,7 @@ internal interface DefaultApi { * @param iclaimAchievementRequest * @return [Unit] */ @POST("i/claim-achievement") - suspend fun iClaimAchievement( + public suspend fun iClaimAchievement( @Body iclaimAchievementRequest: IClaimAchievementRequest, ): Unit @@ -99,7 +99,7 @@ internal interface DefaultApi { * @param adminAccountsDeleteRequest * @return [Unit] */ @POST("users/achievements") - suspend fun usersAchievements( + public suspend fun usersAchievements( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): Unit @@ -117,7 +117,7 @@ internal interface DefaultApi { * @param usersListsCreateFromPublicRequest * @return [UserList] */ @POST("users/lists/create-from-public") - suspend fun usersListsCreateFromPublic( + public suspend fun usersListsCreateFromPublic( @Body usersListsCreateFromPublicRequest: UsersListsCreateFromPublicRequest, ): UserList @@ -135,7 +135,7 @@ internal interface DefaultApi { * @param usersListsDeleteRequest * @return [Unit] */ @POST("users/lists/favorite") - suspend fun usersListsFavorite( + public suspend fun usersListsFavorite( @Body usersListsDeleteRequest: UsersListsDeleteRequest, ): Unit @@ -153,7 +153,7 @@ internal interface DefaultApi { * @param usersListsDeleteRequest * @return [Unit] */ @POST("users/lists/unfavorite") - suspend fun usersListsUnfavorite( + public suspend fun usersListsUnfavorite( @Body usersListsDeleteRequest: UsersListsDeleteRequest, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/DriveApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/DriveApi.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/DriveApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/DriveApi.kt index 6a1e17cc4a..27b2baad5e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/DriveApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/DriveApi.kt @@ -22,7 +22,7 @@ import dev.dimension.flare.data.network.misskey.api.model.DriveStreamRequest import dev.dimension.flare.data.network.misskey.api.model.Note import io.ktor.client.request.forms.MultiPartFormDataContent -internal interface DriveApi { +public interface DriveApi { /** * drive * No description provided. **Credential required**: *Yes* / **Permission**: *read:drive* @@ -37,7 +37,7 @@ internal interface DriveApi { * @param body * @return [Drive200Response] */ @POST("drive") - suspend fun drive( + public suspend fun drive( @Body body: kotlin.Any, ): Drive200Response @@ -55,7 +55,7 @@ internal interface DriveApi { * @param driveFilesRequest * @return [kotlin.collections.List] */ @POST("drive/files") - suspend fun driveFiles( + public suspend fun driveFiles( @Body driveFilesRequest: DriveFilesRequest, ): kotlin.collections.List @@ -73,7 +73,7 @@ internal interface DriveApi { * @param driveFilesAttachedNotesRequest * @return [kotlin.collections.List] */ @POST("drive/files/attached-notes") - suspend fun driveFilesAttachedNotes( + public suspend fun driveFilesAttachedNotes( @Body driveFilesAttachedNotesRequest: DriveFilesAttachedNotesRequest, ): kotlin.collections.List @@ -91,7 +91,7 @@ internal interface DriveApi { * @param driveFilesCheckExistenceRequest * @return [kotlin.Boolean] */ @POST("drive/files/check-existence") - suspend fun driveFilesCheckExistence( + public suspend fun driveFilesCheckExistence( @Body driveFilesCheckExistenceRequest: DriveFilesCheckExistenceRequest, ): kotlin.Boolean @@ -120,7 +120,7 @@ internal interface DriveApi { // suspend fun driveFilesCreate(@Part file: MultipartBody.Part, @Part("folderId") folderId: kotlin.String? = null, @Part("name") name: kotlin.String? = null, @Part("comment") comment: kotlin.String? = null, @Part("isSensitive") isSensitive: kotlin.Boolean? = false, @Part("force") force: kotlin.Boolean? = false): DriveFile @Multipart @POST("drive/files/create") - suspend fun driveFilesCreate( + public suspend fun driveFilesCreate( @Body map: MultiPartFormDataContent, ): DriveFile @@ -138,7 +138,7 @@ internal interface DriveApi { * @param driveFilesAttachedNotesRequest * @return [Unit] */ @POST("drive/files/delete") - suspend fun driveFilesDelete( + public suspend fun driveFilesDelete( @Body driveFilesAttachedNotesRequest: DriveFilesAttachedNotesRequest, ): Unit @@ -156,7 +156,7 @@ internal interface DriveApi { * @param driveFilesFindRequest * @return [kotlin.collections.List] */ @POST("drive/files/find") - suspend fun driveFilesFind( + public suspend fun driveFilesFind( @Body driveFilesFindRequest: DriveFilesFindRequest, ): kotlin.collections.List @@ -174,7 +174,7 @@ internal interface DriveApi { * @param driveFilesCheckExistenceRequest * @return [kotlin.collections.List] */ @POST("drive/files/find-by-hash") - suspend fun driveFilesFindByHash( + public suspend fun driveFilesFindByHash( @Body driveFilesCheckExistenceRequest: DriveFilesCheckExistenceRequest, ): kotlin.collections.List @@ -192,7 +192,7 @@ internal interface DriveApi { * @param adminDriveShowFileRequest * @return [DriveFile] */ @POST("drive/files/show") - suspend fun driveFilesShow( + public suspend fun driveFilesShow( @Body adminDriveShowFileRequest: AdminDriveShowFileRequest, ): DriveFile @@ -210,7 +210,7 @@ internal interface DriveApi { * @param driveFilesUpdateRequest * @return [DriveFile] */ @POST("drive/files/update") - suspend fun driveFilesUpdate( + public suspend fun driveFilesUpdate( @Body driveFilesUpdateRequest: DriveFilesUpdateRequest, ): DriveFile @@ -229,7 +229,7 @@ internal interface DriveApi { * @param driveFilesUploadFromUrlRequest * @return [Unit] */ @POST("drive/files/upload-from-url") - suspend fun driveFilesUploadFromUrl( + public suspend fun driveFilesUploadFromUrl( @Body driveFilesUploadFromUrlRequest: DriveFilesUploadFromUrlRequest, ): Unit @@ -247,7 +247,7 @@ internal interface DriveApi { * @param driveFoldersRequest * @return [kotlin.collections.List] */ @POST("drive/folders") - suspend fun driveFolders( + public suspend fun driveFolders( @Body driveFoldersRequest: DriveFoldersRequest, ): kotlin.collections.List @@ -266,7 +266,7 @@ internal interface DriveApi { * @param driveFoldersCreateRequest * @return [DriveFolder] */ @POST("drive/folders/create") - suspend fun driveFoldersCreate( + public suspend fun driveFoldersCreate( @Body driveFoldersCreateRequest: DriveFoldersCreateRequest, ): DriveFolder @@ -284,7 +284,7 @@ internal interface DriveApi { * @param driveFoldersDeleteRequest * @return [Unit] */ @POST("drive/folders/delete") - suspend fun driveFoldersDelete( + public suspend fun driveFoldersDelete( @Body driveFoldersDeleteRequest: DriveFoldersDeleteRequest, ): Unit @@ -302,7 +302,7 @@ internal interface DriveApi { * @param driveFoldersFindRequest * @return [kotlin.collections.List] */ @POST("drive/folders/find") - suspend fun driveFoldersFind( + public suspend fun driveFoldersFind( @Body driveFoldersFindRequest: DriveFoldersFindRequest, ): kotlin.collections.List @@ -320,7 +320,7 @@ internal interface DriveApi { * @param driveFoldersDeleteRequest * @return [DriveFolder] */ @POST("drive/folders/show") - suspend fun driveFoldersShow( + public suspend fun driveFoldersShow( @Body driveFoldersDeleteRequest: DriveFoldersDeleteRequest, ): DriveFolder @@ -338,7 +338,7 @@ internal interface DriveApi { * @param driveFoldersUpdateRequest * @return [DriveFolder] */ @POST("drive/folders/update") - suspend fun driveFoldersUpdate( + public suspend fun driveFoldersUpdate( @Body driveFoldersUpdateRequest: DriveFoldersUpdateRequest, ): DriveFolder @@ -356,7 +356,7 @@ internal interface DriveApi { * @param driveStreamRequest * @return [kotlin.collections.List] */ @POST("drive/stream") - suspend fun driveStream( + public suspend fun driveStream( @Body driveStreamRequest: DriveStreamRequest, ): kotlin.collections.List } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FederationApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FederationApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FederationApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FederationApi.kt index 731a74ba09..411dea7d7d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FederationApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FederationApi.kt @@ -14,7 +14,7 @@ import dev.dimension.flare.data.network.misskey.api.model.FederationStatsRequest import dev.dimension.flare.data.network.misskey.api.model.Following import dev.dimension.flare.data.network.misskey.api.model.UserDetailedNotMe -internal interface FederationApi { +public interface FederationApi { /** * ap/get * No description provided. **Credential required**: *Yes* @@ -30,7 +30,7 @@ internal interface FederationApi { * @param apGetRequest * @return [kotlin.Any] */ @POST("ap/get") - suspend fun apGet( + public suspend fun apGet( @Body apGetRequest: ApGetRequest, ): kotlin.Any @@ -49,7 +49,7 @@ internal interface FederationApi { * @param apGetRequest * @return [ApShow200Response] */ @POST("ap/show") - suspend fun apShow( + public suspend fun apShow( @Body apGetRequest: ApGetRequest, ): ApShow200Response @@ -67,7 +67,7 @@ internal interface FederationApi { * @param federationFollowersRequest * @return [kotlin.collections.List] */ @POST("federation/followers") - suspend fun federationFollowers( + public suspend fun federationFollowers( @Body federationFollowersRequest: FederationFollowersRequest, ): kotlin.collections.List @@ -85,7 +85,7 @@ internal interface FederationApi { * @param federationFollowersRequest * @return [kotlin.collections.List] */ @POST("federation/following") - suspend fun federationFollowing( + public suspend fun federationFollowing( @Body federationFollowersRequest: FederationFollowersRequest, ): kotlin.collections.List @@ -103,7 +103,7 @@ internal interface FederationApi { * @param federationInstancesRequest * @return [kotlin.collections.List] */ @POST("federation/instances") - suspend fun federationInstances( + public suspend fun federationInstances( @Body federationInstancesRequest: FederationInstancesRequest, ): kotlin.collections.List @@ -121,7 +121,7 @@ internal interface FederationApi { * @param adminFederationDeleteAllFilesRequest * @return [FederationShowInstance200Response] */ @POST("federation/show-instance") - suspend fun federationShowInstance( + public suspend fun federationShowInstance( @Body adminFederationDeleteAllFilesRequest: AdminFederationDeleteAllFilesRequest, ): FederationShowInstance200Response @@ -139,7 +139,7 @@ internal interface FederationApi { * @param federationStatsRequest * @return [Unit] */ @POST("federation/stats") - suspend fun federationStats( + public suspend fun federationStats( @Body federationStatsRequest: FederationStatsRequest, ): Unit @@ -157,7 +157,7 @@ internal interface FederationApi { * @param adminAccountsDeleteRequest * @return [Unit] */ @POST("federation/update-remote-user") - suspend fun federationUpdateRemoteUser( + public suspend fun federationUpdateRemoteUser( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): Unit @@ -175,7 +175,7 @@ internal interface FederationApi { * @param federationFollowersRequest * @return [kotlin.collections.List] */ @POST("federation/users") - suspend fun federationUsers( + public suspend fun federationUsers( @Body federationFollowersRequest: FederationFollowersRequest, ): kotlin.collections.List } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FlashApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FlashApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FlashApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FlashApi.kt index 4d4c95fd9d..4001cbf33a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FlashApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FlashApi.kt @@ -7,7 +7,7 @@ import dev.dimension.flare.data.network.misskey.api.model.FlashCreateRequest import dev.dimension.flare.data.network.misskey.api.model.FlashDeleteRequest import dev.dimension.flare.data.network.misskey.api.model.FlashUpdateRequest -internal interface FlashApi { +public interface FlashApi { /** * flash/create * No description provided. **Credential required**: *Yes* / **Permission**: *write:flash* @@ -23,7 +23,7 @@ internal interface FlashApi { * @param flashCreateRequest * @return [Unit] */ @POST("flash/create") - suspend fun flashCreate( + public suspend fun flashCreate( @Body flashCreateRequest: FlashCreateRequest, ): Unit @@ -41,7 +41,7 @@ internal interface FlashApi { * @param body * @return [kotlin.collections.List] */ @POST("flash/featured") - suspend fun flashFeatured( + public suspend fun flashFeatured( @Body body: kotlin.Any, ): kotlin.collections.List @@ -59,7 +59,7 @@ internal interface FlashApi { * @param flashDeleteRequest * @return [Unit] */ @POST("flash/like") - suspend fun flashLike( + public suspend fun flashLike( @Body flashDeleteRequest: FlashDeleteRequest, ): Unit @@ -77,7 +77,7 @@ internal interface FlashApi { * @param flashDeleteRequest * @return [Unit] */ @POST("flash/unlike") - suspend fun flashUnlike( + public suspend fun flashUnlike( @Body flashDeleteRequest: FlashDeleteRequest, ): Unit @@ -96,7 +96,7 @@ internal interface FlashApi { * @param flashUpdateRequest * @return [Unit] */ @POST("flash/update") - suspend fun flashUpdate( + public suspend fun flashUpdate( @Body flashUpdateRequest: FlashUpdateRequest, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FlashsApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FlashsApi.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FlashsApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FlashsApi.kt index e03955ad47..252b582785 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FlashsApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FlashsApi.kt @@ -5,7 +5,7 @@ import de.jensklingenberg.ktorfit.http.POST import dev.dimension.flare.data.network.misskey.api.model.Flash import dev.dimension.flare.data.network.misskey.api.model.FlashDeleteRequest -internal interface FlashsApi { +public interface FlashsApi { /** * flash/delete * No description provided. **Credential required**: *Yes* / **Permission**: *write:flash* @@ -20,7 +20,7 @@ internal interface FlashsApi { * @param flashDeleteRequest * @return [Unit] */ @POST("flash/delete") - suspend fun flashDelete( + public suspend fun flashDelete( @Body flashDeleteRequest: FlashDeleteRequest, ): Unit @@ -38,7 +38,7 @@ internal interface FlashsApi { * @param flashDeleteRequest * @return [Flash] */ @POST("flash/show") - suspend fun flashShow( + public suspend fun flashShow( @Body flashDeleteRequest: FlashDeleteRequest, ): Flash } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FollowingApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FollowingApi.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FollowingApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FollowingApi.kt index 362f9b022d..b75587d93c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FollowingApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/FollowingApi.kt @@ -7,7 +7,7 @@ import dev.dimension.flare.data.network.misskey.api.model.FollowingRequestsList2 import dev.dimension.flare.data.network.misskey.api.model.FollowingRequestsListRequest import dev.dimension.flare.data.network.misskey.api.model.UserLite -internal interface FollowingApi { +public interface FollowingApi { /** * following/create * No description provided. **Credential required**: *Yes* / **Permission**: *write:following* @@ -23,7 +23,7 @@ internal interface FollowingApi { * @param adminAccountsDeleteRequest * @return [UserLite] */ @POST("following/create") - suspend fun followingCreate( + public suspend fun followingCreate( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): UserLite @@ -42,7 +42,7 @@ internal interface FollowingApi { * @param adminAccountsDeleteRequest * @return [UserLite] */ @POST("following/delete") - suspend fun followingDelete( + public suspend fun followingDelete( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): UserLite @@ -61,7 +61,7 @@ internal interface FollowingApi { * @param adminAccountsDeleteRequest * @return [UserLite] */ @POST("following/invalidate") - suspend fun followingInvalidate( + public suspend fun followingInvalidate( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): UserLite @@ -79,7 +79,7 @@ internal interface FollowingApi { * @param adminAccountsDeleteRequest * @return [Unit] */ @POST("following/requests/accept") - suspend fun followingRequestsAccept( + public suspend fun followingRequestsAccept( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): Unit @@ -97,7 +97,7 @@ internal interface FollowingApi { * @param adminAccountsDeleteRequest * @return [UserLite] */ @POST("following/requests/cancel") - suspend fun followingRequestsCancel( + public suspend fun followingRequestsCancel( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): UserLite @@ -115,7 +115,7 @@ internal interface FollowingApi { * @param followingRequestsListRequest * @return [kotlin.collections.List] */ @POST("following/requests/list") - suspend fun followingRequestsList( + public suspend fun followingRequestsList( @Body followingRequestsListRequest: FollowingRequestsListRequest, ): kotlin.collections.List @@ -133,7 +133,7 @@ internal interface FollowingApi { * @param adminAccountsDeleteRequest * @return [Unit] */ @POST("following/requests/reject") - suspend fun followingRequestsReject( + public suspend fun followingRequestsReject( @Body adminAccountsDeleteRequest: AdminAccountsDeleteRequest, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/GalleryApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/GalleryApi.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/GalleryApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/GalleryApi.kt index 2ee1d4a74c..02d5157f42 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/GalleryApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/GalleryApi.kt @@ -8,7 +8,7 @@ import dev.dimension.flare.data.network.misskey.api.model.GalleryPostsCreateRequ import dev.dimension.flare.data.network.misskey.api.model.GalleryPostsDeleteRequest import dev.dimension.flare.data.network.misskey.api.model.GalleryPostsUpdateRequest -internal interface GalleryApi { +public interface GalleryApi { /** * gallery/featured * No description provided. **Credential required**: *No* @@ -23,7 +23,7 @@ internal interface GalleryApi { * @param body * @return [kotlin.collections.List] */ @POST("gallery/featured") - suspend fun galleryFeatured( + public suspend fun galleryFeatured( @Body body: kotlin.Any, ): kotlin.collections.List @@ -41,7 +41,7 @@ internal interface GalleryApi { * @param body * @return [kotlin.collections.List] */ @POST("gallery/popular") - suspend fun galleryPopular( + public suspend fun galleryPopular( @Body body: kotlin.Any, ): kotlin.collections.List @@ -59,7 +59,7 @@ internal interface GalleryApi { * @param adminAdListRequest * @return [kotlin.collections.List] */ @POST("gallery/posts") - suspend fun galleryPosts( + public suspend fun galleryPosts( @Body adminAdListRequest: AdminAdListRequest, ): kotlin.collections.List @@ -78,7 +78,7 @@ internal interface GalleryApi { * @param galleryPostsCreateRequest * @return [GalleryPost] */ @POST("gallery/posts/create") - suspend fun galleryPostsCreate( + public suspend fun galleryPostsCreate( @Body galleryPostsCreateRequest: GalleryPostsCreateRequest, ): GalleryPost @@ -96,7 +96,7 @@ internal interface GalleryApi { * @param galleryPostsDeleteRequest * @return [Unit] */ @POST("gallery/posts/delete") - suspend fun galleryPostsDelete( + public suspend fun galleryPostsDelete( @Body galleryPostsDeleteRequest: GalleryPostsDeleteRequest, ): Unit @@ -114,7 +114,7 @@ internal interface GalleryApi { * @param galleryPostsDeleteRequest * @return [Unit] */ @POST("gallery/posts/like") - suspend fun galleryPostsLike( + public suspend fun galleryPostsLike( @Body galleryPostsDeleteRequest: GalleryPostsDeleteRequest, ): Unit @@ -132,7 +132,7 @@ internal interface GalleryApi { * @param galleryPostsDeleteRequest * @return [GalleryPost] */ @POST("gallery/posts/show") - suspend fun galleryPostsShow( + public suspend fun galleryPostsShow( @Body galleryPostsDeleteRequest: GalleryPostsDeleteRequest, ): GalleryPost @@ -150,7 +150,7 @@ internal interface GalleryApi { * @param galleryPostsDeleteRequest * @return [Unit] */ @POST("gallery/posts/unlike") - suspend fun galleryPostsUnlike( + public suspend fun galleryPostsUnlike( @Body galleryPostsDeleteRequest: GalleryPostsDeleteRequest, ): Unit @@ -169,7 +169,7 @@ internal interface GalleryApi { * @param galleryPostsUpdateRequest * @return [GalleryPost] */ @POST("gallery/posts/update") - suspend fun galleryPostsUpdate( + public suspend fun galleryPostsUpdate( @Body galleryPostsUpdateRequest: GalleryPostsUpdateRequest, ): GalleryPost } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/HashtagsApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/HashtagsApi.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/HashtagsApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/HashtagsApi.kt index a7b4e92284..1dfc76fa33 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/HashtagsApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/HashtagsApi.kt @@ -11,7 +11,7 @@ import dev.dimension.flare.data.network.misskey.api.model.HashtagsTrend200Respon import dev.dimension.flare.data.network.misskey.api.model.HashtagsUsersRequest import dev.dimension.flare.data.network.misskey.api.model.User -internal interface HashtagsApi { +public interface HashtagsApi { /** * hashtags/list * No description provided. **Credential required**: *No* @@ -26,7 +26,7 @@ internal interface HashtagsApi { * @param hashtagsListRequest * @return [kotlin.collections.List] */ @POST("hashtags/list") - suspend fun hashtagsList( + public suspend fun hashtagsList( @Body hashtagsListRequest: HashtagsListRequest, ): kotlin.collections.List @@ -44,7 +44,7 @@ internal interface HashtagsApi { * @param hashtagsSearchRequest * @return [kotlin.collections.List] */ @POST("hashtags/search") - suspend fun hashtagsSearch( + public suspend fun hashtagsSearch( @Body hashtagsSearchRequest: HashtagsSearchRequest, ): kotlin.collections.List @@ -62,7 +62,7 @@ internal interface HashtagsApi { * @param hashtagsShowRequest * @return [Hashtag] */ @POST("hashtags/show") - suspend fun hashtagsShow( + public suspend fun hashtagsShow( @Body hashtagsShowRequest: HashtagsShowRequest, ): Hashtag @@ -80,7 +80,7 @@ internal interface HashtagsApi { * @param body * @return [kotlin.collections.List] */ @GET("hashtags/trend") - suspend fun hashtagsTrend(): kotlin.collections.List + public suspend fun hashtagsTrend(): kotlin.collections.List /** * hashtags/users @@ -96,7 +96,7 @@ internal interface HashtagsApi { * @param hashtagsUsersRequest * @return [kotlin.collections.List] */ @POST("hashtags/users") - suspend fun hashtagsUsers( + public suspend fun hashtagsUsers( @Body hashtagsUsersRequest: HashtagsUsersRequest, ): kotlin.collections.List } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ListsApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ListsApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ListsApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ListsApi.kt index 3e21756ef8..47e7e41af7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ListsApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ListsApi.kt @@ -12,7 +12,7 @@ import dev.dimension.flare.data.network.misskey.api.model.UsersListsPullRequest import dev.dimension.flare.data.network.misskey.api.model.UsersListsShowRequest import dev.dimension.flare.data.network.misskey.api.model.UsersListsUpdateRequest -internal interface ListsApi { +public interface ListsApi { /** * users/lists/create * Create a new list of users. **Credential required**: *Yes* / **Permission**: *write:account* @@ -27,7 +27,7 @@ internal interface ListsApi { * @param usersListsCreateRequest * @return [UserList] */ @POST("users/lists/create") - suspend fun usersListsCreate( + public suspend fun usersListsCreate( @Body usersListsCreateRequest: UsersListsCreateRequest, ): UserList @@ -45,7 +45,7 @@ internal interface ListsApi { * @param usersListsDeleteRequest * @return [Unit] */ @POST("users/lists/delete") - suspend fun usersListsDelete( + public suspend fun usersListsDelete( @Body usersListsDeleteRequest: UsersListsDeleteRequest, ): Unit @@ -63,7 +63,7 @@ internal interface ListsApi { * @param usersListsListRequest * @return [kotlin.collections.List] */ @POST("users/lists/list") - suspend fun usersListsList( + public suspend fun usersListsList( @Body usersListsListRequest: UsersListsListRequest, ): kotlin.collections.List @@ -81,7 +81,7 @@ internal interface ListsApi { * @param usersListsPullRequest * @return [Unit] */ @POST("users/lists/pull") - suspend fun usersListsPull( + public suspend fun usersListsPull( @Body usersListsPullRequest: UsersListsPullRequest, ): Unit @@ -100,7 +100,7 @@ internal interface ListsApi { * @param usersListsPullRequest * @return [Unit] */ @POST("users/lists/push") - suspend fun usersListsPush( + public suspend fun usersListsPush( @Body usersListsPullRequest: UsersListsPullRequest, ): Unit @@ -118,7 +118,7 @@ internal interface ListsApi { * @param usersListsShowRequest * @return [UserList] */ @POST("users/lists/show") - suspend fun usersListsShow( + public suspend fun usersListsShow( @Body usersListsShowRequest: UsersListsShowRequest, ): UserList @@ -136,12 +136,12 @@ internal interface ListsApi { * @param usersListsUpdateRequest * @return [UserList] */ @POST("users/lists/update") - suspend fun usersListsUpdate( + public suspend fun usersListsUpdate( @Body usersListsUpdateRequest: UsersListsUpdateRequest, ): UserList @POST("users/lists/get-memberships") - suspend fun usersListsGetMemberships( + public suspend fun usersListsGetMemberships( @Body usersListsMembershipRequest: UsersListsMembershipRequest, ): kotlin.collections.List } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/MetaApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/MetaApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/MetaApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/MetaApi.kt index 38a6dc5bf4..eaf6550ecd 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/MetaApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/MetaApi.kt @@ -20,7 +20,7 @@ import dev.dimension.flare.data.network.misskey.api.model.MetaRequest import dev.dimension.flare.data.network.misskey.api.model.Ping200Response import dev.dimension.flare.data.network.misskey.api.model.Stats200Response -internal interface MetaApi { +public interface MetaApi { /** * admin/meta * No description provided. **Credential required**: *Yes* @@ -35,7 +35,7 @@ internal interface MetaApi { * @param body * @return [AdminMeta200Response] */ @POST("admin/meta") - suspend fun adminMeta( + public suspend fun adminMeta( @Body body: kotlin.Any, ): AdminMeta200Response @@ -53,7 +53,7 @@ internal interface MetaApi { * @param announcementsRequest * @return [kotlin.collections.List] */ @POST("announcements") - suspend fun announcements( + public suspend fun announcements( @Body announcementsRequest: AnnouncementsRequest, ): kotlin.collections.List @@ -71,7 +71,7 @@ internal interface MetaApi { * @param emojiRequest * @return [EmojiDetailed] */ @POST("emoji") - suspend fun emoji( + public suspend fun emoji( @Body emojiRequest: EmojiRequest, ): EmojiDetailed @@ -89,7 +89,7 @@ internal interface MetaApi { * @return [Emojis200Response] */ @GET("emojis") - suspend fun emojis(): Emojis200Response + public suspend fun emojis(): Emojis200Response /** * endpoint @@ -105,7 +105,7 @@ internal interface MetaApi { * @param endpointRequest * @return [Unit] */ @POST("endpoint") - suspend fun endpoint( + public suspend fun endpoint( @Body endpointRequest: EndpointRequest, ): Unit @@ -123,7 +123,7 @@ internal interface MetaApi { * @param body * @return [kotlin.collections.List] */ @POST("endpoints") - suspend fun endpoints( + public suspend fun endpoints( @Body body: kotlin.Any, ): kotlin.collections.List @@ -141,7 +141,7 @@ internal interface MetaApi { * @param fetchRssRequest * @return [Unit] */ @POST("fetch-rss") - suspend fun fetchRss( + public suspend fun fetchRss( @Body fetchRssRequest: FetchRssRequest, ): Unit @@ -159,7 +159,7 @@ internal interface MetaApi { * @param body * @return [Unit] */ @POST("get-online-users-count") - suspend fun getOnlineUsersCount( + public suspend fun getOnlineUsersCount( @Body body: kotlin.Any, ): Unit @@ -177,7 +177,7 @@ internal interface MetaApi { * @param body * @return [AdminInviteCreate200ResponseInner] */ @POST("invite/create") - suspend fun inviteCreate( + public suspend fun inviteCreate( @Body body: kotlin.Any, ): AdminInviteCreate200ResponseInner @@ -195,7 +195,7 @@ internal interface MetaApi { * @param inviteDeleteRequest * @return [Unit] */ @POST("invite/delete") - suspend fun inviteDelete( + public suspend fun inviteDelete( @Body inviteDeleteRequest: InviteDeleteRequest, ): Unit @@ -213,7 +213,7 @@ internal interface MetaApi { * @param body * @return [InviteLimit200Response] */ @POST("invite/limit") - suspend fun inviteLimit( + public suspend fun inviteLimit( @Body body: kotlin.Any, ): InviteLimit200Response @@ -231,7 +231,7 @@ internal interface MetaApi { * @param blockingListRequest * @return [kotlin.collections.List] */ @POST("invite/list") - suspend fun inviteList( + public suspend fun inviteList( @Body blockingListRequest: BlockingListRequest, ): kotlin.collections.List @@ -249,7 +249,7 @@ internal interface MetaApi { * @param metaRequest * @return [Meta200Response] */ @POST("meta") - suspend fun meta( + public suspend fun meta( @Body metaRequest: MetaRequest, ): Meta200Response @@ -267,7 +267,7 @@ internal interface MetaApi { * @param body * @return [Ping200Response] */ @POST("ping") - suspend fun ping( + public suspend fun ping( @Body body: kotlin.Any, ): Ping200Response @@ -285,7 +285,7 @@ internal interface MetaApi { * @param body * @return [Unit] */ @POST("server-info") - suspend fun serverInfo( + public suspend fun serverInfo( @Body body: kotlin.Any, ): Unit @@ -303,7 +303,7 @@ internal interface MetaApi { * @param body * @return [Stats200Response] */ @POST("stats") - suspend fun stats( + public suspend fun stats( @Body body: kotlin.Any, ): Stats200Response } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/MisskeyInstanceAppApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/MisskeyInstanceAppApi.kt similarity index 68% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/MisskeyInstanceAppApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/MisskeyInstanceAppApi.kt index 105a082423..e2b5424b48 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/MisskeyInstanceAppApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/MisskeyInstanceAppApi.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.data.network.misskey.api import de.jensklingenberg.ktorfit.http.GET import dev.dimension.flare.data.network.misskey.api.model.MisskeyInstance -internal interface MisskeyInstanceAppApi { +public interface MisskeyInstanceAppApi { @GET("instances.json") - suspend fun instances(): MisskeyInstance + public suspend fun instances(): MisskeyInstance } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NonProductiveApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NonProductiveApi.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NonProductiveApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NonProductiveApi.kt index de8524ea88..7c420f2d66 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NonProductiveApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NonProductiveApi.kt @@ -4,7 +4,7 @@ import de.jensklingenberg.ktorfit.http.Body import de.jensklingenberg.ktorfit.http.POST import dev.dimension.flare.data.network.misskey.api.model.TestRequest -internal interface NonProductiveApi { +public interface NonProductiveApi { /** * reset-db * Only available when running with <code>NODE_ENV=testing</code>. Reset the database and flush Redis. **Credential required**: *No* @@ -19,7 +19,7 @@ internal interface NonProductiveApi { * @param body * @return [Unit] */ @POST("reset-db") - suspend fun resetDb( + public suspend fun resetDb( @Body body: kotlin.Any, ): Unit @@ -37,7 +37,7 @@ internal interface NonProductiveApi { * @param testRequest * @return [Unit] */ @POST("test") - suspend fun test( + public suspend fun test( @Body testRequest: TestRequest, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NotesApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NotesApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NotesApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NotesApi.kt index 747c674945..6abaa69df2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NotesApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NotesApi.kt @@ -27,7 +27,7 @@ import dev.dimension.flare.data.network.misskey.api.model.NotesState200Response import dev.dimension.flare.data.network.misskey.api.model.NotesTranslateRequest import dev.dimension.flare.data.network.misskey.api.model.NotesUserListTimelineRequest -internal interface NotesApi { +public interface NotesApi { /** * channels/timeline * No description provided. **Credential required**: *No* @@ -42,7 +42,7 @@ internal interface NotesApi { * @param channelsTimelineRequest * @return [kotlin.collections.List] */ @POST("channels/timeline") - suspend fun channelsTimeline( + public suspend fun channelsTimeline( @Header("Content-Type") contentType: kotlin.String = "application/json", @Body channelsTimelineRequest: ChannelsTimelineRequest, ): kotlin.collections.List @@ -61,7 +61,7 @@ internal interface NotesApi { * @param notesRequest * @return [kotlin.collections.List] */ @POST("notes") - suspend fun notes( + public suspend fun notes( @Body notesRequest: NotesRequest, ): kotlin.collections.List @@ -79,7 +79,7 @@ internal interface NotesApi { * @param notesChildrenRequest * @return [kotlin.collections.List] */ @POST("notes/children") - suspend fun notesChildren( + public suspend fun notesChildren( @Body notesChildrenRequest: NotesChildrenRequest, ): kotlin.collections.List @@ -97,7 +97,7 @@ internal interface NotesApi { * @param notesConversationRequest * @return [kotlin.collections.List] */ @POST("notes/conversation") - suspend fun notesConversation( + public suspend fun notesConversation( @Body notesConversationRequest: NotesConversationRequest, ): kotlin.collections.List @@ -116,7 +116,7 @@ internal interface NotesApi { * @param notesCreateRequest * @return [NotesCreate200Response] */ @POST("notes/create") - suspend fun notesCreate( + public suspend fun notesCreate( @Body notesCreateRequest: NotesCreateRequest, ): NotesCreate200Response @@ -135,7 +135,7 @@ internal interface NotesApi { * @param ipinRequest * @return [Unit] */ @POST("notes/delete") - suspend fun notesDelete( + public suspend fun notesDelete( @Body ipinRequest: IPinRequest, ): Unit @@ -154,7 +154,7 @@ internal interface NotesApi { * @param ipinRequest * @return [Unit] */ @POST("notes/favorites/create") - suspend fun notesFavoritesCreate( + public suspend fun notesFavoritesCreate( @Body ipinRequest: IPinRequest, ): Unit @@ -172,7 +172,7 @@ internal interface NotesApi { * @param ipinRequest * @return [Unit] */ @POST("notes/favorites/delete") - suspend fun notesFavoritesDelete( + public suspend fun notesFavoritesDelete( @Body ipinRequest: IPinRequest, ): Unit @@ -190,7 +190,7 @@ internal interface NotesApi { * @param notesFeaturedRequest * @return [kotlin.collections.List] */ @POST("notes/featured") - suspend fun notesFeatured( + public suspend fun notesFeatured( @Body notesFeaturedRequest: NotesFeaturedRequest, ): kotlin.collections.List @@ -208,7 +208,7 @@ internal interface NotesApi { * @param notesGlobalTimelineRequest * @return [kotlin.collections.List] */ @POST("notes/global-timeline") - suspend fun notesGlobalTimeline( + public suspend fun notesGlobalTimeline( @Body notesGlobalTimelineRequest: NotesGlobalTimelineRequest, ): kotlin.collections.List @@ -226,7 +226,7 @@ internal interface NotesApi { * @param notesHybridTimelineRequest * @return [kotlin.collections.List] */ @POST("notes/hybrid-timeline") - suspend fun notesHybridTimeline( + public suspend fun notesHybridTimeline( @Body notesHybridTimelineRequest: NotesHybridTimelineRequest, ): kotlin.collections.List @@ -244,7 +244,7 @@ internal interface NotesApi { * @param notesLocalTimelineRequest * @return [kotlin.collections.List] */ @POST("notes/local-timeline") - suspend fun notesLocalTimeline( + public suspend fun notesLocalTimeline( @Body notesLocalTimelineRequest: NotesLocalTimelineRequest, ): kotlin.collections.List @@ -262,7 +262,7 @@ internal interface NotesApi { * @param notesMentionsRequest * @return [kotlin.collections.List] */ @POST("notes/mentions") - suspend fun notesMentions( + public suspend fun notesMentions( @Body notesMentionsRequest: NotesMentionsRequest, ): kotlin.collections.List @@ -280,7 +280,7 @@ internal interface NotesApi { * @param myAppsRequest * @return [kotlin.collections.List] */ @POST("notes/polls/recommendation") - suspend fun notesPollsRecommendation( + public suspend fun notesPollsRecommendation( @Body myAppsRequest: MyAppsRequest, ): kotlin.collections.List @@ -298,7 +298,7 @@ internal interface NotesApi { * @param notesPollsVoteRequest * @return [Unit] */ @POST("notes/polls/vote") - suspend fun notesPollsVote( + public suspend fun notesPollsVote( @Body notesPollsVoteRequest: NotesPollsVoteRequest, ): Unit @@ -316,7 +316,7 @@ internal interface NotesApi { * @param notesReactionsRequest * @return [kotlin.collections.List] */ @POST("notes/reactions") - suspend fun notesReactions( + public suspend fun notesReactions( @Body notesReactionsRequest: NotesReactionsRequest, ): kotlin.collections.List @@ -334,7 +334,7 @@ internal interface NotesApi { * @param notesChildrenRequest * @return [kotlin.collections.List] */ @POST("notes/renotes") - suspend fun notesRenotes( + public suspend fun notesRenotes( @Body notesChildrenRequest: NotesChildrenRequest, ): kotlin.collections.List @@ -352,7 +352,7 @@ internal interface NotesApi { * @param notesRepliesRequest * @return [kotlin.collections.List] */ @POST("notes/replies") - suspend fun notesReplies( + public suspend fun notesReplies( @Body notesRepliesRequest: NotesRepliesRequest, ): kotlin.collections.List @@ -370,7 +370,7 @@ internal interface NotesApi { * @param notesSearchRequest * @return [kotlin.collections.List] */ @POST("notes/search") - suspend fun notesSearch( + public suspend fun notesSearch( @Body notesSearchRequest: NotesSearchRequest, ): kotlin.collections.List @@ -388,7 +388,7 @@ internal interface NotesApi { * @param notesSearchByTagRequest * @return [kotlin.collections.List] */ @POST("notes/search-by-tag") - suspend fun notesSearchByTag( + public suspend fun notesSearchByTag( @Body notesSearchByTagRequest: NotesSearchByTagRequest, ): kotlin.collections.List @@ -406,7 +406,7 @@ internal interface NotesApi { * @param ipinRequest * @return [Note] */ @POST("notes/show") - suspend fun notesShow( + public suspend fun notesShow( @Body ipinRequest: IPinRequest, ): Note @@ -424,7 +424,7 @@ internal interface NotesApi { * @param ipinRequest * @return [NotesState200Response] */ @POST("notes/state") - suspend fun notesState( + public suspend fun notesState( @Body ipinRequest: IPinRequest, ): NotesState200Response @@ -443,7 +443,7 @@ internal interface NotesApi { * @param ipinRequest * @return [Unit] */ @POST("notes/thread-muting/create") - suspend fun notesThreadMutingCreate( + public suspend fun notesThreadMutingCreate( @Body ipinRequest: IPinRequest, ): Unit @@ -461,7 +461,7 @@ internal interface NotesApi { * @param ipinRequest * @return [Unit] */ @POST("notes/thread-muting/delete") - suspend fun notesThreadMutingDelete( + public suspend fun notesThreadMutingDelete( @Body ipinRequest: IPinRequest, ): Unit @@ -479,7 +479,7 @@ internal interface NotesApi { * @param notesHybridTimelineRequest * @return [kotlin.collections.List] */ @POST("notes/timeline") - suspend fun notesTimeline( + public suspend fun notesTimeline( @Body notesHybridTimelineRequest: NotesHybridTimelineRequest, ): kotlin.collections.List @@ -497,7 +497,7 @@ internal interface NotesApi { * @param notesTranslateRequest * @return [kotlin.Any] */ @POST("notes/translate") - suspend fun notesTranslate( + public suspend fun notesTranslate( @Body notesTranslateRequest: NotesTranslateRequest, ): kotlin.Any @@ -516,7 +516,7 @@ internal interface NotesApi { * @param ipinRequest * @return [Unit] */ @POST("notes/unrenote") - suspend fun notesUnrenote( + public suspend fun notesUnrenote( @Body ipinRequest: IPinRequest, ): Unit @@ -534,7 +534,7 @@ internal interface NotesApi { * @param notesUserListTimelineRequest * @return [kotlin.collections.List] */ @POST("notes/user-list-timeline") - suspend fun notesUserListTimeline( + public suspend fun notesUserListTimeline( @Body notesUserListTimelineRequest: NotesUserListTimelineRequest, ): kotlin.collections.List @@ -552,7 +552,7 @@ internal interface NotesApi { * @param ipinRequest * @return [Unit] */ @POST("promo/read") - suspend fun promoRead( + public suspend fun promoRead( @Body ipinRequest: IPinRequest, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NotificationsApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NotificationsApi.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NotificationsApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NotificationsApi.kt index 15e50155c6..cae20fc353 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NotificationsApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/NotificationsApi.kt @@ -4,7 +4,7 @@ import de.jensklingenberg.ktorfit.http.Body import de.jensklingenberg.ktorfit.http.POST import dev.dimension.flare.data.network.misskey.api.model.NotificationsCreateRequest -internal interface NotificationsApi { +public interface NotificationsApi { /** * notifications/create * No description provided. **Credential required**: *Yes* / **Permission**: *write:notifications* @@ -19,7 +19,7 @@ internal interface NotificationsApi { * @param notificationsCreateRequest * @return [Unit] */ @POST("notifications/create") - suspend fun notificationsCreate( + public suspend fun notificationsCreate( @Body notificationsCreateRequest: NotificationsCreateRequest, ): Unit @@ -37,7 +37,7 @@ internal interface NotificationsApi { * @param body * @return [Unit] */ @POST("notifications/mark-all-as-read") - suspend fun notificationsMarkAllAsRead( + public suspend fun notificationsMarkAllAsRead( @Body body: kotlin.Any, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/PagesApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/PagesApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/PagesApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/PagesApi.kt index 5a0441bc2d..959645a197 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/PagesApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/PagesApi.kt @@ -8,7 +8,7 @@ import dev.dimension.flare.data.network.misskey.api.model.PagesDeleteRequest import dev.dimension.flare.data.network.misskey.api.model.PagesShowRequest import dev.dimension.flare.data.network.misskey.api.model.PagesUpdateRequest -internal interface PagesApi { +public interface PagesApi { /** * pages/create * No description provided. **Credential required**: *Yes* / **Permission**: *write:pages* @@ -24,7 +24,7 @@ internal interface PagesApi { * @param pagesCreateRequest * @return [Page] */ @POST("pages/create") - suspend fun pagesCreate( + public suspend fun pagesCreate( @Body pagesCreateRequest: PagesCreateRequest, ): Page @@ -42,7 +42,7 @@ internal interface PagesApi { * @param pagesDeleteRequest * @return [Unit] */ @POST("pages/delete") - suspend fun pagesDelete( + public suspend fun pagesDelete( @Body pagesDeleteRequest: PagesDeleteRequest, ): Unit @@ -60,7 +60,7 @@ internal interface PagesApi { * @param body * @return [kotlin.collections.List] */ @POST("pages/featured") - suspend fun pagesFeatured( + public suspend fun pagesFeatured( @Body body: kotlin.Any, ): kotlin.collections.List @@ -78,7 +78,7 @@ internal interface PagesApi { * @param pagesDeleteRequest * @return [Unit] */ @POST("pages/like") - suspend fun pagesLike( + public suspend fun pagesLike( @Body pagesDeleteRequest: PagesDeleteRequest, ): Unit @@ -96,7 +96,7 @@ internal interface PagesApi { * @param pagesShowRequest * @return [Page] */ @POST("pages/show") - suspend fun pagesShow( + public suspend fun pagesShow( @Body pagesShowRequest: PagesShowRequest, ): Page @@ -114,7 +114,7 @@ internal interface PagesApi { * @param pagesDeleteRequest * @return [Unit] */ @POST("pages/unlike") - suspend fun pagesUnlike( + public suspend fun pagesUnlike( @Body pagesDeleteRequest: PagesDeleteRequest, ): Unit @@ -133,7 +133,7 @@ internal interface PagesApi { * @param pagesUpdateRequest * @return [Unit] */ @POST("pages/update") - suspend fun pagesUpdate( + public suspend fun pagesUpdate( @Body pagesUpdateRequest: PagesUpdateRequest, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ReactionsApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ReactionsApi.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ReactionsApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ReactionsApi.kt index 959b7c6392..156318dd6e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ReactionsApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ReactionsApi.kt @@ -5,7 +5,7 @@ import de.jensklingenberg.ktorfit.http.POST import dev.dimension.flare.data.network.misskey.api.model.IPinRequest import dev.dimension.flare.data.network.misskey.api.model.NotesReactionsCreateRequest -internal interface ReactionsApi { +public interface ReactionsApi { /** * notes/reactions/create * No description provided. **Credential required**: *Yes* / **Permission**: *write:reactions* @@ -20,7 +20,7 @@ internal interface ReactionsApi { * @param notesReactionsCreateRequest * @return [Unit] */ @POST("notes/reactions/create") - suspend fun notesReactionsCreate( + public suspend fun notesReactionsCreate( @Body notesReactionsCreateRequest: NotesReactionsCreateRequest, ): Unit @@ -39,7 +39,7 @@ internal interface ReactionsApi { * @param ipinRequest * @return [Unit] */ @POST("notes/reactions/delete") - suspend fun notesReactionsDelete( + public suspend fun notesReactionsDelete( @Body ipinRequest: IPinRequest, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ResetPasswordApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ResetPasswordApi.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ResetPasswordApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ResetPasswordApi.kt index 2120d41502..adec8e1dfd 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ResetPasswordApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/ResetPasswordApi.kt @@ -5,7 +5,7 @@ import de.jensklingenberg.ktorfit.http.POST import dev.dimension.flare.data.network.misskey.api.model.RequestResetPasswordRequest import dev.dimension.flare.data.network.misskey.api.model.ResetPasswordRequest -internal interface ResetPasswordApi { +public interface ResetPasswordApi { /** * request-reset-password * Request a users password to be reset. **Credential required**: *No* @@ -21,7 +21,7 @@ internal interface ResetPasswordApi { * @param requestResetPasswordRequest * @return [Unit] */ @POST("request-reset-password") - suspend fun requestResetPassword( + public suspend fun requestResetPassword( @Body requestResetPasswordRequest: RequestResetPasswordRequest, ): Unit @@ -39,7 +39,7 @@ internal interface ResetPasswordApi { * @param resetPasswordRequest * @return [Unit] */ @POST("reset-password") - suspend fun resetPassword( + public suspend fun resetPassword( @Body resetPasswordRequest: ResetPasswordRequest, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/RoleApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/RoleApi.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/RoleApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/RoleApi.kt index d981d3f77e..773596b371 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/RoleApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/RoleApi.kt @@ -7,7 +7,7 @@ import dev.dimension.flare.data.network.misskey.api.model.AdminRolesUsersRequest import dev.dimension.flare.data.network.misskey.api.model.Note import dev.dimension.flare.data.network.misskey.api.model.RolesNotesRequest -internal interface RoleApi { +public interface RoleApi { /** * roles/list * No description provided. **Credential required**: *Yes* @@ -22,7 +22,7 @@ internal interface RoleApi { * @param body * @return [Unit] */ @POST("roles/list") - suspend fun rolesList( + public suspend fun rolesList( @Body body: kotlin.Any, ): Unit @@ -40,7 +40,7 @@ internal interface RoleApi { * @param rolesNotesRequest * @return [kotlin.collections.List] */ @POST("roles/notes") - suspend fun rolesNotes( + public suspend fun rolesNotes( @Body rolesNotesRequest: RolesNotesRequest, ): kotlin.collections.List @@ -58,7 +58,7 @@ internal interface RoleApi { * @param adminRolesDeleteRequest * @return [Unit] */ @POST("roles/show") - suspend fun rolesShow( + public suspend fun rolesShow( @Body adminRolesDeleteRequest: AdminRolesDeleteRequest, ): Unit @@ -76,7 +76,7 @@ internal interface RoleApi { * @param adminRolesUsersRequest * @return [Unit] */ @POST("roles/users") - suspend fun rolesUsers( + public suspend fun rolesUsers( @Body adminRolesUsersRequest: AdminRolesUsersRequest, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/UsersApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/UsersApi.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/UsersApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/UsersApi.kt index c507cf8aca..45d28db909 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/UsersApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/UsersApi.kt @@ -31,7 +31,7 @@ import dev.dimension.flare.data.network.misskey.api.model.UsersSearchRequest import dev.dimension.flare.data.network.misskey.api.model.UsersShow200Response import dev.dimension.flare.data.network.misskey.api.model.UsersShowRequest -internal interface UsersApi { +public interface UsersApi { /** * email-address/available * No description provided. **Credential required**: *No* @@ -46,7 +46,7 @@ internal interface UsersApi { * @param emailAddressAvailableRequest * @return [EmailAddressAvailable200Response] */ @POST("email-address/available") - suspend fun emailAddressAvailable( + public suspend fun emailAddressAvailable( @Body emailAddressAvailableRequest: EmailAddressAvailableRequest, ): EmailAddressAvailable200Response @@ -64,7 +64,7 @@ internal interface UsersApi { * @param body * @return [kotlin.collections.List] */ @POST("pinned-users") - suspend fun pinnedUsers( + public suspend fun pinnedUsers( @Body body: PinnedUsersRequest, ): kotlin.collections.List @@ -82,7 +82,7 @@ internal interface UsersApi { * @param body * @return [kotlin.Any] */ @POST("retention") - suspend fun retention( + public suspend fun retention( @Body body: kotlin.Any, ): kotlin.Any @@ -100,7 +100,7 @@ internal interface UsersApi { * @param usernameAvailableRequest * @return [UsernameAvailable200Response] */ @POST("username/available") - suspend fun usernameAvailable( + public suspend fun usernameAvailable( @Body usernameAvailableRequest: UsernameAvailableRequest, ): UsernameAvailable200Response @@ -118,7 +118,7 @@ internal interface UsersApi { * @param usersRequest * @return [kotlin.collections.List] */ @POST("users") - suspend fun users( + public suspend fun users( @Body usersRequest: UsersRequest, ): kotlin.collections.List @@ -136,7 +136,7 @@ internal interface UsersApi { * @param usersClipsRequest * @return [kotlin.collections.List] */ @POST("users/clips") - suspend fun usersClips( + public suspend fun usersClips( @Body usersClipsRequest: UsersClipsRequest, ): kotlin.collections.List @@ -154,7 +154,7 @@ internal interface UsersApi { * @param usersFollowersRequest * @return [kotlin.collections.List] */ @POST("users/followers") - suspend fun usersFollowers( + public suspend fun usersFollowers( @Body usersFollowersRequest: UsersFollowersRequest, ): kotlin.collections.List @@ -172,7 +172,7 @@ internal interface UsersApi { * @param usersFollowersRequest * @return [kotlin.collections.List] */ @POST("users/following") - suspend fun usersFollowing( + public suspend fun usersFollowing( @Body usersFollowersRequest: UsersFollowersRequest, ): kotlin.collections.List @@ -190,7 +190,7 @@ internal interface UsersApi { * @param usersClipsRequest * @return [kotlin.collections.List] */ @POST("users/gallery/posts") - suspend fun usersGalleryPosts( + public suspend fun usersGalleryPosts( @Body usersClipsRequest: UsersClipsRequest, ): kotlin.collections.List @@ -208,7 +208,7 @@ internal interface UsersApi { * @param usersGetFrequentlyRepliedUsersRequest * @return [kotlin.collections.List] */ @POST("users/get-frequently-replied-users") - suspend fun usersGetFrequentlyRepliedUsers( + public suspend fun usersGetFrequentlyRepliedUsers( @Body usersGetFrequentlyRepliedUsersRequest: UsersGetFrequentlyRepliedUsersRequest, ): kotlin.collections.List @@ -226,7 +226,7 @@ internal interface UsersApi { * @param usersNotesRequest * @return [kotlin.collections.List] */ @POST("users/notes") - suspend fun usersNotes( + public suspend fun usersNotes( @Body usersNotesRequest: UsersNotesRequest, ): kotlin.collections.List @@ -244,7 +244,7 @@ internal interface UsersApi { * @param usersClipsRequest * @return [kotlin.collections.List] */ @POST("users/pages") - suspend fun usersPages( + public suspend fun usersPages( @Body usersClipsRequest: UsersClipsRequest, ): kotlin.collections.List @@ -262,7 +262,7 @@ internal interface UsersApi { * @param usersReactionsRequest * @return [kotlin.collections.List] */ @POST("users/reactions") - suspend fun usersReactions( + public suspend fun usersReactions( @Body usersReactionsRequest: UsersReactionsRequest, ): kotlin.collections.List @@ -280,7 +280,7 @@ internal interface UsersApi { * @param myAppsRequest * @return [kotlin.collections.List] */ @POST("users/recommendation") - suspend fun usersRecommendation( + public suspend fun usersRecommendation( @Body myAppsRequest: MyAppsRequest, ): kotlin.collections.List @@ -298,7 +298,7 @@ internal interface UsersApi { * @param usersRelationRequest * @return [UsersRelation200Response] */ @POST("users/relation") - suspend fun usersRelation( + public suspend fun usersRelation( @Body usersRelationRequest: UsersRelationRequest, ): UsersRelation200Response @@ -316,7 +316,7 @@ internal interface UsersApi { * @param usersReportAbuseRequest * @return [Unit] */ @POST("users/report-abuse") - suspend fun usersReportAbuse( + public suspend fun usersReportAbuse( @Body usersReportAbuseRequest: UsersReportAbuseRequest, ): Unit @@ -334,7 +334,7 @@ internal interface UsersApi { * @param usersSearchRequest * @return [kotlin.collections.List] */ @POST("users/search") - suspend fun usersSearch( + public suspend fun usersSearch( @Body usersSearchRequest: UsersSearchRequest, ): kotlin.collections.List @@ -352,7 +352,7 @@ internal interface UsersApi { * @param usersSearchByUsernameAndHostRequest * @return [kotlin.collections.List] */ @POST("users/search-by-username-and-host") - suspend fun usersSearchByUsernameAndHost( + public suspend fun usersSearchByUsernameAndHost( @Body usersSearchByUsernameAndHostRequest: UsersSearchByUsernameAndHostRequest, ): kotlin.collections.List @@ -370,7 +370,7 @@ internal interface UsersApi { * @param usersShowRequest * @return [UsersShow200Response] */ @POST("users/show") - suspend fun usersShow( + public suspend fun usersShow( @Body usersShowRequest: UsersShowRequest, ): User } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/WebhooksApi.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/WebhooksApi.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/WebhooksApi.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/WebhooksApi.kt index 9efa5012c4..b4dafdd41b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/WebhooksApi.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/WebhooksApi.kt @@ -6,7 +6,7 @@ import dev.dimension.flare.data.network.misskey.api.model.IWebhooksCreateRequest import dev.dimension.flare.data.network.misskey.api.model.IWebhooksShowRequest import dev.dimension.flare.data.network.misskey.api.model.IWebhooksUpdateRequest -internal interface WebhooksApi { +public interface WebhooksApi { /** * i/webhooks/create * No description provided. **Credential required**: *Yes* / **Permission**: *write:account* @@ -21,7 +21,7 @@ internal interface WebhooksApi { * @param iwebhooksCreateRequest * @return [Unit] */ @POST("i/webhooks/create") - suspend fun iWebhooksCreate( + public suspend fun iWebhooksCreate( @Body iwebhooksCreateRequest: IWebhooksCreateRequest, ): Unit @@ -39,7 +39,7 @@ internal interface WebhooksApi { * @param iwebhooksShowRequest * @return [Unit] */ @POST("i/webhooks/delete") - suspend fun iWebhooksDelete( + public suspend fun iWebhooksDelete( @Body iwebhooksShowRequest: IWebhooksShowRequest, ): Unit @@ -57,7 +57,7 @@ internal interface WebhooksApi { * @param body * @return [Unit] */ @POST("i/webhooks/list") - suspend fun iWebhooksList( + public suspend fun iWebhooksList( @Body body: kotlin.Any, ): Unit @@ -75,7 +75,7 @@ internal interface WebhooksApi { * @param iwebhooksShowRequest * @return [Unit] */ @POST("i/webhooks/show") - suspend fun iWebhooksShow( + public suspend fun iWebhooksShow( @Body iwebhooksShowRequest: IWebhooksShowRequest, ): Unit @@ -93,7 +93,7 @@ internal interface WebhooksApi { * @param iwebhooksUpdateRequest * @return [Unit] */ @POST("i/webhooks/update") - suspend fun iWebhooksUpdate( + public suspend fun iWebhooksUpdate( @Body iwebhooksUpdateRequest: IWebhooksUpdateRequest, ): Unit } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverCreate200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverCreate200Response.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverCreate200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverCreate200Response.kt index 9b8041de02..4271b498e7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverCreate200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverCreate200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param targetUserPattern * @param reporterPattern * @param reportContentPattern * @param expiresAt * @param forward */ @Serializable -internal data class AdminAbuseReportResolverCreate200Response( +public data class AdminAbuseReportResolverCreate200Response( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "targetUserPattern") val targetUserPattern: kotlin.String? = null, @SerialName(value = "reporterPattern") val reporterPattern: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverCreateRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverCreateRequest.kt index c56a77a595..9220d9f417 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param targetUserPattern * @param reporterPattern * @param reportContentPattern * @param expiresAt * @param forward */ @Serializable -internal data class AdminAbuseReportResolverCreateRequest( +public data class AdminAbuseReportResolverCreateRequest( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "targetUserPattern") val targetUserPattern: kotlin.String? = null, @SerialName(value = "reporterPattern") val reporterPattern: kotlin.String? = null, @@ -35,8 +35,8 @@ internal data class AdminAbuseReportResolverCreateRequest( * Values: _1hour,_12hours,_1day,_1week,_1month,_3months,_6months,_1year,Indefinitely */ @Serializable - enum class ExpiresAt( - val value: kotlin.String, + public enum class ExpiresAt( + public val value: kotlin.String, ) { @SerialName(value = "1hour") _1hour("1hour"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverDeleteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverDeleteRequest.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverDeleteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverDeleteRequest.kt index 3960c1f39b..68b97c90f3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverDeleteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverDeleteRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param resolverId */ @Serializable -internal data class AdminAbuseReportResolverDeleteRequest( +public data class AdminAbuseReportResolverDeleteRequest( @SerialName(value = "resolverId") val resolverId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverListRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverListRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverListRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverListRequest.kt index a21263bb21..8d1aa0a951 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverListRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverListRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param sinceId * @param untilId */ @Serializable -internal data class AdminAbuseReportResolverListRequest( +public data class AdminAbuseReportResolverListRequest( @SerialName(value = "limit") val limit: kotlin.Double? = (10).toDouble(), @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverUpdateRequest.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverUpdateRequest.kt index c6416ba753..14ac92536a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseReportResolverUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param resolverId * @param name * @param targetUserPattern * @param reporterPattern * @param reportContentPattern * @param expiresAt * @param forward */ @Serializable -internal data class AdminAbuseReportResolverUpdateRequest( +public data class AdminAbuseReportResolverUpdateRequest( @SerialName(value = "resolverId") val resolverId: kotlin.String, @SerialName(value = "name") val name: kotlin.String? = null, @SerialName(value = "targetUserPattern") val targetUserPattern: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseUserReports200ResponseInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseUserReports200ResponseInner.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseUserReports200ResponseInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseUserReports200ResponseInner.kt index 70ab137ceb..9fdbabbba4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseUserReports200ResponseInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseUserReports200ResponseInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param comment * @param resolved * @param reporterId * @param targetUserId * @param assigneeId * @param reporter * @param targetUser * @param assignee */ @Serializable -internal data class AdminAbuseUserReports200ResponseInner( +public data class AdminAbuseUserReports200ResponseInner( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "comment") val comment: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseUserReportsRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseUserReportsRequest.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseUserReportsRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseUserReportsRequest.kt index ef1e47690a..aacca19614 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseUserReportsRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAbuseUserReportsRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param sinceId * @param untilId * @param state * @param reporterOrigin * @param targetUserOrigin * @param forwarded */ @Serializable -internal data class AdminAbuseUserReportsRequest( +public data class AdminAbuseUserReportsRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, @@ -38,8 +38,8 @@ internal data class AdminAbuseUserReportsRequest( * Values: Combined,Local,Remote */ @Serializable - enum class ReporterOrigin( - val value: kotlin.String, + public enum class ReporterOrigin( + public val value: kotlin.String, ) { @SerialName(value = "combined") Combined("combined"), @@ -56,8 +56,8 @@ internal data class AdminAbuseUserReportsRequest( * Values: Combined,Local,Remote */ @Serializable - enum class TargetUserOrigin( - val value: kotlin.String, + public enum class TargetUserOrigin( + public val value: kotlin.String, ) { @SerialName(value = "combined") Combined("combined"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAccountsCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAccountsCreateRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAccountsCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAccountsCreateRequest.kt index e4318612c1..1fed0f53c3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAccountsCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAccountsCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param username * @param password */ @Serializable -internal data class AdminAccountsCreateRequest( +public data class AdminAccountsCreateRequest( @SerialName(value = "username") val username: kotlin.String, @SerialName(value = "password") val password: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAccountsDeleteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAccountsDeleteRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAccountsDeleteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAccountsDeleteRequest.kt index c998621add..7783340a01 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAccountsDeleteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAccountsDeleteRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param userId */ @Serializable -internal data class AdminAccountsDeleteRequest( +public data class AdminAccountsDeleteRequest( @SerialName(value = "userId") val userId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdCreateRequest.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdCreateRequest.kt index 1d91a0320d..8b3c4805c1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param url * @param memo * @param place * @param priority * @param ratio * @param expiresAt * @param startsAt * @param imageUrl * @param dayOfWeek */ @Serializable -internal data class AdminAdCreateRequest( +public data class AdminAdCreateRequest( @SerialName(value = "url") val url: kotlin.String, @SerialName(value = "memo") val memo: kotlin.String, @SerialName(value = "place") val place: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdDeleteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdDeleteRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdDeleteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdDeleteRequest.kt index 8277f2ba6f..ca7254d3a7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdDeleteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdDeleteRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param id */ @Serializable -internal data class AdminAdDeleteRequest( +public data class AdminAdDeleteRequest( @SerialName(value = "id") val id: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdListRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdListRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdListRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdListRequest.kt index 9f9d28c794..43ea85bbfc 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdListRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdListRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param sinceId * @param untilId */ @Serializable -internal data class AdminAdListRequest( +public data class AdminAdListRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdUpdateRequest.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdUpdateRequest.kt index 19bab17ead..9dc9de416c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAdUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param memo * @param url * @param imageUrl * @param place * @param priority * @param ratio * @param expiresAt * @param startsAt * @param dayOfWeek */ @Serializable -internal data class AdminAdUpdateRequest( +public data class AdminAdUpdateRequest( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "memo") val memo: kotlin.String, @SerialName(value = "url") val url: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsCreate200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsCreate200Response.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsCreate200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsCreate200Response.kt index 37efed0fb0..f01ad03034 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsCreate200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsCreate200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param updatedAt * @param title * @param text * @param imageUrl * @param displayOrder * @param userId * @param closeDuration */ @Serializable -internal data class AdminAnnouncementsCreate200Response( +public data class AdminAnnouncementsCreate200Response( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "updatedAt") val updatedAt: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsCreateRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsCreateRequest.kt index 54f50e2fb9..565d4ceb26 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param title * @param text * @param imageUrl * @param displayOrder * @param userId * @param closeDuration */ @Serializable -internal data class AdminAnnouncementsCreateRequest( +public data class AdminAnnouncementsCreateRequest( @SerialName(value = "title") val title: kotlin.String, @SerialName(value = "text") val text: kotlin.String, @SerialName(value = "imageUrl") val imageUrl: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsList200ResponseInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsList200ResponseInner.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsList200ResponseInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsList200ResponseInner.kt index b37b4981d9..43eefd93fa 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsList200ResponseInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsList200ResponseInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param updatedAt * @param text * @param title * @param imageUrl * @param displayOrder * @param userId * @param reads * @param closeDuration * @param user */ @Serializable -internal data class AdminAnnouncementsList200ResponseInner( +public data class AdminAnnouncementsList200ResponseInner( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "updatedAt") val updatedAt: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsListRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsListRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsListRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsListRequest.kt index d0d4f6391d..1b1620f440 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsListRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsListRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param offset * @param userId */ @Serializable -internal data class AdminAnnouncementsListRequest( +public data class AdminAnnouncementsListRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "offset") val offset: kotlin.Int? = 0, @SerialName(value = "userId") val userId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsUpdateRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsUpdateRequest.kt index cb3f8c2f17..cca0a9d7c5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminAnnouncementsUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param title * @param text * @param imageUrl * @param closeDuration * @param displayOrder * @param userId */ @Serializable -internal data class AdminAnnouncementsUpdateRequest( +public data class AdminAnnouncementsUpdateRequest( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "title") val title: kotlin.String, @SerialName(value = "text") val text: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveFilesRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveFilesRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveFilesRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveFilesRequest.kt index 91f76ed69d..51bb56df6c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveFilesRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveFilesRequest.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param limit * @param sinceId * @param untilId * @param userId * @param type * @param origin * @param hostname The local host is represented with `null`. */ @Serializable -internal data class AdminDriveFilesRequest( +public data class AdminDriveFilesRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, @@ -38,8 +38,8 @@ internal data class AdminDriveFilesRequest( * Values: Combined,Local,Remote */ @Serializable - enum class Origin( - val value: kotlin.String, + public enum class Origin( + public val value: kotlin.String, ) { @SerialName(value = "combined") Combined("combined"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveShowFile200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveShowFile200Response.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveShowFile200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveShowFile200Response.kt index 34c65ece36..8abb465b9a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveShowFile200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveShowFile200Response.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param id * @param createdAt * @param userId * @param userHost The local host is represented with `null`. * @param md5 * @param name * @param type * @param propertySize * @param comment * @param blurhash * @param properties * @param storedInternal * @param url * @param thumbnailUrl * @param webpublicUrl * @param accessKey * @param thumbnailAccessKey * @param webpublicAccessKey * @param uri * @param src * @param folderId * @param isSensitive * @param isLink */ @Serializable -internal data class AdminDriveShowFile200Response( +public data class AdminDriveShowFile200Response( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "userId") val userId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveShowFileRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveShowFileRequest.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveShowFileRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveShowFileRequest.kt index fc41814aab..7352e8d642 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveShowFileRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminDriveShowFileRequest.kt @@ -21,7 +21,7 @@ import kotlinx.serialization.Serializable * * */ @Serializable -internal data class AdminDriveShowFileRequest( +public data class AdminDriveShowFileRequest( val fileId: String? = null, val url: String? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiAddAliasesBulkRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiAddAliasesBulkRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiAddAliasesBulkRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiAddAliasesBulkRequest.kt index ea23438f16..bc19385261 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiAddAliasesBulkRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiAddAliasesBulkRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param ids * @param aliases */ @Serializable -internal data class AdminEmojiAddAliasesBulkRequest( +public data class AdminEmojiAddAliasesBulkRequest( @SerialName(value = "ids") val ids: kotlin.collections.List, @SerialName(value = "aliases") val aliases: kotlin.collections.List, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiAddRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiAddRequest.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiAddRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiAddRequest.kt index 0201ac37a0..91145ad388 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiAddRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiAddRequest.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param name * @param fileId * @param category Use `null` to reset the category. * @param aliases * @param license * @param isSensitive * @param localOnly * @param roleIdsThatCanBeUsedThisEmojiAsReaction */ @Serializable -internal data class AdminEmojiAddRequest( +public data class AdminEmojiAddRequest( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "fileId") val fileId: kotlin.String, // Use `null` to reset the category. diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiCopy200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiCopy200Response.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiCopy200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiCopy200Response.kt index 1afa777fc8..f4d682595f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiCopy200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiCopy200Response.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param id */ @Serializable -internal data class AdminEmojiCopy200Response( +public data class AdminEmojiCopy200Response( @SerialName(value = "id") val id: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiCopyRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiCopyRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiCopyRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiCopyRequest.kt index d405f547bc..0fd963110b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiCopyRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiCopyRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param emojiId */ @Serializable -internal data class AdminEmojiCopyRequest( +public data class AdminEmojiCopyRequest( @SerialName(value = "emojiId") val emojiId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiDeleteBulkRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiDeleteBulkRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiDeleteBulkRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiDeleteBulkRequest.kt index 258061b370..34e56daed5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiDeleteBulkRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiDeleteBulkRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param ids */ @Serializable -internal data class AdminEmojiDeleteBulkRequest( +public data class AdminEmojiDeleteBulkRequest( @SerialName(value = "ids") val ids: kotlin.collections.List, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiList200ResponseInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiList200ResponseInner.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiList200ResponseInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiList200ResponseInner.kt index f2f945380b..7c3648c3c1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiList200ResponseInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiList200ResponseInner.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param id * @param aliases * @param name * @param category * @param host The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files. * @param url */ @Serializable -internal data class AdminEmojiList200ResponseInner( +public data class AdminEmojiList200ResponseInner( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "aliases") val aliases: kotlin.collections.List, @SerialName(value = "name") val name: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRemote200ResponseInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRemote200ResponseInner.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRemote200ResponseInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRemote200ResponseInner.kt index 10d50432b5..b5b23be430 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRemote200ResponseInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRemote200ResponseInner.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param id * @param aliases * @param name * @param category * @param host The local host is represented with `null`. * @param url */ @Serializable -internal data class AdminEmojiListRemote200ResponseInner( +public data class AdminEmojiListRemote200ResponseInner( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "aliases") val aliases: kotlin.collections.List, @SerialName(value = "name") val name: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRemoteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRemoteRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRemoteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRemoteRequest.kt index 1f553bd264..baee4e9514 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRemoteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRemoteRequest.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param query * @param host Use `null` to represent the local host. * @param limit * @param sinceId * @param untilId */ @Serializable -internal data class AdminEmojiListRemoteRequest( +public data class AdminEmojiListRemoteRequest( @SerialName(value = "query") val query: kotlin.String? = null, // Use `null` to represent the local host. @SerialName(value = "host") val host: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRequest.kt index adbd7a11ab..b6bba23a7b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiListRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param query * @param limit * @param sinceId * @param untilId */ @Serializable -internal data class AdminEmojiListRequest( +public data class AdminEmojiListRequest( @SerialName(value = "query") val query: kotlin.String? = null, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiSetCategoryBulkRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiSetCategoryBulkRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiSetCategoryBulkRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiSetCategoryBulkRequest.kt index 21d1c95840..129a588731 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiSetCategoryBulkRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiSetCategoryBulkRequest.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param ids * @param category Use `null` to reset the category. */ @Serializable -internal data class AdminEmojiSetCategoryBulkRequest( +public data class AdminEmojiSetCategoryBulkRequest( @SerialName(value = "ids") val ids: kotlin.collections.List, // Use `null` to reset the category. @SerialName(value = "category") val category: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiSetLicenseBulkRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiSetLicenseBulkRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiSetLicenseBulkRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiSetLicenseBulkRequest.kt index cd08dd2555..9b73988223 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiSetLicenseBulkRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiSetLicenseBulkRequest.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param ids * @param license Use `null` to reset the license. */ @Serializable -internal data class AdminEmojiSetLicenseBulkRequest( +public data class AdminEmojiSetLicenseBulkRequest( @SerialName(value = "ids") val ids: kotlin.collections.List, // Use `null` to reset the license. @SerialName(value = "license") val license: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiUpdateRequest.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiUpdateRequest.kt index 3a9a0d058c..f80a19b1a0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminEmojiUpdateRequest.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param id * @param name * @param aliases * @param fileId * @param category Use `null` to reset the category. * @param license * @param isSensitive * @param localOnly * @param roleIdsThatCanBeUsedThisEmojiAsReaction */ @Serializable -internal data class AdminEmojiUpdateRequest( +public data class AdminEmojiUpdateRequest( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "aliases") val aliases: kotlin.collections.List, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminFederationDeleteAllFilesRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminFederationDeleteAllFilesRequest.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminFederationDeleteAllFilesRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminFederationDeleteAllFilesRequest.kt index 541b7c5596..dc587e6ea7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminFederationDeleteAllFilesRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminFederationDeleteAllFilesRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param host */ @Serializable -internal data class AdminFederationDeleteAllFilesRequest( +public data class AdminFederationDeleteAllFilesRequest( @SerialName(value = "host") val host: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminFederationUpdateInstanceRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminFederationUpdateInstanceRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminFederationUpdateInstanceRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminFederationUpdateInstanceRequest.kt index 97138019b3..d47dc18a95 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminFederationUpdateInstanceRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminFederationUpdateInstanceRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param host * @param isSuspended */ @Serializable -internal data class AdminFederationUpdateInstanceRequest( +public data class AdminFederationUpdateInstanceRequest( @SerialName(value = "host") val host: kotlin.String, @SerialName(value = "isSuspended") val isSuspended: kotlin.Boolean, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteCreate200ResponseInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteCreate200ResponseInner.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteCreate200ResponseInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteCreate200ResponseInner.kt index b072df4ae2..8b2f14a413 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteCreate200ResponseInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteCreate200ResponseInner.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param code */ @Serializable -internal data class AdminInviteCreate200ResponseInner( +public data class AdminInviteCreate200ResponseInner( @SerialName(value = "code") val code: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteCreateRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteCreateRequest.kt index 0b5f91b880..5bfeff5f33 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param count * @param expiresAt */ @Serializable -internal data class AdminInviteCreateRequest( +public data class AdminInviteCreateRequest( @SerialName(value = "count") val count: kotlin.Int? = 1, @SerialName(value = "expiresAt") val expiresAt: kotlin.String? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteListRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteListRequest.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteListRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteListRequest.kt index 3266925241..7d3b4448d6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteListRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminInviteListRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param offset * @param type * @param sort */ @Serializable -internal data class AdminInviteListRequest( +public data class AdminInviteListRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 30, @SerialName(value = "offset") val offset: kotlin.Int? = 0, @SerialName(value = "type") val type: AdminInviteListRequest.Type? = Type.All, @@ -33,8 +33,8 @@ internal data class AdminInviteListRequest( * Values: Unused,Used,Expired,All */ @Serializable - enum class Type( - val value: kotlin.String, + public enum class Type( + public val value: kotlin.String, ) { @SerialName(value = "unused") Unused("unused"), @@ -54,8 +54,8 @@ internal data class AdminInviteListRequest( * Values: PlusCreatedAt,MinusCreatedAt,PlusUsedAt,MinusUsedAt */ @Serializable - enum class Sort( - val value: kotlin.String, + public enum class Sort( + public val value: kotlin.String, ) { @SerialName(value = "+createdAt") PlusCreatedAt("+createdAt"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminMeta200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminMeta200Response.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminMeta200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminMeta200Response.kt index cafdafa969..200455cac0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminMeta200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminMeta200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param cacheRemoteFiles * @param cacheRemoteSensitiveFiles * @param emailRequiredForSignup * @param enableHcaptcha * @param hcaptchaSiteKey * @param enableRecaptcha * @param recaptchaSiteKey * @param enableTurnstile * @param turnstileSiteKey * @param swPublickey * @param mascotImageUrl * @param bannerUrl * @param serverErrorImageUrl * @param infoImageUrl * @param notFoundImageUrl * @param iconUrl * @param enableEmail * @param enableServiceWorker * @param translatorAvailable * @param preservedUsernames * @param enableChartsForRemoteUser * @param enableChartsForFederatedInstances * @param enableServerMachineStats * @param enableIdenticonGeneration * @param policies * @param userStarForReactionFallback * @param pinnedUsers * @param hiddenTags * @param blockedHosts * @param sensitiveWords * @param hcaptchaSecretKey * @param recaptchaSecretKey * @param turnstileSecretKey * @param sensitiveMediaDetection * @param sensitiveMediaDetectionSensitivity * @param setSensitiveFlagAutomatically * @param enableSensitiveMediaDetectionForVideos * @param proxyAccountId * @param summaryProxy * @param email * @param smtpSecure * @param smtpHost * @param smtpPort * @param smtpUser * @param smtpPass * @param swPrivateKey * @param useObjectStorage * @param objectStorageBaseUrl * @param objectStorageBucket * @param objectStoragePrefix * @param objectStorageEndpoint * @param objectStorageRegion * @param objectStoragePort * @param objectStorageAccessKey * @param objectStorageSecretKey * @param objectStorageUseSSL * @param objectStorageUseProxy * @param objectStorageSetPublicRead * @param enableIpLogging * @param enableActiveEmailValidation */ @Serializable -internal data class AdminMeta200Response( +public data class AdminMeta200Response( @SerialName(value = "cacheRemoteFiles") val cacheRemoteFiles: kotlin.Boolean, @SerialName(value = "cacheRemoteSensitiveFiles") val cacheRemoteSensitiveFiles: kotlin.Boolean, @SerialName(value = "emailRequiredForSignup") val emailRequiredForSignup: kotlin.Boolean, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminPromoCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminPromoCreateRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminPromoCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminPromoCreateRequest.kt index c5845f6b14..e00e0deb2c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminPromoCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminPromoCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param noteId * @param expiresAt */ @Serializable -internal data class AdminPromoCreateRequest( +public data class AdminPromoCreateRequest( @SerialName(value = "noteId") val noteId: kotlin.String, @SerialName(value = "expiresAt") val expiresAt: kotlin.Int, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueueDeliverDelayed200ResponseInnerInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueueDeliverDelayed200ResponseInnerInner.kt similarity index 86% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueueDeliverDelayed200ResponseInnerInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueueDeliverDelayed200ResponseInnerInner.kt index 10ef1dfb04..b2a18193e6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueueDeliverDelayed200ResponseInnerInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueueDeliverDelayed200ResponseInnerInner.kt @@ -21,4 +21,4 @@ import kotlinx.serialization.Serializable * * */ @Serializable -internal object AdminQueueDeliverDelayed200ResponseInnerInner +public object AdminQueueDeliverDelayed200ResponseInnerInner diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueuePromoteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueuePromoteRequest.kt similarity index 86% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueuePromoteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueuePromoteRequest.kt index ce80ba7626..1f1a49e41b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueuePromoteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueuePromoteRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param type */ @Serializable -internal data class AdminQueuePromoteRequest( +public data class AdminQueuePromoteRequest( @SerialName(value = "type") val type: AdminQueuePromoteRequest.Type, ) { /** @@ -30,8 +30,8 @@ internal data class AdminQueuePromoteRequest( * Values: Deliver,Inbox */ @Serializable - enum class Type( - val value: kotlin.String, + public enum class Type( + public val value: kotlin.String, ) { @SerialName(value = "deliver") Deliver("deliver"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueueStats200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueueStats200Response.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueueStats200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueueStats200Response.kt index 88c63d6486..bd29ee297f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueueStats200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminQueueStats200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param deliver * @param inbox * @param db * @param objectStorage */ @Serializable -internal data class AdminQueueStats200Response( +public data class AdminQueueStats200Response( @SerialName(value = "deliver") val deliver: QueueCount, @SerialName(value = "inbox") val inbox: QueueCount, @SerialName(value = "db") val db: QueueCount, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRelaysAdd200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRelaysAdd200Response.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRelaysAdd200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRelaysAdd200Response.kt index aa92ea5bb3..2b11227674 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRelaysAdd200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRelaysAdd200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param inbox * @param status */ @Serializable -internal data class AdminRelaysAdd200Response( +public data class AdminRelaysAdd200Response( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "inbox") val inbox: kotlin.String, @SerialName(value = "status") val status: AdminRelaysAdd200Response.Status = Status.Requesting, @@ -32,8 +32,8 @@ internal data class AdminRelaysAdd200Response( * Values: Requesting,Accepted,Rejected */ @Serializable - enum class Status( - val value: kotlin.String, + public enum class Status( + public val value: kotlin.String, ) { @SerialName(value = "requesting") Requesting("requesting"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRelaysAddRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRelaysAddRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRelaysAddRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRelaysAddRequest.kt index a1ba7e2a72..00acbab915 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRelaysAddRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRelaysAddRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param inbox */ @Serializable -internal data class AdminRelaysAddRequest( +public data class AdminRelaysAddRequest( @SerialName(value = "inbox") val inbox: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminResetPassword200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminResetPassword200Response.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminResetPassword200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminResetPassword200Response.kt index 77750e815e..ee1ff7aadc 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminResetPassword200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminResetPassword200Response.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param password */ @Serializable -internal data class AdminResetPassword200Response( +public data class AdminResetPassword200Response( @SerialName(value = "password") val password: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminResolveAbuseUserReportRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminResolveAbuseUserReportRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminResolveAbuseUserReportRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminResolveAbuseUserReportRequest.kt index 847b6c9417..1a880a0bed 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminResolveAbuseUserReportRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminResolveAbuseUserReportRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param reportId * @param forward */ @Serializable -internal data class AdminResolveAbuseUserReportRequest( +public data class AdminResolveAbuseUserReportRequest( @SerialName(value = "reportId") val reportId: kotlin.String, @SerialName(value = "forward") val forward: kotlin.Boolean? = false, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesAssignRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesAssignRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesAssignRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesAssignRequest.kt index 4ca6572bbd..c318cb9462 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesAssignRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesAssignRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param roleId * @param userId * @param expiresAt */ @Serializable -internal data class AdminRolesAssignRequest( +public data class AdminRolesAssignRequest( @SerialName(value = "roleId") val roleId: kotlin.String, @SerialName(value = "userId") val userId: kotlin.String, @SerialName(value = "expiresAt") val expiresAt: kotlin.Int? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesCreateRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesCreateRequest.kt index 294781cc10..4d9d51beef 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param description * @param color * @param iconUrl * @param target * @param condFormula * @param isPublic * @param isModerator * @param isAdministrator * @param asBadge * @param canEditMembersByModerator * @param displayOrder * @param policies * @param isExplorable */ @Serializable -internal data class AdminRolesCreateRequest( +public data class AdminRolesCreateRequest( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "description") val description: kotlin.String, @SerialName(value = "color") val color: kotlin.String? = null, @@ -43,8 +43,8 @@ internal data class AdminRolesCreateRequest( * Values: Manual,Conditional */ @Serializable - enum class Target( - val value: kotlin.String, + public enum class Target( + public val value: kotlin.String, ) { @SerialName(value = "manual") Manual("manual"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesDeleteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesDeleteRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesDeleteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesDeleteRequest.kt index cfb731b2c4..19e3dd279e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesDeleteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesDeleteRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param roleId */ @Serializable -internal data class AdminRolesDeleteRequest( +public data class AdminRolesDeleteRequest( @SerialName(value = "roleId") val roleId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUnassignRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUnassignRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUnassignRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUnassignRequest.kt index 31331a1c68..1850802ba5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUnassignRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUnassignRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param roleId * @param userId */ @Serializable -internal data class AdminRolesUnassignRequest( +public data class AdminRolesUnassignRequest( @SerialName(value = "roleId") val roleId: kotlin.String, @SerialName(value = "userId") val userId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUpdateDefaultPoliciesRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUpdateDefaultPoliciesRequest.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUpdateDefaultPoliciesRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUpdateDefaultPoliciesRequest.kt index ea16a18a97..9a628ba370 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUpdateDefaultPoliciesRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUpdateDefaultPoliciesRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param policies */ @Serializable -internal data class AdminRolesUpdateDefaultPoliciesRequest( +public data class AdminRolesUpdateDefaultPoliciesRequest( @SerialName(value = "policies") val policies: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUpdateRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUpdateRequest.kt index 567aa82e26..ce40f5f7d4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param roleId * @param name * @param description * @param color * @param iconUrl * @param target * @param condFormula * @param isPublic * @param isModerator * @param isAdministrator * @param asBadge * @param canEditMembersByModerator * @param displayOrder * @param policies * @param isExplorable */ @Serializable -internal data class AdminRolesUpdateRequest( +public data class AdminRolesUpdateRequest( @SerialName(value = "roleId") val roleId: kotlin.String, @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "description") val description: kotlin.String, @@ -44,8 +44,8 @@ internal data class AdminRolesUpdateRequest( * Values: Manual,Conditional */ @Serializable - enum class Target( - val value: kotlin.String, + public enum class Target( + public val value: kotlin.String, ) { @SerialName(value = "manual") Manual("manual"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUsersRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUsersRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUsersRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUsersRequest.kt index 02fd5663f1..901b627a99 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUsersRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminRolesUsersRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param roleId * @param sinceId * @param untilId * @param limit */ @Serializable -internal data class AdminRolesUsersRequest( +public data class AdminRolesUsersRequest( @SerialName(value = "roleId") val roleId: kotlin.String, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminSendEmailRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminSendEmailRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminSendEmailRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminSendEmailRequest.kt index 2c54ae76ee..c786687024 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminSendEmailRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminSendEmailRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param to * @param subject * @param text */ @Serializable -internal data class AdminSendEmailRequest( +public data class AdminSendEmailRequest( @SerialName(value = "to") val to: kotlin.String, @SerialName(value = "subject") val subject: kotlin.String, @SerialName(value = "text") val text: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200Response.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200Response.kt index 3ef403e2f2..88636e333d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param machine * @param os * @param node * @param psql * @param cpu * @param mem * @param fs * @param net */ @Serializable -internal data class AdminServerInfo200Response( +public data class AdminServerInfo200Response( @SerialName(value = "machine") val machine: kotlin.String, @SerialName(value = "os") val os: kotlin.String, @SerialName(value = "node") val node: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseCpu.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseCpu.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseCpu.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseCpu.kt index f9860dc4ec..92740165b9 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseCpu.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseCpu.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param model * @param cores */ @Serializable -internal data class AdminServerInfo200ResponseCpu( +public data class AdminServerInfo200ResponseCpu( @SerialName(value = "model") val model: kotlin.String, @SerialName(value = "cores") val cores: kotlin.Double, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseFs.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseFs.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseFs.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseFs.kt index 6a941a58b6..1e81c27481 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseFs.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseFs.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param total * @param used */ @Serializable -internal data class AdminServerInfo200ResponseFs( +public data class AdminServerInfo200ResponseFs( @SerialName(value = "total") val total: kotlin.Double, @SerialName(value = "used") val used: kotlin.Double, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseMem.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseMem.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseMem.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseMem.kt index e4d18b2431..9511b7b207 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseMem.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseMem.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param total */ @Serializable -internal data class AdminServerInfo200ResponseMem( +public data class AdminServerInfo200ResponseMem( @SerialName(value = "total") val total: kotlin.Double, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseNet.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseNet.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseNet.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseNet.kt index dc7ddd2e8a..a493d466cd 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseNet.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminServerInfo200ResponseNet.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param `interface` */ @Serializable -internal data class AdminServerInfo200ResponseNet( +public data class AdminServerInfo200ResponseNet( @SerialName(value = "interface") val interface_: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminShowModerationLogs200ResponseInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminShowModerationLogs200ResponseInner.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminShowModerationLogs200ResponseInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminShowModerationLogs200ResponseInner.kt index a0ea1f6e25..06f2716a32 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminShowModerationLogs200ResponseInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminShowModerationLogs200ResponseInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param type * @param info * @param userId * @param user */ @Serializable -internal data class AdminShowModerationLogs200ResponseInner( +public data class AdminShowModerationLogs200ResponseInner( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "type") val type: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminShowUsersRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminShowUsersRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminShowUsersRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminShowUsersRequest.kt index 198d42e1bc..bca953bd6a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminShowUsersRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminShowUsersRequest.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param limit * @param offset * @param sort * @param state * @param origin * @param username * @param hostname The local host is represented with `null`. */ @Serializable -internal data class AdminShowUsersRequest( +public data class AdminShowUsersRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "offset") val offset: kotlin.Int? = 0, @SerialName(value = "sort") val sort: AdminShowUsersRequest.Sort? = null, @@ -38,8 +38,8 @@ internal data class AdminShowUsersRequest( * Values: PlusFollower,MinusFollower,PlusCreatedAt,MinusCreatedAt,PlusUpdatedAt,MinusUpdatedAt,PlusLastActiveDate,MinusLastActiveDate */ @Serializable - enum class Sort( - val value: kotlin.String, + public enum class Sort( + public val value: kotlin.String, ) { @SerialName(value = "+follower") PlusFollower("+follower"), @@ -71,8 +71,8 @@ internal data class AdminShowUsersRequest( * Values: All,Alive,Available,Admin,Moderator,AdminOrModerator,Suspended */ @Serializable - enum class State( - val value: kotlin.String, + public enum class State( + public val value: kotlin.String, ) { @SerialName(value = "all") All("all"), @@ -101,8 +101,8 @@ internal data class AdminShowUsersRequest( * Values: Combined,Local,Remote */ @Serializable - enum class Origin( - val value: kotlin.String, + public enum class Origin( + public val value: kotlin.String, ) { @SerialName(value = "combined") Combined("combined"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminUpdateMetaRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminUpdateMetaRequest.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminUpdateMetaRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminUpdateMetaRequest.kt index 250372526f..210499d477 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminUpdateMetaRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminUpdateMetaRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param disableRegistration * @param pinnedUsers * @param hiddenTags * @param blockedHosts * @param sensitiveWords * @param themeColor * @param mascotImageUrl * @param bannerUrl * @param serverErrorImageUrl * @param infoImageUrl * @param notFoundImageUrl * @param iconUrl * @param backgroundImageUrl * @param logoImageUrl * @param name * @param description * @param defaultLightTheme * @param defaultDarkTheme * @param cacheRemoteFiles * @param cacheRemoteSensitiveFiles * @param emailRequiredForSignup * @param enableHcaptcha * @param hcaptchaSiteKey * @param hcaptchaSecretKey * @param enableRecaptcha * @param recaptchaSiteKey * @param recaptchaSecretKey * @param enableTurnstile * @param turnstileSiteKey * @param turnstileSecretKey * @param sensitiveMediaDetection * @param sensitiveMediaDetectionSensitivity * @param setSensitiveFlagAutomatically * @param enableSensitiveMediaDetectionForVideos * @param proxyAccountId * @param maintainerName * @param maintainerEmail * @param langs * @param summalyProxy * @param deeplAuthKey * @param deeplIsPro * @param enableEmail * @param email * @param smtpSecure * @param smtpHost * @param smtpPort * @param smtpUser * @param smtpPass * @param enableServiceWorker * @param swPublicKey * @param swPrivateKey * @param tosUrl * @param repositoryUrl * @param feedbackUrl * @param useObjectStorage * @param objectStorageBaseUrl * @param objectStorageBucket * @param objectStoragePrefix * @param objectStorageEndpoint * @param objectStorageRegion * @param objectStoragePort * @param objectStorageAccessKey * @param objectStorageSecretKey * @param objectStorageUseSSL * @param objectStorageUseProxy * @param objectStorageSetPublicRead * @param objectStorageS3ForcePathStyle * @param enableIpLogging * @param enableActiveEmailValidation * @param enableChartsForRemoteUser * @param enableChartsForFederatedInstances * @param enableServerMachineStats * @param enableIdenticonGeneration * @param serverRules * @param preservedUsernames */ @Serializable -internal data class AdminUpdateMetaRequest( +public data class AdminUpdateMetaRequest( @SerialName(value = "disableRegistration") val disableRegistration: kotlin.Boolean? = null, @SerialName(value = "pinnedUsers") val pinnedUsers: kotlin.collections.List? = null, @SerialName(value = "hiddenTags") val hiddenTags: kotlin.collections.List? = null, @@ -105,8 +105,8 @@ internal data class AdminUpdateMetaRequest( * Values: None,All,Local,Remote */ @Serializable - enum class SensitiveMediaDetection( - val value: kotlin.String, + public enum class SensitiveMediaDetection( + public val value: kotlin.String, ) { @SerialName(value = "none") None("none"), @@ -126,8 +126,8 @@ internal data class AdminUpdateMetaRequest( * Values: Medium,Low,High,VeryLow,VeryHigh */ @Serializable - enum class SensitiveMediaDetectionSensitivity( - val value: kotlin.String, + public enum class SensitiveMediaDetectionSensitivity( + public val value: kotlin.String, ) { @SerialName(value = "medium") Medium("medium"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminUpdateUserNoteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminUpdateUserNoteRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminUpdateUserNoteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminUpdateUserNoteRequest.kt index 1dada2ade5..ce7f2d2337 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminUpdateUserNoteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AdminUpdateUserNoteRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param userId * @param text */ @Serializable -internal data class AdminUpdateUserNoteRequest( +public data class AdminUpdateUserNoteRequest( @SerialName(value = "userId") val userId: kotlin.String, @SerialName(value = "text") val text: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Announcements200ResponseInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Announcements200ResponseInner.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Announcements200ResponseInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Announcements200ResponseInner.kt index 978a5394c9..e51b485a92 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Announcements200ResponseInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Announcements200ResponseInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param updatedAt * @param text * @param title * @param imageUrl * @param isPrivate * @param closeDuration * @param isRead */ @Serializable -internal data class Announcements200ResponseInner( +public data class Announcements200ResponseInner( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "updatedAt") val updatedAt: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AnnouncementsRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AnnouncementsRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AnnouncementsRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AnnouncementsRequest.kt index f00bcc3af8..70b5e447c0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AnnouncementsRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AnnouncementsRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param offset * @param withUnreads * @param privateOnly */ @Serializable -internal data class AnnouncementsRequest( +public data class AnnouncementsRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "offset") val offset: kotlin.Int? = 0, @SerialName(value = "withUnreads") val withUnreads: kotlin.Boolean? = false, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Antenna.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Antenna.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Antenna.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Antenna.kt index b31d70f89d..e3a3f9c8a9 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Antenna.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Antenna.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param name * @param keywords * @param excludeKeywords * @param src * @param userListId * @param users * @param caseSensitive * @param notify * @param withReplies * @param withFile * @param isActive * @param hasUnreadNote */ @Serializable -internal data class Antenna( +public data class Antenna( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "name") val name: kotlin.String, @@ -44,8 +44,8 @@ internal data class Antenna( * Values: Home,All,Users,List */ @Serializable - enum class Src( - val value: kotlin.String, + public enum class Src( + public val value: kotlin.String, ) { @SerialName(value = "home") Home("home"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasCreateRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasCreateRequest.kt index 8962795414..5f2a1712ff 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param src * @param keywords * @param excludeKeywords * @param users * @param caseSensitive * @param withReplies * @param withFile * @param notify * @param userListId */ @Serializable -internal data class AntennasCreateRequest( +public data class AntennasCreateRequest( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "src") val src: AntennasCreateRequest.Src, @SerialName(value = "keywords") val keywords: kotlin.collections.List>, @@ -39,8 +39,8 @@ internal data class AntennasCreateRequest( * Values: Home,All,Users,List */ @Serializable - enum class Src( - val value: kotlin.String, + public enum class Src( + public val value: kotlin.String, ) { @SerialName(value = "home") Home("home"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasDeleteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasDeleteRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasDeleteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasDeleteRequest.kt index 0a573b9b5f..e5348acef7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasDeleteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasDeleteRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param antennaId */ @Serializable -internal data class AntennasDeleteRequest( +public data class AntennasDeleteRequest( @SerialName(value = "antennaId") val antennaId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasNotesRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasNotesRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasNotesRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasNotesRequest.kt index 593ebf35b8..2705c8a428 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasNotesRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasNotesRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param antennaId * @param limit * @param sinceId * @param untilId * @param sinceDate * @param untilDate */ @Serializable -internal data class AntennasNotesRequest( +public data class AntennasNotesRequest( @SerialName(value = "antennaId") val antennaId: kotlin.String, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasUpdateRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasUpdateRequest.kt index 537dcf96ba..d2d214500e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AntennasUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param antennaId * @param name * @param src * @param keywords * @param excludeKeywords * @param users * @param caseSensitive * @param withReplies * @param withFile * @param notify * @param userListId */ @Serializable -internal data class AntennasUpdateRequest( +public data class AntennasUpdateRequest( @SerialName(value = "antennaId") val antennaId: kotlin.String, @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "src") val src: AntennasUpdateRequest.Src, @@ -40,8 +40,8 @@ internal data class AntennasUpdateRequest( * Values: Home,All,Users,List */ @Serializable - enum class Src( - val value: kotlin.String, + public enum class Src( + public val value: kotlin.String, ) { @SerialName(value = "home") Home("home"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApGetRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApGetRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApGetRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApGetRequest.kt index 6487397e15..99bf0a35ee 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApGetRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApGetRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param uri */ @Serializable -internal data class ApGetRequest( +public data class ApGetRequest( @SerialName(value = "uri") val uri: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200Response.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200Response.kt index ee13981ae5..9a1c8e92db 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param type * @param `object` */ @Serializable -internal data class ApShow200Response( +public data class ApShow200Response( @SerialName(value = "type") val type: ApShow200Response.Type, @SerialName(value = "object") val `object`: Note, ) { @@ -31,8 +31,8 @@ internal data class ApShow200Response( * Values: Note */ @Serializable - enum class Type( - val value: kotlin.String, + public enum class Type( + public val value: kotlin.String, ) { @SerialName(value = "Note") Note("Note"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200ResponseOneOf.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200ResponseOneOf.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200ResponseOneOf.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200ResponseOneOf.kt index 2797a989b2..ceb50f728a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200ResponseOneOf.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200ResponseOneOf.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param type * @param `object` */ @Serializable -internal data class ApShow200ResponseOneOf( +public data class ApShow200ResponseOneOf( @SerialName(value = "type") val type: ApShow200ResponseOneOf.Type, @SerialName(value = "object") val `object`: UserDetailedNotMe, ) { @@ -31,8 +31,8 @@ internal data class ApShow200ResponseOneOf( * Values: User */ @Serializable - enum class Type( - val value: kotlin.String, + public enum class Type( + public val value: kotlin.String, ) { @SerialName(value = "User") User("User"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200ResponseOneOf1.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200ResponseOneOf1.kt similarity index 86% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200ResponseOneOf1.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200ResponseOneOf1.kt index 979fd122f2..b3a4ea5ee4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200ResponseOneOf1.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ApShow200ResponseOneOf1.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param type * @param `object` */ @Serializable -internal data class ApShow200ResponseOneOf1( +public data class ApShow200ResponseOneOf1( @SerialName(value = "type") val type: ApShow200ResponseOneOf1.Type, @SerialName(value = "object") val `object`: Note, ) { @@ -31,8 +31,8 @@ internal data class ApShow200ResponseOneOf1( * Values: Note */ @Serializable - enum class Type( - val value: kotlin.String, + public enum class Type( + public val value: kotlin.String, ) { @SerialName(value = "Note") Note("Note"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/App.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/App.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/App.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/App.kt index 81520a9e95..ab29e521de 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/App.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/App.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param name * @param callbackUrl * @param permission * @param secret * @param isAuthorized */ @Serializable -internal data class App( +public data class App( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "callbackUrl") val callbackUrl: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AppCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AppCreateRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AppCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AppCreateRequest.kt index 7008180fe3..7a7c6866f3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AppCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AppCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param description * @param permission * @param callbackUrl */ @Serializable -internal data class AppCreateRequest( +public data class AppCreateRequest( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "description") val description: kotlin.String, @SerialName(value = "permission") val permission: kotlin.collections.Set, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AppShowRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AppShowRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AppShowRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AppShowRequest.kt index 5bbee5117a..1d7e17bc3f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AppShowRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AppShowRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param appId */ @Serializable -internal data class AppShowRequest( +public data class AppShowRequest( @SerialName(value = "appId") val appId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionGenerate200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionGenerate200Response.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionGenerate200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionGenerate200Response.kt index 99852df966..11351ab78d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionGenerate200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionGenerate200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param token * @param url */ @Serializable -internal data class AuthSessionGenerate200Response( +public data class AuthSessionGenerate200Response( @SerialName(value = "token") val token: kotlin.String, @SerialName(value = "url") val url: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionGenerateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionGenerateRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionGenerateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionGenerateRequest.kt index 3d037db530..28675405a3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionGenerateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionGenerateRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param appSecret */ @Serializable -internal data class AuthSessionGenerateRequest( +public data class AuthSessionGenerateRequest( @SerialName(value = "appSecret") val appSecret: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionShow200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionShow200Response.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionShow200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionShow200Response.kt index 10feb190e8..96da5f216d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionShow200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionShow200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param app * @param token */ @Serializable -internal data class AuthSessionShow200Response( +public data class AuthSessionShow200Response( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "app") val app: App, @SerialName(value = "token") val token: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionShowRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionShowRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionShowRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionShowRequest.kt index ad884f0f31..75f2e3f265 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionShowRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionShowRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param token */ @Serializable -internal data class AuthSessionShowRequest( +public data class AuthSessionShowRequest( @SerialName(value = "token") val token: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionUserkey200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionUserkey200Response.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionUserkey200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionUserkey200Response.kt index 833c95c261..d174319930 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionUserkey200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionUserkey200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param accessToken * @param user */ @Serializable -internal data class AuthSessionUserkey200Response( +public data class AuthSessionUserkey200Response( @SerialName(value = "accessToken") val accessToken: kotlin.String, @SerialName(value = "user") val user: UserDetailedNotMe, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionUserkeyRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionUserkeyRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionUserkeyRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionUserkeyRequest.kt index e0759ec039..fb027515a4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionUserkeyRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/AuthSessionUserkeyRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param appSecret * @param token */ @Serializable -internal data class AuthSessionUserkeyRequest( +public data class AuthSessionUserkeyRequest( @SerialName(value = "appSecret") val appSecret: kotlin.String, @SerialName(value = "token") val token: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Blocking.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Blocking.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Blocking.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Blocking.kt index 4ac606c369..6e88184a24 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Blocking.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Blocking.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param blockeeId * @param blockee */ @Serializable -internal data class Blocking( +public data class Blocking( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "blockeeId") val blockeeId: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/BlockingListRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/BlockingListRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/BlockingListRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/BlockingListRequest.kt index 958ec64ec7..6d8c426d73 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/BlockingListRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/BlockingListRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param sinceId * @param untilId */ @Serializable -internal data class BlockingListRequest( +public data class BlockingListRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 30, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Channel.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Channel.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Channel.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Channel.kt index e1f145bc6f..fa84e863a6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Channel.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Channel.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param lastNotedAt * @param name * @param description * @param bannerUrl * @param isArchived * @param notesCount * @param usersCount * @param userId * @param pinnedNoteIds * @param color * @param isFollowing * @param isFavorited */ @Serializable -internal data class Channel( +public data class Channel( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "lastNotedAt") val lastNotedAt: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsCreateRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsCreateRequest.kt index 14221ad5d8..c182a003b7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param description * @param bannerId * @param color */ @Serializable -internal data class ChannelsCreateRequest( +public data class ChannelsCreateRequest( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "description") val description: kotlin.String? = null, @SerialName(value = "bannerId") val bannerId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFeaturedRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFeaturedRequest.kt similarity index 86% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFeaturedRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFeaturedRequest.kt index d68bb4515d..2196fe9048 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFeaturedRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFeaturedRequest.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class ChannelsFeaturedRequest( +public data class ChannelsFeaturedRequest( @SerialName(value = "limit") val limit: Int? = 10, @SerialName(value = "allowPartial") val allowPartial: Boolean? = true, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFollowRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFollowRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFollowRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFollowRequest.kt index a385578eb4..e7c705097e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFollowRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFollowRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param channelId */ @Serializable -internal data class ChannelsFollowRequest( +public data class ChannelsFollowRequest( @SerialName(value = "channelId") val channelId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFollowedRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFollowedRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFollowedRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFollowedRequest.kt index aaf900ed90..dcea6351c3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFollowedRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsFollowedRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param sinceId * @param untilId * @param limit */ @Serializable -internal data class ChannelsFollowedRequest( +public data class ChannelsFollowedRequest( @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, @SerialName(value = "limit") val limit: kotlin.Int? = 5, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsSearchRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsSearchRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsSearchRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsSearchRequest.kt index e93325a3c5..d3cdf6b108 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsSearchRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsSearchRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param query * @param type * @param sinceId * @param untilId * @param limit */ @Serializable -internal data class ChannelsSearchRequest( +public data class ChannelsSearchRequest( @SerialName(value = "query") val query: kotlin.String, @SerialName(value = "type") val type: ChannelsSearchRequest.Type? = Type.NameAndDescription, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @@ -34,8 +34,8 @@ internal data class ChannelsSearchRequest( * Values: NameAndDescription,NameOnly */ @Serializable - enum class Type( - val value: kotlin.String, + public enum class Type( + public val value: kotlin.String, ) { @SerialName(value = "nameAndDescription") NameAndDescription("nameAndDescription"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsTimelineRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsTimelineRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsTimelineRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsTimelineRequest.kt index e568158e60..87b692490b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsTimelineRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsTimelineRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param channelId * @param limit * @param sinceId * @param untilId * @param sinceDate * @param untilDate */ @Serializable -internal data class ChannelsTimelineRequest( +public data class ChannelsTimelineRequest( @SerialName(value = "channelId") val channelId: kotlin.String, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsUpdateRequest.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsUpdateRequest.kt index 2dc8e4f3f0..ac2dc70750 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChannelsUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param channelId * @param name * @param description * @param bannerId * @param isArchived * @param pinnedNoteIds * @param color */ @Serializable -internal data class ChannelsUpdateRequest( +public data class ChannelsUpdateRequest( @SerialName(value = "channelId") val channelId: kotlin.String, @SerialName(value = "name") val name: kotlin.String? = null, @SerialName(value = "description") val description: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsActiveUsers200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsActiveUsers200Response.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsActiveUsers200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsActiveUsers200Response.kt index 02d3055273..7dc54caea5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsActiveUsers200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsActiveUsers200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param readWrite * @param read * @param write * @param registeredWithinWeek * @param registeredWithinMonth * @param registeredWithinYear * @param registeredOutsideWeek * @param registeredOutsideMonth * @param registeredOutsideYear */ @Serializable -internal data class ChartsActiveUsers200Response( +public data class ChartsActiveUsers200Response( @SerialName(value = "readWrite") val readWrite: kotlin.collections.List, @SerialName(value = "read") val read: kotlin.collections.List, @SerialName(value = "write") val write: kotlin.collections.List, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsActiveUsersRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsActiveUsersRequest.kt similarity index 88% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsActiveUsersRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsActiveUsersRequest.kt index efbc94f1c7..2829a57aa7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsActiveUsersRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsActiveUsersRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param span * @param limit * @param offset */ @Serializable -internal data class ChartsActiveUsersRequest( +public data class ChartsActiveUsersRequest( @SerialName(value = "span") val span: ChartsActiveUsersRequest.Span, @SerialName(value = "limit") val limit: kotlin.Int? = 30, @SerialName(value = "offset") val offset: kotlin.Int? = null, @@ -32,8 +32,8 @@ internal data class ChartsActiveUsersRequest( * Values: Day,Hour */ @Serializable - enum class Span( - val value: kotlin.String, + public enum class Span( + public val value: kotlin.String, ) { @SerialName(value = "day") Day("day"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsApRequest200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsApRequest200Response.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsApRequest200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsApRequest200Response.kt index 5fe6de9425..b87d26355d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsApRequest200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsApRequest200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param deliverFailed * @param deliverSucceeded * @param inboxReceived */ @Serializable -internal data class ChartsApRequest200Response( +public data class ChartsApRequest200Response( @SerialName(value = "deliverFailed") val deliverFailed: kotlin.collections.List, @SerialName(value = "deliverSucceeded") val deliverSucceeded: kotlin.collections.List, @SerialName(value = "inboxReceived") val inboxReceived: kotlin.collections.List, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsDrive200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsDrive200Response.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsDrive200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsDrive200Response.kt index da83fa039a..436f96221d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsDrive200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsDrive200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param localIncCount * @param localIncSize * @param localDecCount * @param localDecSize * @param remoteIncCount * @param remoteIncSize * @param remoteDecCount * @param remoteDecSize */ @Serializable -internal data class ChartsDrive200Response( +public data class ChartsDrive200Response( @SerialName(value = "local.incCount") val localIncCount: kotlin.collections.List, @SerialName(value = "local.incSize") val localIncSize: kotlin.collections.List, @SerialName(value = "local.decCount") val localDecCount: kotlin.collections.List, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsFederation200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsFederation200Response.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsFederation200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsFederation200Response.kt index e38d8fc86c..221861765a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsFederation200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsFederation200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param deliveredInstances * @param inboxInstances * @param stalled * @param sub * @param pub * @param pubsub * @param subActive * @param pubActive */ @Serializable -internal data class ChartsFederation200Response( +public data class ChartsFederation200Response( @SerialName(value = "deliveredInstances") val deliveredInstances: kotlin.collections.List, @SerialName(value = "inboxInstances") val inboxInstances: kotlin.collections.List, @SerialName(value = "stalled") val stalled: kotlin.collections.List, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsInstance200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsInstance200Response.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsInstance200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsInstance200Response.kt index 4daaf8412a..07285313bf 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsInstance200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsInstance200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param requestsFailed * @param requestsSucceeded * @param requestsReceived * @param notesTotal * @param notesInc * @param notesDec * @param notesDiffsNormal * @param notesDiffsReply * @param notesDiffsRenote * @param notesDiffsWithFile * @param usersTotal * @param usersInc * @param usersDec * @param followingTotal * @param followingInc * @param followingDec * @param followersTotal * @param followersInc * @param followersDec * @param driveTotalFiles * @param driveIncFiles * @param driveDecFiles * @param driveIncUsage * @param driveDecUsage */ @Serializable -internal data class ChartsInstance200Response( +public data class ChartsInstance200Response( @SerialName(value = "requests.failed") val requestsFailed: kotlin.collections.List, @SerialName(value = "requests.succeeded") val requestsSucceeded: kotlin.collections.List, @SerialName(value = "requests.received") val requestsReceived: kotlin.collections.List, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsInstanceRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsInstanceRequest.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsInstanceRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsInstanceRequest.kt index eb841d6e6b..c29e550dc4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsInstanceRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsInstanceRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param span * @param host * @param limit * @param offset */ @Serializable -internal data class ChartsInstanceRequest( +public data class ChartsInstanceRequest( @SerialName(value = "span") val span: ChartsInstanceRequest.Span, @SerialName(value = "host") val host: kotlin.String, @SerialName(value = "limit") val limit: kotlin.Int? = 30, @@ -33,8 +33,8 @@ internal data class ChartsInstanceRequest( * Values: Day,Hour */ @Serializable - enum class Span( - val value: kotlin.String, + public enum class Span( + public val value: kotlin.String, ) { @SerialName(value = "day") Day("day"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsNotes200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsNotes200Response.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsNotes200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsNotes200Response.kt index 6724502c51..43ac5488e8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsNotes200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsNotes200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param localTotal * @param localInc * @param localDec * @param localDiffsNormal * @param localDiffsReply * @param localDiffsRenote * @param localDiffsWithFile * @param remoteTotal * @param remoteInc * @param remoteDec * @param remoteDiffsNormal * @param remoteDiffsReply * @param remoteDiffsRenote * @param remoteDiffsWithFile */ @Serializable -internal data class ChartsNotes200Response( +public data class ChartsNotes200Response( @SerialName(value = "local.total") val localTotal: kotlin.collections.List, @SerialName(value = "local.inc") val localInc: kotlin.collections.List, @SerialName(value = "local.dec") val localDec: kotlin.collections.List, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserDrive200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserDrive200Response.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserDrive200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserDrive200Response.kt index d873c1babb..4117c512bf 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserDrive200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserDrive200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param totalCount * @param totalSize * @param incCount * @param incSize * @param decCount * @param decSize */ @Serializable -internal data class ChartsUserDrive200Response( +public data class ChartsUserDrive200Response( @SerialName(value = "totalCount") val totalCount: kotlin.collections.List, @SerialName(value = "totalSize") val totalSize: kotlin.collections.List, @SerialName(value = "incCount") val incCount: kotlin.collections.List, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserDriveRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserDriveRequest.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserDriveRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserDriveRequest.kt index 7c8386c711..da34373198 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserDriveRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserDriveRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param span * @param userId * @param limit * @param offset */ @Serializable -internal data class ChartsUserDriveRequest( +public data class ChartsUserDriveRequest( @SerialName(value = "span") val span: ChartsUserDriveRequest.Span, @SerialName(value = "userId") val userId: kotlin.String, @SerialName(value = "limit") val limit: kotlin.Int? = 30, @@ -33,8 +33,8 @@ internal data class ChartsUserDriveRequest( * Values: Day,Hour */ @Serializable - enum class Span( - val value: kotlin.String, + public enum class Span( + public val value: kotlin.String, ) { @SerialName(value = "day") Day("day"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserFollowing200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserFollowing200Response.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserFollowing200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserFollowing200Response.kt index e6b60b800c..3c78f07150 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserFollowing200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserFollowing200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param localFollowingsTotal * @param localFollowingsInc * @param localFollowingsDec * @param localFollowersTotal * @param localFollowersInc * @param localFollowersDec * @param remoteFollowingsTotal * @param remoteFollowingsInc * @param remoteFollowingsDec * @param remoteFollowersTotal * @param remoteFollowersInc * @param remoteFollowersDec */ @Serializable -internal data class ChartsUserFollowing200Response( +public data class ChartsUserFollowing200Response( @SerialName(value = "local.followings.total") val localFollowingsTotal: kotlin.collections.List, @SerialName(value = "local.followings.inc") val localFollowingsInc: kotlin.collections.List, @SerialName(value = "local.followings.dec") val localFollowingsDec: kotlin.collections.List, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserNotes200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserNotes200Response.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserNotes200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserNotes200Response.kt index b59dd7c89a..e0bf96aded 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserNotes200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserNotes200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param total * @param inc * @param dec * @param diffsNormal * @param diffsReply * @param diffsRenote * @param diffsWithFile */ @Serializable -internal data class ChartsUserNotes200Response( +public data class ChartsUserNotes200Response( @SerialName(value = "total") val total: kotlin.collections.List, @SerialName(value = "inc") val inc: kotlin.collections.List, @SerialName(value = "dec") val dec: kotlin.collections.List, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserPv200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserPv200Response.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserPv200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserPv200Response.kt index e78befdacb..f7a62d3d63 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserPv200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserPv200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param upvUser * @param pvUser * @param upvVisitor * @param pvVisitor */ @Serializable -internal data class ChartsUserPv200Response( +public data class ChartsUserPv200Response( @SerialName(value = "upv.user") val upvUser: kotlin.collections.List, @SerialName(value = "pv.user") val pvUser: kotlin.collections.List, @SerialName(value = "upv.visitor") val upvVisitor: kotlin.collections.List, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserReactions200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserReactions200Response.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserReactions200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserReactions200Response.kt index 644a05ffeb..713c5f49bd 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserReactions200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUserReactions200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param localCount * @param remoteCount */ @Serializable -internal data class ChartsUserReactions200Response( +public data class ChartsUserReactions200Response( @SerialName(value = "local.count") val localCount: kotlin.collections.List, @SerialName(value = "remote.count") val remoteCount: kotlin.collections.List, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUsers200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUsers200Response.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUsers200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUsers200Response.kt index 7df6fa146f..270be79cee 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUsers200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ChartsUsers200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param localTotal * @param localInc * @param localDec * @param remoteTotal * @param remoteInc * @param remoteDec */ @Serializable -internal data class ChartsUsers200Response( +public data class ChartsUsers200Response( @SerialName(value = "local.total") val localTotal: kotlin.collections.List, @SerialName(value = "local.inc") val localInc: kotlin.collections.List, @SerialName(value = "local.dec") val localDec: kotlin.collections.List, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Clip.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Clip.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Clip.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Clip.kt index 72c5cdeaec..02e669c5db 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Clip.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Clip.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param lastClippedAt * @param userId * @param user * @param name * @param description * @param isPublic * @param favoritedCount * @param isFavorited */ @Serializable -internal data class Clip( +public data class Clip( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "lastClippedAt") val lastClippedAt: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsAddNoteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsAddNoteRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsAddNoteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsAddNoteRequest.kt index 1171a8c536..87aa6c0cdb 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsAddNoteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsAddNoteRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param clipId * @param noteId */ @Serializable -internal data class ClipsAddNoteRequest( +public data class ClipsAddNoteRequest( @SerialName(value = "clipId") val clipId: kotlin.String, @SerialName(value = "noteId") val noteId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsCreateRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsCreateRequest.kt index 4e6a6b0575..e4e9932c2a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param isPublic * @param description */ @Serializable -internal data class ClipsCreateRequest( +public data class ClipsCreateRequest( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "isPublic") val isPublic: kotlin.Boolean? = false, @SerialName(value = "description") val description: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsDeleteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsDeleteRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsDeleteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsDeleteRequest.kt index 7d0adbd697..d33beb2ad2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsDeleteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsDeleteRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param clipId */ @Serializable -internal data class ClipsDeleteRequest( +public data class ClipsDeleteRequest( @SerialName(value = "clipId") val clipId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsNotesRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsNotesRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsNotesRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsNotesRequest.kt index 8eade5803c..961f1a0485 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsNotesRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsNotesRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param clipId * @param limit * @param sinceId * @param untilId */ @Serializable -internal data class ClipsNotesRequest( +public data class ClipsNotesRequest( @SerialName(value = "clipId") val clipId: kotlin.String, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsUpdateRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsUpdateRequest.kt index 3f835c50e2..3cb418e99f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ClipsUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param clipId * @param name * @param isPublic * @param description */ @Serializable -internal data class ClipsUpdateRequest( +public data class ClipsUpdateRequest( @SerialName(value = "clipId") val clipId: kotlin.String, @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "isPublic") val isPublic: kotlin.Boolean? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Drive200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Drive200Response.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Drive200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Drive200Response.kt index 4f734b14fb..02f3d46ebc 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Drive200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Drive200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param capacity * @param usage */ @Serializable -internal data class Drive200Response( +public data class Drive200Response( @SerialName(value = "capacity") val capacity: kotlin.Double, @SerialName(value = "usage") val usage: kotlin.Double, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFile.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFile.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFile.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFile.kt index dbf10c275a..f3689ccfb1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFile.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFile.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param name * @param type * @param md5 * @param propertySize * @param isSensitive * @param blurhash * @param properties * @param url * @param thumbnailUrl * @param comment * @param folderId * @param userId * @param folder * @param user */ @Serializable -internal data class DriveFile( +public data class DriveFile( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "name") val name: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFileProperties.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFileProperties.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFileProperties.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFileProperties.kt index f4a0bc7775..38c916c731 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFileProperties.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFileProperties.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param width * @param height * @param orientation * @param avgColor */ @Serializable -internal data class DriveFileProperties( +public data class DriveFileProperties( @SerialName(value = "width") val width: kotlin.Double? = null, @SerialName(value = "height") val height: kotlin.Double? = null, @SerialName(value = "orientation") val orientation: kotlin.Double? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesAttachedNotesRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesAttachedNotesRequest.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesAttachedNotesRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesAttachedNotesRequest.kt index 1c6aec2030..076b2e20da 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesAttachedNotesRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesAttachedNotesRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param fileId */ @Serializable -internal data class DriveFilesAttachedNotesRequest( +public data class DriveFilesAttachedNotesRequest( @SerialName(value = "fileId") val fileId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesCheckExistenceRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesCheckExistenceRequest.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesCheckExistenceRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesCheckExistenceRequest.kt index 3c1eb063e2..c7d8ace925 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesCheckExistenceRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesCheckExistenceRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param md5 */ @Serializable -internal data class DriveFilesCheckExistenceRequest( +public data class DriveFilesCheckExistenceRequest( @SerialName(value = "md5") val md5: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesFindRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesFindRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesFindRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesFindRequest.kt index f2057850d7..4cd975a56f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesFindRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesFindRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param folderId */ @Serializable -internal data class DriveFilesFindRequest( +public data class DriveFilesFindRequest( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "folderId") val folderId: kotlin.String? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesRequest.kt index b641a9a09f..c91bf325d4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param sinceId * @param untilId * @param folderId * @param type * @param sort */ @Serializable -internal data class DriveFilesRequest( +public data class DriveFilesRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, @@ -35,8 +35,8 @@ internal data class DriveFilesRequest( * Values: PlusCreatedAt,MinusCreatedAt,PlusName,MinusName,PlusSize,MinusSize */ @Serializable - enum class Sort( - val value: kotlin.String, + public enum class Sort( + public val value: kotlin.String, ) { @SerialName(value = "+createdAt") PlusCreatedAt("+createdAt"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesUpdateRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesUpdateRequest.kt index 27b27eb562..e01ab7bd11 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param fileId * @param folderId * @param name * @param isSensitive * @param comment */ @Serializable -internal data class DriveFilesUpdateRequest( +public data class DriveFilesUpdateRequest( @SerialName(value = "fileId") val fileId: kotlin.String, @SerialName(value = "folderId") val folderId: kotlin.String? = null, @SerialName(value = "name") val name: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesUploadFromUrlRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesUploadFromUrlRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesUploadFromUrlRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesUploadFromUrlRequest.kt index 7ed4560d68..aca02ffe69 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesUploadFromUrlRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFilesUploadFromUrlRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param url * @param folderId * @param isSensitive * @param comment * @param marker * @param force */ @Serializable -internal data class DriveFilesUploadFromUrlRequest( +public data class DriveFilesUploadFromUrlRequest( @SerialName(value = "url") val url: kotlin.String, @SerialName(value = "folderId") val folderId: kotlin.String? = null, @SerialName(value = "isSensitive") val isSensitive: kotlin.Boolean? = false, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFolder.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFolder.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFolder.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFolder.kt index 33dec3aa4c..c0ed3f3f94 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFolder.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFolder.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param name * @param parentId * @param foldersCount * @param filesCount * @param parent */ @Serializable -internal data class DriveFolder( +public data class DriveFolder( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "name") val name: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersCreateRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersCreateRequest.kt index 478a5ec99e..e02fccdcdd 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param parentId */ @Serializable -internal data class DriveFoldersCreateRequest( +public data class DriveFoldersCreateRequest( @SerialName(value = "name") val name: kotlin.String? = "Untitled", @SerialName(value = "parentId") val parentId: kotlin.String? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersDeleteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersDeleteRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersDeleteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersDeleteRequest.kt index 2b05a354ee..054e047186 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersDeleteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersDeleteRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param folderId */ @Serializable -internal data class DriveFoldersDeleteRequest( +public data class DriveFoldersDeleteRequest( @SerialName(value = "folderId") val folderId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersFindRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersFindRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersFindRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersFindRequest.kt index afda78ccd1..a11fdc4a74 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersFindRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersFindRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param parentId */ @Serializable -internal data class DriveFoldersFindRequest( +public data class DriveFoldersFindRequest( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "parentId") val parentId: kotlin.String? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersRequest.kt index 72777c0e7d..4b63074025 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param sinceId * @param untilId * @param folderId */ @Serializable -internal data class DriveFoldersRequest( +public data class DriveFoldersRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersUpdateRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersUpdateRequest.kt index 4adb6d13ae..e645df01f2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveFoldersUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param folderId * @param name * @param parentId */ @Serializable -internal data class DriveFoldersUpdateRequest( +public data class DriveFoldersUpdateRequest( @SerialName(value = "folderId") val folderId: kotlin.String, @SerialName(value = "name") val name: kotlin.String? = null, @SerialName(value = "parentId") val parentId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveStreamRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveStreamRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveStreamRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveStreamRequest.kt index 4a20900db4..6432d797ae 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveStreamRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/DriveStreamRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param sinceId * @param untilId * @param type */ @Serializable -internal data class DriveStreamRequest( +public data class DriveStreamRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmailAddressAvailable200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmailAddressAvailable200Response.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmailAddressAvailable200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmailAddressAvailable200Response.kt index 91303f89c0..87a472f59b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmailAddressAvailable200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmailAddressAvailable200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param available * @param reason */ @Serializable -internal data class EmailAddressAvailable200Response( +public data class EmailAddressAvailable200Response( @SerialName(value = "available") val available: kotlin.Boolean, @SerialName(value = "reason") val reason: kotlin.String?, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmailAddressAvailableRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmailAddressAvailableRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmailAddressAvailableRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmailAddressAvailableRequest.kt index a5507d85e9..b8028c5748 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmailAddressAvailableRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmailAddressAvailableRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param emailAddress */ @Serializable -internal data class EmailAddressAvailableRequest( +public data class EmailAddressAvailableRequest( @SerialName(value = "emailAddress") val emailAddress: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiDetailed.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiDetailed.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiDetailed.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiDetailed.kt index 078020f832..a95a163c30 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiDetailed.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiDetailed.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param id * @param aliases * @param name * @param category * @param host The local host is represented with `null`. * @param url * @param license * @param isSensitive * @param localOnly * @param roleIdsThatCanBeUsedThisEmojiAsReaction */ @Serializable -internal data class EmojiDetailed( +public data class EmojiDetailed( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "aliases") val aliases: kotlin.collections.List, @SerialName(value = "name") val name: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiRequest.kt index ea859cb8cd..8071718cbd 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param name */ @Serializable -internal data class EmojiRequest( +public data class EmojiRequest( @SerialName(value = "name") val name: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiSimple.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiSimple.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiSimple.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiSimple.kt index 0373ea9de9..9d52347634 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiSimple.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EmojiSimple.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param aliases * @param name * @param category * @param url * @param isSensitive * @param roleIdsThatCanBeUsedThisEmojiAsReaction */ @Serializable -internal data class EmojiSimple( +public data class EmojiSimple( @SerialName(value = "aliases") val aliases: kotlin.collections.List, @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "category") val category: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Emojis200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Emojis200Response.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Emojis200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Emojis200Response.kt index a54f21c819..270dff3241 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Emojis200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Emojis200Response.kt @@ -23,6 +23,6 @@ import kotlinx.serialization.Serializable * * * @param emojis */ @Serializable -internal data class Emojis200Response( +public data class Emojis200Response( @SerialName(value = "emojis") val emojis: kotlin.collections.List<@Contextual EmojiSimple>, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EndpointRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EndpointRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EndpointRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EndpointRequest.kt index 6b8a2ceb5c..2d83ef9c66 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EndpointRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/EndpointRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param endpoint */ @Serializable -internal data class EndpointRequest( +public data class EndpointRequest( @SerialName(value = "endpoint") val endpoint: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Error.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Error.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Error.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Error.kt index d40c052018..c93ae6c3c7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Error.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Error.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param error */ @Serializable -internal data class Error( +public data class Error( @SerialName(value = "error") val error: ErrorError, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ErrorError.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ErrorError.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ErrorError.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ErrorError.kt index 1c620df9dc..5cd9f0ae22 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ErrorError.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ErrorError.kt @@ -26,7 +26,7 @@ import kotlinx.serialization.Serializable * @param id An error ID. This ID is static. */ @Serializable -internal data class ErrorError( +public data class ErrorError( // An error code. Unique within the endpoint. @SerialName(value = "code") val code: kotlin.String, // An error message. diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationFollowersRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationFollowersRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationFollowersRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationFollowersRequest.kt index a3054c2a7e..3a92d20ead 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationFollowersRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationFollowersRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param host * @param sinceId * @param untilId * @param limit */ @Serializable -internal data class FederationFollowersRequest( +public data class FederationFollowersRequest( @SerialName(value = "host") val host: kotlin.String, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationInstance.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationInstance.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationInstance.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationInstance.kt index 768670d655..4d839d6cba 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationInstance.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationInstance.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param firstRetrievedAt * @param host * @param usersCount * @param notesCount * @param followingCount * @param followersCount * @param isNotResponding * @param isSuspended * @param isBlocked * @param softwareName * @param softwareVersion * @param openRegistrations * @param name * @param description * @param maintainerName * @param maintainerEmail * @param iconUrl * @param faviconUrl * @param themeColor * @param infoUpdatedAt */ @Serializable -internal data class FederationInstance( +public data class FederationInstance( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "firstRetrievedAt") val firstRetrievedAt: kotlin.String, @SerialName(value = "host") val host: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationInstancesRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationInstancesRequest.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationInstancesRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationInstancesRequest.kt index 2b62f44f0f..51d9d9aa2a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationInstancesRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationInstancesRequest.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param host Omit or use `null` to not filter by host. * @param blocked * @param notResponding * @param suspended * @param federating * @param subscribing * @param publishing * @param limit * @param offset * @param sort */ @Serializable -internal data class FederationInstancesRequest( +public data class FederationInstancesRequest( // Omit or use `null` to not filter by host. @SerialName(value = "host") val host: kotlin.String? = null, @SerialName(value = "blocked") val blocked: kotlin.Boolean? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationShowInstance200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationShowInstance200Response.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationShowInstance200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationShowInstance200Response.kt index 14aa1c7a94..2bf8adcc99 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationShowInstance200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationShowInstance200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param firstRetrievedAt * @param host * @param usersCount * @param notesCount * @param followingCount * @param followersCount * @param isNotResponding * @param isSuspended * @param isBlocked * @param softwareName * @param softwareVersion * @param openRegistrations * @param name * @param description * @param maintainerName * @param maintainerEmail * @param iconUrl * @param faviconUrl * @param themeColor * @param infoUpdatedAt */ @Serializable -internal data class FederationShowInstance200Response( +public data class FederationShowInstance200Response( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "firstRetrievedAt") val firstRetrievedAt: kotlin.String, @SerialName(value = "host") val host: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationStatsRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationStatsRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationStatsRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationStatsRequest.kt index ea7f15a566..69c5c49245 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationStatsRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FederationStatsRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param limit */ @Serializable -internal data class FederationStatsRequest( +public data class FederationStatsRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FetchRssRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FetchRssRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FetchRssRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FetchRssRequest.kt index b4ba4e5ff4..02d392caa5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FetchRssRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FetchRssRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param url */ @Serializable -internal data class FetchRssRequest( +public data class FetchRssRequest( @SerialName(value = "url") val url: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Flash.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Flash.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Flash.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Flash.kt index d8fdf06134..a0e32e5318 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Flash.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Flash.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param updatedAt * @param title * @param summary * @param script * @param userId * @param user * @param likedCount * @param isLiked */ @Serializable -internal data class Flash( +public data class Flash( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "updatedAt") val updatedAt: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashCreateRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashCreateRequest.kt index c1e645266b..78fe211f5c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param title * @param summary * @param script * @param permissions */ @Serializable -internal data class FlashCreateRequest( +public data class FlashCreateRequest( @SerialName(value = "title") val title: kotlin.String, @SerialName(value = "summary") val summary: kotlin.String, @SerialName(value = "script") val script: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashDeleteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashDeleteRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashDeleteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashDeleteRequest.kt index 9e4b686624..eb88246ae2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashDeleteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashDeleteRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param flashId */ @Serializable -internal data class FlashDeleteRequest( +public data class FlashDeleteRequest( @SerialName(value = "flashId") val flashId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashMyLikes200ResponseInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashMyLikes200ResponseInner.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashMyLikes200ResponseInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashMyLikes200ResponseInner.kt index 9b014c1245..607fbde5b9 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashMyLikes200ResponseInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashMyLikes200ResponseInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param flash */ @Serializable -internal data class FlashMyLikes200ResponseInner( +public data class FlashMyLikes200ResponseInner( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "flash") val flash: Flash, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashUpdateRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashUpdateRequest.kt index 5666065b4d..9f8d084223 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FlashUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param flashId * @param title * @param summary * @param script * @param permissions */ @Serializable -internal data class FlashUpdateRequest( +public data class FlashUpdateRequest( @SerialName(value = "flashId") val flashId: kotlin.String, @SerialName(value = "title") val title: kotlin.String, @SerialName(value = "summary") val summary: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Following.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Following.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Following.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Following.kt index 584afd764d..363eaa1d1e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Following.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Following.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param followeeId * @param followerId * @param followee * @param follower */ @Serializable -internal data class Following( +public data class Following( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "followeeId") val followeeId: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FollowingRequestsList200ResponseInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FollowingRequestsList200ResponseInner.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FollowingRequestsList200ResponseInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FollowingRequestsList200ResponseInner.kt index 59c786001c..0b46cfc36f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FollowingRequestsList200ResponseInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FollowingRequestsList200ResponseInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param follower * @param followee */ @Serializable -internal data class FollowingRequestsList200ResponseInner( +public data class FollowingRequestsList200ResponseInner( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "follower") val follower: UserLite, @SerialName(value = "followee") val followee: UserLite, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FollowingRequestsListRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FollowingRequestsListRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FollowingRequestsListRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FollowingRequestsListRequest.kt index 9bd35584bf..12e92e4c29 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FollowingRequestsListRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/FollowingRequestsListRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param sinceId * @param untilId * @param limit */ @Serializable -internal data class FollowingRequestsListRequest( +public data class FollowingRequestsListRequest( @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, @SerialName(value = "limit") val limit: kotlin.Int? = 10, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPost.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPost.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPost.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPost.kt index 66135a3ef5..d8283a7217 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPost.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPost.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param updatedAt * @param title * @param description * @param userId * @param user * @param isSensitive * @param fileIds * @param files * @param tags */ @Serializable -internal data class GalleryPost( +public data class GalleryPost( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "updatedAt") val updatedAt: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsCreateRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsCreateRequest.kt index bfe8fcdc44..3dfb1e4ac2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param title * @param fileIds * @param description * @param isSensitive */ @Serializable -internal data class GalleryPostsCreateRequest( +public data class GalleryPostsCreateRequest( @SerialName(value = "title") val title: kotlin.String, @SerialName(value = "fileIds") val fileIds: kotlin.collections.Set, @SerialName(value = "description") val description: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsDeleteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsDeleteRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsDeleteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsDeleteRequest.kt index c21a84b389..a34c808c35 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsDeleteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsDeleteRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param postId */ @Serializable -internal data class GalleryPostsDeleteRequest( +public data class GalleryPostsDeleteRequest( @SerialName(value = "postId") val postId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsUpdateRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsUpdateRequest.kt index dc976fb199..a51b012716 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/GalleryPostsUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param postId * @param title * @param fileIds * @param description * @param isSensitive */ @Serializable -internal data class GalleryPostsUpdateRequest( +public data class GalleryPostsUpdateRequest( @SerialName(value = "postId") val postId: kotlin.String, @SerialName(value = "title") val title: kotlin.String, @SerialName(value = "fileIds") val fileIds: kotlin.collections.Set, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Hashtag.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Hashtag.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Hashtag.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Hashtag.kt index 906e28089f..9a9f3e060a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Hashtag.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Hashtag.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param tag * @param mentionedUsersCount * @param mentionedLocalUsersCount * @param mentionedRemoteUsersCount * @param attachedUsersCount * @param attachedLocalUsersCount * @param attachedRemoteUsersCount */ @Serializable -internal data class Hashtag( +public data class Hashtag( @SerialName(value = "tag") val tag: kotlin.String, @SerialName(value = "mentionedUsersCount") val mentionedUsersCount: kotlin.Double, @SerialName(value = "mentionedLocalUsersCount") val mentionedLocalUsersCount: kotlin.Double, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsListRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsListRequest.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsListRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsListRequest.kt index 10356b6410..297d7057b3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsListRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsListRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param sort * @param limit * @param attachedToUserOnly * @param attachedToLocalUserOnly * @param attachedToRemoteUserOnly */ @Serializable -internal data class HashtagsListRequest( +public data class HashtagsListRequest( @SerialName(value = "sort") val sort: HashtagsListRequest.Sort, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "attachedToUserOnly") val attachedToUserOnly: kotlin.Boolean? = false, @@ -34,8 +34,8 @@ internal data class HashtagsListRequest( * Values: PlusMentionedUsers,MinusMentionedUsers,PlusMentionedLocalUsers,MinusMentionedLocalUsers,PlusMentionedRemoteUsers,MinusMentionedRemoteUsers,PlusAttachedUsers,MinusAttachedUsers,PlusAttachedLocalUsers,MinusAttachedLocalUsers,PlusAttachedRemoteUsers,MinusAttachedRemoteUsers */ @Serializable - enum class Sort( - val value: kotlin.String, + public enum class Sort( + public val value: kotlin.String, ) { @SerialName(value = "+mentionedUsers") PlusMentionedUsers("+mentionedUsers"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsSearchRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsSearchRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsSearchRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsSearchRequest.kt index 659bcd1801..f45ba5e401 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsSearchRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsSearchRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param query * @param limit * @param offset */ @Serializable -internal data class HashtagsSearchRequest( +public data class HashtagsSearchRequest( @SerialName(value = "query") val query: kotlin.String, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "offset") val offset: kotlin.Int? = 0, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsShowRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsShowRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsShowRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsShowRequest.kt index 2fe7677398..f9e43fb5fa 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsShowRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsShowRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param tag */ @Serializable -internal data class HashtagsShowRequest( +public data class HashtagsShowRequest( @SerialName(value = "tag") val tag: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsTrend200ResponseInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsTrend200ResponseInner.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsTrend200ResponseInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsTrend200ResponseInner.kt index b3dd72366c..b0c548df98 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsTrend200ResponseInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsTrend200ResponseInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param tag * @param chart * @param usersCount */ @Serializable -internal data class HashtagsTrend200ResponseInner( +public data class HashtagsTrend200ResponseInner( @SerialName(value = "tag") val tag: kotlin.String, @SerialName(value = "chart") val chart: kotlin.collections.List, @SerialName(value = "usersCount") val usersCount: kotlin.Double, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsUsersRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsUsersRequest.kt similarity index 88% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsUsersRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsUsersRequest.kt index 91f4063df3..08509b5c6d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsUsersRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/HashtagsUsersRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param tag * @param sort * @param limit * @param state * @param origin */ @Serializable -internal data class HashtagsUsersRequest( +public data class HashtagsUsersRequest( @SerialName(value = "tag") val tag: kotlin.String, @SerialName(value = "sort") val sort: HashtagsUsersRequest.Sort, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @@ -34,8 +34,8 @@ internal data class HashtagsUsersRequest( * Values: PlusFollower,MinusFollower,PlusCreatedAt,MinusCreatedAt,PlusUpdatedAt,MinusUpdatedAt */ @Serializable - enum class Sort( - val value: kotlin.String, + public enum class Sort( + public val value: kotlin.String, ) { @SerialName(value = "+follower") PlusFollower("+follower"), @@ -61,8 +61,8 @@ internal data class HashtagsUsersRequest( * Values: All,Alive */ @Serializable - enum class State( - val value: kotlin.String, + public enum class State( + public val value: kotlin.String, ) { @SerialName(value = "all") All("all"), @@ -76,8 +76,8 @@ internal data class HashtagsUsersRequest( * Values: Combined,Local,Remote */ @Serializable - enum class Origin( - val value: kotlin.String, + public enum class Origin( + public val value: kotlin.String, ) { @SerialName(value = "combined") Combined("combined"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IClaimAchievementRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IClaimAchievementRequest.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IClaimAchievementRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IClaimAchievementRequest.kt index f236f6cac4..186e08ce8a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IClaimAchievementRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IClaimAchievementRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name */ @Serializable -internal data class IClaimAchievementRequest( +public data class IClaimAchievementRequest( @SerialName(value = "name") val name: IClaimAchievementRequest.Name, ) { /** @@ -30,8 +30,8 @@ internal data class IClaimAchievementRequest( * Values: Notes1,Notes10,Notes100,Notes500,Notes1000,Notes5000,Notes10000,Notes20000,Notes30000,Notes40000,Notes50000,Notes60000,Notes70000,Notes80000,Notes90000,Notes100000,Login3,Login7,Login15,Login30,Login60,Login100,Login200,Login300,Login400,Login500,Login600,Login700,Login800,Login900,Login1000,PassedSinceAccountCreated1,PassedSinceAccountCreated2,PassedSinceAccountCreated3,LoggedInOnBirthday,LoggedInOnNewYearsDay,NoteClipped1,NoteFavorited1,MyNoteFavorited1,ProfileFilled,MarkedAsCat,Following1,Following10,Following50,Following100,Following300,Followers1,Followers10,Followers50,Followers100,Followers300,Followers500,Followers1000,CollectAchievements30,ViewAchievements3min,ILoveMisskey,FoundTreasure,Client30min,Client60min,NoteDeletedWithin1min,PostedAtLateNight,PostedAt0min0sec,SelfQuote,Htl20npm,ViewInstanceChart,OutputHelloWorldOnScratchpad,Open3windows,DriveFolderCircularReference,ReactWithoutRead,ClickedClickHere,JustPlainLucky,SetNameToSyuilo,CookieClicked,BrainDiver */ @Serializable - enum class Name( - val value: kotlin.String, + public enum class Name( + public val value: kotlin.String, ) { @SerialName(value = "notes1") Notes1("notes1"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IGalleryLikes200ResponseInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IGalleryLikes200ResponseInner.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IGalleryLikes200ResponseInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IGalleryLikes200ResponseInner.kt index b1826cc9cf..3ab327b6fd 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IGalleryLikes200ResponseInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IGalleryLikes200ResponseInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param post */ @Serializable -internal data class IGalleryLikes200ResponseInner( +public data class IGalleryLikes200ResponseInner( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "post") val post: GalleryPost, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IGetWordMutedNotesCount200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IGetWordMutedNotesCount200Response.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IGetWordMutedNotesCount200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IGetWordMutedNotesCount200Response.kt index bb5271d4f0..a4b51dacd8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IGetWordMutedNotesCount200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IGetWordMutedNotesCount200Response.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param count */ @Serializable -internal data class IGetWordMutedNotesCount200Response( +public data class IGetWordMutedNotesCount200Response( @SerialName(value = "count") val count: kotlin.Double, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/INotificationsRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/INotificationsRequest.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/INotificationsRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/INotificationsRequest.kt index 89dcb6b67f..a48ad13b6e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/INotificationsRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/INotificationsRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param sinceId * @param untilId * @param markAsRead * @param includeTypes * @param excludeTypes */ @Serializable -internal data class INotificationsRequest( +public data class INotificationsRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, @@ -35,8 +35,8 @@ internal data class INotificationsRequest( * Values: Note,Follow,Mention,Reply,Renote,Quote,Reaction,PollEnded,ScheduledNotePosted,ScheduledNotePostFailed,ReceiveFollowRequest,FollowRequestAccepted,App,RoleAssigned,ChatRoomInvitationReceived,AchievementEarned,ExportCompleted,Test,Login,CreateToken,PollVote,GroupInvited */ @Serializable - enum class IncludeTypes( - val value: kotlin.String, + public enum class IncludeTypes( + public val value: kotlin.String, ) { @SerialName(value = "note") Note("note"), @@ -110,8 +110,8 @@ internal data class INotificationsRequest( * Values: Note,Follow,Mention,Reply,Renote,Quote,Reaction,PollEnded,ScheduledNotePosted,ScheduledNotePostFailed,ReceiveFollowRequest,FollowRequestAccepted,App,RoleAssigned,ChatRoomInvitationReceived,AchievementEarned,ExportCompleted,Test,Login,CreateToken,PollVote,GroupInvited */ @Serializable - enum class ExcludeTypes( - val value: kotlin.String, + public enum class ExcludeTypes( + public val value: kotlin.String, ) { @SerialName(value = "note") Note("note"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IPageLikes200ResponseInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IPageLikes200ResponseInner.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IPageLikes200ResponseInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IPageLikes200ResponseInner.kt index 7edf1120ea..0e34141a7e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IPageLikes200ResponseInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IPageLikes200ResponseInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param page */ @Serializable -internal data class IPageLikes200ResponseInner( +public data class IPageLikes200ResponseInner( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "page") val page: Page, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IPinRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IPinRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IPinRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IPinRequest.kt index 3f4dca4479..a03ab61536 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IPinRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IPinRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param noteId */ @Serializable -internal data class IPinRequest( +public data class IPinRequest( @SerialName(value = "noteId") val noteId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IReadAnnouncementRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IReadAnnouncementRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IReadAnnouncementRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IReadAnnouncementRequest.kt index 42e2f02ed2..2c34647c6c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IReadAnnouncementRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IReadAnnouncementRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param announcementId */ @Serializable -internal data class IReadAnnouncementRequest( +public data class IReadAnnouncementRequest( @SerialName(value = "announcementId") val announcementId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IUpdateRequest.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IUpdateRequest.kt index 179451b0a7..4e0f01580e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param description * @param location * @param birthday * @param lang * @param avatarId * @param bannerId * @param fields * @param isLocked * @param isExplorable * @param hideOnlineStatus * @param publicReactions * @param carefulBot * @param autoAcceptFollowed * @param noCrawle * @param preventAiLearning * @param isBot * @param isCat * @param injectFeaturedNote * @param receiveAnnouncementEmail * @param alwaysMarkNsfw * @param autoSensitive * @param ffVisibility * @param pinnedPageId * @param mutedWords * @param mutedInstances * @param mutingNotificationTypes * @param emailNotificationTypes * @param alsoKnownAs */ @Serializable -internal data class IUpdateRequest( +public data class IUpdateRequest( @SerialName(value = "name") val name: kotlin.String? = null, @SerialName(value = "description") val description: kotlin.String? = null, @SerialName(value = "location") val location: kotlin.String? = null, @@ -59,8 +59,8 @@ internal data class IUpdateRequest( * Values: Null,Ach,Ady,Af,AfMinusNA,AfMinusZA,Ak,Ar,ArMinusAR,ArMinusMA,ArMinusSA,AyMinusBO,Az,AzMinusAZ,BeMinusBY,Bg,BgMinusBG,Bn,BnMinusIN,BnMinusBD,Br,BsMinusBA,Ca,CaMinusES,Cak,CkMinusUS,Cs,CsMinusCZ,Cy,CyMinusGB,Da,DaMinusDK,De,DeMinusAT,DeMinusDE,DeMinusCH,Dsb,El,ElMinusGR,En,EnMinusGB,EnMinusAU,EnMinusCA,EnMinusIE,EnMinusIN,EnMinusPI,EnMinusSG,EnMinusUD,EnMinusUS,EnMinusZA,EnAtPirate,Eo,EoMinusEO,Es,EsMinusAR,EsMinus419,EsMinusCL,EsMinusCO,EsMinusEC,EsMinusES,EsMinusLA,EsMinusNI,EsMinusMX,EsMinusUS,EsMinusVE,Et,EtMinusEE,Eu,EuMinusES,Fa,FaMinusIR,FbMinusLT,Ff,Fi,FiMinusFI,Fo,FoMinusFO,Fr,FrMinusCA,FrMinusFR,FrMinusBE,FrMinusCH,FyMinusNL,Ga,GaMinusIE,Gd,Gl,GlMinusES,GnMinusPY,GuMinusIN,Gv,GxMinusGR,He,HeMinusIL,Hi,HiMinusIN,Hr,HrMinusHR,Hsb,Ht,Hu,HuMinusHU,Hy,HyMinusAM,Id,IdMinusID,Is,IsMinusIS,It,ItMinusIT,Ja,JaMinusJP,JvMinusID,KaMinusGE,KkMinusKZ,Km,Kl,KmMinusKH,Kab,Kn,KnMinusIN,Ko,KoMinusKR,KuMinusTR,Kw,La,LaMinusVA,Lb,LiMinusNL,Lt,LtMinusLT,Lv,LvMinusLV,Mai,MgMinusMG,Mk,MkMinusMK,Ml,MlMinusIN,MnMinusMN,Mr,MrMinusIN,Ms,MsMinusMY,Mt,MtMinusMT,My,No,Nb,NbMinusNO,Ne,NeMinusNP,Nl,NlMinusBE,NlMinusNL,NnMinusNO,Oc,OrMinusIN,Pa,PaMinusIN,Pl,PlMinusPL,PsMinusAF,Pt,PtMinusBR,PtMinusPT,QuMinusPE,RmMinusCH,Ro,RoMinusRO,Ru,RuMinusRU,SaMinusIN,SeMinusNO,Sh,SiMinusLK,Sk,SkMinusSK,Sl,SlMinusSI,SoMinusSO,Sq,SqMinusAL,Sr,SrMinusRS,Su,Sv,SvMinusSE,Sw,SwMinusKE,Ta,TaMinusIN,Te,TeMinusIN,Tg,TgMinusTJ,Th,ThMinusTH,Fil,Tlh,Tr,TrMinusTR,TtMinusRU,Uk,UkMinusUA,Ur,UrMinusPK,Uz,UzMinusUZ,Vi,ViMinusVN,XhMinusZA,Yi,YiMinusDE,Zh,ZhMinusHans,ZhMinusHant,ZhMinusCN,ZhMinusHK,ZhMinusSG,ZhMinusTW,ZuMinusZA */ @Serializable - enum class Lang( - val value: kotlin.String, + public enum class Lang( + public val value: kotlin.String, ) { @SerialName(value = "null") Null("null"), @@ -734,8 +734,8 @@ internal data class IUpdateRequest( * Values: Public,Followers,Private */ @Serializable - enum class FfVisibility( - val value: kotlin.String, + public enum class FfVisibility( + public val value: kotlin.String, ) { @SerialName(value = "public") Public("public"), @@ -752,8 +752,8 @@ internal data class IUpdateRequest( * Values: Follow,Mention,Reply,Renote,Quote,Reaction,PollEnded,ReceiveFollowRequest,FollowRequestAccepted,AchievementEarned,App */ @Serializable - enum class MutingNotificationTypes( - val value: kotlin.String, + public enum class MutingNotificationTypes( + public val value: kotlin.String, ) { @SerialName(value = "follow") Follow("follow"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IUpdateRequestFieldsInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IUpdateRequestFieldsInner.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IUpdateRequestFieldsInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IUpdateRequestFieldsInner.kt index 054d085f6b..df170fa884 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IUpdateRequestFieldsInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IUpdateRequestFieldsInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param `value` */ @Serializable -internal data class IUpdateRequestFieldsInner( +public data class IUpdateRequestFieldsInner( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "value") val `value`: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksCreateRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksCreateRequest.kt index 6d8e6d4b53..f80df4d773 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param url * @param secret * @param on */ @Serializable -internal data class IWebhooksCreateRequest( +public data class IWebhooksCreateRequest( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "url") val url: kotlin.String, @SerialName(value = "secret") val secret: kotlin.String, @@ -33,8 +33,8 @@ internal data class IWebhooksCreateRequest( * Values: Mention,Unfollow,Follow,Followed,Note,Reply,Renote,Reaction */ @Serializable - enum class On( - val value: kotlin.String, + public enum class On( + public val value: kotlin.String, ) { @SerialName(value = "mention") Mention("mention"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksShowRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksShowRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksShowRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksShowRequest.kt index 37691e4775..b1553bba21 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksShowRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksShowRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param webhookId */ @Serializable -internal data class IWebhooksShowRequest( +public data class IWebhooksShowRequest( @SerialName(value = "webhookId") val webhookId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksUpdateRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksUpdateRequest.kt index f07138fbb7..2f1ed363e0 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/IWebhooksUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param webhookId * @param name * @param url * @param secret * @param on * @param active */ @Serializable -internal data class IWebhooksUpdateRequest( +public data class IWebhooksUpdateRequest( @SerialName(value = "webhookId") val webhookId: kotlin.String, @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "url") val url: kotlin.String, @@ -35,8 +35,8 @@ internal data class IWebhooksUpdateRequest( * Values: Mention,Unfollow,Follow,Followed,Note,Reply,Renote,Reaction */ @Serializable - enum class On( - val value: kotlin.String, + public enum class On( + public val value: kotlin.String, ) { @SerialName(value = "mention") Mention("mention"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteCode.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteCode.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteCode.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteCode.kt index eeaa6874d3..25a82917b3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteCode.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteCode.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param code * @param expiresAt * @param createdAt * @param createdBy * @param usedBy * @param usedAt * @param used */ @Serializable -internal data class InviteCode( +public data class InviteCode( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "code") val code: kotlin.String, @SerialName(value = "expiresAt") val expiresAt: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteDeleteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteDeleteRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteDeleteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteDeleteRequest.kt index c8a441fedd..b5f6dd2a56 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteDeleteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteDeleteRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param inviteId */ @Serializable -internal data class InviteDeleteRequest( +public data class InviteDeleteRequest( @SerialName(value = "inviteId") val inviteId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteLimit200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteLimit200Response.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteLimit200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteLimit200Response.kt index ad7feb30b2..b54bacd98c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteLimit200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/InviteLimit200Response.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param remaining */ @Serializable -internal data class InviteLimit200Response( +public data class InviteLimit200Response( @SerialName(value = "remaining") val remaining: kotlin.Int?, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ListMembership.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ListMembership.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ListMembership.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ListMembership.kt index dd4908f464..0b4c53270b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ListMembership.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ListMembership.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class ListMembership( +public data class ListMembership( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "user") val user: UserLite, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MeDetailed.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MeDetailed.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MeDetailed.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MeDetailed.kt index 3317f33524..6948f0844a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MeDetailed.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MeDetailed.kt @@ -24,7 +24,7 @@ import kotlinx.serialization.Serializable * @param id * @param name * @param username * @param host The local host is represented with `null`. * @param avatarUrl * @param avatarBlurhash * @param onlineStatus * @param url * @param uri * @param movedToUri * @param alsoKnownAs * @param createdAt * @param updatedAt * @param lastFetchedAt * @param bannerUrl * @param bannerBlurhash * @param isLocked * @param isSilenced * @param isLimited * @param isSuspended * @param description * @param location * @param birthday * @param lang * @param fields * @param followersCount * @param followingCount * @param notesCount * @param pinnedNoteIds * @param pinnedNotes * @param pinnedPageId * @param pinnedPage * @param publicReactions * @param twoFactorEnabled * @param usePasswordLessLogin * @param securityKeys * @param avatarId * @param bannerId * @param injectFeaturedNote * @param receiveAnnouncementEmail * @param alwaysMarkNsfw * @param autoSensitive * @param carefulBot * @param autoAcceptFollowed * @param noCrawle * @param preventAiLearning * @param isExplorable * @param isDeleted * @param twoFactorBackupCodes * @param hideOnlineStatus * @param hasUnreadSpecifiedNotes * @param hasUnreadMentions * @param hasUnreadAnnouncement * @param hasUnreadAntenna * @param hasUnreadNotification * @param hasPendingReceivedFollowRequest * @param mutedWords * @param mutedInstances * @param mutingNotificationTypes * @param emailNotificationTypes * @param isAdmin * @param isModerator * @param isBot * @param isCat * @param isFollowing * @param isFollowed * @param hasPendingFollowRequestFromYou * @param hasPendingFollowRequestToYou * @param isBlocking * @param isBlocked * @param isMuted * @param isRenoteMuted * @param memo * @param email * @param emailVerified * @param securityKeysList */ @Serializable -internal data class MeDetailed( +public data class MeDetailed( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "name") val name: kotlin.String? = null, @SerialName(value = "username") val username: kotlin.String, @@ -108,8 +108,8 @@ internal data class MeDetailed( * Values: Unknown,Online,Active,Offline */ @Serializable - enum class OnlineStatus( - val value: kotlin.String, + public enum class OnlineStatus( + public val value: kotlin.String, ) { @SerialName(value = "unknown") Unknown("unknown"), @@ -129,8 +129,8 @@ internal data class MeDetailed( * Values: Full,Partial,None */ @Serializable - enum class TwoFactorBackupCodes( - val value: kotlin.String, + public enum class TwoFactorBackupCodes( + public val value: kotlin.String, ) { @SerialName(value = "full") Full("full"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MeDetailedOnly.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MeDetailedOnly.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MeDetailedOnly.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MeDetailedOnly.kt index babb981e9f..579378302e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MeDetailedOnly.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MeDetailedOnly.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param avatarId * @param bannerId * @param injectFeaturedNote * @param receiveAnnouncementEmail * @param alwaysMarkNsfw * @param autoSensitive * @param carefulBot * @param autoAcceptFollowed * @param noCrawle * @param preventAiLearning * @param isExplorable * @param isDeleted * @param twoFactorBackupCodes * @param hideOnlineStatus * @param hasUnreadSpecifiedNotes * @param hasUnreadMentions * @param hasUnreadAnnouncement * @param hasUnreadAntenna * @param hasUnreadNotification * @param hasPendingReceivedFollowRequest * @param mutedWords * @param mutedInstances * @param mutingNotificationTypes * @param emailNotificationTypes * @param email * @param emailVerified * @param securityKeysList */ @Serializable -internal data class MeDetailedOnly( +public data class MeDetailedOnly( @SerialName(value = "avatarId") val avatarId: kotlin.String? = null, @SerialName(value = "bannerId") val bannerId: kotlin.String? = null, @SerialName(value = "injectFeaturedNote") val injectFeaturedNote: kotlin.Boolean? = null, @@ -56,8 +56,8 @@ internal data class MeDetailedOnly( * Values: Full,Partial,None */ @Serializable - enum class TwoFactorBackupCodes( - val value: kotlin.String, + public enum class TwoFactorBackupCodes( + public val value: kotlin.String, ) { @SerialName(value = "full") Full("full"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200Response.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200Response.kt index da5b01f773..b6986a709d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200Response.kt @@ -19,7 +19,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal data class Meta200Response( +public data class Meta200Response( val maintainerName: String? = null, val maintainerEmail: String? = null, val version: String? = null, @@ -93,7 +93,7 @@ internal data class Meta200Response( ) @Serializable -internal data class Ad( +public data class Ad( val id: String? = null, val url: String? = null, val place: String? = null, @@ -104,7 +104,7 @@ internal data class Ad( ) @Serializable -internal data class Policies( +public data class Policies( val gtlAvailable: Boolean? = null, val ltlAvailable: Boolean? = null, val canPublicNote: Boolean? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200ResponseAdsInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200ResponseAdsInner.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200ResponseAdsInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200ResponseAdsInner.kt index c050d96487..a9ad7b11a2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200ResponseAdsInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200ResponseAdsInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param place * @param url * @param imageUrl */ @Serializable -internal data class Meta200ResponseAdsInner( +public data class Meta200ResponseAdsInner( @SerialName(value = "place") val place: kotlin.String, @SerialName(value = "url") val url: kotlin.String, @SerialName(value = "imageUrl") val imageUrl: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200ResponseFeatures.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200ResponseFeatures.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200ResponseFeatures.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200ResponseFeatures.kt index 6aa70d7da5..f3a5f4501e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200ResponseFeatures.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Meta200ResponseFeatures.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param registration * @param localTimeLine * @param globalTimeLine * @param hcaptcha * @param recaptcha * @param objectStorage * @param serviceWorker * @param miauth */ @Serializable -internal data class Meta200ResponseFeatures( +public data class Meta200ResponseFeatures( @SerialName(value = "registration") val registration: kotlin.Boolean, @SerialName(value = "localTimeLine") val localTimeLine: kotlin.Boolean, @SerialName(value = "globalTimeLine") val globalTimeLine: kotlin.Boolean, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MetaRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MetaRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MetaRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MetaRequest.kt index 7dad64e0e3..b526f5077a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MetaRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MetaRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param detail */ @Serializable -internal data class MetaRequest( +public data class MetaRequest( @SerialName(value = "detail") val detail: kotlin.Boolean? = true, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyException.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyException.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyException.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyException.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyInstance.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyInstance.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyInstance.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyInstance.kt index 2990ccd0ba..016060209c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyInstance.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyInstance.kt @@ -3,14 +3,14 @@ package dev.dimension.flare.data.network.misskey.api.model import kotlinx.serialization.Serializable @Serializable -internal data class MisskeyInstance( +public data class MisskeyInstance( val date: String, // val stats: MisskeyInstanceStats, val instancesInfos: List, ) @Serializable -internal data class InstancesInfo( +public data class InstancesInfo( val url: String, // val value: Double, val meta: Meta200Response? = null, @@ -216,7 +216,7 @@ internal data class InstancesInfo( // ) // @Serializable -internal data class Nodeinfo( +public data class Nodeinfo( // val version: String, // val software: Software, // val protocols: List, @@ -328,21 +328,21 @@ internal data class Nodeinfo( // ) // @Serializable -internal data class Usage( +public data class Usage( val users: Users? = null, // val localPosts: Long? = null, // val localComments: Long? = null, ) @Serializable -internal data class Users( +public data class Users( val total: Long? = null, // val activeHalfyear: Long? = null, // val activeMonth: Long? = null, ) @Serializable -internal data class InstancesInfoStats( +public data class InstancesInfoStats( // val notesCount: Long, // val originalNotesCount: Long, val usersCount: Long? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MuteCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MuteCreateRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MuteCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MuteCreateRequest.kt index 9f598af567..6f7a42dff6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MuteCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MuteCreateRequest.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param userId * @param expiresAt A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute. */ @Serializable -internal data class MuteCreateRequest( +public data class MuteCreateRequest( @SerialName(value = "userId") val userId: kotlin.String, // A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute. @SerialName(value = "expiresAt") val expiresAt: kotlin.Int? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Muting.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Muting.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Muting.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Muting.kt index 7bcde3ccc2..d9c4a767ee 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Muting.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Muting.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param expiresAt * @param muteeId * @param mutee */ @Serializable -internal data class Muting( +public data class Muting( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "expiresAt") val expiresAt: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MyAppsRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MyAppsRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MyAppsRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MyAppsRequest.kt index 52f422205c..f5f4bc69f1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MyAppsRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/MyAppsRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param offset */ @Serializable -internal data class MyAppsRequest( +public data class MyAppsRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "offset") val offset: kotlin.Int? = 0, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Note.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Note.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Note.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Note.kt index f7c1d5487a..99404f028a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Note.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Note.kt @@ -24,7 +24,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param text * @param userId * @param user * @param visibility * @param reactionAcceptance * @param reactions * @param renoteCount * @param repliesCount * @param deletedAt * @param cw * @param replyId * @param renoteId * @param reply * @param renote * @param isHidden * @param mentions * @param visibleUserIds * @param fileIds * @param files * @param tags * @param poll * @param channelId * @param channel * @param localOnly * @param uri * @param url * @param myReaction */ @Serializable -internal data class Note( +public data class Note( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "text") val text: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteChannelInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteChannelInner.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteChannelInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteChannelInner.kt index 9ca9cde0e7..7af3b17e28 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteChannelInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteChannelInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param name */ @Serializable -internal data class NoteChannelInner( +public data class NoteChannelInner( @SerialName(value = "id") val id: kotlin.String? = null, @SerialName(value = "name") val name: kotlin.String? = null, @SerialName(value = "color") val color: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteFavorite.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteFavorite.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteFavorite.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteFavorite.kt index 5b219f6f54..c20423284e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteFavorite.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteFavorite.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param note * @param noteId */ @Serializable -internal data class NoteFavorite( +public data class NoteFavorite( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "note") val note: Note, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteReaction.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteReaction.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteReaction.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteReaction.kt index 94e58e8bd9..34c3ada46d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteReaction.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NoteReaction.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param user * @param type */ @Serializable -internal data class NoteReaction( +public data class NoteReaction( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "user") val user: UserLite, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesChildrenRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesChildrenRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesChildrenRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesChildrenRequest.kt index ff77d23274..b76dad2a60 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesChildrenRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesChildrenRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param noteId * @param limit * @param sinceId * @param untilId */ @Serializable -internal data class NotesChildrenRequest( +public data class NotesChildrenRequest( @SerialName(value = "noteId") val noteId: kotlin.String, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesConversationRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesConversationRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesConversationRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesConversationRequest.kt index f869def691..40de75d0d5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesConversationRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesConversationRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param noteId * @param limit * @param offset */ @Serializable -internal data class NotesConversationRequest( +public data class NotesConversationRequest( @SerialName(value = "noteId") val noteId: kotlin.String, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "offset") val offset: kotlin.Int? = 0, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreate200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreate200Response.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreate200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreate200Response.kt index 661ab1e5e8..ac2f94be1f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreate200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreate200Response.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param createdNote */ @Serializable -internal data class NotesCreate200Response( +public data class NotesCreate200Response( @SerialName(value = "createdNote") val createdNote: Note, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreateRequest.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreateRequest.kt index bfd1a78583..bc6084c07a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * */ @Serializable -internal data class NotesCreateRequest( +public data class NotesCreateRequest( @SerialName("visibility") val visibility: String? = null, @SerialName("visibleUserIds") diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreateRequestPoll.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreateRequestPoll.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreateRequestPoll.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreateRequestPoll.kt index 6ff8c6b745..80116b4dba 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreateRequestPoll.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesCreateRequestPoll.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param choices * @param multiple * @param expiresAt * @param expiredAfter */ @Serializable -internal data class NotesCreateRequestPoll( +public data class NotesCreateRequestPoll( @SerialName(value = "choices") val choices: kotlin.collections.Set, @SerialName(value = "multiple") val multiple: kotlin.Boolean? = null, @SerialName(value = "expiresAt") val expiresAt: kotlin.Int? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesFeaturedRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesFeaturedRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesFeaturedRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesFeaturedRequest.kt index 2af24009f6..fc3de63e17 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesFeaturedRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesFeaturedRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param offset * @param channelId */ @Serializable -internal data class NotesFeaturedRequest( +public data class NotesFeaturedRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "untilId") val untilId: kotlin.String? = null, @SerialName(value = "channelId") val channelId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesGlobalTimelineRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesGlobalTimelineRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesGlobalTimelineRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesGlobalTimelineRequest.kt index 42ebbaf48b..fad7f0805d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesGlobalTimelineRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesGlobalTimelineRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param withFiles * @param withReplies * @param limit * @param sinceId * @param untilId * @param sinceDate * @param untilDate */ @Serializable -internal data class NotesGlobalTimelineRequest( +public data class NotesGlobalTimelineRequest( @SerialName(value = "withFiles") val withFiles: kotlin.Boolean? = false, @SerialName(value = "withReplies") val withReplies: kotlin.Boolean? = false, @SerialName(value = "limit") val limit: kotlin.Int? = 10, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesHybridTimelineRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesHybridTimelineRequest.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesHybridTimelineRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesHybridTimelineRequest.kt index e281b3419f..70af53df88 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesHybridTimelineRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesHybridTimelineRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param limit * @param sinceId * @param untilId * @param sinceDate * @param untilDate * @param includeMyRenotes * @param includeRenotedMyNotes * @param includeLocalRenotes * @param withFiles * @param withReplies */ @Serializable -internal data class NotesHybridTimelineRequest( +public data class NotesHybridTimelineRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesLocalTimelineRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesLocalTimelineRequest.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesLocalTimelineRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesLocalTimelineRequest.kt index 54328ec76b..f0593c1916 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesLocalTimelineRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesLocalTimelineRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param withFiles * @param withReplies * @param fileType * @param excludeNsfw * @param limit * @param sinceId * @param untilId * @param sinceDate * @param untilDate */ @Serializable -internal data class NotesLocalTimelineRequest( +public data class NotesLocalTimelineRequest( @SerialName(value = "withFiles") val withFiles: kotlin.Boolean? = false, @SerialName(value = "withReplies") val withReplies: kotlin.Boolean? = false, @SerialName(value = "fileType") val fileType: kotlin.collections.List? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesMentionsRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesMentionsRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesMentionsRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesMentionsRequest.kt index cbeb93d867..a92f2bfbf7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesMentionsRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesMentionsRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param following * @param limit * @param sinceId * @param untilId * @param visibility */ @Serializable -internal data class NotesMentionsRequest( +public data class NotesMentionsRequest( @SerialName(value = "following") val following: kotlin.Boolean? = false, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesPollsVoteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesPollsVoteRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesPollsVoteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesPollsVoteRequest.kt index a58f401a42..a1e83c3a7c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesPollsVoteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesPollsVoteRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param noteId * @param choice */ @Serializable -internal data class NotesPollsVoteRequest( +public data class NotesPollsVoteRequest( @SerialName(value = "noteId") val noteId: kotlin.String, @SerialName(value = "choice") val choice: kotlin.Int, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesReactionsCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesReactionsCreateRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesReactionsCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesReactionsCreateRequest.kt index 99d2c9dada..7a909893c7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesReactionsCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesReactionsCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param noteId * @param reaction */ @Serializable -internal data class NotesReactionsCreateRequest( +public data class NotesReactionsCreateRequest( @SerialName(value = "noteId") val noteId: kotlin.String, @SerialName(value = "reaction") val reaction: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesReactionsRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesReactionsRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesReactionsRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesReactionsRequest.kt index e5421f0ad8..de4eede588 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesReactionsRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesReactionsRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param noteId * @param type * @param limit * @param offset * @param sinceId * @param untilId */ @Serializable -internal data class NotesReactionsRequest( +public data class NotesReactionsRequest( @SerialName(value = "noteId") val noteId: kotlin.String, @SerialName(value = "type") val type: kotlin.String? = null, @SerialName(value = "limit") val limit: kotlin.Int? = 10, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesRepliesRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesRepliesRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesRepliesRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesRepliesRequest.kt index b9d791781e..03ba5d444a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesRepliesRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesRepliesRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param noteId * @param sinceId * @param untilId * @param limit */ @Serializable -internal data class NotesRepliesRequest( +public data class NotesRepliesRequest( @SerialName(value = "noteId") val noteId: kotlin.String, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesRequest.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesRequest.kt index b2ea386846..74dbeb4254 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param local * @param reply * @param renote * @param withFiles * @param poll * @param limit * @param sinceId * @param untilId */ @Serializable -internal data class NotesRequest( +public data class NotesRequest( @SerialName(value = "local") val local: kotlin.Boolean? = false, @SerialName(value = "reply") val reply: kotlin.Boolean? = null, @SerialName(value = "renote") val renote: kotlin.Boolean? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesSearchByTagRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesSearchByTagRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesSearchByTagRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesSearchByTagRequest.kt index 0b7727853c..ad02b1ac95 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesSearchByTagRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesSearchByTagRequest.kt @@ -21,7 +21,7 @@ import kotlinx.serialization.Serializable * * */ @Serializable -internal data class NotesSearchByTagRequest( +public data class NotesSearchByTagRequest( val reply: Boolean? = null, val renote: Boolean? = null, val withFiles: Boolean? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesSearchRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesSearchRequest.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesSearchRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesSearchRequest.kt index 4d97ce50f7..36ebbe2b9c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesSearchRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesSearchRequest.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param query * @param sinceId * @param untilId * @param limit * @param offset * @param host The local host is represented with `.`. * @param userId * @param channelId */ @Serializable -internal data class NotesSearchRequest( +public data class NotesSearchRequest( @SerialName(value = "query") val query: kotlin.String, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, @SerialName(value = "untilId") val untilId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesState200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesState200Response.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesState200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesState200Response.kt index 35a9814464..0bac4dd7b9 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesState200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesState200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param isFavorited * @param isMutedThread */ @Serializable -internal data class NotesState200Response( +public data class NotesState200Response( @SerialName(value = "isFavorited") val isFavorited: kotlin.Boolean, @SerialName(value = "isMutedThread") val isMutedThread: kotlin.Boolean, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesTranslateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesTranslateRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesTranslateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesTranslateRequest.kt index ce15d78068..5967d8b328 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesTranslateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesTranslateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param noteId * @param targetLang */ @Serializable -internal data class NotesTranslateRequest( +public data class NotesTranslateRequest( @SerialName(value = "noteId") val noteId: kotlin.String, @SerialName(value = "targetLang") val targetLang: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesUserListTimelineRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesUserListTimelineRequest.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesUserListTimelineRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesUserListTimelineRequest.kt index 2c74f0a712..f91f27b218 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesUserListTimelineRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotesUserListTimelineRequest.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param listId * @param limit * @param sinceId * @param untilId * @param sinceDate * @param untilDate * @param includeMyRenotes * @param includeRenotedMyNotes * @param includeLocalRenotes * @param withFiles Only show notes that have attached files. */ @Serializable -internal data class NotesUserListTimelineRequest( +public data class NotesUserListTimelineRequest( @SerialName(value = "listId") val listId: kotlin.String, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Notification.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Notification.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Notification.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Notification.kt index ad68b4743e..d09a9727ee 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Notification.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Notification.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param type * @param user * @param userId * @param note * @param reaction * @param choice * @param invitation * @param body * @param header * @param icon */ @Serializable -internal data class Notification( +public data class Notification( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "type") val type: String, @@ -62,8 +62,8 @@ internal data class Notification( * 'test', */ @Serializable -internal enum class NotificationType( - val value: kotlin.String, +public enum class NotificationType( + public val value: kotlin.String, ) { @SerialName("note") Note("note"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotificationsCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotificationsCreateRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotificationsCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotificationsCreateRequest.kt index a1c68dc5c8..b735cc2ad4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotificationsCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/NotificationsCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param body * @param header * @param icon */ @Serializable -internal data class NotificationsCreateRequest( +public data class NotificationsCreateRequest( @SerialName(value = "body") val body: kotlin.String, @SerialName(value = "header") val header: kotlin.String? = null, @SerialName(value = "icon") val icon: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Page.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Page.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Page.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Page.kt index 10813ccf2e..f6e5cc4b75 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Page.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Page.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param updatedAt * @param title * @param name * @param summary * @param content * @param variables * @param userId * @param user */ @Serializable -internal data class Page( +public data class Page( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "updatedAt") val updatedAt: kotlin.String, @@ -36,7 +36,7 @@ internal data class Page( ) @Serializable -internal data class PageContent( +public data class PageContent( @SerialName(value = "id") val id: kotlin.String? = null, @SerialName(value = "type") val type: String? = null, @SerialName(value = "text") val text: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesCreateRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesCreateRequest.kt index 4722a9df6b..011a0aeed3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesCreateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param title * @param name * @param content * @param variables * @param script * @param summary * @param eyeCatchingImageId * @param font * @param alignCenter * @param hideTitleWhenPinned */ @Serializable -internal data class PagesCreateRequest( +public data class PagesCreateRequest( @SerialName(value = "title") val title: kotlin.String, @SerialName(value = "name") val name: kotlin.String, // @SerialName(value = "content") val content: kotlin.collections.List<@Contextual kotlin.collections.Map>, @@ -39,8 +39,8 @@ internal data class PagesCreateRequest( * Values: Serif,SansMinusSerif */ @Serializable - enum class Font( - val value: kotlin.String, + public enum class Font( + public val value: kotlin.String, ) { @SerialName(value = "serif") Serif("serif"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesDeleteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesDeleteRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesDeleteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesDeleteRequest.kt index 0d3b6c7b96..09859640d6 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesDeleteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesDeleteRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param pageId */ @Serializable -internal data class PagesDeleteRequest( +public data class PagesDeleteRequest( @SerialName(value = "pageId") val pageId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesShowRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesShowRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesShowRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesShowRequest.kt index b59c7aed88..af018f2c05 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesShowRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesShowRequest.kt @@ -21,7 +21,7 @@ import kotlinx.serialization.Serializable * * */ @Serializable -internal data class PagesShowRequest( +public data class PagesShowRequest( val pageId: String? = null, val name: String? = null, val username: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesUpdateRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesUpdateRequest.kt index 0d500c2137..3bfc5c3f8b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PagesUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param pageId * @param title * @param name * @param content * @param variables * @param script * @param summary * @param eyeCatchingImageId * @param font * @param alignCenter * @param hideTitleWhenPinned */ @Serializable -internal data class PagesUpdateRequest( +public data class PagesUpdateRequest( @SerialName(value = "pageId") val pageId: kotlin.String, @SerialName(value = "title") val title: kotlin.String, @SerialName(value = "name") val name: kotlin.String, @@ -40,8 +40,8 @@ internal data class PagesUpdateRequest( * Values: Serif,SansMinusSerif */ @Serializable - enum class Font( - val value: kotlin.String, + public enum class Font( + public val value: kotlin.String, ) { @SerialName(value = "serif") Serif("serif"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Ping200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Ping200Response.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Ping200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Ping200Response.kt index c10a1a944e..ea7abae455 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Ping200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Ping200Response.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param pong */ @Serializable -internal data class Ping200Response( +public data class Ping200Response( @SerialName(value = "pong") val pong: kotlin.Double, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PinnedUsersRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PinnedUsersRequest.kt similarity index 78% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PinnedUsersRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PinnedUsersRequest.kt index f6cd2a08c5..a94eb81fb4 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PinnedUsersRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/PinnedUsersRequest.kt @@ -3,6 +3,6 @@ package dev.dimension.flare.data.network.misskey.api.model import kotlinx.serialization.Serializable @Serializable -internal data class PinnedUsersRequest( +public data class PinnedUsersRequest( val limit: Int? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Poll.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Poll.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Poll.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Poll.kt index 358e273944..963c079260 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Poll.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Poll.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable import kotlin.time.Instant @Serializable -internal data class Poll( +public data class Poll( @SerialName("choices") val choices: List, @SerialName("expiresAt") @@ -14,7 +14,7 @@ internal data class Poll( val multiple: Boolean, ) { @Serializable - data class Choice( + public data class Choice( @SerialName("text") val text: String, @SerialName("votes") diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/QueueCount.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/QueueCount.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/QueueCount.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/QueueCount.kt index ff608d8c6f..bcd0692bd1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/QueueCount.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/QueueCount.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param waiting * @param active * @param completed * @param failed * @param delayed */ @Serializable -internal data class QueueCount( +public data class QueueCount( @SerialName(value = "waiting") val waiting: kotlin.Double, @SerialName(value = "active") val active: kotlin.Double, @SerialName(value = "completed") val completed: kotlin.Double, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RenoteMuting.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RenoteMuting.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RenoteMuting.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RenoteMuting.kt index a9fad62afc..4d4c37b7cf 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RenoteMuting.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RenoteMuting.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param muteeId * @param mutee */ @Serializable -internal data class RenoteMuting( +public data class RenoteMuting( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "muteeId") val muteeId: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RequestResetPasswordRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RequestResetPasswordRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RequestResetPasswordRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RequestResetPasswordRequest.kt index 8c3dd166de..2bc8e01cf3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RequestResetPasswordRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RequestResetPasswordRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param username * @param email */ @Serializable -internal data class RequestResetPasswordRequest( +public data class RequestResetPasswordRequest( @SerialName(value = "username") val username: kotlin.String, @SerialName(value = "email") val email: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ResetPasswordRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ResetPasswordRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ResetPasswordRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ResetPasswordRequest.kt index 4549574704..c99c9e5e13 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ResetPasswordRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/ResetPasswordRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param token * @param password */ @Serializable -internal data class ResetPasswordRequest( +public data class ResetPasswordRequest( @SerialName(value = "token") val token: kotlin.String, @SerialName(value = "password") val password: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RolesNotesRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RolesNotesRequest.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RolesNotesRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RolesNotesRequest.kt index 3c04b521b3..4f84af5718 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RolesNotesRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/RolesNotesRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param roleId * @param limit * @param sinceId * @param untilId * @param sinceDate * @param untilDate */ @Serializable -internal data class RolesNotesRequest( +public data class RolesNotesRequest( @SerialName(value = "roleId") val roleId: kotlin.String, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Stats200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Stats200Response.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Stats200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Stats200Response.kt index 7761997672..0fb4dda4ff 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Stats200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Stats200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param notesCount * @param originalNotesCount * @param usersCount * @param originalUsersCount * @param instances * @param driveUsageLocal * @param driveUsageRemote */ @Serializable -internal data class Stats200Response( +public data class Stats200Response( @SerialName(value = "notesCount") val notesCount: kotlin.Double, @SerialName(value = "originalNotesCount") val originalNotesCount: kotlin.Double, @SerialName(value = "usersCount") val usersCount: kotlin.Double, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwRegister200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwRegister200Response.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwRegister200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwRegister200Response.kt index 1a644ec7e5..21ee22090c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwRegister200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwRegister200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param key * @param userId * @param endpoint * @param sendReadMessage * @param state */ @Serializable -internal data class SwRegister200Response( +public data class SwRegister200Response( @SerialName(value = "key") val key: kotlin.String? = null, @SerialName(value = "userId") val userId: kotlin.String, @SerialName(value = "endpoint") val endpoint: kotlin.String, @@ -34,8 +34,8 @@ internal data class SwRegister200Response( * Values: AlreadyMinusSubscribed,Subscribed */ @Serializable - enum class State( - val value: kotlin.String, + public enum class State( + public val value: kotlin.String, ) { @SerialName(value = "already-subscribed") AlreadyMinusSubscribed("already-subscribed"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwRegisterRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwRegisterRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwRegisterRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwRegisterRequest.kt index 3cfb734985..92a6a6c131 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwRegisterRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwRegisterRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param endpoint * @param auth * @param publickey * @param sendReadMessage */ @Serializable -internal data class SwRegisterRequest( +public data class SwRegisterRequest( @SerialName(value = "endpoint") val endpoint: kotlin.String, @SerialName(value = "auth") val auth: kotlin.String, @SerialName(value = "publickey") val publickey: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwShowRegistration200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwShowRegistration200Response.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwShowRegistration200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwShowRegistration200Response.kt index 5583144c73..c8248c1494 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwShowRegistration200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwShowRegistration200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param userId * @param endpoint * @param sendReadMessage */ @Serializable -internal data class SwShowRegistration200Response( +public data class SwShowRegistration200Response( @SerialName(value = "userId") val userId: kotlin.String, @SerialName(value = "endpoint") val endpoint: kotlin.String, @SerialName(value = "sendReadMessage") val sendReadMessage: kotlin.Boolean, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwUpdateRegistration200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwUpdateRegistration200Response.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwUpdateRegistration200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwUpdateRegistration200Response.kt index 31dc64e8cb..1b1d6e268c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwUpdateRegistration200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwUpdateRegistration200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param userId * @param endpoint * @param sendReadMessage */ @Serializable -internal data class SwUpdateRegistration200Response( +public data class SwUpdateRegistration200Response( @SerialName(value = "userId") val userId: kotlin.String, @SerialName(value = "endpoint") val endpoint: kotlin.String, @SerialName(value = "sendReadMessage") val sendReadMessage: kotlin.Boolean, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwUpdateRegistrationRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwUpdateRegistrationRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwUpdateRegistrationRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwUpdateRegistrationRequest.kt index 0764f05ceb..ab858e60d9 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwUpdateRegistrationRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/SwUpdateRegistrationRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param endpoint * @param sendReadMessage */ @Serializable -internal data class SwUpdateRegistrationRequest( +public data class SwUpdateRegistrationRequest( @SerialName(value = "endpoint") val endpoint: kotlin.String, @SerialName(value = "sendReadMessage") val sendReadMessage: kotlin.Boolean? = null, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/TestRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/TestRequest.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/TestRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/TestRequest.kt index 811b557dfc..8b79cbd148 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/TestRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/TestRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param required * @param string * @param default * @param nullableDefault * @param id */ @Serializable -internal data class TestRequest( +public data class TestRequest( @SerialName(value = "required") val required: kotlin.Boolean, @SerialName(value = "string") val string: kotlin.String? = null, @SerialName(value = "default") val default: kotlin.String? = "hello", diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/User.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/User.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/User.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/User.kt index 11faa202b5..9b02932e1b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/User.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/User.kt @@ -25,7 +25,7 @@ import kotlinx.serialization.Serializable * @param id * @param name * @param username * @param host The local host is represented with `null`. * @param avatarUrl * @param avatarBlurhash * @param onlineStatus * @param url * @param uri * @param movedToUri * @param alsoKnownAs * @param createdAt * @param updatedAt * @param lastFetchedAt * @param bannerUrl * @param bannerBlurhash * @param isLocked * @param isSilenced * @param isLimited * @param isSuspended * @param description * @param location * @param birthday * @param lang * @param fields * @param followersCount * @param followingCount * @param notesCount * @param pinnedNoteIds * @param pinnedNotes * @param pinnedPageId * @param pinnedPage * @param publicReactions * @param twoFactorEnabled * @param usePasswordLessLogin * @param securityKeys * @param avatarId * @param bannerId * @param injectFeaturedNote * @param receiveAnnouncementEmail * @param alwaysMarkNsfw * @param autoSensitive * @param carefulBot * @param autoAcceptFollowed * @param noCrawle * @param preventAiLearning * @param isExplorable * @param isDeleted * @param twoFactorBackupCodes * @param hideOnlineStatus * @param hasUnreadSpecifiedNotes * @param hasUnreadMentions * @param hasUnreadAnnouncement * @param hasUnreadAntenna * @param hasUnreadNotification * @param hasPendingReceivedFollowRequest * @param mutedWords * @param mutedInstances * @param mutingNotificationTypes * @param emailNotificationTypes * @param isAdmin * @param isModerator * @param isBot * @param isCat * @param isFollowing * @param isFollowed * @param hasPendingFollowRequestFromYou * @param hasPendingFollowRequestToYou * @param isBlocking * @param isBlocked * @param isMuted * @param isRenoteMuted * @param memo * @param email * @param emailVerified * @param securityKeysList */ @Serializable -internal data class User( +public data class User( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "name") val name: kotlin.String? = null, @SerialName(value = "username") val username: kotlin.String, @@ -111,8 +111,8 @@ internal data class User( * Values: Unknown,Online,Active,Offline */ @Serializable - enum class OnlineStatus( - val value: kotlin.String, + public enum class OnlineStatus( + public val value: kotlin.String, ) { @SerialName(value = "unknown") Unknown("unknown"), @@ -132,8 +132,8 @@ internal data class User( * Values: Full,Partial,None */ @Serializable - enum class TwoFactorBackupCodes( - val value: kotlin.String, + public enum class TwoFactorBackupCodes( + public val value: kotlin.String, ) { @SerialName(value = "full") Full("full"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailed.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailed.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailed.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailed.kt index 5cb9af3a56..d3dec73936 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailed.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailed.kt @@ -20,4 +20,4 @@ package dev.dimension.flare.data.network.misskey.api.model * @param id * @param name * @param username * @param host The local host is represented with `null`. * @param avatarUrl * @param avatarBlurhash * @param onlineStatus * @param url * @param uri * @param movedToUri * @param alsoKnownAs * @param createdAt * @param updatedAt * @param lastFetchedAt * @param bannerUrl * @param bannerBlurhash * @param isLocked * @param isSilenced * @param isLimited * @param isSuspended * @param description * @param location * @param birthday * @param lang * @param fields * @param followersCount * @param followingCount * @param notesCount * @param pinnedNoteIds * @param pinnedNotes * @param pinnedPageId * @param pinnedPage * @param publicReactions * @param twoFactorEnabled * @param usePasswordLessLogin * @param securityKeys * @param avatarId * @param bannerId * @param injectFeaturedNote * @param receiveAnnouncementEmail * @param alwaysMarkNsfw * @param autoSensitive * @param carefulBot * @param autoAcceptFollowed * @param noCrawle * @param preventAiLearning * @param isExplorable * @param isDeleted * @param twoFactorBackupCodes * @param hideOnlineStatus * @param hasUnreadSpecifiedNotes * @param hasUnreadMentions * @param hasUnreadAnnouncement * @param hasUnreadAntenna * @param hasUnreadNotification * @param hasPendingReceivedFollowRequest * @param mutedWords * @param mutedInstances * @param mutingNotificationTypes * @param emailNotificationTypes * @param isAdmin * @param isModerator * @param isBot * @param isCat * @param isFollowing * @param isFollowed * @param hasPendingFollowRequestFromYou * @param hasPendingFollowRequestToYou * @param isBlocking * @param isBlocked * @param isMuted * @param isRenoteMuted * @param memo * @param email * @param emailVerified * @param securityKeysList */ -internal typealias UserDetailed = User +public typealias UserDetailed = User diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMe.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMe.kt similarity index 98% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMe.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMe.kt index 2dc4ae4641..4e225bd6b7 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMe.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMe.kt @@ -24,7 +24,7 @@ import kotlinx.serialization.Serializable * @param id * @param name * @param username * @param host The local host is represented with `null`. * @param avatarUrl * @param avatarBlurhash * @param onlineStatus * @param url * @param uri * @param movedToUri * @param alsoKnownAs * @param createdAt * @param updatedAt * @param lastFetchedAt * @param bannerUrl * @param bannerBlurhash * @param isLocked * @param isSilenced * @param isLimited * @param isSuspended * @param description * @param location * @param birthday * @param lang * @param fields * @param followersCount * @param followingCount * @param notesCount * @param pinnedNoteIds * @param pinnedNotes * @param pinnedPageId * @param pinnedPage * @param publicReactions * @param twoFactorEnabled * @param usePasswordLessLogin * @param securityKeys * @param isAdmin * @param isModerator * @param isBot * @param isCat * @param isFollowing * @param isFollowed * @param hasPendingFollowRequestFromYou * @param hasPendingFollowRequestToYou * @param isBlocking * @param isBlocked * @param isMuted * @param isRenoteMuted * @param memo */ @Serializable -internal data class UserDetailedNotMe( +public data class UserDetailedNotMe( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "name") val name: kotlin.String? = null, @SerialName(value = "username") val username: kotlin.String, @@ -81,8 +81,8 @@ internal data class UserDetailedNotMe( * Values: Unknown,Online,Active,Offline */ @Serializable - enum class OnlineStatus( - val value: kotlin.String, + public enum class OnlineStatus( + public val value: kotlin.String, ) { @SerialName(value = "unknown") Unknown("unknown"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMeOnly.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMeOnly.kt similarity index 99% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMeOnly.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMeOnly.kt index 9134911b20..019f0b65fb 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMeOnly.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMeOnly.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * * * @param url * @param uri * @param movedToUri * @param alsoKnownAs * @param createdAt * @param updatedAt * @param lastFetchedAt * @param bannerUrl * @param bannerBlurhash * @param isLocked * @param isSilenced * @param isLimited * @param isSuspended * @param description * @param location * @param birthday * @param lang * @param fields * @param followersCount * @param followingCount * @param notesCount * @param pinnedNoteIds * @param pinnedNotes * @param pinnedPageId * @param pinnedPage * @param publicReactions * @param twoFactorEnabled * @param usePasswordLessLogin * @param securityKeys * @param isFollowing * @param isFollowed * @param hasPendingFollowRequestFromYou * @param hasPendingFollowRequestToYou * @param isBlocking * @param isBlocked * @param isMuted * @param isRenoteMuted * @param memo */ @Serializable -internal data class UserDetailedNotMeOnly( +public data class UserDetailedNotMeOnly( @SerialName(value = "url") val url: kotlin.String? = null, @SerialName(value = "uri") val uri: kotlin.String? = null, @SerialName(value = "movedToUri") val movedToUri: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMeOnlyFieldsInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMeOnlyFieldsInner.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMeOnlyFieldsInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMeOnlyFieldsInner.kt index 30c04e03ea..9896355b96 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMeOnlyFieldsInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserDetailedNotMeOnlyFieldsInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param `value` */ @Serializable -internal data class UserDetailedNotMeOnlyFieldsInner( +public data class UserDetailedNotMeOnlyFieldsInner( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "value") val `value`: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserList.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserList.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserList.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserList.kt index e7dd01f91a..d1c62fc34d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserList.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserList.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param createdAt * @param name * @param isPublic * @param userIds */ @Serializable -internal data class UserList( +public data class UserList( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "createdAt") val createdAt: kotlin.String, @SerialName(value = "name") val name: kotlin.String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserLite.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserLite.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserLite.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserLite.kt index 73e9a26300..646ea27756 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserLite.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserLite.kt @@ -24,7 +24,7 @@ import kotlinx.serialization.Serializable * @param id * @param name * @param username * @param host The local host is represented with `null`. * @param avatarUrl * @param avatarBlurhash * @param onlineStatus * @param isAdmin * @param isModerator * @param isBot * @param isCat */ @Serializable -internal data class UserLite( +public data class UserLite( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "name") val name: kotlin.String? = null, @SerialName(value = "username") val username: kotlin.String, @@ -46,8 +46,8 @@ internal data class UserLite( * Values: Unknown,Online,Active,Offline */ @Serializable - enum class OnlineStatus( - val value: kotlin.String, + public enum class OnlineStatus( + public val value: kotlin.String, ) { @SerialName(value = "unknown") Unknown("unknown"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsernameAvailable200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsernameAvailable200Response.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsernameAvailable200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsernameAvailable200Response.kt index 06ed241cef..315ca6c59e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsernameAvailable200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsernameAvailable200Response.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param available */ @Serializable -internal data class UsernameAvailable200Response( +public data class UsernameAvailable200Response( @SerialName(value = "available") val available: kotlin.Boolean, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsernameAvailableRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsernameAvailableRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsernameAvailableRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsernameAvailableRequest.kt index 8f78009a0a..3b364705a5 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsernameAvailableRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsernameAvailableRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param username */ @Serializable -internal data class UsernameAvailableRequest( +public data class UsernameAvailableRequest( @SerialName(value = "username") val username: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersClipsRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersClipsRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersClipsRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersClipsRequest.kt index 92e72588a1..b37728a01e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersClipsRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersClipsRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param userId * @param limit * @param sinceId * @param untilId */ @Serializable -internal data class UsersClipsRequest( +public data class UsersClipsRequest( @SerialName(value = "userId") val userId: kotlin.String, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersFollowersRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersFollowersRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersFollowersRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersFollowersRequest.kt index 08499b4e6b..78d763a0a8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersFollowersRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersFollowersRequest.kt @@ -21,7 +21,7 @@ import kotlinx.serialization.Serializable * * */ @Serializable -internal data class UsersFollowersRequest( +public data class UsersFollowersRequest( val sinceId: String? = null, val untilId: String? = null, val limit: Int? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersGetFrequentlyRepliedUsers200ResponseInner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersGetFrequentlyRepliedUsers200ResponseInner.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersGetFrequentlyRepliedUsers200ResponseInner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersGetFrequentlyRepliedUsers200ResponseInner.kt index 33fca8e93f..9dc1485b6f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersGetFrequentlyRepliedUsers200ResponseInner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersGetFrequentlyRepliedUsers200ResponseInner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param user * @param weight */ @Serializable -internal data class UsersGetFrequentlyRepliedUsers200ResponseInner( +public data class UsersGetFrequentlyRepliedUsers200ResponseInner( @SerialName(value = "user") val user: UserDetailed, @SerialName(value = "weight") val weight: kotlin.Double, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersGetFrequentlyRepliedUsersRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersGetFrequentlyRepliedUsersRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersGetFrequentlyRepliedUsersRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersGetFrequentlyRepliedUsersRequest.kt index 21d927ef91..3e48db2590 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersGetFrequentlyRepliedUsersRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersGetFrequentlyRepliedUsersRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param userId * @param limit */ @Serializable -internal data class UsersGetFrequentlyRepliedUsersRequest( +public data class UsersGetFrequentlyRepliedUsersRequest( @SerialName(value = "userId") val userId: kotlin.String, @SerialName(value = "limit") val limit: kotlin.Int? = 10, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsCreateFromPublicRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsCreateFromPublicRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsCreateFromPublicRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsCreateFromPublicRequest.kt index 2666510729..fb50abee24 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsCreateFromPublicRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsCreateFromPublicRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param name * @param listId */ @Serializable -internal data class UsersListsCreateFromPublicRequest( +public data class UsersListsCreateFromPublicRequest( @SerialName(value = "name") val name: kotlin.String, @SerialName(value = "listId") val listId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsCreateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsCreateRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsCreateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsCreateRequest.kt index 3f7d26b37a..bb0eb6ee2b 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsCreateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsCreateRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param name */ @Serializable -internal data class UsersListsCreateRequest( +public data class UsersListsCreateRequest( @SerialName(value = "name") val name: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsDeleteRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsDeleteRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsDeleteRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsDeleteRequest.kt index e3e68aacdb..833907efcc 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsDeleteRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsDeleteRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param listId */ @Serializable -internal data class UsersListsDeleteRequest( +public data class UsersListsDeleteRequest( @SerialName(value = "listId") val listId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsListRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsListRequest.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsListRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsListRequest.kt index 2e2a596009..b3eecf4138 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsListRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsListRequest.kt @@ -22,12 +22,12 @@ import kotlinx.serialization.Serializable * * * @param userId */ @Serializable -internal data class UsersListsListRequest( +public data class UsersListsListRequest( @SerialName(value = "userId") val userId: kotlin.String? = null, ) @Serializable -internal data class UsersListsMembershipRequest( +public data class UsersListsMembershipRequest( @SerialName(value = "listId") val listId: kotlin.String, @SerialName(value = "forPublic") val forPublic: kotlin.Boolean = false, @SerialName(value = "limit") val limit: kotlin.Int = 10, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsPullRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsPullRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsPullRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsPullRequest.kt index 28e62e43da..2d8659607a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsPullRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsPullRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param listId * @param userId */ @Serializable -internal data class UsersListsPullRequest( +public data class UsersListsPullRequest( @SerialName(value = "listId") val listId: kotlin.String, @SerialName(value = "userId") val userId: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsShowRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsShowRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsShowRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsShowRequest.kt index 06259974de..2de1f9cb0c 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsShowRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsShowRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param listId * @param forPublic */ @Serializable -internal data class UsersListsShowRequest( +public data class UsersListsShowRequest( @SerialName(value = "listId") val listId: kotlin.String, @SerialName(value = "forPublic") val forPublic: kotlin.Boolean? = false, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsUpdateRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsUpdateRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsUpdateRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsUpdateRequest.kt index 92bdcf6795..616f65d4c1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsUpdateRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersListsUpdateRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param listId * @param name * @param isPublic */ @Serializable -internal data class UsersListsUpdateRequest( +public data class UsersListsUpdateRequest( @SerialName(value = "listId") val listId: kotlin.String, @SerialName(value = "name") val name: kotlin.String? = null, @SerialName(value = "isPublic") val isPublic: kotlin.Boolean? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersNotesRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersNotesRequest.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersNotesRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersNotesRequest.kt index 9768348ec4..bf74f2d07f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersNotesRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersNotesRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param userId * @param includeReplies * @param limit * @param sinceId * @param untilId * @param sinceDate * @param untilDate * @param includeMyRenotes * @param withFiles * @param fileType * @param excludeNsfw */ @Serializable -internal data class UsersNotesRequest( +public data class UsersNotesRequest( @SerialName(value = "userId") val userId: kotlin.String, @SerialName(value = "includeReplies") val includeReplies: kotlin.Boolean? = true, @SerialName(value = "limit") val limit: kotlin.Int? = 10, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersReactionsRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersReactionsRequest.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersReactionsRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersReactionsRequest.kt index d10aab4072..158e3cc7e2 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersReactionsRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersReactionsRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param userId * @param limit * @param sinceId * @param untilId * @param sinceDate * @param untilDate */ @Serializable -internal data class UsersReactionsRequest( +public data class UsersReactionsRequest( @SerialName(value = "userId") val userId: kotlin.String, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "sinceId") val sinceId: kotlin.String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200Response.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200Response.kt index 20152f0b89..b4862b9949 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200Response.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param isFollowing * @param hasPendingFollowRequestFromYou * @param hasPendingFollowRequestToYou * @param isFollowed * @param isBlocking * @param isBlocked * @param isMuted * @param isRenoteMuted */ @Serializable -internal data class UsersRelation200Response( +public data class UsersRelation200Response( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "isFollowing") val isFollowing: kotlin.Boolean, @SerialName(value = "hasPendingFollowRequestFromYou") val hasPendingFollowRequestFromYou: kotlin.Boolean, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200ResponseOneOf.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200ResponseOneOf.kt similarity index 96% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200ResponseOneOf.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200ResponseOneOf.kt index 31f4a2ac0d..0928b64e6e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200ResponseOneOf.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200ResponseOneOf.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param isFollowing * @param hasPendingFollowRequestFromYou * @param hasPendingFollowRequestToYou * @param isFollowed * @param isBlocking * @param isBlocked * @param isMuted * @param isRenoteMuted */ @Serializable -internal data class UsersRelation200ResponseOneOf( +public data class UsersRelation200ResponseOneOf( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "isFollowing") val isFollowing: kotlin.Boolean, @SerialName(value = "hasPendingFollowRequestFromYou") val hasPendingFollowRequestFromYou: kotlin.Boolean, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200ResponseOneOf1Inner.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200ResponseOneOf1Inner.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200ResponseOneOf1Inner.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200ResponseOneOf1Inner.kt index 26edecdd49..d5a5752693 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200ResponseOneOf1Inner.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelation200ResponseOneOf1Inner.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param id * @param isFollowing * @param hasPendingFollowRequestFromYou * @param hasPendingFollowRequestToYou * @param isFollowed * @param isBlocking * @param isBlocked * @param isMuted * @param isRenoteMuted */ @Serializable -internal data class UsersRelation200ResponseOneOf1Inner( +public data class UsersRelation200ResponseOneOf1Inner( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "isFollowing") val isFollowing: kotlin.Boolean, @SerialName(value = "hasPendingFollowRequestFromYou") val hasPendingFollowRequestFromYou: kotlin.Boolean, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelationRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelationRequest.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelationRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelationRequest.kt index da568b44f8..5c2be8dffa 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelationRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRelationRequest.kt @@ -22,6 +22,6 @@ import kotlinx.serialization.Serializable * * * @param userId */ @Serializable -internal data class UsersRelationRequest( +public data class UsersRelationRequest( @SerialName(value = "userId") val userId: List, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersReportAbuseRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersReportAbuseRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersReportAbuseRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersReportAbuseRequest.kt index beddfd4929..090ab9ce5d 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersReportAbuseRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersReportAbuseRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param userId * @param comment */ @Serializable -internal data class UsersReportAbuseRequest( +public data class UsersReportAbuseRequest( @SerialName(value = "userId") val userId: kotlin.String, @SerialName(value = "comment") val comment: kotlin.String, ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRequest.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRequest.kt index 11970b1f12..55e849617a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersRequest.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param limit * @param offset * @param sort * @param state * @param origin * @param hostname The local host is represented with `null`. */ @Serializable -internal data class UsersRequest( +public data class UsersRequest( @SerialName(value = "limit") val limit: kotlin.Int? = 10, @SerialName(value = "offset") val offset: kotlin.Int? = 0, @SerialName(value = "sort") val sort: UsersRequest.Sort? = null, @@ -37,8 +37,8 @@ internal data class UsersRequest( * Values: PlusFollower,MinusFollower,PlusCreatedAt,MinusCreatedAt,PlusUpdatedAt,MinusUpdatedAt */ @Serializable - enum class Sort( - val value: kotlin.String, + public enum class Sort( + public val value: kotlin.String, ) { @SerialName(value = "+follower") PlusFollower("+follower"), @@ -64,8 +64,8 @@ internal data class UsersRequest( * Values: All,Alive */ @Serializable - enum class State( - val value: kotlin.String, + public enum class State( + public val value: kotlin.String, ) { @SerialName(value = "all") All("all"), @@ -79,8 +79,8 @@ internal data class UsersRequest( * Values: Combined,Local,Remote */ @Serializable - enum class Origin( - val value: kotlin.String, + public enum class Origin( + public val value: kotlin.String, ) { @SerialName(value = "combined") Combined("combined"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersSearchByUsernameAndHostRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersSearchByUsernameAndHostRequest.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersSearchByUsernameAndHostRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersSearchByUsernameAndHostRequest.kt index 02f01bf4d0..983c57869f 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersSearchByUsernameAndHostRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersSearchByUsernameAndHostRequest.kt @@ -21,7 +21,7 @@ import kotlinx.serialization.Serializable * * */ @Serializable -internal data class UsersSearchByUsernameAndHostRequest( +public data class UsersSearchByUsernameAndHostRequest( val limit: Int? = null, val detail: Boolean? = null, val username: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersSearchRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersSearchRequest.kt similarity index 91% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersSearchRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersSearchRequest.kt index 72ce2bd0de..90e0f14291 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersSearchRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersSearchRequest.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable * * * @param query * @param offset * @param limit * @param origin * @param detail */ @Serializable -internal data class UsersSearchRequest( +public data class UsersSearchRequest( @SerialName(value = "query") val query: kotlin.String, @SerialName(value = "offset") val offset: kotlin.Int? = 0, @SerialName(value = "limit") val limit: kotlin.Int? = 10, @@ -34,8 +34,8 @@ internal data class UsersSearchRequest( * Values: Local,Remote,Combined */ @Serializable - enum class Origin( - val value: kotlin.String, + public enum class Origin( + public val value: kotlin.String, ) { @SerialName(value = "local") Local("local"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersShow200Response.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersShow200Response.kt similarity index 97% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersShow200Response.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersShow200Response.kt index 8e8d6e3f43..83d2d33a00 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersShow200Response.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersShow200Response.kt @@ -24,7 +24,7 @@ import kotlinx.serialization.Serializable * @param id * @param name * @param username * @param host The local host is represented with `null`. * @param avatarUrl * @param avatarBlurhash * @param onlineStatus * @param url * @param uri * @param movedToUri * @param alsoKnownAs * @param createdAt * @param updatedAt * @param lastFetchedAt * @param bannerUrl * @param bannerBlurhash * @param isLocked * @param isSilenced * @param isLimited * @param isSuspended * @param description * @param location * @param birthday * @param lang * @param fields * @param followersCount * @param followingCount * @param notesCount * @param pinnedNoteIds * @param pinnedNotes * @param pinnedPageId * @param pinnedPage * @param publicReactions * @param twoFactorEnabled * @param usePasswordLessLogin * @param securityKeys * @param avatarId * @param bannerId * @param injectFeaturedNote * @param receiveAnnouncementEmail * @param alwaysMarkNsfw * @param autoSensitive * @param carefulBot * @param autoAcceptFollowed * @param noCrawle * @param preventAiLearning * @param isExplorable * @param isDeleted * @param twoFactorBackupCodes * @param hideOnlineStatus * @param hasUnreadSpecifiedNotes * @param hasUnreadMentions * @param hasUnreadAnnouncement * @param hasUnreadAntenna * @param hasUnreadNotification * @param hasPendingReceivedFollowRequest * @param mutedWords * @param mutedInstances * @param mutingNotificationTypes * @param emailNotificationTypes * @param isAdmin * @param isModerator * @param isBot * @param isCat * @param isFollowing * @param isFollowed * @param hasPendingFollowRequestFromYou * @param hasPendingFollowRequestToYou * @param isBlocking * @param isBlocked * @param isMuted * @param isRenoteMuted * @param memo * @param email * @param emailVerified * @param securityKeysList */ @Serializable -internal data class UsersShow200Response( +public data class UsersShow200Response( @SerialName(value = "id") val id: kotlin.String, @SerialName(value = "name") val name: kotlin.String? = null, @SerialName(value = "username") val username: kotlin.String, @@ -108,8 +108,8 @@ internal data class UsersShow200Response( * Values: Unknown,Online,Active,Offline */ @Serializable - enum class OnlineStatus( - val value: kotlin.String, + public enum class OnlineStatus( + public val value: kotlin.String, ) { @SerialName(value = "unknown") Unknown("unknown"), @@ -129,8 +129,8 @@ internal data class UsersShow200Response( * Values: Full,Partial,None */ @Serializable - enum class TwoFactorBackupCodes( - val value: kotlin.String, + public enum class TwoFactorBackupCodes( + public val value: kotlin.String, ) { @SerialName(value = "full") Full("full"), diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersShowRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersShowRequest.kt similarity index 93% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersShowRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersShowRequest.kt index 7a95b76824..9b4c59c833 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersShowRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersShowRequest.kt @@ -21,7 +21,7 @@ import kotlinx.serialization.Serializable * * */ @Serializable -internal data class UsersShowRequest( +public data class UsersShowRequest( val userId: String? = null, val userIds: List? = null, val username: String? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersUpdateMemoRequest.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersUpdateMemoRequest.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersUpdateMemoRequest.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersUpdateMemoRequest.kt index 6b5525f1c8..ced8ee10f1 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersUpdateMemoRequest.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/UsersUpdateMemoRequest.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable * @param userId * @param memo A personal memo for the target user. If null or empty, delete the memo. */ @Serializable -internal data class UsersUpdateMemoRequest( +public data class UsersUpdateMemoRequest( @SerialName(value = "userId") val userId: kotlin.String, // A personal memo for the target user. If null or empty, delete the memo. @SerialName(value = "memo") val memo: kotlin.String?, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Visibility.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Visibility.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Visibility.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Visibility.kt index a9da7574ec..7f42ba130a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Visibility.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/Visibility.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -internal enum class Visibility { +public enum class Visibility { @SerialName("public") Public, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/response/MiAuthCheckResponse.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/response/MiAuthCheckResponse.kt similarity index 87% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/response/MiAuthCheckResponse.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/response/MiAuthCheckResponse.kt index 943c3f7587..86bd5c240e 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/response/MiAuthCheckResponse.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/model/response/MiAuthCheckResponse.kt @@ -4,7 +4,7 @@ import dev.dimension.flare.data.network.misskey.api.model.User import kotlinx.serialization.Serializable @Serializable -internal data class MiAuthCheckResponse( +public data class MiAuthCheckResponse( val ok: Boolean? = null, val token: String? = null, val user: User? = null, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/serializer/MisskeyEmojiMapSerializer.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/serializer/MisskeyEmojiMapSerializer.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/serializer/MisskeyEmojiMapSerializer.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/serializer/MisskeyEmojiMapSerializer.kt index 175d4387db..f7ecefbb52 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/serializer/MisskeyEmojiMapSerializer.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/network/misskey/api/serializer/MisskeyEmojiMapSerializer.kt @@ -10,7 +10,7 @@ import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonTransformingSerializer @OptIn(ExperimentalSerializationApi::class) -internal object MisskeyEmojiMapSerializer : +public object MisskeyEmojiMapSerializer : JsonTransformingSerializer>(MapSerializer(String.serializer(), String.serializer())) { override fun transformDeserialize(element: JsonElement): JsonElement = when (element) { diff --git a/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/platform/MisskeySocialPlatformPlugin.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/platform/MisskeySocialPlatformPlugin.kt new file mode 100644 index 0000000000..39fd9f9b10 --- /dev/null +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/platform/MisskeySocialPlatformPlugin.kt @@ -0,0 +1,88 @@ +package dev.dimension.flare.data.platform + +import dev.dimension.flare.common.deeplink.DeepLinkMapping +import dev.dimension.flare.common.deeplink.DeepLinkPattern +import dev.dimension.flare.common.tryRun +import dev.dimension.flare.data.datasource.microblog.MicroblogDataSource +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.misskey.MisskeyDataSource +import dev.dimension.flare.data.network.misskey.JoinMisskeyService +import dev.dimension.flare.data.network.misskey.MisskeyPlatformDetector +import dev.dimension.flare.data.network.misskey.MisskeyService +import dev.dimension.flare.data.network.misskey.api.model.MetaRequest +import dev.dimension.flare.data.network.nodeinfo.PlatformDetector +import dev.dimension.flare.model.PlatformType +import dev.dimension.flare.model.PlatformTypeMetadata +import dev.dimension.flare.model.SocialPlatformPlugin +import dev.dimension.flare.model.SocialPlatformSpec +import dev.dimension.flare.ui.model.UiAccount +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiInstance +import dev.dimension.flare.ui.model.UiInstanceMetadata +import dev.dimension.flare.ui.model.mapper.render +import io.ktor.http.Url +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +public data object MisskeySocialPlatformPlugin : SocialPlatformPlugin { + public override val spec: SocialPlatformSpec = MisskeySocialPlatformSpec + + public override fun createDataSource(account: UiAccount): MicroblogDataSource? = + (account as? UiAccount.Misskey)?.let { + MisskeyDataSource( + accountKey = it.accountKey, + host = it.host, + ) + } + + public override suspend fun recommendedInstances(): List = + tryRun { + JoinMisskeyService + .instances() + .instancesInfos + .map { + UiInstance( + name = it.name, + description = it.description, + iconUrl = it.meta?.iconURL, + domain = it.url, + type = PlatformType.Misskey, + bannerUrl = it.meta?.bannerURL, + usersCount = + it.stats?.usersCount ?: it.nodeinfo + ?.usage + ?.users + ?.total ?: 0, + ) + }.sortedByDescending { it.usersCount } + }.getOrDefault(emptyList()) +} + +public data object MisskeySocialPlatformSpec : SocialPlatformSpec { + public override val type: PlatformType = PlatformType.Misskey + public override val timelineSpecs: ImmutableList> = MisskeyTimelineSpecs.timelineSpecs + public override val metadata: PlatformTypeMetadata = + PlatformTypeMetadata( + displayName = "Misskey", + icon = UiIcon.Misskey, + ) + public override val detector: PlatformDetector = MisskeyPlatformDetector + + public override fun agreementUrl(host: String): String = "https://$host/about" + + public override fun deepLinkPatterns(host: String): ImmutableList> = + persistentListOf( + DeepLinkPattern(DeepLinkMapping.Type.Profile.serializer(), Url("https://$host/@{handle}")), + DeepLinkPattern(DeepLinkMapping.Type.Post.serializer(), Url("https://$host/notes/{id}")), + ) + + public override suspend fun instanceMetadata(host: String): UiInstanceMetadata = + MisskeyService("https://$host/api/").meta(MetaRequest()).render() + + public override fun guestDataSource( + host: String, + locale: String, + ): MicroblogDataSource = unsupportedGuestDataSource() +} + +private fun unsupportedGuestDataSource(): Nothing = throw UnsupportedOperationException("Misskey guest data source is not supported yet.") diff --git a/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/platform/MisskeyTimelineDescriptorFactories.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/platform/MisskeyTimelineDescriptorFactories.kt new file mode 100644 index 0000000000..c5d1a1ae3e --- /dev/null +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/platform/MisskeyTimelineDescriptorFactories.kt @@ -0,0 +1,25 @@ +package dev.dimension.flare.data.platform + +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineTabDescriptor +import dev.dimension.flare.data.datasource.microblog.timeline.toTimelineTabDescriptor +import dev.dimension.flare.data.model.IconType +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiList +import dev.dimension.flare.ui.model.UiText +import dev.dimension.flare.ui.model.asType + +public fun UiList.Antenna.toTimelineTabDescriptor(accountKey: MicroBlogKey): TimelineTabDescriptor.Source = + MisskeyTimelineSpecs.antenna.toTimelineTabDescriptor( + data = TimelineSpec.AccountResourceData(accountKey, id), + title = UiText.Raw(title), + icon = UiIcon.Rss.asType(), + ) + +public fun UiList.Channel.toTimelineTabDescriptor(accountKey: MicroBlogKey): TimelineTabDescriptor.Source = + MisskeyTimelineSpecs.channel.toTimelineTabDescriptor( + data = TimelineSpec.AccountResourceData(accountKey, id), + title = UiText.Raw(title), + icon = banner?.let { IconType.Url(it) } ?: UiIcon.Channel.asType(), + ) diff --git a/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/platform/MisskeyTimelineSpecs.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/platform/MisskeyTimelineSpecs.kt new file mode 100644 index 0000000000..c0b3d64af1 --- /dev/null +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/data/platform/MisskeyTimelineSpecs.kt @@ -0,0 +1,119 @@ +package dev.dimension.flare.data.platform + +import dev.dimension.flare.data.datasource.microblog.paging.RemoteLoader +import dev.dimension.flare.data.datasource.microblog.timeline.AccountTimelineSpec +import dev.dimension.flare.data.datasource.microblog.timeline.CommonTimelineSpecs +import dev.dimension.flare.data.datasource.microblog.timeline.TimelineSpec +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.model.UiStrings +import dev.dimension.flare.ui.model.UiTimelineV2 +import dev.dimension.flare.ui.model.asType +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +public interface MisskeyTimelineDataSource { + public fun favouriteTimelineLoader(): RemoteLoader + + public fun hybridTimelineLoader(): RemoteLoader + + public fun localTimelineLoader(): RemoteLoader + + public fun publicTimelineLoader(): RemoteLoader + + public fun antennasTimelineLoader(id: String): RemoteLoader + + public fun channelTimelineLoader(id: String): RemoteLoader +} + +public object MisskeyTimelineSpecs { + public val favourite: AccountTimelineSpec = + AccountTimelineSpec( + id = "misskey.favourite", + title = UiStrings.Favourite, + icon = UiIcon.Favourite.asType(), + serializer = TimelineSpec.AccountBasedData.serializer(), + stableKeyFactory = { it.accountKey.toString() }, + loaderFactory = { service, _ -> + require(service is MisskeyTimelineDataSource) + service.favouriteTimelineLoader() + }, + ) + + public val hybrid: AccountTimelineSpec = + AccountTimelineSpec( + id = "misskey.hybrid", + title = UiStrings.Social, + icon = UiIcon.Featured.asType(), + serializer = TimelineSpec.AccountBasedData.serializer(), + stableKeyFactory = { it.accountKey.toString() }, + loaderFactory = { service, _ -> + require(service is MisskeyTimelineDataSource) + service.hybridTimelineLoader() + }, + ) + + public val local: AccountTimelineSpec = + AccountTimelineSpec( + id = "misskey.local", + title = UiStrings.MastodonLocal, + icon = UiIcon.Local.asType(), + serializer = TimelineSpec.AccountBasedData.serializer(), + stableKeyFactory = { it.accountKey.toString() }, + loaderFactory = { service, _ -> + require(service is MisskeyTimelineDataSource) + service.localTimelineLoader() + }, + ) + + public val global: AccountTimelineSpec = + AccountTimelineSpec( + id = "misskey.global", + title = UiStrings.MastodonPublic, + icon = UiIcon.World.asType(), + serializer = TimelineSpec.AccountBasedData.serializer(), + stableKeyFactory = { it.accountKey.toString() }, + loaderFactory = { service, _ -> + require(service is MisskeyTimelineDataSource) + service.publicTimelineLoader() + }, + ) + + public val antenna: AccountTimelineSpec = + AccountTimelineSpec( + id = "misskey.antenna", + title = UiStrings.Antenna, + icon = UiIcon.Rss.asType(), + serializer = TimelineSpec.AccountResourceData.serializer(), + stableKeyFactory = { "${it.accountKey}:${it.resourceId}" }, + loaderFactory = { service, data -> + require(service is MisskeyTimelineDataSource) + service.antennasTimelineLoader(data.resourceId) + }, + ) + + public val channel: AccountTimelineSpec = + AccountTimelineSpec( + id = "misskey.channel", + title = UiStrings.Channel, + icon = UiIcon.Channel.asType(), + serializer = TimelineSpec.AccountResourceData.serializer(), + stableKeyFactory = { "${it.accountKey}:${it.resourceId}" }, + loaderFactory = { service, data -> + require(service is MisskeyTimelineDataSource) + service.channelTimelineLoader(data.resourceId) + }, + ) + + public val timelineSpecs: ImmutableList> = + persistentListOf( + CommonTimelineSpecs.home, + CommonTimelineSpecs.discover, + CommonTimelineSpecs.list, + favourite, + hybrid, + local, + global, + antenna, + channel, + ) +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Misskey.kt b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Misskey.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Misskey.kt rename to social/misskey/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Misskey.kt index 123e861e35..94bd59abf9 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Misskey.kt +++ b/social/misskey/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Misskey.kt @@ -1,7 +1,6 @@ package dev.dimension.flare.ui.model.mapper import dev.dimension.flare.data.datasource.microblog.ActionMenu -import dev.dimension.flare.data.datasource.microblog.PostEvent import dev.dimension.flare.data.datasource.microblog.userActionsMenu import dev.dimension.flare.data.network.misskey.api.model.Antenna import dev.dimension.flare.data.network.misskey.api.model.Channel @@ -21,6 +20,7 @@ import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.model.PlatformType import dev.dimension.flare.model.toAccountType import dev.dimension.flare.ui.model.ClickEvent +import dev.dimension.flare.ui.model.PostEvent import dev.dimension.flare.ui.model.UiEmoji import dev.dimension.flare.ui.model.UiHandle import dev.dimension.flare.ui.model.UiIcon @@ -76,12 +76,12 @@ import kotlin.math.roundToLong import kotlin.time.Instant @JvmName("renderMisskeyNotes") -internal fun List.render(accountKey: MicroBlogKey): List = map { it.render(accountKey) } +public fun List.render(accountKey: MicroBlogKey): List = map { it.render(accountKey) } @JvmName("renderMisskeyNotifications") -internal fun List.render(accountKey: MicroBlogKey): List = map { it.render(accountKey) } +public fun List.render(accountKey: MicroBlogKey): List = map { it.render(accountKey) } -internal fun Notification.render(accountKey: MicroBlogKey): UiTimelineV2 { +public fun Notification.render(accountKey: MicroBlogKey): UiTimelineV2 { val user = user?.render(accountKey) val notificationType = runCatching { @@ -387,7 +387,7 @@ public enum class MisskeyAchievement( } } -internal fun Note.render(accountKey: MicroBlogKey): UiTimelineV2 { +public fun Note.render(accountKey: MicroBlogKey): UiTimelineV2 { val wrapperStatus = this.renderStatus(accountKey) val actualStatus = if (!text.isNullOrEmpty() || !files.isNullOrEmpty() || poll != null || cw != null) { @@ -427,23 +427,32 @@ internal fun Note.render(accountKey: MicroBlogKey): UiTimelineV2 { } private fun Note.renderStatus(accountKey: MicroBlogKey): UiTimelineV2.Post { + val noteUser = user + val noteUserHost = noteUser.host val remoteHost = - if (user.host.isNullOrEmpty()) { + if (noteUserHost.isNullOrEmpty()) { accountKey.host } else { - user.host + noteUserHost } val parent = reply - val user = user.render(accountKey) + val user = noteUser.render(accountKey) val isFromMe = user.key == accountKey + val noteChannel = channel + val noteText = text + val noteContentWarning = cw + val noteBodyText = noteText?.takeIf { it.isNotEmpty() } + val noteContentWarningText = noteContentWarning?.takeIf { it.isNotEmpty() } + val noteFiles = files + val notePoll = poll val canReblog = - (channel == null || channel.allowRenoteToExternal == null || channel.allowRenoteToExternal) && + (noteChannel?.allowRenoteToExternal != false) && ( visibility in listOf(Visibility.Public, Visibility.Home) || (isFromMe && visibility != Visibility.Specified) ) val renderedVisibility = - if (channel?.id != null) { + if (noteChannel?.id != null) { UiTimelineV2.Post.Visibility.Channel } else { when (visibility) { @@ -480,13 +489,16 @@ private fun Note.renderStatus(accountKey: MicroBlogKey): UiTimelineV2.Post { }.sortedByDescending { it.count.value } .toImmutableList() val sourceChannel = - if (channel?.id != null && channel.name != null) { + noteChannel?.let { channel -> + val channelId = channel.id + val channelName = channel.name + if (channelId == null || channelName == null) { + return@let null + } UiTimelineV2.Post.SourceChannel( - id = channel.id, - name = channel.name, + id = channelId, + name = channelName, ) - } else { - null } val postUrl = buildString { @@ -507,30 +519,30 @@ private fun Note.renderStatus(accountKey: MicroBlogKey): UiTimelineV2.Post { parent?.renderStatus(accountKey), ).toPersistentList(), images = - files + noteFiles ?.mapNotNull { file -> file.toUi() }?.toPersistentList() ?: persistentListOf(), contentWarning = - if (!cw.isNullOrEmpty() && !text.isNullOrEmpty()) { - parseMisskeyText(cw, accountKey, emojis, remoteHost) + if (noteContentWarningText != null && noteBodyText != null) { + parseMisskeyText(noteContentWarningText, accountKey, emojis, remoteHost) } else { null }, user = user, quote = listOfNotNull( - if (text != null || !files.isNullOrEmpty() || cw != null || poll != null) { + if (noteText != null || !noteFiles.isNullOrEmpty() || noteContentWarning != null || notePoll != null) { renote?.renderStatus(accountKey) } else { null }, ).toImmutableList(), content = - if (!text.isNullOrEmpty()) { - parseMisskeyText(text, accountKey, emojis, remoteHost) - } else if (!cw.isNullOrEmpty()) { - parseMisskeyText(cw, accountKey, emojis, remoteHost) + if (noteBodyText != null) { + parseMisskeyText(noteBodyText, accountKey, emojis, remoteHost) + } else if (noteContentWarningText != null) { + parseMisskeyText(noteContentWarningText, accountKey, emojis, remoteHost) } else { "".toUiPlainText() }, @@ -672,12 +684,13 @@ private fun Note.renderStatus(accountKey: MicroBlogKey): UiTimelineV2.Post { ), ).toImmutableList(), poll = - poll?.let { + notePoll?.let { poll -> + val choices = poll.choices UiPoll( // misskey poll doesn't have id id = "", options = - poll.choices + choices .map { option -> UiPoll.Option( title = option.text, @@ -686,14 +699,14 @@ private fun Note.renderStatus(accountKey: MicroBlogKey): UiTimelineV2.Post { option.votes .toFloat() .div( - poll.choices.sumOf { it.votes }.toFloat(), + choices.sumOf { it.votes }.toFloat(), ).takeUnless { it.isNaN() } ?: 0f, ) }.toPersistentList(), expiresAt = poll.expiresAt ?: Instant.DISTANT_PAST, multiple = poll.multiple, ownVotes = - poll.choices + choices .mapIndexedNotNull { index, choice -> index.takeIf { choice.isVoted } }.toPersistentList(), @@ -731,7 +744,7 @@ private fun Note.renderStatus(accountKey: MicroBlogKey): UiTimelineV2.Post { ) } -internal fun ActionMenu.Companion.misskeyRenote( +public fun ActionMenu.Companion.misskeyRenote( postKey: MicroBlogKey, count: Long, accountKey: MicroBlogKey?, @@ -753,7 +766,7 @@ internal fun ActionMenu.Companion.misskeyRenote( }, ) -internal fun ActionMenu.Companion.misskeyReact( +public fun ActionMenu.Companion.misskeyReact( postKey: MicroBlogKey, hasReacted: Boolean, reaction: String?, @@ -799,7 +812,7 @@ internal fun ActionMenu.Companion.misskeyReact( }, ) -internal fun ActionMenu.Companion.misskeyFavourite( +public fun ActionMenu.Companion.misskeyFavourite( postKey: MicroBlogKey, favourited: Boolean, accountKey: MicroBlogKey?, @@ -854,12 +867,13 @@ private fun DriveFile.toUi(): UiMedia? { } } -internal fun UserLite.render(accountKey: MicroBlogKey): UiProfile { +public fun UserLite.render(accountKey: MicroBlogKey): UiProfile { + val userHost = host val remoteHost = - if (host.isNullOrEmpty()) { + if (userHost.isNullOrEmpty()) { accountKey.host } else { - host + userHost } val userKey = MicroBlogKey( @@ -897,12 +911,13 @@ internal fun UserLite.render(accountKey: MicroBlogKey): UiProfile { ) } -internal fun User.render(accountKey: MicroBlogKey): UiProfile { +public fun User.render(accountKey: MicroBlogKey): UiProfile { + val userHost = host val remoteHost = - if (host.isNullOrEmpty()) { + if (userHost.isNullOrEmpty()) { accountKey.host } else { - host + userHost } val userKey = MicroBlogKey( @@ -970,7 +985,7 @@ internal fun User.render(accountKey: MicroBlogKey): UiProfile { ) } -internal fun EmojiSimple.toUi(): UiEmoji = +public fun EmojiSimple.toUi(): UiEmoji = UiEmoji( shortcode = ":$name:", url = url, @@ -979,7 +994,7 @@ internal fun EmojiSimple.toUi(): UiEmoji = insertText = " :$name: ", ) -internal fun resolveMisskeyEmoji( +public fun resolveMisskeyEmoji( name: String, accountHost: String, emojis: Map, @@ -1293,13 +1308,13 @@ private class MisskeyRichTextBuilder( } } -internal fun UserList.render(): UiList.List = +public fun UserList.render(): UiList.List = UiList.List( id = id, title = name, ) -internal fun Meta200Response.render(): UiInstanceMetadata { +public fun Meta200Response.render(): UiInstanceMetadata { val configuration = UiInstanceMetadata.Configuration( registration = @@ -1349,13 +1364,13 @@ internal fun Meta200Response.render(): UiInstanceMetadata { ) } -internal fun Antenna.render(): UiList.Antenna = +public fun Antenna.render(): UiList.Antenna = UiList.Antenna( id = id, title = name, ) -internal fun Channel.render(accountKey: MicroBlogKey): UiList.Channel = +public fun Channel.render(accountKey: MicroBlogKey): UiList.Channel = UiList.Channel( id = id, title = name, diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyNotificationTypeTest.kt b/social/misskey/src/commonTest/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyNotificationTypeTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyNotificationTypeTest.kt rename to social/misskey/src/commonTest/kotlin/dev/dimension/flare/data/network/misskey/api/model/MisskeyNotificationTypeTest.kt diff --git a/shared/src/commonTest/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserLiteTest.kt b/social/misskey/src/commonTest/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserLiteTest.kt similarity index 100% rename from shared/src/commonTest/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserLiteTest.kt rename to social/misskey/src/commonTest/kotlin/dev/dimension/flare/data/network/misskey/api/model/UserLiteTest.kt diff --git a/social/model/build.gradle.kts b/social/model/build.gradle.kts new file mode 100644 index 0000000000..ab2ac0e252 --- /dev/null +++ b/social/model/build.gradle.kts @@ -0,0 +1,77 @@ +import dev.dimension.flare.buildlogic.FlarePlatform +import dev.dimension.flare.buildlogic.flare + +plugins { + id("dev.dimension.flare.multiplatform-library") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + flare { + namespace = "dev.dimension.flare.social.model" + platforms( + FlarePlatform.ANDROID, + FlarePlatform.JVM, + FlarePlatform.IOS, + FlarePlatform.WEB, + ) + } + + tasks + .withType>() + .configureEach { + if (name == "compileCommonMainKotlinMetadata") { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_social_model_commonMain", + ) + } + } + } + + targets.configureEach { + if (name != "wasmJs" && name != "metadata") { + compilations.configureEach { + if (name == "main") { + compileTaskProvider.configure { + compilerOptions { + freeCompilerArgs.addAll( + "-module-name", + "flare_social_model", + ) + } + } + } + } + } + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.core.common) + api(projects.foundation.deeplink) + api(projects.core.model) + api(projects.modules.account.model) + api(projects.social.rss.model) + implementation(projects.core.humanizer) + implementation(projects.foundation.filesystem) + api(projects.ui.model) + api(projects.ui.richtext) + api(dependencies.platform(libs.compose.bom)) + api(libs.compose.runtime) + api(libs.kotlinx.immutable) + api(libs.kotlinx.serialization.json) + implementation(libs.ksoup) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + } +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ActionMenu.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ActionMenu.kt similarity index 71% rename from shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ActionMenu.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ActionMenu.kt index bf2dbaa499..d4e8776158 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ActionMenu.kt +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ActionMenu.kt @@ -2,13 +2,11 @@ package dev.dimension.flare.data.datasource.microblog import androidx.compose.runtime.Immutable import dev.dimension.flare.common.SerializableImmutableList -import dev.dimension.flare.model.MicroBlogKey import dev.dimension.flare.ui.model.ClickContext import dev.dimension.flare.ui.model.ClickEvent import dev.dimension.flare.ui.model.UiIcon import dev.dimension.flare.ui.model.UiNumber import dev.dimension.flare.ui.model.onClicked -import dev.dimension.flare.ui.route.DeeplinkRoute import kotlinx.collections.immutable.persistentListOf import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -18,7 +16,7 @@ import kotlinx.serialization.Serializable public sealed class ActionMenu { @Immutable @Serializable - public data class Group internal constructor( + public data class Group public constructor( val displayItem: Item, val actions: SerializableImmutableList, ) : ActionMenu() @@ -36,8 +34,8 @@ public sealed class ActionMenu { @Immutable @Serializable - public data class Item internal constructor( - internal val updateKey: String = "", + public data class Item public constructor( + val updateKey: String = "", val icon: UiIcon? = null, val text: Text? = null, val count: UiNumber? = null, @@ -115,35 +113,3 @@ public sealed class ActionMenu { } } } - -internal fun userActionsMenu( - accountKey: MicroBlogKey?, - userKey: MicroBlogKey, - handle: String, -): List = - listOfNotNull( - ActionMenu.Item( - icon = UiIcon.Mute, - text = - ActionMenu.Item.Text.Localized( - type = ActionMenu.Item.Text.Localized.Type.MuteWithHandleParameter, - parameters = persistentListOf(handle), - ), - clickEvent = - ClickEvent.Deeplink( - DeeplinkRoute.MuteUser(accountKey, userKey), - ), - ), - ActionMenu.Item( - icon = UiIcon.Block, - text = - ActionMenu.Item.Text.Localized( - type = ActionMenu.Item.Text.Localized.Type.BlockWithHandleParameter, - parameters = persistentListOf(handle), - ), - clickEvent = - ClickEvent.Deeplink( - DeeplinkRoute.BlockUser(accountKey, userKey), - ), - ), - ) diff --git a/social/model/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/UserActionsMenu.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/UserActionsMenu.kt new file mode 100644 index 0000000000..ea6eb57f44 --- /dev/null +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/UserActionsMenu.kt @@ -0,0 +1,39 @@ +package dev.dimension.flare.data.datasource.microblog + +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.ClickEvent +import dev.dimension.flare.ui.model.UiIcon +import dev.dimension.flare.ui.route.DeeplinkRoute +import kotlinx.collections.immutable.persistentListOf + +public fun userActionsMenu( + accountKey: MicroBlogKey?, + userKey: MicroBlogKey, + handle: String, +): List = + listOfNotNull( + ActionMenu.Item( + icon = UiIcon.Mute, + text = + ActionMenu.Item.Text.Localized( + type = ActionMenu.Item.Text.Localized.Type.MuteWithHandleParameter, + parameters = persistentListOf(handle), + ), + clickEvent = + ClickEvent.Deeplink( + DeeplinkRoute.MuteUser(accountKey, userKey), + ), + ), + ActionMenu.Item( + icon = UiIcon.Block, + text = + ActionMenu.Item.Text.Localized( + type = ActionMenu.Item.Text.Localized.Type.BlockWithHandleParameter, + parameters = persistentListOf(handle), + ), + clickEvent = + ClickEvent.Deeplink( + DeeplinkRoute.BlockUser(accountKey, userKey), + ), + ), + ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/model/ReferenceType.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/model/ReferenceType.kt similarity index 80% rename from shared/src/commonMain/kotlin/dev/dimension/flare/model/ReferenceType.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/model/ReferenceType.kt index 907fd0a712..d7fc7a3da8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/model/ReferenceType.kt +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/model/ReferenceType.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.model import kotlinx.serialization.Serializable @Serializable -internal enum class ReferenceType { +public enum class ReferenceType { Retweet, Reply, Quote, diff --git a/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/ClickEvent.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/ClickEvent.kt new file mode 100644 index 0000000000..63b13a6612 --- /dev/null +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/ClickEvent.kt @@ -0,0 +1,67 @@ +package dev.dimension.flare.ui.model + +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.route.DeeplinkRoute +import dev.dimension.flare.ui.route.toUri +import kotlinx.serialization.Serializable + +@Serializable +public sealed interface ClickEvent { + @Serializable + public data object Noop : ClickEvent + + @Serializable + public data class Deeplink private constructor( + public val url: String, + ) : ClickEvent { + public constructor(route: DeeplinkRoute) : this(route.toUri()) + public constructor(route: DeeplinkEvent) : this(route.toUri()) + } + + public companion object { + public fun event( + accountKey: MicroBlogKey?, + postEvent: PostEvent, + ): ClickEvent = + if (accountKey == null) { + Noop + } else { + Deeplink( + DeeplinkEvent( + accountKey = accountKey, + postEvent = postEvent, + ), + ) + } + + public inline fun event( + accountKey: MicroBlogKey?, + eventCreator: (accountKey: MicroBlogKey) -> PostEvent, + ): ClickEvent = + if (accountKey == null) { + Noop + } else { + Deeplink( + DeeplinkEvent( + accountKey = accountKey, + postEvent = eventCreator.invoke(accountKey), + ), + ) + } + } +} + +public val ClickEvent.onClicked: ClickContext.() -> Unit + get() { + when (this) { + is ClickEvent.Deeplink -> { + return { + launcher.launch(url) + } + } + + is ClickEvent.Noop -> { + return {} + } + } + } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/DeeplinkEvent.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/DeeplinkEvent.kt similarity index 68% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/DeeplinkEvent.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/DeeplinkEvent.kt index 8cef658de1..205e5235e3 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/DeeplinkEvent.kt +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/DeeplinkEvent.kt @@ -1,6 +1,5 @@ package dev.dimension.flare.ui.model -import dev.dimension.flare.data.datasource.microblog.PostEvent import dev.dimension.flare.model.MicroBlogKey import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable @@ -10,7 +9,7 @@ import kotlinx.serialization.protobuf.ProtoBuf @Serializable @OptIn(ExperimentalSerializationApi::class) -internal data class DeeplinkEvent( +public data class DeeplinkEvent( val accountKey: MicroBlogKey, val translationEvent: TranslationEvent? = null, val postEvent: PostEvent? = null, @@ -21,33 +20,33 @@ internal data class DeeplinkEvent( } } - companion object { - const val SCHEME = "flare-event" + public companion object { + public const val SCHEME: String = "flare-event" - fun parse(uri: String): DeeplinkEvent? = + public fun parse(uri: String): DeeplinkEvent? = runCatching { ProtoBuf.decodeFromHexString(uri.removePrefix("$SCHEME://")) }.getOrNull() - fun isDeeplinkEvent(uri: String): Boolean = uri.startsWith("$SCHEME://") + public fun isDeeplinkEvent(uri: String): Boolean = uri.startsWith("$SCHEME://") } - fun toUri(): String = "$SCHEME://${ProtoBuf.encodeToHexString(this)}" + public fun toUri(): String = "$SCHEME://${ProtoBuf.encodeToHexString(this)}" @Serializable - sealed interface TranslationEvent { + public sealed interface TranslationEvent { @Serializable - data class RetryTranslation( + public data class RetryTranslation( val statusKey: MicroBlogKey, ) : TranslationEvent @Serializable - data class Translate( + public data class Translate( val statusKey: MicroBlogKey, ) : TranslationEvent @Serializable - data class ShowOriginal( + public data class ShowOriginal( val statusKey: MicroBlogKey, ) : TranslationEvent } diff --git a/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/PostEvent.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/PostEvent.kt new file mode 100644 index 0000000000..131835ceff --- /dev/null +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/PostEvent.kt @@ -0,0 +1,239 @@ +package dev.dimension.flare.ui.model + +import dev.dimension.flare.common.SerializableImmutableList +import dev.dimension.flare.model.MicroBlogKey +import kotlinx.collections.immutable.toImmutableList +import kotlinx.serialization.Serializable + +@Serializable +public sealed interface PostEvent { + public val postKey: MicroBlogKey + + @Serializable + public sealed interface PollEvent : PostEvent { + public val accountKey: MicroBlogKey + public val options: SerializableImmutableList + + public fun copyWithOptions(options: List): PollEvent + } + + @Serializable + public sealed interface Mastodon : PostEvent { + @Serializable + public data class Reblog( + override val postKey: MicroBlogKey, + val reblogged: Boolean, + val count: Long, + val accountKey: MicroBlogKey, + ) : Mastodon + + @Serializable + public data class Like( + override val postKey: MicroBlogKey, + val liked: Boolean, + val accountKey: MicroBlogKey, + val count: Long, + ) : Mastodon + + @Serializable + public data class Bookmark( + override val postKey: MicroBlogKey, + val bookmarked: Boolean, + val accountKey: MicroBlogKey, + ) : Mastodon + + @Serializable + public data class Vote( + val id: String, + override val accountKey: MicroBlogKey, + override val postKey: MicroBlogKey, + override val options: SerializableImmutableList, + ) : Mastodon, + PollEvent { + override fun copyWithOptions(options: List): PollEvent = copy(options = options.toImmutableList()) + } + + @Serializable + public data class AcceptFollowRequest( + override val postKey: MicroBlogKey, + val userKey: MicroBlogKey, + ) : Mastodon + + @Serializable + public data class RejectFollowRequest( + override val postKey: MicroBlogKey, + val userKey: MicroBlogKey, + ) : Mastodon + } + + @Serializable + public sealed interface Pleroma : PostEvent { + @Serializable + public data class React( + override val postKey: MicroBlogKey, + val hasReacted: Boolean, + val reaction: String, + ) : Pleroma + } + + @Serializable + public sealed interface Misskey : PostEvent { + @Serializable + public data class React( + override val postKey: MicroBlogKey, + val hasReacted: Boolean, + val reaction: String, + val count: Long = 0, + val accountKey: MicroBlogKey? = null, + ) : Misskey + + @Serializable + public data class Renote( + override val postKey: MicroBlogKey, + val count: Long = 0, + val accountKey: MicroBlogKey? = null, + ) : Misskey + + @Serializable + public data class Vote( + override val accountKey: MicroBlogKey, + override val postKey: MicroBlogKey, + override val options: SerializableImmutableList, + ) : Misskey, + PollEvent { + override fun copyWithOptions(options: List): PollEvent = copy(options = options.toImmutableList()) + } + + @Serializable + public data class Favourite( + override val postKey: MicroBlogKey, + val favourited: Boolean, + val accountKey: MicroBlogKey? = null, + ) : Misskey + + @Serializable + public data class AcceptFollowRequest( + override val postKey: MicroBlogKey, + val userKey: MicroBlogKey, + val notificationStatusKey: MicroBlogKey, + ) : Misskey + + @Serializable + public data class RejectFollowRequest( + override val postKey: MicroBlogKey, + val userKey: MicroBlogKey, + val notificationStatusKey: MicroBlogKey, + ) : Misskey + } + + @Serializable + public sealed interface Bluesky : PostEvent { + @Serializable + public data class Reblog( + override val postKey: MicroBlogKey, + val count: Long, + val cid: String, + val uri: String, + val repostUri: String?, + val accountKey: MicroBlogKey, + ) : Bluesky + + @Serializable + public data class Like( + override val postKey: MicroBlogKey, + val cid: String, + val uri: String, + val likedUri: String?, + val count: Long, + val accountKey: MicroBlogKey, + ) : Bluesky + + @Serializable + public data class Bookmark( + override val postKey: MicroBlogKey, + val uri: String, + val cid: String, + val bookmarked: Boolean, + val accountKey: MicroBlogKey, + val count: Long, + ) : Bluesky + } + + @Serializable + public sealed interface XQT : PostEvent { + @Serializable + public data class Retweet( + override val postKey: MicroBlogKey, + val retweeted: Boolean, + val count: Long = 0, + val accountKey: MicroBlogKey, + ) : XQT + + @Serializable + public data class Like( + override val postKey: MicroBlogKey, + val liked: Boolean, + val count: Long = 0, + val accountKey: MicroBlogKey, + ) : XQT + + @Serializable + public data class Bookmark( + override val postKey: MicroBlogKey, + val bookmarked: Boolean, + val count: Long = 0, + val accountKey: MicroBlogKey, + ) : XQT + } + + @Serializable + public sealed interface VVO : PostEvent { + @Serializable + public data class Like( + override val postKey: MicroBlogKey, + val liked: Boolean, + val count: Long = 0, + val accountKey: MicroBlogKey, + ) : VVO + + @Serializable + public data class LikeComment( + override val postKey: MicroBlogKey, + val liked: Boolean, + val count: Long = 0, + val accountKey: MicroBlogKey, + ) : VVO + + @Serializable + public data class Favorite( + override val postKey: MicroBlogKey, + val favorited: Boolean, + val accountKey: MicroBlogKey, + ) : VVO + } + + @Serializable + public sealed interface Nostr : PostEvent { + @Serializable + public data class Repost( + override val postKey: MicroBlogKey, + val repostEventId: String?, + val count: Long = 0, + val accountKey: MicroBlogKey, + ) : Nostr + + @Serializable + public data class Like( + override val postKey: MicroBlogKey, + val reactionEventId: String?, + val count: Long = 0, + val accountKey: MicroBlogKey, + ) : Nostr + + @Serializable + public data class Report( + override val postKey: MicroBlogKey, + val accountKey: MicroBlogKey, + ) : Nostr + } +} diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/TranslationDisplayState.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/TranslationDisplayState.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/TranslationDisplayState.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/TranslationDisplayState.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiCard.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiCard.kt similarity index 84% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiCard.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiCard.kt index 87722e36b3..6d9820b7ed 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiCard.kt +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiCard.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable @Serializable @Immutable -public data class UiCard internal constructor( +public data class UiCard( val title: String, val description: String?, val media: UiMedia?, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiDMRoom.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiDMRoom.kt similarity index 94% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiDMRoom.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiDMRoom.kt index 4b863cb3f5..6b128b4f27 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiDMRoom.kt +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiDMRoom.kt @@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable @Serializable @Immutable -public data class UiDMRoom internal constructor( +public data class UiDMRoom public constructor( val key: MicroBlogKey, val users: SerializableImmutableList, val lastMessage: UiDMItem?, @@ -34,7 +34,7 @@ public data class UiDMRoom internal constructor( @Serializable @Immutable -public data class UiDMItem internal constructor( +public data class UiDMItem public constructor( val key: MicroBlogKey, val user: UiProfile, val content: Message, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiEmoji.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiEmoji.kt similarity index 90% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiEmoji.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiEmoji.kt index 643e2e22c3..7f02e55429 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiEmoji.kt +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiEmoji.kt @@ -8,7 +8,7 @@ import kotlinx.serialization.Serializable @Serializable @Immutable -public data class UiEmoji internal constructor( +public data class UiEmoji( val shortcode: String, val url: String, val category: String, @@ -18,7 +18,7 @@ public data class UiEmoji internal constructor( // compatibility class for Kotlin native @Immutable -public data class EmojiData internal constructor( +public data class EmojiData( val data: SerializableImmutableMap>, val accountType: AccountType, ) { diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiHandle.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiHandle.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiHandle.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiHandle.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiHashtag.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiHashtag.kt similarity index 78% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiHashtag.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiHashtag.kt index bab8ae2217..5513b27df8 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiHashtag.kt +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiHashtag.kt @@ -3,7 +3,7 @@ package dev.dimension.flare.ui.model import androidx.compose.runtime.Immutable @Immutable -public data class UiHashtag internal constructor( +public data class UiHashtag( val hashtag: String, val description: String?, val searchContent: String, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiInstance.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiInstance.kt similarity index 86% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiInstance.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiInstance.kt index a20fd3bf12..699fca5b9a 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiInstance.kt +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiInstance.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.Immutable import dev.dimension.flare.model.PlatformType @Immutable -public data class UiInstance internal constructor( +public data class UiInstance( val name: String, val description: String?, val iconUrl: String?, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiInstanceMetadata.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiInstanceMetadata.kt similarity index 95% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiInstanceMetadata.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiInstanceMetadata.kt index 72524dee36..929104a696 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiInstanceMetadata.kt +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiInstanceMetadata.kt @@ -5,7 +5,7 @@ import dev.dimension.flare.common.SerializableImmutableList import dev.dimension.flare.common.SerializableImmutableMap @Immutable -public data class UiInstanceMetadata internal constructor( +public data class UiInstanceMetadata( val instance: UiInstance, val rules: SerializableImmutableMap, val configuration: Configuration, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiList.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiList.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiList.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiList.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiMedia.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiMedia.kt similarity index 89% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiMedia.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiMedia.kt index 99ec52fc1a..676b474b34 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiMedia.kt +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiMedia.kt @@ -2,7 +2,7 @@ package dev.dimension.flare.ui.model import androidx.compose.runtime.Immutable import dev.dimension.flare.common.SerializableImmutableMap -import dev.dimension.flare.common.sanitizeFileName +import dev.dimension.flare.data.io.sanitizeFileName import kotlinx.serialization.Serializable @Serializable @@ -14,7 +14,7 @@ public sealed interface UiMedia { @Serializable @Immutable - public data class Image internal constructor( + public data class Image( override val url: String, val previewUrl: String, override val description: String?, @@ -23,7 +23,7 @@ public sealed interface UiMedia { val sensitive: Boolean, override val customHeaders: SerializableImmutableMap? = null, ) : UiMedia { - internal constructor(url: String, customHeaders: SerializableImmutableMap? = null) : this( + public constructor(url: String, customHeaders: SerializableImmutableMap? = null) : this( url = url, previewUrl = url, description = null, @@ -39,7 +39,7 @@ public sealed interface UiMedia { @Serializable @Immutable - public data class Video internal constructor( + public data class Video( override val url: String, val thumbnailUrl: String, override val description: String?, @@ -53,7 +53,7 @@ public sealed interface UiMedia { @Serializable @Immutable - public data class Gif internal constructor( + public data class Gif( override val url: String, val previewUrl: String, override val description: String?, @@ -67,7 +67,7 @@ public sealed interface UiMedia { @Serializable @Immutable - public data class Audio internal constructor( + public data class Audio( override val url: String, override val description: String?, val previewUrl: String?, diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiPodcast.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiPodcast.kt similarity index 100% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiPodcast.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiPodcast.kt diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiPoll.kt b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiPoll.kt similarity index 92% rename from shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiPoll.kt rename to social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiPoll.kt index 0ecd82c990..04dbdf2417 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiPoll.kt +++ b/social/model/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiPoll.kt @@ -2,7 +2,6 @@ package dev.dimension.flare.ui.model import androidx.compose.runtime.Immutable import dev.dimension.flare.common.SerializableImmutableList -import dev.dimension.flare.data.datasource.microblog.PostEvent import dev.dimension.flare.ui.humanizer.humanizePercentage import dev.dimension.flare.ui.render.UiDateTime import dev.dimension.flare.ui.render.toUi @@ -13,7 +12,7 @@ import kotlin.time.Instant @Serializable @Immutable -public data class UiPoll internal constructor( +public data class UiPoll public constructor( val id: String, val options: SerializableImmutableList