mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-09 04:18:40 +01:00
feat(search): implement community list
This commit is contained in:
parent
bb4b099735
commit
8015b065e3
33
README.md
33
README.md
@ -1,4 +1,5 @@
|
||||
# Raccon for Lemmy
|
||||
|
||||
A Kotlin Multiplatform Mobile client for Lemmy.
|
||||
|
||||
<div align="center">
|
||||
@ -14,21 +15,29 @@ A Kotlin Multiplatform Mobile client for Lemmy.
|
||||
</table>
|
||||
</div>
|
||||
|
||||
This is mostly an exercise to play around with KMM and Compose Multiplatform and implement a Lemmy client.
|
||||
This is mostly an exercise to play around with KMM and Compose Multiplatform and implement a Lemmy
|
||||
client.
|
||||
|
||||
The project is still at an early stage and not ready for production, expect things to change and even major changes to the source code.
|
||||
The project is still at an early stage and not ready for production, expect things to change and
|
||||
even major changes to the source code.
|
||||
|
||||
Libraries used:
|
||||
|
||||
- Koin for dependency injection
|
||||
- Voyager for screen navigation
|
||||
- Ktor with Ktorfit for networking in conjunction with kotlinx-serialization for JSON marshalling
|
||||
- Moko resources for resource management
|
||||
- Kamel for lazy image loading
|
||||
- Multiplatform settings for encrypted preferences
|
||||
- Markdown by Jetbrains for markdown parsing
|
||||
- [Koin](https://github.com/InsertKoinIO/koin) for dependency injection
|
||||
- [Voyager](https://github.com/adrielcafe/voyager) for screen navigation
|
||||
- [Ktor](https://github.com/ktorio/ktor) with [Ktorfit](https://github.com/Foso/Ktorfit) for
|
||||
networking in conjunction with kotlinx-serialization for JSON marshalling
|
||||
- [Moko resources](https://github.com/icerockdev/moko-resources) for resource management
|
||||
- [Kamel](https://github.com/Kamel-Media/Kamel) for lazy image loading
|
||||
- [Multiplatform settings](https://github.com/russhwolf/multiplatform-settings) for encrypted
|
||||
preferences
|
||||
- [Markdown](https://github.com/JetBrains/markdown) for markdown parsing
|
||||
- ... more to come (e.g. SQLdelight for persistence)
|
||||
|
||||
Credits:
|
||||
- the `core-api` module is heavily inspired by [Jerboa for Lemmy](https://github.com/dessalines/jerboa)
|
||||
- the `core-md` module is copied from [Multiplatform Markdown Renderer](https://github.com/mikepenz/multiplatform-markdown-renderer)
|
||||
Credits:
|
||||
|
||||
- the `core-api` module is heavily inspired
|
||||
by [Jerboa for Lemmy](https://github.com/dessalines/jerboa)
|
||||
- the `core-md` module is copied
|
||||
from [Multiplatform Markdown Renderer](https://github.com/mikepenz/multiplatform-markdown-renderer)
|
||||
- the UI is vaguely inspired by the [Thunder](https://github.com/thunder-app/thunder) app
|
||||
|
39
core-api/core_api.podspec
Normal file
39
core-api/core_api.podspec
Normal file
@ -0,0 +1,39 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'core_api'
|
||||
spec.version = '1.0'
|
||||
spec.homepage = 'Link to the Shared Module homepage'
|
||||
spec.source = { :http=> ''}
|
||||
spec.authors = ''
|
||||
spec.license = ''
|
||||
spec.summary = 'Some description for the Shared Module'
|
||||
spec.vendored_frameworks = 'build/cocoapods/framework/core-api.framework'
|
||||
spec.libraries = 'c++'
|
||||
spec.ios.deployment_target = '14.1'
|
||||
|
||||
|
||||
spec.pod_target_xcconfig = {
|
||||
'KOTLIN_PROJECT_PATH' => ':core-api',
|
||||
'PRODUCT_MODULE_NAME' => 'core-api',
|
||||
}
|
||||
|
||||
spec.script_phases = [
|
||||
{
|
||||
:name => 'Build core_api',
|
||||
:execution_position => :before_compile,
|
||||
:shell_path => '/bin/sh',
|
||||
:script => <<-SCRIPT
|
||||
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
|
||||
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
|
||||
exit 0
|
||||
fi
|
||||
set -ev
|
||||
REPO_ROOT="$PODS_TARGET_SRCROOT"
|
||||
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
|
||||
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
|
||||
-Pkotlin.native.cocoapods.archs="$ARCHS" \
|
||||
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
|
||||
SCRIPT
|
||||
}
|
||||
]
|
||||
|
||||
end
|
@ -0,0 +1,13 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core.api.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SearchResponse(
|
||||
@SerialName("type_") val type: SearchType,
|
||||
@SerialName("comments") val comments: List<CommentView>,
|
||||
@SerialName("posts") val posts: List<PostView>,
|
||||
@SerialName("communities") val communities: List<CommunityView>,
|
||||
@SerialName("users") val users: List<PersonView>,
|
||||
)
|
@ -0,0 +1,23 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core.api.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
|
||||
enum class SearchType {
|
||||
@SerialName("All")
|
||||
All,
|
||||
|
||||
@SerialName("Comments")
|
||||
Comments,
|
||||
|
||||
@SerialName("Posts")
|
||||
Posts,
|
||||
|
||||
@SerialName("Communities")
|
||||
Communities,
|
||||
|
||||
@SerialName("Users")
|
||||
Users,
|
||||
|
||||
@SerialName("Url")
|
||||
Url,
|
||||
}
|
@ -4,6 +4,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.api.service.AuthService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.service.CommentService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.service.CommunityService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.service.PostService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.service.SearchService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.service.SiteService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.service.UserService
|
||||
import de.jensklingenberg.ktorfit.Ktorfit
|
||||
@ -43,6 +44,9 @@ internal class DefaultServiceProvider : ServiceProvider {
|
||||
override lateinit var comment: CommentService
|
||||
private set
|
||||
|
||||
override lateinit var search: SearchService
|
||||
private set
|
||||
|
||||
private val baseUrl: String get() = "https://$currentInstance/api/$VERSION/"
|
||||
private val client = HttpClient {
|
||||
defaultRequest {
|
||||
@ -79,5 +83,6 @@ internal class DefaultServiceProvider : ServiceProvider {
|
||||
user = ktorfit.create()
|
||||
site = ktorfit.create()
|
||||
comment = ktorfit.create()
|
||||
search = ktorfit.create()
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.api.service.AuthService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.service.CommentService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.service.CommunityService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.service.PostService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.service.SearchService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.service.SiteService
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.service.UserService
|
||||
|
||||
@ -16,6 +17,7 @@ interface ServiceProvider {
|
||||
val user: UserService
|
||||
val site: SiteService
|
||||
val comment: CommentService
|
||||
val search: SearchService
|
||||
|
||||
fun changeInstance(value: String)
|
||||
}
|
||||
|
@ -14,15 +14,15 @@ import de.jensklingenberg.ktorfit.http.Query
|
||||
interface CommunityService {
|
||||
|
||||
@GET("community")
|
||||
suspend fun getCommunity(
|
||||
suspend fun get(
|
||||
@Query("auth") auth: String? = null,
|
||||
@Query("id") id: Int? = null,
|
||||
@Query("name") name: String? = null,
|
||||
): Response<GetCommunityResponse>
|
||||
|
||||
@POST("community/follow")
|
||||
suspend fun followCommunity(@Body form: FollowCommunityForm): Response<CommunityResponse>
|
||||
suspend fun follow(@Body form: FollowCommunityForm): Response<CommunityResponse>
|
||||
|
||||
@POST("community/block")
|
||||
suspend fun blockCommunity(@Body form: BlockCommunityForm): Response<BlockCommunityResponse>
|
||||
suspend fun block(@Body form: BlockCommunityForm): Response<BlockCommunityResponse>
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core.api.service
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.ListingType
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.SearchResponse
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.SearchType
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.SortType
|
||||
import de.jensklingenberg.ktorfit.Response
|
||||
import de.jensklingenberg.ktorfit.http.GET
|
||||
import de.jensklingenberg.ktorfit.http.Query
|
||||
|
||||
interface SearchService {
|
||||
@GET("search")
|
||||
suspend fun search(
|
||||
@Query("q") q: String,
|
||||
@Query("community_id") communityId: Int? = null,
|
||||
@Query("community_name") communityName: String? = null,
|
||||
@Query("creator_id") creatorId: Int? = null,
|
||||
@Query("type_") type: SearchType? = null,
|
||||
@Query("sort") sort: SortType? = null,
|
||||
@Query("listing_type") listingType: ListingType? = null,
|
||||
@Query("page") page: Int? = null,
|
||||
@Query("limit") limit: Int? = null,
|
||||
@Query("auth") auth: String? = null,
|
||||
): Response<SearchResponse>
|
||||
}
|
@ -7,7 +7,7 @@ import de.jensklingenberg.ktorfit.http.Query
|
||||
|
||||
interface SiteService {
|
||||
@GET("site")
|
||||
suspend fun getSite(
|
||||
suspend fun get(
|
||||
@Query("auth") auth: String? = null,
|
||||
): Response<GetSiteResponse>
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import de.jensklingenberg.ktorfit.http.Query
|
||||
interface UserService {
|
||||
|
||||
@GET("user")
|
||||
suspend fun getPersonDetails(
|
||||
suspend fun getDetails(
|
||||
@Query("auth") auth: String? = null,
|
||||
@Query("community_id") communityId: Int? = null,
|
||||
@Query("person_id") personId: Int? = null,
|
||||
|
39
core-appearance/core_appearance.podspec
Normal file
39
core-appearance/core_appearance.podspec
Normal file
@ -0,0 +1,39 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'core_appearance'
|
||||
spec.version = '1.0.0'
|
||||
spec.homepage = 'Link to the Shared Module homepage'
|
||||
spec.source = { :http=> ''}
|
||||
spec.authors = ''
|
||||
spec.license = ''
|
||||
spec.summary = 'Some description for the Shared Module'
|
||||
spec.vendored_frameworks = 'build/cocoapods/framework/core-appearance.framework'
|
||||
spec.libraries = 'c++'
|
||||
spec.ios.deployment_target = '14.1'
|
||||
|
||||
|
||||
spec.pod_target_xcconfig = {
|
||||
'KOTLIN_PROJECT_PATH' => ':core-appearance',
|
||||
'PRODUCT_MODULE_NAME' => 'core-appearance',
|
||||
}
|
||||
|
||||
spec.script_phases = [
|
||||
{
|
||||
:name => 'Build core_appearance',
|
||||
:execution_position => :before_compile,
|
||||
:shell_path => '/bin/sh',
|
||||
:script => <<-SCRIPT
|
||||
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
|
||||
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
|
||||
exit 0
|
||||
fi
|
||||
set -ev
|
||||
REPO_ROOT="$PODS_TARGET_SRCROOT"
|
||||
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
|
||||
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
|
||||
-Pkotlin.native.cocoapods.archs="$ARCHS" \
|
||||
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
|
||||
SCRIPT
|
||||
}
|
||||
]
|
||||
|
||||
end
|
39
core-architecture/core_architecture.podspec
Normal file
39
core-architecture/core_architecture.podspec
Normal file
@ -0,0 +1,39 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'core_architecture'
|
||||
spec.version = '1.0'
|
||||
spec.homepage = 'Link to the Shared Module homepage'
|
||||
spec.source = { :http=> ''}
|
||||
spec.authors = ''
|
||||
spec.license = ''
|
||||
spec.summary = 'Some description for the Shared Module'
|
||||
spec.vendored_frameworks = 'build/cocoapods/framework/core-architecture.framework'
|
||||
spec.libraries = 'c++'
|
||||
spec.ios.deployment_target = '14.1'
|
||||
|
||||
|
||||
spec.pod_target_xcconfig = {
|
||||
'KOTLIN_PROJECT_PATH' => ':core-architecture',
|
||||
'PRODUCT_MODULE_NAME' => 'core-architecture',
|
||||
}
|
||||
|
||||
spec.script_phases = [
|
||||
{
|
||||
:name => 'Build core_architecture',
|
||||
:execution_position => :before_compile,
|
||||
:shell_path => '/bin/sh',
|
||||
:script => <<-SCRIPT
|
||||
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
|
||||
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
|
||||
exit 0
|
||||
fi
|
||||
set -ev
|
||||
REPO_ROOT="$PODS_TARGET_SRCROOT"
|
||||
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
|
||||
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
|
||||
-Pkotlin.native.cocoapods.archs="$ARCHS" \
|
||||
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
|
||||
SCRIPT
|
||||
}
|
||||
]
|
||||
|
||||
end
|
39
core-commonui/core_commonui.podspec
Normal file
39
core-commonui/core_commonui.podspec
Normal file
@ -0,0 +1,39 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'core_commonui'
|
||||
spec.version = '1.0'
|
||||
spec.homepage = 'Link to the Shared Module homepage'
|
||||
spec.source = { :http=> ''}
|
||||
spec.authors = ''
|
||||
spec.license = ''
|
||||
spec.summary = 'Some description for the Shared Module'
|
||||
spec.vendored_frameworks = 'build/cocoapods/framework/core-commonui.framework'
|
||||
spec.libraries = 'c++'
|
||||
spec.ios.deployment_target = '14.1'
|
||||
|
||||
|
||||
spec.pod_target_xcconfig = {
|
||||
'KOTLIN_PROJECT_PATH' => ':core-commonui',
|
||||
'PRODUCT_MODULE_NAME' => 'core-commonui',
|
||||
}
|
||||
|
||||
spec.script_phases = [
|
||||
{
|
||||
:name => 'Build core_commonui',
|
||||
:execution_position => :before_compile,
|
||||
:shell_path => '/bin/sh',
|
||||
:script => <<-SCRIPT
|
||||
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
|
||||
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
|
||||
exit 0
|
||||
fi
|
||||
set -ev
|
||||
REPO_ROOT="$PODS_TARGET_SRCROOT"
|
||||
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
|
||||
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
|
||||
-Pkotlin.native.cocoapods.archs="$ARCHS" \
|
||||
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
|
||||
SCRIPT
|
||||
}
|
||||
]
|
||||
|
||||
end
|
@ -86,7 +86,7 @@ class PostDetailScreenViewModel(
|
||||
val auth = identityRepository.authToken.value
|
||||
val refreshing = currentState.refreshing
|
||||
val sort = keyStore[KeyStoreKeys.DefaultCommentSortType, 3].toSortType()
|
||||
val commentList = commentRepository.getComments(
|
||||
val commentList = commentRepository.getAll(
|
||||
auth = auth,
|
||||
postId = post.id,
|
||||
page = currentPage,
|
||||
|
39
core-preferences/core_preferences.podspec
Normal file
39
core-preferences/core_preferences.podspec
Normal file
@ -0,0 +1,39 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'core_preferences'
|
||||
spec.version = '1.0.0'
|
||||
spec.homepage = 'Link to the Shared Module homepage'
|
||||
spec.source = { :http=> ''}
|
||||
spec.authors = ''
|
||||
spec.license = ''
|
||||
spec.summary = 'Some description for the Shared Module'
|
||||
spec.vendored_frameworks = 'build/cocoapods/framework/core-preferences.framework'
|
||||
spec.libraries = 'c++'
|
||||
spec.ios.deployment_target = '14.1'
|
||||
|
||||
|
||||
spec.pod_target_xcconfig = {
|
||||
'KOTLIN_PROJECT_PATH' => ':core-preferences',
|
||||
'PRODUCT_MODULE_NAME' => 'core-preferences',
|
||||
}
|
||||
|
||||
spec.script_phases = [
|
||||
{
|
||||
:name => 'Build core_preferences',
|
||||
:execution_position => :before_compile,
|
||||
:shell_path => '/bin/sh',
|
||||
:script => <<-SCRIPT
|
||||
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
|
||||
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
|
||||
exit 0
|
||||
fi
|
||||
set -ev
|
||||
REPO_ROOT="$PODS_TARGET_SRCROOT"
|
||||
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
|
||||
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
|
||||
-Pkotlin.native.cocoapods.archs="$ARCHS" \
|
||||
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
|
||||
SCRIPT
|
||||
}
|
||||
]
|
||||
|
||||
end
|
39
core-utils/core_utils.podspec
Normal file
39
core-utils/core_utils.podspec
Normal file
@ -0,0 +1,39 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'core_utils'
|
||||
spec.version = '1.0.0'
|
||||
spec.homepage = 'Link to the Shared Module homepage'
|
||||
spec.source = { :http=> ''}
|
||||
spec.authors = ''
|
||||
spec.license = ''
|
||||
spec.summary = 'Some description for the Shared Module'
|
||||
spec.vendored_frameworks = 'build/cocoapods/framework/core-utils.framework'
|
||||
spec.libraries = 'c++'
|
||||
spec.ios.deployment_target = '14.1'
|
||||
|
||||
|
||||
spec.pod_target_xcconfig = {
|
||||
'KOTLIN_PROJECT_PATH' => ':core-utils',
|
||||
'PRODUCT_MODULE_NAME' => 'core-utils',
|
||||
}
|
||||
|
||||
spec.script_phases = [
|
||||
{
|
||||
:name => 'Build core_utils',
|
||||
:execution_position => :before_compile,
|
||||
:shell_path => '/bin/sh',
|
||||
:script => <<-SCRIPT
|
||||
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
|
||||
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
|
||||
exit 0
|
||||
fi
|
||||
set -ev
|
||||
REPO_ROOT="$PODS_TARGET_SRCROOT"
|
||||
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
|
||||
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
|
||||
-Pkotlin.native.cocoapods.archs="$ARCHS" \
|
||||
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
|
||||
SCRIPT
|
||||
}
|
||||
]
|
||||
|
||||
end
|
39
domain-identity/domain_identity.podspec
Normal file
39
domain-identity/domain_identity.podspec
Normal file
@ -0,0 +1,39 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'domain_identity'
|
||||
spec.version = '1.0'
|
||||
spec.homepage = 'Link to the Shared Module homepage'
|
||||
spec.source = { :http=> ''}
|
||||
spec.authors = ''
|
||||
spec.license = ''
|
||||
spec.summary = 'Some description for the Shared Module'
|
||||
spec.vendored_frameworks = 'build/cocoapods/framework/domain-identity.framework'
|
||||
spec.libraries = 'c++'
|
||||
spec.ios.deployment_target = '14.1'
|
||||
|
||||
|
||||
spec.pod_target_xcconfig = {
|
||||
'KOTLIN_PROJECT_PATH' => ':domain-identity',
|
||||
'PRODUCT_MODULE_NAME' => 'domain-identity',
|
||||
}
|
||||
|
||||
spec.script_phases = [
|
||||
{
|
||||
:name => 'Build domain_identity',
|
||||
:execution_position => :before_compile,
|
||||
:shell_path => '/bin/sh',
|
||||
:script => <<-SCRIPT
|
||||
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
|
||||
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
|
||||
exit 0
|
||||
fi
|
||||
set -ev
|
||||
REPO_ROOT="$PODS_TARGET_SRCROOT"
|
||||
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
|
||||
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
|
||||
-Pkotlin.native.cocoapods.archs="$ARCHS" \
|
||||
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
|
||||
SCRIPT
|
||||
}
|
||||
]
|
||||
|
||||
end
|
@ -3,6 +3,7 @@ package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
|
||||
data class CommunityModel(
|
||||
val id: Int = 0,
|
||||
val name: String = "",
|
||||
val title: String = "",
|
||||
val host: String = "",
|
||||
val icon: String? = null,
|
||||
)
|
||||
|
@ -16,7 +16,7 @@ class CommentRepository(
|
||||
const val DEFAULT_PAGE_SIZE = 20
|
||||
}
|
||||
|
||||
suspend fun getComments(
|
||||
suspend fun getAll(
|
||||
postId: Int,
|
||||
auth: String? = null,
|
||||
page: Int,
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CommunityView
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.SearchType
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.provider.ServiceProvider
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.utils.toModel
|
||||
@ -9,11 +10,40 @@ class CommunityRepository(
|
||||
private val services: ServiceProvider,
|
||||
) {
|
||||
|
||||
suspend fun getCommunity(
|
||||
companion object {
|
||||
const val DEFAULT_PAGE_SIZE = 20
|
||||
}
|
||||
|
||||
suspend fun getAll(
|
||||
query: String = "",
|
||||
auth: String? = null,
|
||||
page: Int,
|
||||
limit: Int = DEFAULT_PAGE_SIZE,
|
||||
): List<CommunityModel> {
|
||||
val response = services.search.search(
|
||||
q = query,
|
||||
auth = auth,
|
||||
page = page,
|
||||
limit = limit,
|
||||
type = SearchType.Communities,
|
||||
).body()
|
||||
return response?.communities?.map {
|
||||
it.toModel()
|
||||
}.orEmpty()
|
||||
}
|
||||
|
||||
suspend fun getSubscribed(
|
||||
auth: String? = null,
|
||||
): List<CommunityModel> {
|
||||
val response = services.site.get(auth).body()
|
||||
return response?.myUser?.follows?.map { it.toModel() }.orEmpty()
|
||||
}
|
||||
|
||||
suspend fun get(
|
||||
auth: String? = null,
|
||||
id: Int,
|
||||
): CommunityModel? {
|
||||
val response = services.community.getCommunity(
|
||||
val response = services.community.get(
|
||||
auth = auth,
|
||||
id = id,
|
||||
).body()
|
||||
|
@ -17,7 +17,7 @@ class PostsRepository(
|
||||
const val DEFAULT_PAGE_SIZE = 20
|
||||
}
|
||||
|
||||
suspend fun getPosts(
|
||||
suspend fun getAll(
|
||||
auth: String? = null,
|
||||
page: Int,
|
||||
limit: Int = DEFAULT_PAGE_SIZE,
|
||||
|
@ -8,7 +8,7 @@ class SiteRepository(
|
||||
private val serviceProvider: ServiceProvider,
|
||||
) {
|
||||
suspend fun getCurrentUser(auth: String): UserModel? {
|
||||
val response = serviceProvider.site.getSite(
|
||||
val response = serviceProvider.site.get(
|
||||
auth = auth,
|
||||
)
|
||||
return response.body()?.myUser?.let {
|
||||
|
@ -13,11 +13,11 @@ class UserRepository(
|
||||
private val serviceProvider: ServiceProvider,
|
||||
) {
|
||||
|
||||
suspend fun getUser(
|
||||
suspend fun get(
|
||||
id: Int,
|
||||
auth: String? = null,
|
||||
): UserModel? {
|
||||
val response = serviceProvider.user.getPersonDetails(
|
||||
val response = serviceProvider.user.getDetails(
|
||||
auth = auth,
|
||||
personId = id,
|
||||
)
|
||||
@ -31,7 +31,7 @@ class UserRepository(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getUserPosts(
|
||||
suspend fun getPosts(
|
||||
id: Int,
|
||||
auth: String? = null,
|
||||
page: Int,
|
||||
@ -39,7 +39,7 @@ class UserRepository(
|
||||
sort: SortType = SortType.Active,
|
||||
savedOnly: Boolean = false,
|
||||
): List<PostModel> {
|
||||
val response = serviceProvider.user.getPersonDetails(
|
||||
val response = serviceProvider.user.getDetails(
|
||||
auth = auth,
|
||||
personId = id,
|
||||
page = page,
|
||||
@ -51,14 +51,14 @@ class UserRepository(
|
||||
return dto.posts.map { it.toModel() }
|
||||
}
|
||||
|
||||
suspend fun getUserComments(
|
||||
suspend fun getComments(
|
||||
id: Int,
|
||||
auth: String? = null,
|
||||
page: Int,
|
||||
limit: Int = PostsRepository.DEFAULT_PAGE_SIZE,
|
||||
sort: SortType = SortType.Active,
|
||||
): List<CommentModel> {
|
||||
val response = serviceProvider.user.getPersonDetails(
|
||||
val response = serviceProvider.user.getDetails(
|
||||
auth = auth,
|
||||
personId = id,
|
||||
page = page,
|
||||
|
@ -2,6 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.utils
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CommentView
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.Community
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CommunityFollowerView
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.ListingType.All
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.ListingType.Local
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.ListingType.Subscribed
|
||||
@ -88,10 +89,19 @@ internal fun CommentView.toModel() = CommentModel(
|
||||
internal fun Community.toModel() = CommunityModel(
|
||||
id = id,
|
||||
name = name,
|
||||
title = title,
|
||||
icon = icon,
|
||||
host = actorId.toHost(),
|
||||
)
|
||||
|
||||
internal fun CommunityFollowerView.toModel() = CommunityModel(
|
||||
id = community.id,
|
||||
name = community.name,
|
||||
title = community.title,
|
||||
icon = community.icon,
|
||||
host = community.actorId.toHost(),
|
||||
)
|
||||
|
||||
internal fun String.toHost(): String = this.replace("https://", "").let {
|
||||
val i = it.indexOf("/")
|
||||
it.substring(0, i)
|
||||
|
@ -102,7 +102,7 @@ class HomeScreenModel(
|
||||
val type = currentState.listingType
|
||||
val sort = currentState.sortType
|
||||
val refreshing = currentState.refreshing
|
||||
val postList = postsRepository.getPosts(
|
||||
val postList = postsRepository.getAll(
|
||||
auth = auth,
|
||||
page = currentPage,
|
||||
type = type,
|
||||
|
@ -49,7 +49,7 @@ class ProfileCommentsViewModel(
|
||||
mvi.updateState { it.copy(loading = true) }
|
||||
val auth = identityRepository.authToken.value
|
||||
val refreshing = currentState.refreshing
|
||||
val commentList = userRepository.getUserComments(
|
||||
val commentList = userRepository.getComments(
|
||||
auth = auth,
|
||||
id = user.id,
|
||||
page = currentPage,
|
||||
|
@ -50,7 +50,7 @@ class ProfilePostsViewModel(
|
||||
mvi.updateState { it.copy(loading = true) }
|
||||
val auth = identityRepository.authToken.value
|
||||
val refreshing = currentState.refreshing
|
||||
val postList = userRepository.getUserPosts(
|
||||
val postList = userRepository.getPosts(
|
||||
auth = auth,
|
||||
id = user.id,
|
||||
savedOnly = savedOnly,
|
||||
|
@ -41,13 +41,20 @@ kotlin {
|
||||
implementation(compose.materialIconsExtended)
|
||||
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
|
||||
implementation(compose.components.resources)
|
||||
implementation(compose.material)
|
||||
|
||||
implementation(libs.voyager.navigator)
|
||||
implementation(libs.voyager.tab)
|
||||
implementation(libs.kamel)
|
||||
|
||||
implementation(projects.resources)
|
||||
implementation(projects.coreArchitecture)
|
||||
implementation(projects.coreAppearance)
|
||||
|
||||
implementation(projects.domainIdentity)
|
||||
implementation(projects.domainLemmy.data)
|
||||
implementation(projects.domainLemmy.repository)
|
||||
|
||||
implementation(projects.resources)
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
|
@ -1,18 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
||||
import org.koin.dsl.module
|
||||
import org.koin.java.KoinJavaComponent.inject
|
||||
|
||||
actual val searchTabModule = module {
|
||||
factory {
|
||||
SearchScreenModel(
|
||||
mvi = DefaultMviModel(SearchScreenMviModel.UiState()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun getSearchScreenModel(): SearchScreenModel {
|
||||
val res: SearchScreenModel by inject(SearchScreenModel::class.java)
|
||||
return res
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search.di
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.viewmodel.SearchScreenModel
|
||||
import org.koin.java.KoinJavaComponent.inject
|
||||
|
||||
actual fun getSearchScreenModel(): SearchScreenModel {
|
||||
val res: SearchScreenModel by inject(SearchScreenModel::class.java)
|
||||
return res
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search
|
||||
|
||||
import org.koin.core.module.Module
|
||||
|
||||
expect val searchTabModule: Module
|
||||
|
||||
expect fun getSearchScreenModel(): SearchScreenModel
|
@ -1,10 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search
|
||||
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
|
||||
class SearchScreenModel(
|
||||
private val mvi: DefaultMviModel<SearchScreenMviModel.Intent, SearchScreenMviModel.UiState, SearchScreenMviModel.Effect>,
|
||||
) : ScreenModel,
|
||||
MviModel<SearchScreenMviModel.Intent, SearchScreenMviModel.UiState, SearchScreenMviModel.Effect> by mvi
|
@ -1,12 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
|
||||
interface SearchScreenMviModel :
|
||||
MviModel<SearchScreenMviModel.Intent, SearchScreenMviModel.UiState, SearchScreenMviModel.Effect> {
|
||||
sealed interface Intent
|
||||
|
||||
data class UiState(val loading: Boolean = false)
|
||||
|
||||
sealed interface Effect
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Explore
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.di.getLanguageRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.di.staticString
|
||||
import dev.icerock.moko.resources.desc.desc
|
||||
|
||||
object SearchTab : Tab {
|
||||
|
||||
override val options: TabOptions
|
||||
@Composable
|
||||
get() {
|
||||
val icon = rememberVectorPainter(Icons.Default.Explore)
|
||||
val languageRepository = remember { getLanguageRepository() }
|
||||
val lang by languageRepository.currentLanguage.collectAsState()
|
||||
return remember(lang) {
|
||||
val title = staticString(MR.strings.navigation_search.desc())
|
||||
TabOptions(
|
||||
index = 1u,
|
||||
title = title,
|
||||
icon = icon,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = rememberScreenModel { getSearchScreenModel() }
|
||||
model.bindToLifecycle(key)
|
||||
|
||||
Column(modifier = Modifier.padding(Spacing.xs)) {
|
||||
Text(
|
||||
text = "Search content",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search.di
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.viewmodel.SearchScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.viewmodel.SearchScreenMviModel
|
||||
import org.koin.dsl.module
|
||||
|
||||
val searchTabModule = module {
|
||||
factory {
|
||||
SearchScreenModel(
|
||||
mvi = DefaultMviModel(SearchScreenMviModel.UiState()),
|
||||
apiConfigRepository = get(),
|
||||
identityRepository = get(),
|
||||
communityRepository = get(),
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search.di
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.viewmodel.SearchScreenModel
|
||||
|
||||
expect fun getSearchScreenModel(): SearchScreenModel
|
@ -0,0 +1,84 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search.ui
|
||||
|
||||
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.domain.lemmy.data.CommunityModel
|
||||
import io.kamel.image.KamelImage
|
||||
import io.kamel.image.asyncPainterResource
|
||||
|
||||
@Composable
|
||||
internal fun CommunityItem(
|
||||
community: CommunityModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val title = community.title
|
||||
val communityName = community.name
|
||||
val communityIcon = community.icon.orEmpty()
|
||||
val communityHost = community.host
|
||||
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()) {
|
||||
val painterResource = asyncPainterResource(data = communityIcon)
|
||||
KamelImage(
|
||||
modifier = Modifier.padding(Spacing.xxxs).size(iconSize)
|
||||
.clip(RoundedCornerShape(iconSize / 2)),
|
||||
resource = painterResource,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillBounds,
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier.padding(Spacing.xxxs).size(iconSize)
|
||||
.background(MaterialTheme.colorScheme.primary)
|
||||
.clip(RoundedCornerShape(iconSize / 2)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = community.name.firstOrNull()?.toString().orEmpty(),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
Column {
|
||||
Text(
|
||||
text = buildString {
|
||||
append(title)
|
||||
},
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
Text(
|
||||
text = buildString {
|
||||
append("!")
|
||||
append(communityName)
|
||||
if (communityHost.isNotEmpty()) {
|
||||
append("@$communityHost")
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search.ui
|
||||
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Explore
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.CheckboxDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
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.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.di.getSearchScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.viewmodel.SearchScreenMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.di.getLanguageRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.di.staticString
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import dev.icerock.moko.resources.desc.desc
|
||||
|
||||
object SearchTab : Tab {
|
||||
|
||||
override val options: TabOptions
|
||||
@Composable get() {
|
||||
val icon = rememberVectorPainter(Icons.Default.Explore)
|
||||
val languageRepository = remember { getLanguageRepository() }
|
||||
val lang by languageRepository.currentLanguage.collectAsState()
|
||||
return remember(lang) {
|
||||
val title = staticString(MR.strings.navigation_search.desc())
|
||||
TabOptions(
|
||||
index = 1u,
|
||||
title = title,
|
||||
icon = icon,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = rememberScreenModel { getSearchScreenModel() }
|
||||
model.bindToLifecycle(key)
|
||||
val uiState by model.uiState.collectAsState()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(Spacing.xxs),
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.xxs),
|
||||
) {
|
||||
TextField(
|
||||
modifier = Modifier.padding(
|
||||
horizontal = Spacing.m,
|
||||
vertical = Spacing.s,
|
||||
).fillMaxWidth(),
|
||||
label = {
|
||||
Text(text = stringResource(MR.strings.explore_search_placeholder))
|
||||
},
|
||||
singleLine = true,
|
||||
value = uiState.searchText,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Text,
|
||||
),
|
||||
onValueChange = { value ->
|
||||
model.reduce(SearchScreenMviModel.Intent.SetSearch(value))
|
||||
},
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = Spacing.xxs),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (uiState.isLogged) {
|
||||
Checkbox(
|
||||
checked = uiState.subscribedOnly,
|
||||
onCheckedChange = {
|
||||
model.reduce(SearchScreenMviModel.Intent.SetSubscribedOnly(it))
|
||||
},
|
||||
colors = CheckboxDefaults.colors(checkedColor = MaterialTheme.colorScheme.primary),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(MR.strings.explore_subscribed_only),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Button(onClick = {
|
||||
model.reduce(SearchScreenMviModel.Intent.SearchFired)
|
||||
}) {
|
||||
Text(
|
||||
text = stringResource(MR.strings.button_search),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val pullRefreshState = rememberPullRefreshState(uiState.refreshing, {
|
||||
model.reduce(SearchScreenMviModel.Intent.Refresh)
|
||||
})
|
||||
Box(
|
||||
modifier = Modifier.padding(Spacing.xxs).pullRefresh(pullRefreshState),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||
) {
|
||||
items(uiState.communities) { community ->
|
||||
CommunityItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
community = community,
|
||||
)
|
||||
}
|
||||
item {
|
||||
if (!uiState.loading && !uiState.refreshing && uiState.canFetchMore) {
|
||||
model.reduce(SearchScreenMviModel.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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = uiState.refreshing,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search.viewmodel
|
||||
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommunityRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostsRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SearchScreenModel(
|
||||
private val mvi: DefaultMviModel<SearchScreenMviModel.Intent, SearchScreenMviModel.UiState, SearchScreenMviModel.Effect>,
|
||||
private val apiConfigRepository: ApiConfigurationRepository,
|
||||
private val identityRepository: IdentityRepository,
|
||||
private val communityRepository: CommunityRepository,
|
||||
) : ScreenModel,
|
||||
MviModel<SearchScreenMviModel.Intent, SearchScreenMviModel.UiState, SearchScreenMviModel.Effect> by mvi {
|
||||
|
||||
private var currentPage: Int = 1
|
||||
|
||||
override fun onStarted() {
|
||||
mvi.onStarted()
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
instance = apiConfigRepository.getInstance(),
|
||||
)
|
||||
}
|
||||
mvi.scope.launch {
|
||||
identityRepository.authToken.map { !it.isNullOrEmpty() }.onEach { isLogged ->
|
||||
mvi.updateState {
|
||||
it.copy(isLogged = isLogged)
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun reduce(intent: SearchScreenMviModel.Intent) {
|
||||
when (intent) {
|
||||
SearchScreenMviModel.Intent.LoadNextPage -> loadNextPage()
|
||||
SearchScreenMviModel.Intent.Refresh -> refresh()
|
||||
SearchScreenMviModel.Intent.SearchFired -> refresh()
|
||||
is SearchScreenMviModel.Intent.SetSearch -> setSearch(intent.value)
|
||||
is SearchScreenMviModel.Intent.SetSubscribedOnly -> applySubscribedOnly(intent.value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSearch(value: String) {
|
||||
mvi.updateState { it.copy(searchText = value) }
|
||||
}
|
||||
|
||||
private fun applySubscribedOnly(value: Boolean) {
|
||||
mvi.updateState { it.copy(subscribedOnly = value) }
|
||||
refresh()
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
currentPage = 1
|
||||
mvi.updateState { it.copy(canFetchMore = true, refreshing = true) }
|
||||
loadNextPage()
|
||||
}
|
||||
|
||||
private fun loadNextPage() {
|
||||
val currentState = mvi.uiState.value
|
||||
if (!currentState.canFetchMore || currentState.loading) {
|
||||
return
|
||||
}
|
||||
|
||||
mvi.scope.launch(Dispatchers.IO) {
|
||||
mvi.updateState { it.copy(loading = true) }
|
||||
val searchText = mvi.uiState.value.searchText
|
||||
val auth = identityRepository.authToken.value
|
||||
val refreshing = currentState.refreshing
|
||||
val subscribedOnly = currentState.subscribedOnly
|
||||
if (subscribedOnly) {
|
||||
val items = communityRepository.getSubscribed(
|
||||
auth = auth,
|
||||
).filter {
|
||||
it.name.contains(searchText)
|
||||
}
|
||||
currentPage++
|
||||
mvi.updateState {
|
||||
val newItems = if (refreshing) {
|
||||
items
|
||||
} else {
|
||||
it.communities + items
|
||||
}
|
||||
it.copy(
|
||||
communities = newItems,
|
||||
loading = false,
|
||||
canFetchMore = false,
|
||||
refreshing = false,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val items = communityRepository.getAll(
|
||||
query = searchText,
|
||||
auth = auth,
|
||||
page = currentPage,
|
||||
)
|
||||
currentPage++
|
||||
val canFetchMore = items.size >= PostsRepository.DEFAULT_PAGE_SIZE
|
||||
mvi.updateState {
|
||||
val newItems = if (refreshing) {
|
||||
items
|
||||
} else {
|
||||
it.communities + items
|
||||
}
|
||||
it.copy(
|
||||
communities = newItems,
|
||||
loading = false,
|
||||
canFetchMore = canFetchMore,
|
||||
refreshing = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search.viewmodel
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
|
||||
|
||||
interface SearchScreenMviModel :
|
||||
MviModel<SearchScreenMviModel.Intent, SearchScreenMviModel.UiState, SearchScreenMviModel.Effect> {
|
||||
sealed interface Intent {
|
||||
object Refresh : Intent
|
||||
object LoadNextPage : Intent
|
||||
object SearchFired : Intent
|
||||
data class SetSearch(val value: String) : Intent
|
||||
data class SetSubscribedOnly(val value: Boolean) : Intent
|
||||
}
|
||||
|
||||
data class UiState(
|
||||
val refreshing: Boolean = false,
|
||||
val loading: Boolean = false,
|
||||
val canFetchMore: Boolean = true,
|
||||
val isLogged: Boolean = false,
|
||||
val instance: String = "",
|
||||
val searchText: String = "",
|
||||
val subscribedOnly: Boolean = true,
|
||||
val communities: List<CommunityModel> = emptyList(),
|
||||
)
|
||||
|
||||
sealed interface Effect
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual val searchTabModule = module {
|
||||
factory {
|
||||
SearchScreenModel(
|
||||
mvi = DefaultMviModel(SearchScreenMviModel.UiState()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun getSearchScreenModel() = SearchScreenModelHelper.model
|
||||
|
||||
object SearchScreenModelHelper : KoinComponent {
|
||||
val model: SearchScreenModel by inject()
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.search.di
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.viewmodel.SearchScreenModel
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
actual fun getSearchScreenModel() = SearchScreenModelHelper.model
|
||||
|
||||
object SearchScreenModelHelper : KoinComponent {
|
||||
val model: SearchScreenModel by inject()
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
<string name="message_missing_field">Missing field</string>
|
||||
|
||||
<string name="button_confirm">Confirm</string>
|
||||
<string name="button_search">Search</string>
|
||||
|
||||
<string name="home_listing_type_all">All</string>
|
||||
<string name="home_listing_type_local">Local</string>
|
||||
@ -36,6 +37,9 @@
|
||||
<string name="home_sort_type_top_year">Top year</string>
|
||||
<string name="home_instance_via">via %1$s</string>
|
||||
|
||||
<string name="explore_search_placeholder">Search for communities</string>
|
||||
<string name="explore_subscribed_only">Subscribed only</string>
|
||||
|
||||
<string name="profile_not_logged_message">You are currently not logged in.\nPlease add an
|
||||
account to continue.
|
||||
</string>
|
||||
|
@ -12,6 +12,7 @@
|
||||
<string name="message_missing_field">Campo obbligatorio</string>
|
||||
|
||||
<string name="button_confirm">Conferma</string>
|
||||
<string name="button_search">Cerca</string>
|
||||
|
||||
<string name="home_listing_type_all">Tutti</string>
|
||||
<string name="home_listing_type_local">Locali</string>
|
||||
@ -33,6 +34,9 @@
|
||||
<string name="home_sort_type_top_year">Top anno</string>
|
||||
<string name="home_instance_via">via %1$s</string>
|
||||
|
||||
<string name="explore_search_placeholder">Cerca comunità</string>
|
||||
<string name="explore_subscribed_only">Solo sottoscrizioni</string>
|
||||
|
||||
<string name="profile_not_logged_message">Login non effettuato.\nAggiungi un account per
|
||||
continuare.
|
||||
</string>
|
||||
|
@ -8,7 +8,7 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.identity.di.coreIdentityM
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.home.di.homeTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.inboxTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.di.profileTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.searchTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.di.searchTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.settings.di.settingsTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.di.localizationModule
|
||||
import org.koin.dsl.module
|
||||
|
@ -22,7 +22,7 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.identity.di.getApiConfigu
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.home.ui.HomeTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.InboxTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.ui.ProfileTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.SearchTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.ui.SearchTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.settings.ui.SettingsTab
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.di.getLanguageRepository
|
||||
|
@ -8,7 +8,7 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.identity.di.coreIdentityM
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.home.di.homeTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.inbox.inboxTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.di.profileTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.searchTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.search.di.searchTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.settings.di.settingsTabModule
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.di.localizationModule
|
||||
import org.koin.core.context.startKoin
|
||||
|
Loading…
x
Reference in New Issue
Block a user