mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-09 10:28:41 +01:00
feat: multi-communities (#36)
* chore: add persistence * chore: move serializable to core-utils * fix: leftover color in user detail * chore: new messages * feat: add multi-communities in subscription list * feat: add multi-community editor * feat: add multi-community detail
This commit is contained in:
parent
8414cf5019
commit
abc9b3632a
@ -0,0 +1,72 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MultiCommunityItem(
|
||||||
|
community: MultiCommunityModel,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val title = community.name
|
||||||
|
val communityIcon = community.icon.orEmpty()
|
||||||
|
val iconSize = 30.dp
|
||||||
|
Row(
|
||||||
|
modifier = modifier.padding(
|
||||||
|
vertical = Spacing.xs,
|
||||||
|
horizontal = Spacing.s,
|
||||||
|
),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||||
|
) {
|
||||||
|
if (communityIcon.isNotEmpty()) {
|
||||||
|
CustomImage(
|
||||||
|
modifier = Modifier.padding(Spacing.xxxs).size(iconSize)
|
||||||
|
.clip(RoundedCornerShape(iconSize / 2)),
|
||||||
|
url = communityIcon,
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.FillBounds,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(Spacing.xxxs).size(iconSize)
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
shape = RoundedCornerShape(iconSize / 2),
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title.firstOrNull()?.toString().orEmpty().uppercase(),
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(vertical = Spacing.s),
|
||||||
|
text = buildString {
|
||||||
|
append(title)
|
||||||
|
},
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -438,7 +438,7 @@ class UserDetailScreen(
|
|||||||
},
|
},
|
||||||
backgroundColor = {
|
backgroundColor = {
|
||||||
when (it) {
|
when (it) {
|
||||||
DismissValue.DismissedToStart -> MaterialTheme.colorScheme.secondary
|
DismissValue.DismissedToStart -> MaterialTheme.colorScheme.surfaceTint
|
||||||
DismissValue.DismissedToEnd -> MaterialTheme.colorScheme.tertiary
|
DismissValue.DismissedToEnd -> MaterialTheme.colorScheme.tertiary
|
||||||
else -> Color.Transparent
|
else -> Color.Transparent
|
||||||
}
|
}
|
||||||
|
@ -15,4 +15,5 @@ object NotificationCenterContractKeys {
|
|||||||
const val PostUpdated = "postUpdated"
|
const val PostUpdated = "postUpdated"
|
||||||
const val PostDeleted = "postDeleted"
|
const val PostDeleted = "postDeleted"
|
||||||
const val ChangeColor = "changeColor"
|
const val ChangeColor = "changeColor"
|
||||||
|
const val MultiCommunityCreated = "multiCommunityCreated"
|
||||||
}
|
}
|
@ -48,6 +48,7 @@ kotlin {
|
|||||||
implementation(libs.koin.core)
|
implementation(libs.koin.core)
|
||||||
|
|
||||||
implementation(projects.corePreferences)
|
implementation(projects.corePreferences)
|
||||||
|
implementation(projects.coreUtils)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data
|
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
|
||||||
|
|
||||||
data class AccountModel(
|
data class AccountModel(
|
||||||
val id: Long? = null,
|
val id: Long? = null,
|
||||||
val username: String,
|
val username: String,
|
||||||
@ -7,4 +9,4 @@ data class AccountModel(
|
|||||||
val instance: String,
|
val instance: String,
|
||||||
val jwt: String,
|
val jwt: String,
|
||||||
val active: Boolean = false,
|
val active: Boolean = false,
|
||||||
)
|
) : JavaSerializable
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
|
||||||
|
|
||||||
|
data class MultiCommunityModel(
|
||||||
|
val id: Long? = null,
|
||||||
|
val name: String = "",
|
||||||
|
val communityIds: List<Int> = emptyList(),
|
||||||
|
val icon: String? = null,
|
||||||
|
) : JavaSerializable
|
@ -1,5 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data
|
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
|
||||||
|
|
||||||
data class SettingsModel(
|
data class SettingsModel(
|
||||||
val id: Long? = null,
|
val id: Long? = null,
|
||||||
val theme: Int? = null,
|
val theme: Int? = null,
|
||||||
@ -16,4 +18,4 @@ data class SettingsModel(
|
|||||||
val enableSwipeActions: Boolean = true,
|
val enableSwipeActions: Boolean = true,
|
||||||
val customSeedColor: Int? = null,
|
val customSeedColor: Int? = null,
|
||||||
val postLayout: Int = 0,
|
val postLayout: Int = 0,
|
||||||
)
|
) : JavaSerializable
|
||||||
|
@ -4,7 +4,9 @@ import com.github.diegoberaldin.raccoonforlemmy.core.persistence.DatabaseProvide
|
|||||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.DefaultDatabaseProvider
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.DefaultDatabaseProvider
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultAccountRepository
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultAccountRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultMultiCommunityRepository
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultSettingsRepository
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.DefaultSettingsRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.MultiCommunityRepository
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
@ -26,4 +28,9 @@ val corePersistenceModule = module {
|
|||||||
keyStore = get(),
|
keyStore = get(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
single<MultiCommunityRepository> {
|
||||||
|
DefaultMultiCommunityRepository(
|
||||||
|
provider = get()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.DatabaseProvider
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.MultiCommunityEntity
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.IO
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
internal class DefaultMultiCommunityRepository(
|
||||||
|
val provider: DatabaseProvider,
|
||||||
|
) : MultiCommunityRepository {
|
||||||
|
|
||||||
|
private val db = provider.getDatabase()
|
||||||
|
|
||||||
|
override suspend fun getAll(accountId: Long?): List<MultiCommunityModel> =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.multicommunitiesQueries.getAll(accountId)
|
||||||
|
.executeAsList().map { it.toModel() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(model: MultiCommunityModel, accountId: Long): Long =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.multicommunitiesQueries.create(
|
||||||
|
name = model.name,
|
||||||
|
icon = model.icon,
|
||||||
|
communityIds = model.communityIds.joinToString(","),
|
||||||
|
account_id = accountId,
|
||||||
|
)
|
||||||
|
val id = db.multicommunitiesQueries.getBy(name = model.name, account_id = accountId)
|
||||||
|
.executeAsOneOrNull()?.id
|
||||||
|
id ?: 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(model: MultiCommunityModel) =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.multicommunitiesQueries.update(
|
||||||
|
name = model.name,
|
||||||
|
icon = model.icon,
|
||||||
|
communityIds = model.communityIds.joinToString(","),
|
||||||
|
id = model.id ?: 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(model: MultiCommunityModel) =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.multicommunitiesQueries.delete(model.id ?: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MultiCommunityEntity.toModel() = MultiCommunityModel(
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
icon = icon,
|
||||||
|
communityIds = communityIds
|
||||||
|
.split(",")
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.map { it.toInt() }
|
||||||
|
)
|
@ -26,7 +26,7 @@ private object KeyStoreKeys {
|
|||||||
const val PostLayout = "postLayout"
|
const val PostLayout = "postLayout"
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultSettingsRepository(
|
internal class DefaultSettingsRepository(
|
||||||
val provider: DatabaseProvider,
|
val provider: DatabaseProvider,
|
||||||
private val keyStore: TemporaryKeyStore,
|
private val keyStore: TemporaryKeyStore,
|
||||||
) : SettingsRepository {
|
) : SettingsRepository {
|
||||||
@ -37,7 +37,7 @@ class DefaultSettingsRepository(
|
|||||||
|
|
||||||
override suspend fun createSettings(settings: SettingsModel, accountId: Long) =
|
override suspend fun createSettings(settings: SettingsModel, accountId: Long) =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
db.settingsQueries.createSettings(
|
db.settingsQueries.create(
|
||||||
theme = settings.theme?.toLong(),
|
theme = settings.theme?.toLong(),
|
||||||
contentFontScale = settings.contentFontScale.toDouble(),
|
contentFontScale = settings.contentFontScale.toDouble(),
|
||||||
locale = settings.locale,
|
locale = settings.locale,
|
||||||
@ -77,7 +77,7 @@ class DefaultSettingsRepository(
|
|||||||
postLayout = keyStore[KeyStoreKeys.PostLayout, 0],
|
postLayout = keyStore[KeyStoreKeys.PostLayout, 0],
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
db.settingsQueries.getSettings(accountId)
|
db.settingsQueries.getBy(accountId)
|
||||||
.executeAsOneOrNull()?.toModel() ?: SettingsModel()
|
.executeAsOneOrNull()?.toModel() ?: SettingsModel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,7 +110,7 @@ class DefaultSettingsRepository(
|
|||||||
}
|
}
|
||||||
keyStore.save(KeyStoreKeys.PostLayout, settings.postLayout)
|
keyStore.save(KeyStoreKeys.PostLayout, settings.postLayout)
|
||||||
} else {
|
} else {
|
||||||
db.settingsQueries.updateSettings(
|
db.settingsQueries.update(
|
||||||
theme = settings.theme?.toLong(),
|
theme = settings.theme?.toLong(),
|
||||||
contentFontScale = settings.contentFontScale.toDouble(),
|
contentFontScale = settings.contentFontScale.toDouble(),
|
||||||
locale = settings.locale,
|
locale = settings.locale,
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
|
||||||
|
|
||||||
|
interface MultiCommunityRepository {
|
||||||
|
suspend fun getAll(accountId: Long?): List<MultiCommunityModel>
|
||||||
|
|
||||||
|
suspend fun create(model: MultiCommunityModel, accountId: Long): Long
|
||||||
|
|
||||||
|
suspend fun update(model: MultiCommunityModel)
|
||||||
|
|
||||||
|
suspend fun delete(model: MultiCommunityModel)
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE MultiCommunityEntity (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL DEFAULT "",
|
||||||
|
icon TEXT DEFAULT NULL,
|
||||||
|
communityIds TEXT NOT NULL DEFAULT "",
|
||||||
|
account_id INTEGER,
|
||||||
|
FOREIGN KEY (account_id) REFERENCES AccountEntity(id) ON DELETE CASCADE
|
||||||
|
);
|
@ -0,0 +1,44 @@
|
|||||||
|
CREATE TABLE MultiCommunityEntity (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL DEFAULT "",
|
||||||
|
icon TEXT DEFAULT NULL,
|
||||||
|
communityIds TEXT NOT NULL DEFAULT "",
|
||||||
|
account_id INTEGER,
|
||||||
|
FOREIGN KEY (account_id) REFERENCES AccountEntity(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(name, account_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
getAll:
|
||||||
|
SELECT *
|
||||||
|
FROM MultiCommunityEntity
|
||||||
|
WHERE account_id = ?;
|
||||||
|
|
||||||
|
getBy:
|
||||||
|
SELECT *
|
||||||
|
FROM MultiCommunityEntity
|
||||||
|
WHERE name = ? AND account_id = ?;
|
||||||
|
|
||||||
|
create:
|
||||||
|
INSERT OR IGNORE INTO MultiCommunityEntity (
|
||||||
|
name,
|
||||||
|
icon,
|
||||||
|
communityIds,
|
||||||
|
account_id
|
||||||
|
) VALUES (
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?
|
||||||
|
);
|
||||||
|
|
||||||
|
update:
|
||||||
|
UPDATE OR IGNORE MultiCommunityEntity
|
||||||
|
SET
|
||||||
|
name = ?,
|
||||||
|
icon = ?,
|
||||||
|
communityIds = ?
|
||||||
|
WHERE id = ?;
|
||||||
|
|
||||||
|
delete:
|
||||||
|
DELETE FROM MultiCommunityEntity
|
||||||
|
WHERE id = ?;
|
@ -19,7 +19,7 @@ CREATE TABLE SettingsEntity (
|
|||||||
UNIQUE(account_id)
|
UNIQUE(account_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
createSettings:
|
create:
|
||||||
INSERT OR IGNORE INTO SettingsEntity (
|
INSERT OR IGNORE INTO SettingsEntity (
|
||||||
theme,
|
theme,
|
||||||
contentFontScale,
|
contentFontScale,
|
||||||
@ -54,7 +54,7 @@ INSERT OR IGNORE INTO SettingsEntity (
|
|||||||
?
|
?
|
||||||
);
|
);
|
||||||
|
|
||||||
updateSettings:
|
update:
|
||||||
UPDATE SettingsEntity
|
UPDATE SettingsEntity
|
||||||
SET theme = ?,
|
SET theme = ?,
|
||||||
contentFontScale = ?,
|
contentFontScale = ?,
|
||||||
@ -72,7 +72,7 @@ SET theme = ?,
|
|||||||
postLayout = ?
|
postLayout = ?
|
||||||
WHERE account_id = ?;
|
WHERE account_id = ?;
|
||||||
|
|
||||||
getSettings:
|
getBy:
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM SettingsEntity
|
FROM SettingsEntity
|
||||||
WHERE account_id = ?;
|
WHERE account_id = ?;
|
@ -0,0 +1,3 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.core.utils
|
||||||
|
|
||||||
|
actual typealias JavaSerializable = java.io.Serializable
|
@ -0,0 +1,3 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.core.utils
|
||||||
|
|
||||||
|
expect interface JavaSerializable
|
@ -0,0 +1,3 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.core.utils
|
||||||
|
|
||||||
|
actual interface JavaSerializable
|
@ -31,7 +31,9 @@ kotlin {
|
|||||||
implementation(compose.runtime)
|
implementation(compose.runtime)
|
||||||
implementation(compose.foundation)
|
implementation(compose.foundation)
|
||||||
implementation(compose.materialIconsExtended)
|
implementation(compose.materialIconsExtended)
|
||||||
|
|
||||||
implementation(projects.resources)
|
implementation(projects.resources)
|
||||||
|
implementation(projects.coreUtils)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
|
||||||
|
|
||||||
actual typealias JavaSerializable = java.io.Serializable
|
|
@ -1,5 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
|
||||||
|
|
||||||
data class CommentModel(
|
data class CommentModel(
|
||||||
val id: Int = 0,
|
val id: Int = 0,
|
||||||
val postId: Int = 0,
|
val postId: Int = 0,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
|
||||||
|
|
||||||
data class CommunityModel(
|
data class CommunityModel(
|
||||||
val id: Int = 0,
|
val id: Int = 0,
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
|
||||||
|
|
||||||
data class MetadataModel(
|
data class MetadataModel(
|
||||||
val title: String = "",
|
val title: String = "",
|
||||||
val description: String = "",
|
val description: String = "",
|
||||||
): JavaSerializable
|
) : JavaSerializable
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
|
||||||
|
|
||||||
data class PersonMentionModel(
|
data class PersonMentionModel(
|
||||||
val id: Int = 0,
|
val id: Int = 0,
|
||||||
val post: PostModel,
|
val post: PostModel,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
|
||||||
|
|
||||||
data class PostModel(
|
data class PostModel(
|
||||||
val id: Int = 0,
|
val id: Int = 0,
|
||||||
val title: String = "",
|
val title: String = "",
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
|
||||||
|
|
||||||
data class PrivateMessageModel(
|
data class PrivateMessageModel(
|
||||||
val id: Int = 0,
|
val id: Int = 0,
|
||||||
val content: String? = null,
|
val content: String? = null,
|
||||||
@ -8,4 +10,4 @@ data class PrivateMessageModel(
|
|||||||
val publishDate: String? = null,
|
val publishDate: String? = null,
|
||||||
val updateDate: String? = null,
|
val updateDate: String? = null,
|
||||||
val read: Boolean = false,
|
val read: Boolean = false,
|
||||||
)
|
) : JavaSerializable
|
@ -1,5 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
|
||||||
|
|
||||||
data class UserModel(
|
data class UserModel(
|
||||||
val id: Int = 0,
|
val id: Int = 0,
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
|
||||||
|
|
||||||
data class UserScoreModel(
|
data class UserScoreModel(
|
||||||
val postScore: Int = 0,
|
val postScore: Int = 0,
|
||||||
val commentScore: Int = 0,
|
val commentScore: Int = 0,
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
|
||||||
|
|
||||||
expect interface JavaSerializable
|
|
@ -1,3 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
|
||||||
|
|
||||||
actual interface JavaSerializable
|
|
@ -1,3 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
|
||||||
|
|
||||||
actual interface JavaSerializable
|
|
@ -1,7 +1,11 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search.di
|
package com.github.diegoberaldin.raccoonforlemmy.feature.search.di
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.main.ExploreViewModel
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.main.ExploreViewModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscriptions.ManageSubscriptionsViewModel
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscriptions.ManageSubscriptionsViewModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.detail.MultiCommunityViewModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.editor.MultiCommunityEditorViewModel
|
||||||
|
import org.koin.core.parameter.parametersOf
|
||||||
import org.koin.java.KoinJavaComponent.inject
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
|
||||||
actual fun getExploreViewModel(): ExploreViewModel {
|
actual fun getExploreViewModel(): ExploreViewModel {
|
||||||
@ -14,3 +18,18 @@ actual fun getManageSubscriptionsViewModel(): ManageSubscriptionsViewModel {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actual fun getMultiCommunityViewModel(community: MultiCommunityModel): MultiCommunityViewModel {
|
||||||
|
val res: MultiCommunityViewModel by inject(
|
||||||
|
MultiCommunityViewModel::class.java,
|
||||||
|
parameters = { parametersOf(community) }
|
||||||
|
)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getMultiCommunityEditorViewModel(editedCommunity: MultiCommunityModel?): MultiCommunityEditorViewModel {
|
||||||
|
val res: MultiCommunityEditorViewModel by inject(
|
||||||
|
MultiCommunityEditorViewModel::class.java,
|
||||||
|
parameters = { parametersOf(editedCommunity) }
|
||||||
|
)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
@ -5,6 +5,12 @@ import com.github.diegoberaldin.raccoonforlemmy.feature.search.main.ExploreMviMo
|
|||||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.main.ExploreViewModel
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.main.ExploreViewModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscriptions.ManageSubscriptionsMviModel
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscriptions.ManageSubscriptionsMviModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscriptions.ManageSubscriptionsViewModel
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscriptions.ManageSubscriptionsViewModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.detail.MultiCommunityMviModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.detail.MultiCommunityViewModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.editor.MultiCommunityEditorMviModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.editor.MultiCommunityEditorViewModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.utils.DefaultMultiCommunityPaginator
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.utils.MultiCommunityPaginator
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val searchTabModule = module {
|
val searchTabModule = module {
|
||||||
@ -27,7 +33,40 @@ val searchTabModule = module {
|
|||||||
mvi = DefaultMviModel(ManageSubscriptionsMviModel.UiState()),
|
mvi = DefaultMviModel(ManageSubscriptionsMviModel.UiState()),
|
||||||
identityRepository = get(),
|
identityRepository = get(),
|
||||||
communityRepository = get(),
|
communityRepository = get(),
|
||||||
|
accountRepository = get(),
|
||||||
|
multiCommunityRepository = get(),
|
||||||
hapticFeedback = get(),
|
hapticFeedback = get(),
|
||||||
|
notificationCenter = get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
factory { params ->
|
||||||
|
MultiCommunityViewModel(
|
||||||
|
mvi = DefaultMviModel(MultiCommunityMviModel.UiState()),
|
||||||
|
community = params[0],
|
||||||
|
postRepository = get(),
|
||||||
|
identityRepository = get(),
|
||||||
|
themeRepository = get(),
|
||||||
|
shareHelper = get(),
|
||||||
|
settingsRepository = get(),
|
||||||
|
notificationCenter = get(),
|
||||||
|
hapticFeedback = get(),
|
||||||
|
paginator = get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
factory<MultiCommunityPaginator> {
|
||||||
|
DefaultMultiCommunityPaginator(
|
||||||
|
postRepository = get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
factory { params ->
|
||||||
|
MultiCommunityEditorViewModel(
|
||||||
|
mvi = DefaultMviModel(MultiCommunityEditorMviModel.UiState()),
|
||||||
|
editedCommunity = params[0],
|
||||||
|
identityRepository = get(),
|
||||||
|
communityRepository = get(),
|
||||||
|
accountRepository = get(),
|
||||||
|
multiCommunityRepository = get(),
|
||||||
|
notificationCenter = get(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search.di
|
package com.github.diegoberaldin.raccoonforlemmy.feature.search.di
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.main.ExploreViewModel
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.main.ExploreViewModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscriptions.ManageSubscriptionsViewModel
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscriptions.ManageSubscriptionsViewModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.detail.MultiCommunityViewModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.editor.MultiCommunityEditorViewModel
|
||||||
|
|
||||||
expect fun getExploreViewModel(): ExploreViewModel
|
expect fun getExploreViewModel(): ExploreViewModel
|
||||||
|
|
||||||
expect fun getManageSubscriptionsViewModel(): ManageSubscriptionsViewModel
|
expect fun getManageSubscriptionsViewModel(): ManageSubscriptionsViewModel
|
||||||
|
|
||||||
|
expect fun getMultiCommunityViewModel(community: MultiCommunityModel): MultiCommunityViewModel
|
||||||
|
|
||||||
|
expect fun getMultiCommunityEditorViewModel(editedCommunity: MultiCommunityModel?): MultiCommunityEditorViewModel
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscriptions
|
package com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscriptions
|
||||||
|
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
|
||||||
|
|
||||||
interface ManageSubscriptionsMviModel :
|
interface ManageSubscriptionsMviModel :
|
||||||
@ -8,12 +9,13 @@ interface ManageSubscriptionsMviModel :
|
|||||||
sealed interface Intent {
|
sealed interface Intent {
|
||||||
data object Refresh : Intent
|
data object Refresh : Intent
|
||||||
data object HapticIndication : Intent
|
data object HapticIndication : Intent
|
||||||
|
|
||||||
data class Unsubscribe(val index: Int) : Intent
|
data class Unsubscribe(val index: Int) : Intent
|
||||||
|
data class DeleteMultiCommunity(val index: Int) : Intent
|
||||||
}
|
}
|
||||||
|
|
||||||
data class UiState(
|
data class UiState(
|
||||||
val refreshing: Boolean = false,
|
val refreshing: Boolean = false,
|
||||||
|
val multiCommunities: List<MultiCommunityModel> = emptyList(),
|
||||||
val communities: List<CommunityModel> = emptyList(),
|
val communities: List<CommunityModel> = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -4,18 +4,22 @@ import androidx.compose.foundation.Image
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material.DismissDirection
|
import androidx.compose.material.DismissDirection
|
||||||
import androidx.compose.material.DismissValue
|
import androidx.compose.material.DismissValue
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.AddCircle
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.material.icons.filled.Unsubscribe
|
import androidx.compose.material.icons.filled.Unsubscribe
|
||||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||||
import androidx.compose.material.pullrefresh.pullRefresh
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
@ -41,10 +45,13 @@ import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
|||||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
|
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommunityItem
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommunityItem
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.MultiCommunityItem
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SwipeableCard
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SwipeableCard
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.onClick
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.onClick
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.di.getManageSubscriptionsViewModel
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.di.getManageSubscriptionsViewModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.detail.MultiCommunityScreen
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.editor.MultiCommunityEditorScreen
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
|
||||||
@ -81,15 +88,11 @@ class ManageSubscriptionsScreen : Screen {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
val pullRefreshState = rememberPullRefreshState(
|
val pullRefreshState = rememberPullRefreshState(uiState.refreshing, {
|
||||||
uiState.refreshing,
|
model.reduce(ManageSubscriptionsMviModel.Intent.Refresh)
|
||||||
{
|
})
|
||||||
model.reduce(ManageSubscriptionsMviModel.Intent.Refresh)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(paddingValues)
|
||||||
.padding(paddingValues)
|
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
.pullRefresh(pullRefreshState),
|
.pullRefresh(pullRefreshState),
|
||||||
) {
|
) {
|
||||||
@ -97,6 +100,87 @@ class ManageSubscriptionsScreen : Screen {
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
verticalArrangement = Arrangement.spacedBy(Spacing.xxs),
|
verticalArrangement = Arrangement.spacedBy(Spacing.xxs),
|
||||||
) {
|
) {
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = Spacing.s),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(MR.strings.manage_subscriptions_header_multicommunities),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.onClick {
|
||||||
|
navigator?.push(MultiCommunityEditorScreen())
|
||||||
|
},
|
||||||
|
imageVector = Icons.Default.AddCircle,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onBackground,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemsIndexed(uiState.multiCommunities) { idx, community ->
|
||||||
|
SwipeableCard(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
backgroundColor = {
|
||||||
|
when (it) {
|
||||||
|
DismissValue.DismissedToStart -> MaterialTheme.colorScheme.surfaceTint
|
||||||
|
DismissValue.DismissedToEnd -> MaterialTheme.colorScheme.tertiary
|
||||||
|
else -> Color.Transparent
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onGestureBegin = {
|
||||||
|
model.reduce(ManageSubscriptionsMviModel.Intent.HapticIndication)
|
||||||
|
},
|
||||||
|
onDismissToStart = {
|
||||||
|
navigator?.push(
|
||||||
|
MultiCommunityEditorScreen(community),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onDismissToEnd = {
|
||||||
|
model.reduce(
|
||||||
|
ManageSubscriptionsMviModel.Intent.DeleteMultiCommunity(idx),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
swipeContent = { direction ->
|
||||||
|
val icon = when (direction) {
|
||||||
|
DismissDirection.StartToEnd -> Icons.Default.Delete
|
||||||
|
DismissDirection.EndToStart -> Icons.Default.Edit
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.padding(Spacing.xs),
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = {
|
||||||
|
MultiCommunityItem(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
.background(MaterialTheme.colorScheme.background).onClick {
|
||||||
|
navigator?.push(
|
||||||
|
MultiCommunityScreen(community),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
community = community,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = Spacing.s),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(MR.strings.manage_subscriptions_header_subscriptions),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
itemsIndexed(uiState.communities) { idx, community ->
|
itemsIndexed(uiState.communities) { idx, community ->
|
||||||
SwipeableCard(
|
SwipeableCard(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@ -117,21 +201,16 @@ class ManageSubscriptionsScreen : Screen {
|
|||||||
},
|
},
|
||||||
swipeContent = { _ ->
|
swipeContent = { _ ->
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.background(
|
modifier = Modifier.padding(Spacing.xs),
|
||||||
color = MaterialTheme.colorScheme.onSecondary,
|
|
||||||
shape = CircleShape,
|
|
||||||
).padding(Spacing.xs),
|
|
||||||
imageVector = Icons.Default.Unsubscribe,
|
imageVector = Icons.Default.Unsubscribe,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.secondary,
|
tint = Color.White,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
content = {
|
content = {
|
||||||
CommunityItem(
|
CommunityItem(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth()
|
||||||
.fillMaxWidth()
|
.background(MaterialTheme.colorScheme.background).onClick {
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.onClick {
|
|
||||||
navigator?.push(
|
navigator?.push(
|
||||||
CommunityDetailScreen(community),
|
CommunityDetailScreen(community),
|
||||||
)
|
)
|
||||||
|
@ -3,6 +3,11 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscripti
|
|||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.MultiCommunityRepository
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.HapticFeedback
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.HapticFeedback
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
|
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
|
||||||
@ -15,10 +20,29 @@ class ManageSubscriptionsViewModel(
|
|||||||
private val mvi: DefaultMviModel<ManageSubscriptionsMviModel.Intent, ManageSubscriptionsMviModel.UiState, ManageSubscriptionsMviModel.Effect>,
|
private val mvi: DefaultMviModel<ManageSubscriptionsMviModel.Intent, ManageSubscriptionsMviModel.UiState, ManageSubscriptionsMviModel.Effect>,
|
||||||
private val identityRepository: IdentityRepository,
|
private val identityRepository: IdentityRepository,
|
||||||
private val communityRepository: CommunityRepository,
|
private val communityRepository: CommunityRepository,
|
||||||
|
private val accountRepository: AccountRepository,
|
||||||
|
private val multiCommunityRepository: MultiCommunityRepository,
|
||||||
private val hapticFeedback: HapticFeedback,
|
private val hapticFeedback: HapticFeedback,
|
||||||
|
private val notificationCenter: NotificationCenter,
|
||||||
) : ScreenModel,
|
) : ScreenModel,
|
||||||
MviModel<ManageSubscriptionsMviModel.Intent, ManageSubscriptionsMviModel.UiState, ManageSubscriptionsMviModel.Effect> by mvi {
|
MviModel<ManageSubscriptionsMviModel.Intent, ManageSubscriptionsMviModel.UiState, ManageSubscriptionsMviModel.Effect> by mvi {
|
||||||
|
|
||||||
|
init {
|
||||||
|
notificationCenter.addObserver(
|
||||||
|
{ evt ->
|
||||||
|
(evt as? MultiCommunityModel)?.also {
|
||||||
|
handleMultiCommunityCreated(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this::class.simpleName.orEmpty(),
|
||||||
|
NotificationCenterContractKeys.MultiCommunityCreated
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finalize() {
|
||||||
|
notificationCenter.removeObserver(this::class.simpleName.orEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStarted() {
|
override fun onStarted() {
|
||||||
mvi.onStarted()
|
mvi.onStarted()
|
||||||
if (uiState.value.communities.isEmpty()) {
|
if (uiState.value.communities.isEmpty()) {
|
||||||
@ -31,7 +55,11 @@ class ManageSubscriptionsViewModel(
|
|||||||
ManageSubscriptionsMviModel.Intent.HapticIndication -> hapticFeedback.vibrate()
|
ManageSubscriptionsMviModel.Intent.HapticIndication -> hapticFeedback.vibrate()
|
||||||
ManageSubscriptionsMviModel.Intent.Refresh -> refresh()
|
ManageSubscriptionsMviModel.Intent.Refresh -> refresh()
|
||||||
is ManageSubscriptionsMviModel.Intent.Unsubscribe -> handleUnsubscription(
|
is ManageSubscriptionsMviModel.Intent.Unsubscribe -> handleUnsubscription(
|
||||||
community = uiState.value.communities[intent.index]
|
community = uiState.value.communities[intent.index],
|
||||||
|
)
|
||||||
|
|
||||||
|
is ManageSubscriptionsMviModel.Intent.DeleteMultiCommunity -> deleteMultiCommunity(
|
||||||
|
community = uiState.value.multiCommunities[intent.index],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,11 +71,15 @@ class ManageSubscriptionsViewModel(
|
|||||||
mvi.updateState { it.copy(refreshing = true) }
|
mvi.updateState { it.copy(refreshing = true) }
|
||||||
mvi.scope?.launch(Dispatchers.IO) {
|
mvi.scope?.launch(Dispatchers.IO) {
|
||||||
val auth = identityRepository.authToken.value
|
val auth = identityRepository.authToken.value
|
||||||
val items = communityRepository.getSubscribed(auth).sortedBy { it.name }
|
val communities = communityRepository.getSubscribed(auth).sortedBy { it.name }
|
||||||
|
val accountId = accountRepository.getActive()?.id ?: 0L
|
||||||
|
val multiCommunitites = multiCommunityRepository.getAll(accountId).sortedBy { it.name }
|
||||||
|
|
||||||
mvi.updateState {
|
mvi.updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
refreshing = false,
|
refreshing = false,
|
||||||
communities = items,
|
communities = communities,
|
||||||
|
multiCommunities = multiCommunitites,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,4 +96,30 @@ class ManageSubscriptionsViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun deleteMultiCommunity(community: MultiCommunityModel) {
|
||||||
|
mvi.scope?.launch(Dispatchers.IO) {
|
||||||
|
multiCommunityRepository.delete(community)
|
||||||
|
mvi.updateState {
|
||||||
|
val newCommunities = it.multiCommunities.filter { c -> c.id != community.id }
|
||||||
|
it.copy(multiCommunities = newCommunities)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleMultiCommunityCreated(community: MultiCommunityModel) {
|
||||||
|
val oldCommunities = uiState.value.multiCommunities
|
||||||
|
val newCommunities = if (oldCommunities.any { it.id == community.id }) {
|
||||||
|
oldCommunities.map {
|
||||||
|
if (it.id == community.id) {
|
||||||
|
community
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
oldCommunities + community
|
||||||
|
}.sortedBy { it.name }
|
||||||
|
mvi.updateState { it.copy(multiCommunities = newCommunities) }
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.detail
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.PostLayout
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
|
||||||
|
|
||||||
|
interface MultiCommunityMviModel :
|
||||||
|
MviModel<MultiCommunityMviModel.Intent, MultiCommunityMviModel.UiState, MultiCommunityMviModel.Effect> {
|
||||||
|
sealed interface Intent {
|
||||||
|
data object Refresh : Intent
|
||||||
|
data object LoadNextPage : Intent
|
||||||
|
data class ChangeSort(val value: SortType) : Intent
|
||||||
|
data object HapticIndication : Intent
|
||||||
|
data class UpVotePost(val index: Int, val feedback: Boolean = false) : Intent
|
||||||
|
data class DownVotePost(val index: Int, val feedback: Boolean = false) : Intent
|
||||||
|
data class SavePost(val index: Int, val feedback: Boolean = false) : Intent
|
||||||
|
data class SharePost(val index: Int) : Intent
|
||||||
|
}
|
||||||
|
|
||||||
|
data class UiState(
|
||||||
|
val refreshing: Boolean = false,
|
||||||
|
val loading: Boolean = false,
|
||||||
|
val canFetchMore: Boolean = true,
|
||||||
|
val instance: String = "",
|
||||||
|
val isLogged: Boolean = false,
|
||||||
|
val sortType: SortType? = null,
|
||||||
|
val posts: List<PostModel> = emptyList(),
|
||||||
|
val blurNsfw: Boolean = true,
|
||||||
|
val swipeActionsEnabled: Boolean = true,
|
||||||
|
val postLayout: PostLayout = PostLayout.Card,
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed interface Effect
|
||||||
|
}
|
@ -0,0 +1,346 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.detail
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.material.DismissDirection
|
||||||
|
import androidx.compose.material.DismissValue
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.ArrowCircleDown
|
||||||
|
import androidx.compose.material.icons.filled.ArrowCircleUp
|
||||||
|
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||||
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
|
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.PostLayout
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.di.getThemeRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCard
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCardPlaceholder
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SwipeableCard
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.createcomment.CreateCommentScreen
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.image.ZoomableImageScreen
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.SortBottomSheet
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailScreen
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailScreen
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.onClick
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.di.getMultiCommunityViewModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||||
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
|
||||||
|
class MultiCommunityScreen(
|
||||||
|
private val community: MultiCommunityModel,
|
||||||
|
) : Screen {
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val model = rememberScreenModel { getMultiCommunityViewModel(community) }
|
||||||
|
model.bindToLifecycle(key)
|
||||||
|
val uiState by model.uiState.collectAsState()
|
||||||
|
val bottomSheetNavigator = LocalBottomSheetNavigator.current
|
||||||
|
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||||
|
val bottomNavCoordinator = remember { getNavigationCoordinator() }
|
||||||
|
val navigator = remember { bottomNavCoordinator.getRootNavigator() }
|
||||||
|
val notificationCenter = remember { getNotificationCenter() }
|
||||||
|
DisposableEffect(key) {
|
||||||
|
onDispose {
|
||||||
|
notificationCenter.removeObserver(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
val sortType = uiState.sortType
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = community.name,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
navigationIcon = {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.onClick {
|
||||||
|
navigator?.pop()
|
||||||
|
},
|
||||||
|
imageVector = Icons.Default.ArrowBack,
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
Row {
|
||||||
|
val additionalLabel = when (sortType) {
|
||||||
|
SortType.Top.Day -> stringResource(MR.strings.home_sort_type_top_day_short)
|
||||||
|
SortType.Top.Month -> stringResource(MR.strings.home_sort_type_top_month_short)
|
||||||
|
SortType.Top.Past12Hours -> stringResource(MR.strings.home_sort_type_top_12_hours_short)
|
||||||
|
SortType.Top.Past6Hours -> stringResource(MR.strings.home_sort_type_top_6_hours_short)
|
||||||
|
SortType.Top.PastHour -> stringResource(MR.strings.home_sort_type_top_hour_short)
|
||||||
|
SortType.Top.Week -> stringResource(MR.strings.home_sort_type_top_week_short)
|
||||||
|
SortType.Top.Year -> stringResource(MR.strings.home_sort_type_top_year_short)
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
if (additionalLabel.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = buildString {
|
||||||
|
append("(")
|
||||||
|
append(additionalLabel)
|
||||||
|
append(")")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (sortType != null) {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.onClick {
|
||||||
|
val sheet = SortBottomSheet(
|
||||||
|
expandTop = true,
|
||||||
|
)
|
||||||
|
notificationCenter.addObserver({
|
||||||
|
(it as? SortType)?.also { sortType ->
|
||||||
|
model.reduce(
|
||||||
|
MultiCommunityMviModel.Intent.ChangeSort(
|
||||||
|
sortType
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, key, NotificationCenterContractKeys.ChangeSortType)
|
||||||
|
bottomSheetNavigator.show(sheet)
|
||||||
|
},
|
||||||
|
imageVector = sortType.toIcon(),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { padding ->
|
||||||
|
val pullRefreshState = rememberPullRefreshState(uiState.refreshing, {
|
||||||
|
model.reduce(MultiCommunityMviModel.Intent.Refresh)
|
||||||
|
})
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(padding)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.nestedScroll(scrollBehavior.nestedScrollConnection).let {
|
||||||
|
val connection = bottomNavCoordinator.getBottomBarScrollConnection()
|
||||||
|
if (connection != null) {
|
||||||
|
it.nestedScroll(connection)
|
||||||
|
} else it
|
||||||
|
}
|
||||||
|
.pullRefresh(pullRefreshState),
|
||||||
|
) {
|
||||||
|
LazyColumn {
|
||||||
|
if (uiState.posts.isEmpty() && uiState.loading) {
|
||||||
|
items(5) {
|
||||||
|
PostCardPlaceholder(
|
||||||
|
postLayout = uiState.postLayout,
|
||||||
|
)
|
||||||
|
if (uiState.postLayout != PostLayout.Card) {
|
||||||
|
Divider(modifier = Modifier.padding(vertical = Spacing.s))
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = Modifier.height(Spacing.s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemsIndexed(uiState.posts) { idx, post ->
|
||||||
|
val themeRepository = remember { getThemeRepository() }
|
||||||
|
val fontScale by themeRepository.contentFontScale.collectAsState()
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalDensity provides Density(
|
||||||
|
density = LocalDensity.current.density,
|
||||||
|
fontScale = fontScale,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
SwipeableCard(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
enabled = uiState.swipeActionsEnabled,
|
||||||
|
backgroundColor = {
|
||||||
|
when (it) {
|
||||||
|
DismissValue.DismissedToStart -> MaterialTheme.colorScheme.surfaceTint
|
||||||
|
DismissValue.DismissedToEnd -> MaterialTheme.colorScheme.tertiary
|
||||||
|
DismissValue.Default -> Color.Transparent
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onGestureBegin = {
|
||||||
|
model.reduce(MultiCommunityMviModel.Intent.HapticIndication)
|
||||||
|
},
|
||||||
|
onDismissToStart = {
|
||||||
|
model.reduce(MultiCommunityMviModel.Intent.UpVotePost(idx))
|
||||||
|
},
|
||||||
|
onDismissToEnd = {
|
||||||
|
model.reduce(MultiCommunityMviModel.Intent.DownVotePost(idx))
|
||||||
|
},
|
||||||
|
swipeContent = { direction ->
|
||||||
|
val icon = when (direction) {
|
||||||
|
DismissDirection.StartToEnd -> Icons.Default.ArrowCircleDown
|
||||||
|
DismissDirection.EndToStart -> Icons.Default.ArrowCircleUp
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = {
|
||||||
|
PostCard(
|
||||||
|
modifier = Modifier.onClick {
|
||||||
|
navigator?.push(
|
||||||
|
PostDetailScreen(post),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
post = post,
|
||||||
|
postLayout = uiState.postLayout,
|
||||||
|
options = buildList {
|
||||||
|
add(stringResource(MR.strings.post_action_share))
|
||||||
|
},
|
||||||
|
blurNsfw = uiState.blurNsfw,
|
||||||
|
onOpenCommunity = { community ->
|
||||||
|
navigator?.push(
|
||||||
|
CommunityDetailScreen(community),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onOpenCreator = { user ->
|
||||||
|
navigator?.push(
|
||||||
|
UserDetailScreen(user),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onUpVote = {
|
||||||
|
model.reduce(
|
||||||
|
MultiCommunityMviModel.Intent.UpVotePost(
|
||||||
|
index = idx,
|
||||||
|
feedback = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onDownVote = {
|
||||||
|
model.reduce(
|
||||||
|
MultiCommunityMviModel.Intent.DownVotePost(
|
||||||
|
index = idx,
|
||||||
|
feedback = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onSave = {
|
||||||
|
model.reduce(
|
||||||
|
MultiCommunityMviModel.Intent.SavePost(
|
||||||
|
index = idx,
|
||||||
|
feedback = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onReply = {
|
||||||
|
val screen = CreateCommentScreen(
|
||||||
|
originalPost = post,
|
||||||
|
)
|
||||||
|
notificationCenter.addObserver(
|
||||||
|
{
|
||||||
|
model.reduce(MultiCommunityMviModel.Intent.Refresh)
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
NotificationCenterContractKeys.CommentCreated
|
||||||
|
)
|
||||||
|
bottomSheetNavigator.show(screen)
|
||||||
|
},
|
||||||
|
onImageClick = { url ->
|
||||||
|
navigator?.push(
|
||||||
|
ZoomableImageScreen(url),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onOptionSelected = { optionIdx ->
|
||||||
|
when (optionIdx) {
|
||||||
|
else -> model.reduce(
|
||||||
|
MultiCommunityMviModel.Intent.SharePost(idx)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if (uiState.postLayout != PostLayout.Card) {
|
||||||
|
Divider(modifier = Modifier.padding(vertical = Spacing.s))
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = Modifier.height(Spacing.s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
if (!uiState.loading && !uiState.refreshing && uiState.canFetchMore) {
|
||||||
|
model.reduce(MultiCommunityMviModel.Intent.LoadNextPage)
|
||||||
|
}
|
||||||
|
if (uiState.loading && !uiState.refreshing) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(Spacing.xs),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(25.dp),
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(Spacing.xxxl))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(
|
||||||
|
refreshing = uiState.refreshing,
|
||||||
|
state = pullRefreshState,
|
||||||
|
modifier = Modifier.align(Alignment.TopCenter),
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.background,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,316 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.detail
|
||||||
|
|
||||||
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.repository.ThemeRepository
|
||||||
|
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.notifications.NotificationCenterContractKeys
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.HapticFeedback
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.ShareHelper
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.shareUrl
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toSortType
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.utils.MultiCommunityPaginator
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.IO
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class MultiCommunityViewModel(
|
||||||
|
private val mvi: DefaultMviModel<MultiCommunityMviModel.Intent, MultiCommunityMviModel.UiState, MultiCommunityMviModel.Effect>,
|
||||||
|
private val community: MultiCommunityModel,
|
||||||
|
private val postRepository: PostRepository,
|
||||||
|
private val identityRepository: IdentityRepository,
|
||||||
|
private val themeRepository: ThemeRepository,
|
||||||
|
private val shareHelper: ShareHelper,
|
||||||
|
private val settingsRepository: SettingsRepository,
|
||||||
|
private val notificationCenter: NotificationCenter,
|
||||||
|
private val hapticFeedback: HapticFeedback,
|
||||||
|
private val paginator: MultiCommunityPaginator,
|
||||||
|
) : ScreenModel,
|
||||||
|
MviModel<MultiCommunityMviModel.Intent, MultiCommunityMviModel.UiState, MultiCommunityMviModel.Effect> by mvi {
|
||||||
|
|
||||||
|
init {
|
||||||
|
notificationCenter.addObserver({
|
||||||
|
(it as? PostModel)?.also { post ->
|
||||||
|
handlePostUpdate(post)
|
||||||
|
}
|
||||||
|
}, this::class.simpleName.orEmpty(), NotificationCenterContractKeys.PostUpdated)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finalize() {
|
||||||
|
notificationCenter.removeObserver(this::class.simpleName.orEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStarted() {
|
||||||
|
mvi.onStarted()
|
||||||
|
|
||||||
|
mvi.scope?.launch {
|
||||||
|
themeRepository.postLayout.onEach { layout ->
|
||||||
|
mvi.updateState { it.copy(postLayout = layout) }
|
||||||
|
}.launchIn(this)
|
||||||
|
|
||||||
|
settingsRepository.currentSettings.onEach { settings ->
|
||||||
|
mvi.updateState {
|
||||||
|
it.copy(
|
||||||
|
blurNsfw = settings.blurNsfw,
|
||||||
|
swipeActionsEnabled = settings.enableSwipeActions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.launchIn(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
mvi.scope?.launch(Dispatchers.IO) {
|
||||||
|
if (uiState.value.posts.isEmpty()) {
|
||||||
|
val settings = settingsRepository.currentSettings.value
|
||||||
|
mvi.updateState {
|
||||||
|
it.copy(
|
||||||
|
sortType = settings.defaultPostSortType.toSortType(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
paginator.setCommunities(community.communityIds)
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reduce(intent: MultiCommunityMviModel.Intent) {
|
||||||
|
when (intent) {
|
||||||
|
is MultiCommunityMviModel.Intent.ChangeSort -> applySortType(intent.value)
|
||||||
|
is MultiCommunityMviModel.Intent.DownVotePost -> toggleDownVote(
|
||||||
|
post = uiState.value.posts[intent.index],
|
||||||
|
feedback = intent.feedback,
|
||||||
|
)
|
||||||
|
|
||||||
|
MultiCommunityMviModel.Intent.HapticIndication -> hapticFeedback.vibrate()
|
||||||
|
MultiCommunityMviModel.Intent.LoadNextPage -> loadNextPage()
|
||||||
|
MultiCommunityMviModel.Intent.Refresh -> refresh()
|
||||||
|
is MultiCommunityMviModel.Intent.SavePost -> toggleSave(
|
||||||
|
post = uiState.value.posts[intent.index],
|
||||||
|
feedback = intent.feedback,
|
||||||
|
)
|
||||||
|
|
||||||
|
is MultiCommunityMviModel.Intent.SharePost -> share(post = uiState.value.posts[intent.index])
|
||||||
|
is MultiCommunityMviModel.Intent.UpVotePost -> toggleUpVote(
|
||||||
|
post = uiState.value.posts[intent.index],
|
||||||
|
feedback = intent.feedback,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refresh() {
|
||||||
|
paginator.reset()
|
||||||
|
mvi.updateState { it.copy(canFetchMore = true, refreshing = true) }
|
||||||
|
loadNextPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadNextPage() {
|
||||||
|
val currentState = mvi.uiState.value
|
||||||
|
if (!currentState.canFetchMore || currentState.loading) {
|
||||||
|
mvi.updateState { it.copy(refreshing = false) }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mvi.scope?.launch(Dispatchers.IO) {
|
||||||
|
mvi.updateState { it.copy(loading = true) }
|
||||||
|
val auth = identityRepository.authToken.value
|
||||||
|
val sort = currentState.sortType ?: SortType.Active
|
||||||
|
val refreshing = currentState.refreshing
|
||||||
|
val includeNsfw = settingsRepository.currentSettings.value.includeNsfw
|
||||||
|
|
||||||
|
val postList = paginator.loadNextPage(
|
||||||
|
auth = auth,
|
||||||
|
sort = sort,
|
||||||
|
)
|
||||||
|
val canFetchMore = paginator.canFetchMore
|
||||||
|
mvi.updateState {
|
||||||
|
val newPosts = if (refreshing) {
|
||||||
|
postList
|
||||||
|
} else {
|
||||||
|
// prevents accidental duplication
|
||||||
|
it.posts + postList.filter { p -> it.posts.none { e -> e.id == p.id } }
|
||||||
|
}.filter { post ->
|
||||||
|
if (includeNsfw) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
!post.nsfw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.copy(
|
||||||
|
posts = newPosts,
|
||||||
|
loading = false,
|
||||||
|
canFetchMore = canFetchMore,
|
||||||
|
refreshing = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applySortType(value: SortType) {
|
||||||
|
mvi.updateState { it.copy(sortType = value) }
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleUpVote(post: PostModel, feedback: Boolean) {
|
||||||
|
val newVote = post.myVote <= 0
|
||||||
|
val newPost = postRepository.asUpVoted(
|
||||||
|
post = post,
|
||||||
|
voted = newVote,
|
||||||
|
)
|
||||||
|
if (feedback) {
|
||||||
|
hapticFeedback.vibrate()
|
||||||
|
}
|
||||||
|
mvi.updateState {
|
||||||
|
it.copy(
|
||||||
|
posts = it.posts.map { p ->
|
||||||
|
if (p.id == post.id) {
|
||||||
|
newPost
|
||||||
|
} else {
|
||||||
|
p
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
mvi.scope?.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val auth = identityRepository.authToken.value.orEmpty()
|
||||||
|
postRepository.upVote(
|
||||||
|
post = post,
|
||||||
|
auth = auth,
|
||||||
|
voted = newVote,
|
||||||
|
)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
e.printStackTrace()
|
||||||
|
mvi.updateState {
|
||||||
|
it.copy(
|
||||||
|
posts = it.posts.map { p ->
|
||||||
|
if (p.id == post.id) {
|
||||||
|
post
|
||||||
|
} else {
|
||||||
|
p
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleDownVote(post: PostModel, feedback: Boolean) {
|
||||||
|
val newValue = post.myVote >= 0
|
||||||
|
val newPost = postRepository.asDownVoted(
|
||||||
|
post = post,
|
||||||
|
downVoted = newValue,
|
||||||
|
)
|
||||||
|
if (feedback) {
|
||||||
|
hapticFeedback.vibrate()
|
||||||
|
}
|
||||||
|
mvi.updateState {
|
||||||
|
it.copy(
|
||||||
|
posts = it.posts.map { p ->
|
||||||
|
if (p.id == post.id) {
|
||||||
|
newPost
|
||||||
|
} else {
|
||||||
|
p
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
mvi.scope?.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val auth = identityRepository.authToken.value.orEmpty()
|
||||||
|
postRepository.downVote(
|
||||||
|
post = post,
|
||||||
|
auth = auth,
|
||||||
|
downVoted = newValue,
|
||||||
|
)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
e.printStackTrace()
|
||||||
|
mvi.updateState {
|
||||||
|
it.copy(
|
||||||
|
posts = it.posts.map { p ->
|
||||||
|
if (p.id == post.id) {
|
||||||
|
post
|
||||||
|
} else {
|
||||||
|
p
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleSave(post: PostModel, feedback: Boolean) {
|
||||||
|
val newValue = !post.saved
|
||||||
|
val newPost = postRepository.asSaved(
|
||||||
|
post = post,
|
||||||
|
saved = newValue,
|
||||||
|
)
|
||||||
|
if (feedback) {
|
||||||
|
hapticFeedback.vibrate()
|
||||||
|
}
|
||||||
|
mvi.updateState {
|
||||||
|
it.copy(
|
||||||
|
posts = it.posts.map { p ->
|
||||||
|
if (p.id == post.id) {
|
||||||
|
newPost
|
||||||
|
} else {
|
||||||
|
p
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
mvi.scope?.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val auth = identityRepository.authToken.value.orEmpty()
|
||||||
|
postRepository.save(
|
||||||
|
post = post,
|
||||||
|
auth = auth,
|
||||||
|
saved = newValue,
|
||||||
|
)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
e.printStackTrace()
|
||||||
|
mvi.updateState {
|
||||||
|
it.copy(
|
||||||
|
posts = it.posts.map { p ->
|
||||||
|
if (p.id == post.id) {
|
||||||
|
post
|
||||||
|
} else {
|
||||||
|
p
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePostUpdate(post: PostModel) {
|
||||||
|
mvi.updateState {
|
||||||
|
it.copy(
|
||||||
|
posts = it.posts.map { p ->
|
||||||
|
if (p.id == post.id) {
|
||||||
|
post
|
||||||
|
} else {
|
||||||
|
p
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun share(post: PostModel) {
|
||||||
|
val url = post.shareUrl
|
||||||
|
if (url.isNotEmpty()) {
|
||||||
|
shareHelper.share(url, "text/plain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.editor
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
|
||||||
|
import dev.icerock.moko.resources.desc.StringDesc
|
||||||
|
|
||||||
|
interface MultiCommunityEditorMviModel :
|
||||||
|
MviModel<MultiCommunityEditorMviModel.Intent, MultiCommunityEditorMviModel.UiState, MultiCommunityEditorMviModel.Effect> {
|
||||||
|
sealed interface Intent {
|
||||||
|
data class SetName(val value: String) : Intent
|
||||||
|
data class SetSearch(val value: String) : Intent
|
||||||
|
data class SelectImage(val index: Int?) : Intent
|
||||||
|
data class ToggleCommunity(val index: Int) : Intent
|
||||||
|
data object Submit : Intent
|
||||||
|
}
|
||||||
|
|
||||||
|
data class UiState(
|
||||||
|
val name: String = "",
|
||||||
|
val nameError: StringDesc? = null,
|
||||||
|
val icon: String? = null,
|
||||||
|
val availableIcons: List<String> = emptyList(),
|
||||||
|
val communities: List<Pair<CommunityModel, Boolean>> = emptyList(),
|
||||||
|
val searchText: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed interface Effect {
|
||||||
|
data object Close : Effect
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,336 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.editor
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material.icons.filled.Clear
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommunityItem
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomImage
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.onClick
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.di.getMultiCommunityEditorViewModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||||
|
import dev.icerock.moko.resources.compose.localized
|
||||||
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
|
class MultiCommunityEditorScreen(
|
||||||
|
private val editedCommunity: MultiCommunityModel? = null,
|
||||||
|
) : Screen {
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val model = rememberScreenModel { getMultiCommunityEditorViewModel(editedCommunity) }
|
||||||
|
model.bindToLifecycle(key)
|
||||||
|
val uiState by model.uiState.collectAsState()
|
||||||
|
val navigator = remember { getNavigationCoordinator().getRootNavigator() }
|
||||||
|
|
||||||
|
LaunchedEffect(model) {
|
||||||
|
model.effects.onEach {
|
||||||
|
when (it) {
|
||||||
|
MultiCommunityEditorMviModel.Effect.Close -> {
|
||||||
|
navigator?.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.launchIn(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(MR.strings.multi_community_editor_title),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.onClick {
|
||||||
|
navigator?.pop()
|
||||||
|
},
|
||||||
|
imageVector = Icons.Default.ArrowBack,
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
model.reduce(MultiCommunityEditorMviModel.Intent.Submit)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.CheckCircle,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
|
val keyboardScrollConnection = remember {
|
||||||
|
object : NestedScrollConnection {
|
||||||
|
override fun onPreScroll(
|
||||||
|
available: Offset,
|
||||||
|
source: NestedScrollSource,
|
||||||
|
): Offset {
|
||||||
|
focusManager.clearFocus()
|
||||||
|
return Offset.Zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(paddingValues).nestedScroll(keyboardScrollConnection),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Spacing.s),
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
focusedContainerColor = Color.Transparent,
|
||||||
|
unfocusedContainerColor = Color.Transparent,
|
||||||
|
disabledContainerColor = Color.Transparent,
|
||||||
|
),
|
||||||
|
maxLines = 1,
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(MR.strings.multi_community_editor_name))
|
||||||
|
},
|
||||||
|
textStyle = MaterialTheme.typography.bodyMedium,
|
||||||
|
value = uiState.name,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Ascii,
|
||||||
|
autoCorrect = false,
|
||||||
|
imeAction = ImeAction.Next,
|
||||||
|
),
|
||||||
|
onValueChange = { value ->
|
||||||
|
model.reduce(MultiCommunityEditorMviModel.Intent.SetName(value))
|
||||||
|
},
|
||||||
|
isError = uiState.nameError != null,
|
||||||
|
supportingText = {
|
||||||
|
if (uiState.nameError != null) {
|
||||||
|
Text(
|
||||||
|
text = uiState.nameError?.localized().orEmpty(),
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(Spacing.s))
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(horizontal = Spacing.m)
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(MR.strings.multi_community_editor_icon))
|
||||||
|
LazyRow(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(Spacing.s),
|
||||||
|
) {
|
||||||
|
val iconSize = 40.dp
|
||||||
|
itemsIndexed(uiState.availableIcons) { idx, url ->
|
||||||
|
val selected = url == uiState.icon
|
||||||
|
CustomImage(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(iconSize)
|
||||||
|
.clip(RoundedCornerShape(iconSize / 2))
|
||||||
|
.let {
|
||||||
|
if (selected) {
|
||||||
|
it.border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
shape = CircleShape,
|
||||||
|
).padding(1.dp).border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
shape = CircleShape,
|
||||||
|
).padding(1.dp).border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
shape = CircleShape,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}.onClick {
|
||||||
|
model.reduce(
|
||||||
|
MultiCommunityEditorMviModel.Intent.SelectImage(
|
||||||
|
idx,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
url = url,
|
||||||
|
contentScale = ContentScale.FillBounds,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val selected = uiState.icon == null
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(Spacing.xxxs)
|
||||||
|
.size(iconSize)
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
shape = RoundedCornerShape(iconSize / 2),
|
||||||
|
).let {
|
||||||
|
if (selected) {
|
||||||
|
it.border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
shape = CircleShape,
|
||||||
|
).padding(1.dp).border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
shape = CircleShape,
|
||||||
|
).padding(1.dp).border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
shape = CircleShape,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}.onClick {
|
||||||
|
model.reduce(
|
||||||
|
MultiCommunityEditorMviModel.Intent.SelectImage(
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = uiState.name.firstOrNull()?.toString().orEmpty()
|
||||||
|
.uppercase(),
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(Spacing.s))
|
||||||
|
Column {
|
||||||
|
Text(text = stringResource(MR.strings.multi_community_editor_communities))
|
||||||
|
|
||||||
|
// search field
|
||||||
|
TextField(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
focusedContainerColor = Color.Transparent,
|
||||||
|
unfocusedContainerColor = Color.Transparent,
|
||||||
|
disabledContainerColor = Color.Transparent,
|
||||||
|
),
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(MR.strings.explore_search_placeholder))
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
value = uiState.searchText,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Text,
|
||||||
|
),
|
||||||
|
onValueChange = { value ->
|
||||||
|
model.reduce(MultiCommunityEditorMviModel.Intent.SetSearch(value))
|
||||||
|
},
|
||||||
|
trailingIcon = {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.onClick {
|
||||||
|
if (uiState.searchText.isNotEmpty()) {
|
||||||
|
model.reduce(
|
||||||
|
MultiCommunityEditorMviModel.Intent.SetSearch("")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
imageVector = if (uiState.searchText.isEmpty()) Icons.Default.Search else Icons.Default.Clear,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
.padding(horizontal = Spacing.m)
|
||||||
|
.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Spacing.xxs),
|
||||||
|
) {
|
||||||
|
itemsIndexed(uiState.communities) { idx, communityItem ->
|
||||||
|
val community = communityItem.first
|
||||||
|
val selected = communityItem.second
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
CommunityItem(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
.background(MaterialTheme.colorScheme.background),
|
||||||
|
community = community,
|
||||||
|
)
|
||||||
|
Checkbox(
|
||||||
|
checked = selected,
|
||||||
|
onCheckedChange = {
|
||||||
|
model.reduce(
|
||||||
|
MultiCommunityEditorMviModel.Intent.ToggleCommunity(
|
||||||
|
idx
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,158 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.editor
|
||||||
|
|
||||||
|
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.notifications.NotificationCenterContractKeys
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.MultiCommunityRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommunityRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||||
|
import dev.icerock.moko.resources.desc.desc
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.IO
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class MultiCommunityEditorViewModel(
|
||||||
|
private val mvi: DefaultMviModel<MultiCommunityEditorMviModel.Intent, MultiCommunityEditorMviModel.UiState, MultiCommunityEditorMviModel.Effect>,
|
||||||
|
private val editedCommunity: MultiCommunityModel? = null,
|
||||||
|
private val identityRepository: IdentityRepository,
|
||||||
|
private val communityRepository: CommunityRepository,
|
||||||
|
private val multiCommunityRepository: MultiCommunityRepository,
|
||||||
|
private val accountRepository: AccountRepository,
|
||||||
|
private val notificationCenter: NotificationCenter,
|
||||||
|
) : ScreenModel,
|
||||||
|
MviModel<MultiCommunityEditorMviModel.Intent, MultiCommunityEditorMviModel.UiState, MultiCommunityEditorMviModel.Effect> by mvi {
|
||||||
|
|
||||||
|
private var communities: List<Pair<CommunityModel, Boolean>> = emptyList()
|
||||||
|
private var debounceJob: Job? = null
|
||||||
|
|
||||||
|
override fun onStarted() {
|
||||||
|
mvi.onStarted()
|
||||||
|
if (communities.isEmpty()) {
|
||||||
|
populate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reduce(intent: MultiCommunityEditorMviModel.Intent) {
|
||||||
|
when (intent) {
|
||||||
|
is MultiCommunityEditorMviModel.Intent.SelectImage -> selectImage(intent.index)
|
||||||
|
is MultiCommunityEditorMviModel.Intent.SetName -> mvi.updateState { it.copy(name = intent.value) }
|
||||||
|
is MultiCommunityEditorMviModel.Intent.ToggleCommunity -> toggleCommunity(intent.index)
|
||||||
|
is MultiCommunityEditorMviModel.Intent.SetSearch -> setSearch(intent.value)
|
||||||
|
MultiCommunityEditorMviModel.Intent.Submit -> submit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun populate() {
|
||||||
|
mvi.scope?.launch(Dispatchers.IO) {
|
||||||
|
val auth = identityRepository.authToken.value
|
||||||
|
communities = communityRepository.getSubscribed(auth).sortedBy { it.name }.map { c ->
|
||||||
|
c to (editedCommunity?.communityIds?.contains(c.id) == true)
|
||||||
|
}
|
||||||
|
mvi.updateState {
|
||||||
|
val newCommunities = communities
|
||||||
|
val availableIcons =
|
||||||
|
newCommunities.filter { i -> i.second }.mapNotNull { i -> i.first.icon }
|
||||||
|
it.copy(
|
||||||
|
communities = newCommunities,
|
||||||
|
name = editedCommunity?.name.orEmpty(),
|
||||||
|
icon = editedCommunity?.icon,
|
||||||
|
availableIcons = availableIcons,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setSearch(value: String) {
|
||||||
|
debounceJob?.cancel()
|
||||||
|
mvi.updateState { it.copy(searchText = value) }
|
||||||
|
debounceJob = mvi.scope?.launch(Dispatchers.IO) {
|
||||||
|
delay(1_000)
|
||||||
|
filterCommunities()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun filterCommunities() {
|
||||||
|
val searchText = uiState.value.searchText
|
||||||
|
val filtered = if (searchText.isNotEmpty()) {
|
||||||
|
communities.filter { it.first.name.contains(searchText) }
|
||||||
|
} else {
|
||||||
|
communities
|
||||||
|
}
|
||||||
|
mvi.updateState { it.copy(communities = filtered) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectImage(index: Int?) {
|
||||||
|
val image = if (index == null) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
uiState.value.availableIcons[index]
|
||||||
|
}
|
||||||
|
mvi.updateState { it.copy(icon = image) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleCommunity(index: Int) {
|
||||||
|
mvi.updateState { state ->
|
||||||
|
val newCommunities = state.communities.mapIndexed { idx, item ->
|
||||||
|
if (idx == index) {
|
||||||
|
item.first to !item.second
|
||||||
|
} else {
|
||||||
|
item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val availableIcons =
|
||||||
|
newCommunities.filter { i -> i.second }.mapNotNull { i -> i.first.icon }
|
||||||
|
state.copy(
|
||||||
|
communities = newCommunities,
|
||||||
|
availableIcons = availableIcons,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun submit() {
|
||||||
|
mvi.updateState { it.copy(nameError = null) }
|
||||||
|
val currentState = uiState.value
|
||||||
|
var valid = true
|
||||||
|
val name = currentState.name
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
mvi.updateState { it.copy(nameError = MR.strings.message_missing_field.desc()) }
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val icon = currentState.icon
|
||||||
|
val communityIds = currentState.communities.filter { it.second }.map { it.first.id }
|
||||||
|
val multiCommunity = editedCommunity?.copy(
|
||||||
|
name = name,
|
||||||
|
icon = icon,
|
||||||
|
communityIds = communityIds,
|
||||||
|
) ?: MultiCommunityModel(
|
||||||
|
name = name,
|
||||||
|
icon = icon,
|
||||||
|
communityIds = communityIds,
|
||||||
|
)
|
||||||
|
|
||||||
|
mvi.scope?.launch(Dispatchers.IO) {
|
||||||
|
val accountId = accountRepository.getActive()?.id ?: return@launch
|
||||||
|
if (multiCommunity.id == null) {
|
||||||
|
val id = multiCommunityRepository.create(multiCommunity, accountId)
|
||||||
|
notificationCenter.getAllObservers(NotificationCenterContractKeys.MultiCommunityCreated)
|
||||||
|
.forEach { it.invoke(multiCommunity.copy(id = id)) }
|
||||||
|
} else {
|
||||||
|
multiCommunityRepository.update(multiCommunity)
|
||||||
|
notificationCenter.getAllObservers(NotificationCenterContractKeys.MultiCommunityCreated)
|
||||||
|
.forEach { it.invoke(multiCommunity) }
|
||||||
|
}
|
||||||
|
mvi.emitEffect(MultiCommunityEditorMviModel.Effect.Close)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.utils
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.ListingType
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository
|
||||||
|
|
||||||
|
internal class CommunityPaginator(
|
||||||
|
private val communityId: Int,
|
||||||
|
private val postRepository: PostRepository,
|
||||||
|
) {
|
||||||
|
private var currentPage: Int = 1
|
||||||
|
var canFetchMore: Boolean = true
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
currentPage = 1
|
||||||
|
canFetchMore = true
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadNextPage(
|
||||||
|
auth: String?,
|
||||||
|
sort: SortType,
|
||||||
|
): List<PostModel> {
|
||||||
|
val result = postRepository.getAll(
|
||||||
|
auth = auth,
|
||||||
|
page = currentPage,
|
||||||
|
limit = PostRepository.DEFAULT_PAGE_SIZE,
|
||||||
|
type = ListingType.All,
|
||||||
|
sort = sort,
|
||||||
|
communityId = communityId,
|
||||||
|
)
|
||||||
|
canFetchMore = result.size >= PostRepository.DEFAULT_PAGE_SIZE
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.utils
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository
|
||||||
|
|
||||||
|
class DefaultMultiCommunityPaginator(
|
||||||
|
private val postRepository: PostRepository,
|
||||||
|
) : MultiCommunityPaginator {
|
||||||
|
private var paginators = emptyList<CommunityPaginator>()
|
||||||
|
|
||||||
|
override val canFetchMore: Boolean
|
||||||
|
get() = paginators.any { it.canFetchMore }
|
||||||
|
|
||||||
|
override fun setCommunities(ids: List<Int>) {
|
||||||
|
paginators = ids.map {
|
||||||
|
CommunityPaginator(
|
||||||
|
communityId = it,
|
||||||
|
postRepository = postRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset() {
|
||||||
|
paginators.forEach { it.reset() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loadNextPage(
|
||||||
|
auth: String?,
|
||||||
|
sort: SortType,
|
||||||
|
): List<PostModel> = buildList {
|
||||||
|
for (paginator in paginators) {
|
||||||
|
if (paginator.canFetchMore) {
|
||||||
|
val elements = paginator.loadNextPage(
|
||||||
|
auth = auth,
|
||||||
|
sort = sort,
|
||||||
|
)
|
||||||
|
addAll(elements)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.sortedBy { it.publishDate }
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.utils
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
|
||||||
|
|
||||||
|
interface MultiCommunityPaginator {
|
||||||
|
val canFetchMore: Boolean
|
||||||
|
fun setCommunities(ids: List<Int>)
|
||||||
|
fun reset()
|
||||||
|
|
||||||
|
suspend fun loadNextPage(
|
||||||
|
auth: String? = null,
|
||||||
|
sort: SortType,
|
||||||
|
): List<PostModel>
|
||||||
|
}
|
@ -1,15 +1,35 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search.di
|
package com.github.diegoberaldin.raccoonforlemmy.feature.search.di
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.main.ExploreViewModel
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.main.ExploreViewModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscriptions.ManageSubscriptionsViewModel
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.managesubscriptions.ManageSubscriptionsViewModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.detail.MultiCommunityViewModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.editor.MultiCommunityEditorViewModel
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
import org.koin.core.parameter.parametersOf
|
||||||
|
|
||||||
actual fun getExploreViewModel() = SearchScreenModelHelper.model
|
actual fun getExploreViewModel() = SearchScreenModelHelper.model
|
||||||
|
|
||||||
actual fun getManageSubscriptionsViewModel() = SearchScreenModelHelper.manageSuscriptionsViewModel
|
actual fun getManageSubscriptionsViewModel() = SearchScreenModelHelper.manageSuscriptionsViewModel
|
||||||
|
|
||||||
|
actual fun getMultiCommunityViewModel(community: MultiCommunityModel) =
|
||||||
|
SearchScreenModelHelper.getMultiCommunityViewModel(community)
|
||||||
|
|
||||||
|
actual fun getMultiCommunityEditorViewModel(editedCommunity: MultiCommunityModel?) =
|
||||||
|
SearchScreenModelHelper.getMultiCommunityEditorViewModel(editedCommunity)
|
||||||
|
|
||||||
object SearchScreenModelHelper : KoinComponent {
|
object SearchScreenModelHelper : KoinComponent {
|
||||||
val model: ExploreViewModel by inject()
|
val model: ExploreViewModel by inject()
|
||||||
val manageSuscriptionsViewModel: ManageSubscriptionsViewModel by inject()
|
val manageSuscriptionsViewModel: ManageSubscriptionsViewModel by inject()
|
||||||
|
|
||||||
|
internal fun getMultiCommunityViewModel(community: MultiCommunityModel): MultiCommunityViewModel {
|
||||||
|
val res: MultiCommunityViewModel by inject(parameters = { parametersOf(community) })
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun getMultiCommunityEditorViewModel(editedCommunity: MultiCommunityModel?): MultiCommunityEditorViewModel {
|
||||||
|
val res: MultiCommunityEditorViewModel by inject(parameters = { parametersOf(editedCommunity) })
|
||||||
|
return res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
<string name="explore_result_type_comments">Comments</string>
|
<string name="explore_result_type_comments">Comments</string>
|
||||||
<string name="explore_result_type_communities">Communities</string>
|
<string name="explore_result_type_communities">Communities</string>
|
||||||
<string name="explore_result_type_users">Users</string>
|
<string name="explore_result_type_users">Users</string>
|
||||||
<string name="explore_search_placeholder">Search text</string>
|
<string name="explore_search_placeholder">Search</string>
|
||||||
<string name="explore_title_manage_subscriptions">Manage subscriptions</string>
|
<string name="explore_title_manage_subscriptions">Manage subscriptions</string>
|
||||||
|
|
||||||
<string name="profile_not_logged_message">You are currently not logged in.\nPlease add an
|
<string name="profile_not_logged_message">You are currently not logged in.\nPlease add an
|
||||||
@ -162,4 +162,12 @@
|
|||||||
|
|
||||||
<string name="manage_accounts_title">Manage accounts</string>
|
<string name="manage_accounts_title">Manage accounts</string>
|
||||||
<string name="manage_accounts_button_add">Add account</string>
|
<string name="manage_accounts_button_add">Add account</string>
|
||||||
|
|
||||||
|
<string name="manage_subscriptions_header_multicommunities">Multi-communities</string>
|
||||||
|
<string name="manage_subscriptions_header_subscriptions">Subscriptions</string>
|
||||||
|
|
||||||
|
<string name="multi_community_editor_title">Multi-community editor</string>
|
||||||
|
<string name="multi_community_editor_name">Name</string>
|
||||||
|
<string name="multi_community_editor_icon">Icon</string>
|
||||||
|
<string name="multi_community_editor_communities">Communities</string>
|
||||||
</resources>
|
</resources>
|
@ -61,7 +61,7 @@
|
|||||||
<string name="explore_result_type_comments">Comentarios</string>
|
<string name="explore_result_type_comments">Comentarios</string>
|
||||||
<string name="explore_result_type_communities">Comunidades</string>
|
<string name="explore_result_type_communities">Comunidades</string>
|
||||||
<string name="explore_result_type_users">Usuarios</string>
|
<string name="explore_result_type_users">Usuarios</string>
|
||||||
<string name="explore_search_placeholder">Texto de búsqueda</string>
|
<string name="explore_search_placeholder">Búsqueda</string>
|
||||||
<string name="explore_title_manage_subscriptions">Gestionar subscripciones</string>
|
<string name="explore_title_manage_subscriptions">Gestionar subscripciones</string>
|
||||||
|
|
||||||
<string name="profile_not_logged_message">Acceso no efectuado.\nAñadir una cuenta para
|
<string name="profile_not_logged_message">Acceso no efectuado.\nAñadir una cuenta para
|
||||||
@ -159,4 +159,12 @@
|
|||||||
|
|
||||||
<string name="manage_accounts_title">Gestionar cuentas</string>
|
<string name="manage_accounts_title">Gestionar cuentas</string>
|
||||||
<string name="manage_accounts_button_add">Nueva cuenta</string>
|
<string name="manage_accounts_button_add">Nueva cuenta</string>
|
||||||
|
|
||||||
|
<string name="manage_subscriptions_header_multicommunities">Multi-comunidades</string>
|
||||||
|
<string name="manage_subscriptions_header_subscriptions">Subscripciones</string>
|
||||||
|
|
||||||
|
<string name="multi_community_editor_title">Editor multi-comunidad</string>
|
||||||
|
<string name="multi_community_editor_name">Nombre</string>
|
||||||
|
<string name="multi_community_editor_icon">Icono</string>
|
||||||
|
<string name="multi_community_editor_communities">Comunidades</string>
|
||||||
</resources>
|
</resources>
|
@ -61,7 +61,7 @@
|
|||||||
<string name="explore_result_type_comments">Commenti</string>
|
<string name="explore_result_type_comments">Commenti</string>
|
||||||
<string name="explore_result_type_communities">Comunità</string>
|
<string name="explore_result_type_communities">Comunità</string>
|
||||||
<string name="explore_result_type_users">Utenti</string>
|
<string name="explore_result_type_users">Utenti</string>
|
||||||
<string name="explore_search_placeholder">Testo di ricerca</string>
|
<string name="explore_search_placeholder">Ricerca</string>
|
||||||
<string name="explore_title_manage_subscriptions">Gestione iscrizioni</string>
|
<string name="explore_title_manage_subscriptions">Gestione iscrizioni</string>
|
||||||
|
|
||||||
<string name="profile_not_logged_message">Login non effettuato.\nAggiungi un account per
|
<string name="profile_not_logged_message">Login non effettuato.\nAggiungi un account per
|
||||||
@ -159,4 +159,12 @@
|
|||||||
|
|
||||||
<string name="manage_accounts_title">Gestisci account</string>
|
<string name="manage_accounts_title">Gestisci account</string>
|
||||||
<string name="manage_accounts_button_add">Aggiungi account</string>
|
<string name="manage_accounts_button_add">Aggiungi account</string>
|
||||||
|
|
||||||
|
<string name="manage_subscriptions_header_multicommunities">Multi-comunità</string>
|
||||||
|
<string name="manage_subscriptions_header_subscriptions">Iscrizioni</string>
|
||||||
|
|
||||||
|
<string name="multi_community_editor_title">Editor Multi-comunità</string>
|
||||||
|
<string name="multi_community_editor_name">Nome</string>
|
||||||
|
<string name="multi_community_editor_icon">Icona</string>
|
||||||
|
<string name="multi_community_editor_communities">Comunità</string>
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user