fix(posts): ensure synchronization between post list and detail

This commit is contained in:
Diego Beraldin 2023-08-03 23:43:41 +02:00
parent 1440fd6390
commit 1987015cee
23 changed files with 188 additions and 222 deletions

View File

@ -1,39 +0,0 @@
Pod::Spec.new do |spec|
spec.name = 'core_appearance'
spec.version = '1.0.0'
spec.homepage = 'Link to the Shared Module homepage'
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = 'Some description for the Shared Module'
spec.vendored_frameworks = 'build/cocoapods/framework/core-appearance.framework'
spec.libraries = 'c++'
spec.ios.deployment_target = '14.1'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':core-appearance',
'PRODUCT_MODULE_NAME' => 'core-appearance',
}
spec.script_phases = [
{
:name => 'Build core_appearance',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="$PODS_TARGET_SRCROOT"
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="$ARCHS" \
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
SCRIPT
}
]
end

View File

@ -1,39 +0,0 @@
Pod::Spec.new do |spec|
spec.name = 'core_architecture'
spec.version = '1.0'
spec.homepage = 'Link to the Shared Module homepage'
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = 'Some description for the Shared Module'
spec.vendored_frameworks = 'build/cocoapods/framework/core-architecture.framework'
spec.libraries = 'c++'
spec.ios.deployment_target = '14.1'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':core-architecture',
'PRODUCT_MODULE_NAME' => 'core-architecture',
}
spec.script_phases = [
{
:name => 'Build core_architecture',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="$PODS_TARGET_SRCROOT"
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="$ARCHS" \
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
SCRIPT
}
]
end

View File

@ -51,9 +51,12 @@ kotlin {
implementation(projects.coreArchitecture)
implementation(projects.corePreferences)
implementation(projects.coreMd)
implementation(projects.coreNotifications)
implementation(projects.domainLemmy.data)
implementation(projects.domainLemmy.repository)
implementation(projects.domainIdentity)
implementation(projects.resources)
}
}

View File

@ -1,39 +0,0 @@
Pod::Spec.new do |spec|
spec.name = 'core_commonui'
spec.version = '1.0'
spec.homepage = 'Link to the Shared Module homepage'
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = 'Some description for the Shared Module'
spec.vendored_frameworks = 'build/cocoapods/framework/core-commonui.framework'
spec.libraries = 'c++'
spec.ios.deployment_target = '14.1'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':core-commonui',
'PRODUCT_MODULE_NAME' => 'core-commonui',
}
spec.script_phases = [
{
:name => 'Build core_commonui',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="$PODS_TARGET_SRCROOT"
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="$ARCHS" \
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
SCRIPT
}
]
end

View File

@ -14,6 +14,7 @@ val postDetailModule = module {
postsRepository = get(),
commentRepository = get(),
keyStore = get(),
notificationCenter = get(),
)
}
}

View File

@ -3,6 +3,7 @@ package com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail
import cafe.adriel.voyager.core.model.ScreenModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.KeyStoreKeys
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
@ -22,6 +23,7 @@ class PostDetailScreenViewModel(
private val postsRepository: PostsRepository,
private val commentRepository: CommentRepository,
private val keyStore: TemporaryKeyStore,
private val notificationCenter: NotificationCenter,
) : MviModel<PostDetailScreenMviModel.Intent, PostDetailScreenMviModel.UiState, PostDetailScreenMviModel.Effect> by mvi,
ScreenModel {
private var currentPage: Int = 1
@ -117,6 +119,7 @@ class PostDetailScreenViewModel(
voted = value,
)
mvi.updateState { it.copy(post = newPost) }
notificationCenter.send(NotificationCenter.Event.PostUpdate(newPost))
}
}
@ -129,6 +132,7 @@ class PostDetailScreenViewModel(
downVoted = value,
)
mvi.updateState { it.copy(post = newPost) }
notificationCenter.send(NotificationCenter.Event.PostUpdate(newPost))
}
}
@ -141,6 +145,7 @@ class PostDetailScreenViewModel(
saved = value,
)
mvi.updateState { it.copy(post = newPost) }
notificationCenter.send(NotificationCenter.Event.PostUpdate(newPost))
}
}
@ -163,6 +168,7 @@ class PostDetailScreenViewModel(
},
)
}
notificationCenter.send(NotificationCenter.Event.CommentUpdate(newComment))
}
}
@ -185,6 +191,7 @@ class PostDetailScreenViewModel(
},
)
}
notificationCenter.send(NotificationCenter.Event.CommentUpdate(newComment))
}
}
@ -207,6 +214,7 @@ class PostDetailScreenViewModel(
},
)
}
notificationCenter.send(NotificationCenter.Event.CommentUpdate(newComment))
}
}
}

View File

@ -0,0 +1,56 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.library)
alias(libs.plugins.compose)
alias(libs.plugins.native.cocoapods)
}
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
kotlin {
targetHierarchy.default()
android {
compilations.all {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
iosX64()
iosArm64()
iosSimulatorArm64()
cocoapods {
summary = "Some description for the Shared Module"
homepage = "Link to the Shared Module homepage"
version = "1.0"
ios.deploymentTarget = "14.1"
framework {
baseName = "core-notifications"
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(libs.koin.core)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(projects.domainLemmy.data)
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
}
}
android {
namespace = "com.github.diegoberaldin.raccoonforlemmy.core.notifications"
compileSdk = 33
defaultConfig {
minSdk = 26
}
}

View File

@ -1,29 +1,33 @@
Pod::Spec.new do |spec|
spec.name = 'domain_identity'
spec.name = 'core-notifications'
spec.version = '1.0'
spec.homepage = 'Link to the Shared Module homepage'
spec.source = { :http=> ''}
spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" }
spec.authors = ''
spec.license = ''
spec.summary = 'Some description for the Shared Module'
spec.vendored_frameworks = 'build/cocoapods/framework/domain-identity.framework'
spec.libraries = 'c++'
spec.vendored_frameworks = "build/cocoapods/framework/core-notifications.framework"
spec.libraries = "c++"
spec.module_name = "#{spec.name}_umbrella"
spec.ios.deployment_target = '14.1'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':domain-identity',
'PRODUCT_MODULE_NAME' => 'domain-identity',
'KOTLIN_PROJECT_PATH' => ':core-notifications',
'PRODUCT_MODULE_NAME' => 'core-notifications',
}
spec.script_phases = [
{
:name => 'Build domain_identity',
:name => 'Build core-notifications',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
if [ "YES" = "$COCOAPODS_SKIP_KOTLIN_BUILD" ]; then
echo "Skipping Gradle build task invocation due to COCOAPODS_SKIP_KOTLIN_BUILD environment variable set to \"YES\""
exit 0
fi
set -ev
@ -31,9 +35,8 @@ Pod::Spec.new do |spec|
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="$ARCHS" \
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
-Pkotlin.native.cocoapods.configuration=$CONFIGURATION
SCRIPT
}
]
end

View File

@ -1,24 +1,24 @@
Pod::Spec.new do |spec|
spec.name = 'core_api'
spec.name = 'core_notifications'
spec.version = '1.0'
spec.homepage = 'Link to the Shared Module homepage'
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = 'Some description for the Shared Module'
spec.vendored_frameworks = 'build/cocoapods/framework/core-api.framework'
spec.vendored_frameworks = 'build/cocoapods/framework/core-notifications.framework'
spec.libraries = 'c++'
spec.ios.deployment_target = '14.1'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':core-api',
'PRODUCT_MODULE_NAME' => 'core-api',
'KOTLIN_PROJECT_PATH' => ':core-notifications',
'PRODUCT_MODULE_NAME' => 'core-notifications',
}
spec.script_phases = [
{
:name => 'Build core_api',
:name => 'Build core_notifications',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT

View File

@ -0,0 +1,19 @@
package com.github.diegoberaldin.raccoonforlemmy.core.notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
object DefaultNotificationCenter : NotificationCenter {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
override val events = MutableSharedFlow<NotificationCenter.Event>()
override fun send(event: NotificationCenter.Event) {
scope.launch {
events.emit(event)
}
}
}

View File

@ -0,0 +1,31 @@
package com.github.diegoberaldin.raccoonforlemmy.core.notifications
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import kotlinx.coroutines.flow.SharedFlow
/**
* Utility to publish and subscribe for broadcast notifications.
*/
interface NotificationCenter {
/**
* Available event types.
*/
sealed interface Event {
data class PostUpdate(val post: PostModel) : Event
data class CommentUpdate(val comment: CommentModel) : Event
}
/**
* Observable event flow
*/
val events: SharedFlow<Event>
/**
* Publish and event to subscribers.
*
* @param event Event to send
*/
fun send(event: Event)
}

View File

@ -0,0 +1,11 @@
package com.github.diegoberaldin.raccoonforlemmy.core.notifications.di
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.DefaultNotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import org.koin.dsl.module
val coreNotificationModule = module {
single<NotificationCenter> {
DefaultNotificationCenter
}
}

View File

@ -1,39 +0,0 @@
Pod::Spec.new do |spec|
spec.name = 'core_preferences'
spec.version = '1.0.0'
spec.homepage = 'Link to the Shared Module homepage'
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = 'Some description for the Shared Module'
spec.vendored_frameworks = 'build/cocoapods/framework/core-preferences.framework'
spec.libraries = 'c++'
spec.ios.deployment_target = '14.1'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':core-preferences',
'PRODUCT_MODULE_NAME' => 'core-preferences',
}
spec.script_phases = [
{
:name => 'Build core_preferences',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="$PODS_TARGET_SRCROOT"
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="$ARCHS" \
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
SCRIPT
}
]
end

View File

@ -1,39 +0,0 @@
Pod::Spec.new do |spec|
spec.name = 'core_utils'
spec.version = '1.0.0'
spec.homepage = 'Link to the Shared Module homepage'
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = 'Some description for the Shared Module'
spec.vendored_frameworks = 'build/cocoapods/framework/core-utils.framework'
spec.libraries = 'c++'
spec.ios.deployment_target = '14.1'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':core-utils',
'PRODUCT_MODULE_NAME' => 'core-utils',
}
spec.script_phases = [
{
:name => 'Build core_utils',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="$PODS_TARGET_SRCROOT"
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="$ARCHS" \
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
SCRIPT
}
]
end

View File

@ -48,15 +48,18 @@ kotlin {
implementation(libs.voyager.bottomsheet)
implementation(libs.kamel)
implementation(projects.resources)
implementation(projects.coreAppearance)
implementation(projects.coreArchitecture)
implementation(projects.corePreferences)
implementation(projects.coreUtils)
implementation(projects.coreCommonui)
implementation(projects.coreNotifications)
implementation(projects.domainIdentity)
implementation(projects.domainLemmy.data)
implementation(projects.domainLemmy.repository)
implementation(projects.resources)
}
}
val commonTest by getting {

View File

@ -19,6 +19,7 @@ val homeTabModule = module {
apiConfigRepository = get(),
identityRepository = get(),
keyStore = get(),
notificationCenter = get(),
)
}
}

View File

@ -3,6 +3,7 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.home.viewmodel
import cafe.adriel.voyager.core.model.ScreenModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.KeyStoreKeys
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository
@ -15,6 +16,7 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toSortType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostsRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@ -26,6 +28,7 @@ class HomeScreenModel(
private val apiConfigRepository: ApiConfigurationRepository,
private val identityRepository: IdentityRepository,
private val keyStore: TemporaryKeyStore,
private val notificationCenter: NotificationCenter,
) : ScreenModel,
MviModel<HomeScreenMviModel.Intent, HomeScreenMviModel.UiState, HomeScreenMviModel.Effect> by mvi {
@ -54,11 +57,30 @@ class HomeScreenModel(
sortType = sortType,
)
}
identityRepository.authToken.map { !it.isNullOrEmpty() }.onEach { isLogged ->
mvi.updateState {
it.copy(isLogged = isLogged)
}
}.launchIn(mvi.scope)
mvi.scope.launch {
identityRepository.authToken.map { !it.isNullOrEmpty() }.onEach { isLogged ->
mvi.updateState {
it.copy(isLogged = isLogged)
}
}.launchIn(this)
notificationCenter.events.filterIsInstance<NotificationCenter.Event.PostUpdate>()
.onEach { evt ->
val newPost = evt.post
mvi.updateState {
it.copy(
posts = it.posts.map { p ->
if (p.id == newPost.id) {
newPost
} else {
p
}
},
)
}
}.launchIn(this)
}
refresh()
}

View File

@ -1,4 +1,3 @@
# Acknowledgements
This application makes use of the following third party libraries:
Generated by CocoaPods - https://cocoapods.org

View File

@ -1,7 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_Pods_iosApp : NSObject
@end
@implementation PodsDummy_Pods_iosApp
@end

View File

@ -38,3 +38,4 @@ include(":domain-lemmy:data")
include(":domain-identity")
include(":core-md")
include(":core-commonui")
include(":core-notifications")

View File

@ -59,6 +59,7 @@ kotlin {
implementation(projects.coreAppearance)
implementation(projects.corePreferences)
implementation(projects.coreApi)
implementation(projects.coreNotifications)
implementation(projects.domainIdentity)
api(projects.resources)

View File

@ -2,6 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy
import com.github.diegoberaldin.raccoonforlemmy.core.api.di.coreApiModule
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.di.coreAppearanceModule
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.coreNotificationModule
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.di.corePreferencesModule
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.di.coreIdentityModule
import com.github.diegoberaldin.raccoonforlemmy.feature.home.di.homeTabModule
@ -18,6 +19,7 @@ val sharedHelperModule = module {
corePreferencesModule,
coreApiModule,
coreIdentityModule,
coreNotificationModule,
localizationModule,
homeTabModule,
inboxTabModule,

View File

@ -2,6 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy
import com.github.diegoberaldin.raccoonforlemmy.core.api.di.coreApiModule
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.di.coreAppearanceModule
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.coreNotificationModule
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.di.corePreferencesModule
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.di.coreIdentityModule
import com.github.diegoberaldin.raccoonforlemmy.feature.home.di.homeTabModule
@ -19,6 +20,7 @@ fun initKoin() {
corePreferencesModule,
coreApiModule,
coreIdentityModule,
coreNotificationModule,
localizationModule,
homeTabModule,
inboxTabModule,