Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
d8d1ff0
version 1.5.0-beta01
Tlaster May 15, 2026
f9238a5
fix desktop build
Tlaster May 15, 2026
7afd649
fix remove last
Tlaster May 15, 2026
1f21249
fix ios group edit not show in list
Tlaster May 21, 2026
92d3505
fix filter not work in repost
Tlaster May 21, 2026
b2ddf29
fix ios timeline paging placeholders
Tlaster May 21, 2026
f212ed6
refactor platform specs and registry
Tlaster May 21, 2026
83cc5e4
remove legacy tab presenter bindings
Tlaster May 21, 2026
7dafc27
decouple legacy tab migration specs
Tlaster May 22, 2026
5fd350b
refactor account credentials into platform context
Tlaster May 22, 2026
c580063
remove openai words
Tlaster May 22, 2026
1dcb41c
version 1.5.0
Tlaster May 22, 2026
07577fa
add change log
Tlaster May 22, 2026
7aba2d9
fix reply filtering
Tlaster May 23, 2026
cd71f0a
fix ios richtext font
Tlaster May 23, 2026
3303c69
fix shortcut compose activity crash
Tlaster May 23, 2026
229cc3d
update tab icons
Tlaster May 24, 2026
1e3e0ef
update repost text
Tlaster May 24, 2026
f1d8ac9
fix deeplink not opened when cold start
Tlaster May 24, 2026
39a617c
add bsky.app mapping for non bsky.social site
Tlaster May 24, 2026
6afd024
Handle follow requests for locked profiles
Tlaster May 25, 2026
3899f55
Update RSS detail backgrounds
Tlaster May 25, 2026
c9d180b
Fix repeated Bluesky thread parents
Tlaster May 25, 2026
b2824fd
Fix VVO notification filter paging keys
Tlaster May 25, 2026
f6c855e
remove data model from datasource and spces
Tlaster May 25, 2026
92b7c52
add key to profile media
Tlaster May 25, 2026
20ee5f5
add more icons for ios
Tlaster May 25, 2026
40dbd09
fix detail parents
Tlaster May 26, 2026
6d328b3
fix splash background color
Tlaster May 26, 2026
75ba5e6
fix vvo image preview size
Tlaster May 26, 2026
acb63dc
Extract VVO social module
Tlaster May 26, 2026
9d36927
Extract XQT social module
Tlaster May 26, 2026
770a996
Extract Bluesky social module
Tlaster May 26, 2026
a39e6c2
Move recommended instances into platform specs
Tlaster May 27, 2026
0e6e1c6
fix lint
Tlaster May 27, 2026
210b443
Migrate subscription feature module
Tlaster May 27, 2026
ff0616b
Extract Mastodon social module
Tlaster May 27, 2026
8e7c9d1
Extract Misskey social module
Tlaster May 27, 2026
af6a592
Migrate nostr support to social module
Tlaster May 27, 2026
504c90f
migrate to koin.compiler
Tlaster May 27, 2026
4c59100
fix test
Tlaster May 27, 2026
8cac72a
make ios about native
Tlaster May 27, 2026
acddd33
fix bottom sheet crash
Tlaster May 27, 2026
fa2b817
fix ios build
Tlaster May 27, 2026
0e72451
Merge branch 'version/1.5.0-beta01' into feature/social-modular
Tlaster May 28, 2026
1b21eee
rewrite ios login screen
Tlaster May 28, 2026
899eb69
remove compose-ui timeline
Tlaster May 28, 2026
fc6f80b
rewrite ios dm to native
Tlaster May 28, 2026
94a0b8f
fix test
Tlaster May 28, 2026
decdc2f
remove compose ui dependency on ios
Tlaster May 28, 2026
fb48ad2
remove hiddenfromobjc in compose-ui
Tlaster May 28, 2026
be6f6d4
add web target
Tlaster May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,4 @@ jobs:
sed -i -e '/START Non-FOSS component/,/END Non-FOSS component/d' app/build.gradle.kts

- name: Build F-Droid variant
run: ./gradlew :app:assembleRelease :app:check :app:lint --stacktrace
run: ./gradlew :app:assembleRelease :app:check :app:lint --stacktrace
11 changes: 11 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ktorfit)
alias(libs.plugins.koin.compiler)
alias(libs.plugins.google.services) apply false
// START Non-FOSS component
alias(libs.plugins.firebase.crashlytics) apply false
Expand Down Expand Up @@ -105,6 +106,7 @@ dependencies {
implementation(libs.bundles.kotlinx)
implementation(platform(libs.koin.bom))
implementation(libs.bundles.koin)
implementation(libs.koin.annotations)
implementation(libs.ktorfit.lib)
ksp(libs.ktorfit.ksp)
implementation(libs.bundles.coil3)
Expand All @@ -124,6 +126,15 @@ dependencies {
coreLibraryDesugaring(libs.desugar.jdk.libs)
implementation(libs.compose.webview)
implementation(projects.shared)
implementation(projects.social.bluesky)
implementation(projects.social.mastodon)
implementation(projects.social.misskey)
implementation(projects.social.nostr)
implementation(projects.social.vvo)
implementation(projects.social.xqt)
implementation(projects.feature.login)
implementation(projects.feature.subscription)
implementation(projects.feature.tab)
implementation(projects.composeUi)
implementation(libs.androidx.splash)
implementation(libs.materialKolor)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package dev.dimension.flare.common

import android.content.Context
import org.koin.core.annotation.Provided
import org.koin.core.annotation.Single

@Single(binds = [OnDeviceAI::class])
internal class FossOnDeviceAI(
private val context: Context,
@Provided private val context: Context,
) : OnDeviceAI {
override suspend fun isAvailable(): Boolean = false

Expand Down
10 changes: 0 additions & 10 deletions app/src/foss/java/dev/dimension/flare/di/AiModule.kt

This file was deleted.

9 changes: 3 additions & 6 deletions app/src/main/java/dev/dimension/flare/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,17 @@ import coil3.video.VideoFrameDecoder
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 dev.dimension.flare.di.AndroidKoinApplication
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import org.koin.plugin.module.dsl.startKoin

class App :
Application(),
SingletonImageLoader.Factory {
override fun onCreate() {
super.onCreate()
startKoin {
startKoin<AndroidKoinApplication> {
androidContext(this@App)
modules(KoinHelper.modules() + androidModule + aiModule)
}
}

Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/dev/dimension/flare/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.dimension.flare

import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
Expand Down Expand Up @@ -52,6 +53,11 @@ class MainActivity : ComponentActivity() {
}
}

override fun onNewIntent(intent: Intent) {
setIntent(intent)
super.onNewIntent(intent)
}

override fun onResume() {
super.onResume()
videoDownloadHelper.onResume()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import dev.dimension.flare.R
import dev.dimension.flare.data.repository.LoginExpiredException
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.koin.core.annotation.Single

internal sealed interface Notification {
data class Progress(
Expand All @@ -24,6 +25,7 @@ internal sealed interface Notification {
) : Notification
}

@Single(binds = [InAppNotification::class])
internal class ComposeInAppNotification : InAppNotification {
private val _source = MutableStateFlow(Event<Notification>(null, initialHandled = true))
val source
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ import dev.dimension.flare.ui.model.UiPodcast
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import org.koin.core.annotation.Provided
import org.koin.core.annotation.Single

@Stable
@Single
internal class PodcastManager(
private val context: Context,
@Provided private val context: Context,
) : AutoCloseable {
private var currentController: MediaController? = null
private val _currentPodcast = MutableStateFlow<UiPodcast?>(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ import android.webkit.MimeTypeMap
import androidx.compose.runtime.Stable
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import org.koin.core.annotation.Provided
import org.koin.core.annotation.Single

/**
* Download Manager Helper - Simplified Version
* Encapsulates Android's DownloadManager, provides simple video download functionality
*/
@Stable
@Single
internal class VideoDownloadHelper(
private val context: Context,
@Provided private val context: Context,
) {
companion object {
private const val TAG = "VideoDownloadHelper"
Expand Down
38 changes: 38 additions & 0 deletions app/src/main/java/dev/dimension/flare/di/AndroidKoinApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package dev.dimension.flare.di

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.RssTimelineSpecs
import dev.dimension.flare.data.platform.VvoPlatformSpec
import dev.dimension.flare.data.platform.XqtPlatformSpec
import dev.dimension.flare.model.PlatformRuntimeData
import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Configuration
import org.koin.core.annotation.KoinApplication
import org.koin.core.annotation.Module
import org.koin.core.annotation.Single

@KoinApplication
internal class AndroidKoinApplication

@Module
@Configuration
@ComponentScan("dev.dimension.flare.common", "dev.dimension.flare.di")
internal class AndroidKoinModule

@Single
internal fun runtimeData(): PlatformRuntimeData =
PlatformRuntimeData(
platformSpecs =
listOf(
NostrPlatformSpec,
MastodonPlatformSpec,
MisskeyPlatformSpec,
BlueskyPlatformSpec,
XqtPlatformSpec,
VvoPlatformSpec,
),
extraTimelineSpecs = RssTimelineSpecs.timelineSpecs,
)
20 changes: 0 additions & 20 deletions app/src/main/java/dev/dimension/flare/di/AndroidModule.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,31 @@ private class BottomSheetScene<T : Any>(
override suspend fun onRemove() {
sheetState.hide()
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as BottomSheetScene<*>

// Compare by contentKey values rather than NavEntry object identity (content ===).
// NavEntry wrappers are recreated with new closure instances on every recomposition
// (the lambda is passed to a constructor, not a @Composable function, so it is never
// a stable ComposableLambda). Using object identity would cause false inequality,
// causing LaunchedEffect(overlayScenes) to add a new scene on every recomposition
// while the old one is still animating → duplicate ModalBottomSheet dialogs sharing
// the same SaveableStateProvider key → crash.
return key == other.key &&
previousEntries.map { it.contentKey } == other.previousEntries.map { it.contentKey } &&
overlaidEntries.map { it.contentKey } == other.overlaidEntries.map { it.contentKey } &&
entry.contentKey == other.entry.contentKey
}

override fun hashCode(): Int =
key.hashCode() * 31 +
previousEntries.map { it.contentKey }.hashCode() * 31 +
overlaidEntries.map { it.contentKey }.hashCode() * 31 +
entry.contentKey.hashCode()
}

@OptIn(ExperimentalMaterial3Api::class)
Expand Down
10 changes: 5 additions & 5 deletions app/src/main/java/dev/dimension/flare/ui/route/Route.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ 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.SourceTimelineTabItemV2
import dev.dimension.flare.data.model.tab.TimelineTabItemV2
import dev.dimension.flare.data.model.tab.xqtDeviceFollow
import dev.dimension.flare.model.AccountType
import dev.dimension.flare.model.MicroBlogKey
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.serialization.Serializable

@Immutable
@Serializable
internal sealed interface Route : NavKey {
@Serializable
sealed interface Status : Route {
Expand Down Expand Up @@ -174,9 +175,8 @@ internal sealed interface Route : NavKey {
@Serializable
data object Home : Route

@Serializable
data class Timeline(
val source: TimelineSourceRef,
val tabItem: TimelineTabItemV2,
) : Route

@Serializable
Expand Down Expand Up @@ -470,7 +470,7 @@ internal sealed interface Route : NavKey {
(deeplinkRoute.accountType as? AccountType.Specific)?.accountKey
?: return null
Route.Timeline(
source = TimelineSourceRef.xqtDeviceFollow(accountKey),
tabItem = SourceTimelineTabItemV2.xqtDeviceFollow(accountKey),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.ui.ComposeUiFlags
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.core.content.IntentCompat
import dev.dimension.flare.ui.FlareApp
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList

class ShortcutComposeActivity : ComponentActivity() {
@OptIn(ExperimentalComposeUiApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
ComposeUiFlags.isMediaQueryIntegrationEnabled = true
super.onCreate(savedInstanceState)
val initialText =
when {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal fun EntryProviderScope<NavKey>.homeEntryBuilder(
}
entry<Route.Timeline> { args ->
TimelineScreen(
source = args.source,
tabItem = args.tabItem,
onBack = onBack,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ import compose.icons.fontawesomeicons.solid.Pen
import compose.icons.fontawesomeicons.solid.PenToSquare
import compose.icons.fontawesomeicons.solid.SquareRss
import dev.dimension.flare.R
import dev.dimension.flare.data.model.tab.SourceTimelineTabItemV2
import dev.dimension.flare.model.AccountType
import dev.dimension.flare.ui.common.OnNewIntent
import dev.dimension.flare.ui.component.AvatarComponent
Expand Down Expand Up @@ -485,7 +484,7 @@ private fun getDirection(data: SecondaryTabsPresenter.Tab): Route? =
}

is SecondaryTabsPresenter.Destination.Timeline -> {
(target.tabItem as? SourceTimelineTabItemV2)?.source?.let { Route.Timeline(it) }
Route.Timeline(target.tabItem)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import compose.icons.fontawesomeicons.Solid
import compose.icons.fontawesomeicons.solid.Sliders
import dev.dimension.flare.R
import dev.dimension.flare.data.model.BottomBarBehavior
import dev.dimension.flare.data.model.tab.TimelineResolver
import dev.dimension.flare.data.model.tab.TimelineSourceRef
import dev.dimension.flare.data.model.tab.TimelineTabItemV2
import dev.dimension.flare.data.model.tab.resolveTimelineAppearance
import dev.dimension.flare.ui.component.BackButton
Expand All @@ -41,7 +39,6 @@ import dev.dimension.flare.ui.presenter.home.LoggedInState
import dev.dimension.flare.ui.presenter.invoke
import kotlinx.coroutines.launch
import moe.tlaster.precompose.molecule.producePresenter
import org.koin.compose.koinInject

@OptIn(ExperimentalMaterial3Api::class)
@Composable
Expand Down Expand Up @@ -130,23 +127,6 @@ internal fun DeckTimelineScreen(
}
}

@Composable
internal fun TimelineScreen(
source: TimelineSourceRef,
onBack: () -> Unit,
) {
val state by producePresenter("timeline-source:${source.id}") {
val resolver: TimelineResolver = koinInject()
remember {
resolver.toTabItem(source)
}
}
TimelineScreen(
tabItem = state,
onBack = onBack,
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun TimelineScreen(
Expand Down
Loading
Loading