diff --git a/.editorconfig b/.editorconfig index 0a8760a73..a674f0ed5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,9 +6,8 @@ ktlint_standard_if-else-wrapping = disabled ktlint_standard_no-wildcard-imports = disabled ktlint_standard_comment-wrapping = disabled ktlint_function_naming_ignore_when_annotated_with=Composable - -[app/src/main/java/com/jerboa/datatypes/**.{kt,kts}] -ktlint = disabled +ktlint_standard_multiline-expression-wrapping = disabled +ktlint_standard_string-template-indent = disabled [app/src/main/java/com/jerboa/DefaultInstances.kt] ktlint = disabled diff --git a/app/src/main/java/com/jerboa/Constants.kt b/app/src/main/java/com/jerboa/Constants.kt index 08132680a..8dcc189fd 100644 --- a/app/src/main/java/com/jerboa/Constants.kt +++ b/app/src/main/java/com/jerboa/Constants.kt @@ -3,3 +3,5 @@ package com.jerboa const val DEBOUNCE_DELAY = 1000L const val MAX_POST_TITLE_LENGTH = 200 const val DEFAULT_FONT_SIZE = 16 + +const val COMMENTS_DEPTH_MAX = 6 diff --git a/app/src/main/java/com/jerboa/MainActivity.kt b/app/src/main/java/com/jerboa/MainActivity.kt index e0598fd7d..cf3d28b14 100644 --- a/app/src/main/java/com/jerboa/MainActivity.kt +++ b/app/src/main/java/com/jerboa/MainActivity.kt @@ -6,7 +6,6 @@ import android.os.Build import android.os.Bundle import android.util.Patterns import android.widget.TextView -import android.widget.Toast import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity @@ -49,7 +48,6 @@ import com.jerboa.ui.components.common.Route import com.jerboa.ui.components.common.ShowChangelog import com.jerboa.ui.components.common.SwipeToNavigateBack import com.jerboa.ui.components.community.CommunityActivity -import com.jerboa.ui.components.community.list.CommunityListActivity import com.jerboa.ui.components.community.sidebar.CommunitySidebarActivity import com.jerboa.ui.components.home.BottomNavActivity import com.jerboa.ui.components.home.sidebar.SiteSidebarActivity @@ -64,6 +62,7 @@ import com.jerboa.ui.components.privatemessage.CreatePrivateMessageActivity import com.jerboa.ui.components.privatemessage.PrivateMessageReplyActivity import com.jerboa.ui.components.report.comment.CreateCommentReportActivity import com.jerboa.ui.components.report.post.CreatePostReportActivity +import com.jerboa.ui.components.search.SearchActivity import com.jerboa.ui.components.settings.SettingsActivity import com.jerboa.ui.components.settings.about.AboutActivity import com.jerboa.ui.components.settings.account.AccountSettingsActivity @@ -366,12 +365,14 @@ class MainActivity : AppCompatActivity() { ), ) { val args = Route.CommunityListArgs(it) - CommunityListActivity( + SearchActivity( appState = appState, - selectMode = args.select, - blurNSFW = appSettings.blurNSFW, + selectCommunityMode = args.select, + appSettings = appSettings, drawerState = drawerState, followList = siteViewModel.getFollowList(), + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, ) } diff --git a/app/src/main/java/com/jerboa/Utils.kt b/app/src/main/java/com/jerboa/Utils.kt index 440b01d61..aad5e252a 100644 --- a/app/src/main/java/com/jerboa/Utils.kt +++ b/app/src/main/java/com/jerboa/Utils.kt @@ -51,6 +51,7 @@ import coil.annotation.ExperimentalCoilApi import coil.imageLoader import com.jerboa.api.API import com.jerboa.api.ApiState +import com.jerboa.datatypes.getDisplayName import com.jerboa.db.APP_SETTINGS_DEFAULT import com.jerboa.db.entity.AppSettings import com.jerboa.ui.components.common.Route @@ -1098,7 +1099,7 @@ fun showBlockPersonToast( is ApiState.Success -> { Toast.makeText( ctx, - "${blockPersonRes.data.person_view.person.name} Blocked", + "${blockPersonRes.data.person_view.person.getDisplayName()} Blocked", Toast.LENGTH_SHORT, ) .show() @@ -1233,10 +1234,9 @@ fun findAndUpdatePost( posts: List, updatedPostView: PostView, ): List { - val foundIndex = - posts.indexOfFirst { - it.post.id == updatedPostView.post.id - } + val foundIndex = posts.indexOfFirst { + it.post.id == updatedPostView.post.id + } return if (foundIndex != -1) { val mutable = posts.toMutableList() mutable[foundIndex] = updatedPostView diff --git a/app/src/main/java/com/jerboa/datatypes/Others.kt b/app/src/main/java/com/jerboa/datatypes/Others.kt index 670711d58..0c8a3878d 100644 --- a/app/src/main/java/com/jerboa/datatypes/Others.kt +++ b/app/src/main/java/com/jerboa/datatypes/Others.kt @@ -21,7 +21,6 @@ import it.vercruysse.lemmyapi.dto.CommentSortType import it.vercruysse.lemmyapi.dto.ListingType import it.vercruysse.lemmyapi.dto.SortType - data class CommentSortData( @StringRes val text: Int, val icon: ImageVector, @@ -51,7 +50,7 @@ val SortType.data: SortData SortType.Controversial -> SortData( R.string.sorttype_controversial, R.string.sorttype_controversial, - Icons.Outlined.ThumbsUpDown + Icons.Outlined.ThumbsUpDown, ) SortType.TopDay -> SortData(R.string.sorttype_topday, R.string.dialogs_top_day, Icons.Outlined.BarChart) @@ -62,50 +61,49 @@ val SortType.data: SortData SortType.MostComments -> SortData( R.string.sorttype_mostcomments, R.string.sorttype_mostcomments, - Icons.Outlined.FormatListNumbered + Icons.Outlined.FormatListNumbered, ) SortType.NewComments -> SortData( R.string.sorttype_newcomments, R.string.sorttype_newcomments, - Icons.Outlined.NewReleases + Icons.Outlined.NewReleases, ) SortType.TopHour -> SortData(R.string.sorttype_tophour, R.string.dialogs_top_hour, Icons.Outlined.BarChart) SortType.TopSixHour -> SortData( R.string.sorttype_topsixhour, R.string.dialogs_top_six_hour, - Icons.Outlined.BarChart + Icons.Outlined.BarChart, ) SortType.TopTwelveHour -> SortData( R.string.sorttype_toptwelvehour, R.string.dialogs_top_twelve_hour, - Icons.Outlined.BarChart + Icons.Outlined.BarChart, ) SortType.TopThreeMonths -> SortData( R.string.sorttype_topthreemonths, R.string.dialogs_top_three_month, - Icons.Outlined.BarChart + Icons.Outlined.BarChart, ) SortType.TopSixMonths -> SortData( R.string.sorttype_topsixmonths, R.string.dialogs_top_six_month, - Icons.Outlined.BarChart + Icons.Outlined.BarChart, ) SortType.TopNineMonths -> SortData( R.string.sorttype_topninemonths, R.string.dialogs_top_nine_month, - Icons.Outlined.BarChart + Icons.Outlined.BarChart, ) SortType.Scaled -> SortData(R.string.sorttype_scaled, R.string.sorttype_scaled, Icons.Outlined.Scale) } - /** * Returns localized Strings for UserTab Enum */ @@ -113,13 +111,11 @@ fun getLocalizedStringForUserTab( ctx: Context, tab: UserTab, ): String { - val returnString = - when (tab) { - UserTab.About -> ctx.getString(R.string.person_profile_activity_about) - UserTab.Posts -> ctx.getString(R.string.person_profile_activity_posts) - UserTab.Comments -> ctx.getString(R.string.person_profile_activity_comments) - } - return returnString + return when (tab) { + UserTab.About -> ctx.getString(R.string.person_profile_activity_about) + UserTab.Posts -> ctx.getString(R.string.person_profile_activity_posts) + UserTab.Comments -> ctx.getString(R.string.person_profile_activity_comments) + } } /** @@ -129,14 +125,12 @@ fun getLocalizedListingTypeName( ctx: Context, listingType: ListingType, ): String { - val returnString = - when (listingType) { - ListingType.All -> ctx.getString(R.string.home_all) - ListingType.Local -> ctx.getString(R.string.home_local) - ListingType.Subscribed -> ctx.getString(R.string.home_subscribed) - ListingType.ModeratorView -> ctx.getString(R.string.home_moderator_view) - } - return returnString + return when (listingType) { + ListingType.All -> ctx.getString(R.string.home_all) + ListingType.Local -> ctx.getString(R.string.home_local) + ListingType.Subscribed -> ctx.getString(R.string.home_subscribed) + ListingType.ModeratorView -> ctx.getString(R.string.home_moderator_view) + } } /** @@ -146,15 +140,13 @@ fun getLocalizedCommentSortTypeName( ctx: Context, commentSortType: CommentSortType, ): String { - val returnString = - when (commentSortType) { - CommentSortType.Hot -> ctx.getString(R.string.sorttype_hot) - CommentSortType.New -> ctx.getString(R.string.sorttype_new) - CommentSortType.Old -> ctx.getString(R.string.sorttype_old) - CommentSortType.Top -> ctx.getString(R.string.dialogs_top) - CommentSortType.Controversial -> ctx.getString(R.string.sorttype_controversial) - } - return returnString + return when (commentSortType) { + CommentSortType.Hot -> ctx.getString(R.string.sorttype_hot) + CommentSortType.New -> ctx.getString(R.string.sorttype_new) + CommentSortType.Old -> ctx.getString(R.string.sorttype_old) + CommentSortType.Top -> ctx.getString(R.string.dialogs_top) + CommentSortType.Controversial -> ctx.getString(R.string.sorttype_controversial) + } } /** @@ -164,12 +156,10 @@ fun getLocalizedUnreadOrAllName( ctx: Context, unreadOrAll: UnreadOrAll, ): String { - val returnString = - when (unreadOrAll) { - UnreadOrAll.Unread -> ctx.getString(R.string.dialogs_unread) - UnreadOrAll.All -> ctx.getString(R.string.dialogs_all) - } - return returnString + return when (unreadOrAll) { + UnreadOrAll.Unread -> ctx.getString(R.string.dialogs_unread) + UnreadOrAll.All -> ctx.getString(R.string.dialogs_all) + } } /** @@ -179,11 +169,9 @@ fun getLocalizedStringForInboxTab( ctx: Context, tab: InboxTab, ): String { - val returnString = - when (tab) { - InboxTab.Replies -> ctx.getString(R.string.inbox_activity_replies) - InboxTab.Mentions -> ctx.getString(R.string.inbox_activity_mentions) - InboxTab.Messages -> ctx.getString(R.string.inbox_activity_messages) - } - return returnString + return when (tab) { + InboxTab.Replies -> ctx.getString(R.string.inbox_activity_replies) + InboxTab.Mentions -> ctx.getString(R.string.inbox_activity_mentions) + InboxTab.Messages -> ctx.getString(R.string.inbox_activity_messages) + } } diff --git a/app/src/main/java/com/jerboa/datatypes/SampleData.kt b/app/src/main/java/com/jerboa/datatypes/SampleData.kt index 37d7a0389..6efa51020 100644 --- a/app/src/main/java/com/jerboa/datatypes/SampleData.kt +++ b/app/src/main/java/com/jerboa/datatypes/SampleData.kt @@ -158,7 +158,7 @@ val samplePerson = updated = "2021-10-11T07:14:53.548707", actor_id = "https://lemmy.ml/u/homeless", bio = - "This is my bio.\n\nI like trucks, trains, and geese. This is *one* longer line " + + "This is my bio.\n\nI like trucks, trains, and geese. This is *one* longer line " + "that I have in here. But I'm not sure blah blah blah\n\nI have " + "**tres ojos**.", local = true, @@ -311,7 +311,7 @@ val sampleComment = creator_id = 56450, post_id = 139549, content = - "This *looks* really cool and similar to Joplin. **Having issues** getting LaTeX to" + + "This *looks* really cool and similar to Joplin. **Having issues** getting LaTeX to" + " " + "work" + ".\n\nIts kind of a long comment\n\nbut I don't want...", diff --git a/app/src/main/java/com/jerboa/datatypes/Utils.kt b/app/src/main/java/com/jerboa/datatypes/Utils.kt index ea06bc8d2..bc3a00771 100644 --- a/app/src/main/java/com/jerboa/datatypes/Utils.kt +++ b/app/src/main/java/com/jerboa/datatypes/Utils.kt @@ -2,5 +2,4 @@ package com.jerboa.datatypes import it.vercruysse.lemmyapi.v0x19.datatypes.Person - fun Person.getDisplayName(): String = this.display_name ?: this.name diff --git a/app/src/main/java/com/jerboa/model/CommunityListViewModel.kt b/app/src/main/java/com/jerboa/model/CommunityListViewModel.kt deleted file mode 100644 index 5e391ea53..000000000 --- a/app/src/main/java/com/jerboa/model/CommunityListViewModel.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.jerboa.model - -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope -import androidx.lifecycle.viewmodel.CreationExtras -import com.jerboa.api.API -import com.jerboa.api.ApiState -import com.jerboa.api.toApiState -import it.vercruysse.lemmyapi.dto.SearchType -import it.vercruysse.lemmyapi.dto.SubscribedType -import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityAggregates -import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityFollowerView -import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityView -import it.vercruysse.lemmyapi.v0x19.datatypes.Search -import it.vercruysse.lemmyapi.v0x19.datatypes.SearchResponse -import kotlinx.collections.immutable.ImmutableList -import kotlinx.coroutines.launch - -class CommunityListViewModel(communities: ImmutableList) : ViewModel() { - var searchRes: ApiState by mutableStateOf(ApiState.Empty) - private set - - fun searchCommunities(form: Search) { - viewModelScope.launch { - searchRes = ApiState.Loading - searchRes = API.getInstance().search(form).toApiState() - } - } - - init { - setCommunityListFromFollowed(communities) - } - - private fun setCommunityListFromFollowed(myFollows: ImmutableList) { - // A hack to convert communityFollowerView into CommunityView - val followsIntoCommunityViews = - myFollows.map { cfv -> - CommunityView( - community = cfv.community, - subscribed = SubscribedType.Subscribed, - blocked = false, - counts = - CommunityAggregates( - community_id = cfv.community.id, - subscribers = 0, - posts = 0, - comments = 0, - published = "", - users_active_day = 0, - users_active_week = 0, - users_active_month = 0, - users_active_half_year = 0, - ), - ) - } - - searchRes = - ApiState.Success( - SearchResponse( - type_ = SearchType.Communities, - communities = followsIntoCommunityViews, - comments = emptyList(), - posts = emptyList(), - users = emptyList(), - ), - ) - } - - companion object { - class Factory( - private val followedCommunities: ImmutableList, - ) : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create( - modelClass: Class, - extras: CreationExtras, - ): T { - return CommunityListViewModel(followedCommunities) as T - } - } - } -} diff --git a/app/src/main/java/com/jerboa/model/HomeViewModel.kt b/app/src/main/java/com/jerboa/model/HomeViewModel.kt index 56c41246c..b9259da66 100644 --- a/app/src/main/java/com/jerboa/model/HomeViewModel.kt +++ b/app/src/main/java/com/jerboa/model/HomeViewModel.kt @@ -3,26 +3,19 @@ package com.jerboa.model import android.util.Log import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory -import com.jerboa.api.ApiState import com.jerboa.db.entity.AnonAccount import com.jerboa.db.repository.AccountRepository import com.jerboa.jerboaApplication import com.jerboa.toEnumSafe -import it.vercruysse.lemmyapi.v0x19.datatypes.PostResponse import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch class HomeViewModel(private val accountRepository: AccountRepository) : PostsViewModel() { - private var likePostRes: ApiState by mutableStateOf(ApiState.Empty) - private var savePostRes: ApiState by mutableStateOf(ApiState.Empty) - private var deletePostRes: ApiState by mutableStateOf(ApiState.Empty) - val lazyListState = LazyListState() init { diff --git a/app/src/main/java/com/jerboa/model/InboxViewModel.kt b/app/src/main/java/com/jerboa/model/InboxViewModel.kt index dd57a814f..c19656e50 100644 --- a/app/src/main/java/com/jerboa/model/InboxViewModel.kt +++ b/app/src/main/java/com/jerboa/model/InboxViewModel.kt @@ -19,8 +19,7 @@ import com.jerboa.findAndUpdateMention import com.jerboa.findAndUpdatePersonMention import com.jerboa.findAndUpdatePrivateMessage import com.jerboa.getDeduplicateMerge -import com.jerboa.showBlockCommunityToast -import com.jerboa.showBlockPersonToast +import com.jerboa.util.blockPerson import it.vercruysse.lemmyapi.dto.CommentSortType import it.vercruysse.lemmyapi.v0x19.datatypes.* import kotlinx.coroutines.launch @@ -55,9 +54,6 @@ class InboxViewModel(account: Account, siteViewModel: SiteViewModel) : ViewModel private var markAllAsReadRes: ApiState by mutableStateOf(ApiState.Empty) - private var blockCommunityRes: ApiState by - mutableStateOf(ApiState.Empty) - private var blockPersonRes: ApiState by mutableStateOf(ApiState.Empty) @@ -436,27 +432,10 @@ class InboxViewModel(account: Account, siteViewModel: SiteViewModel) : ViewModel } } - fun blockCommunity( - form: BlockCommunity, - ctx: Context, - ) { - viewModelScope.launch { - blockCommunityRes = ApiState.Loading - blockCommunityRes = API.getInstance().blockCommunity(form).toApiState() - showBlockCommunityToast(blockCommunityRes, ctx) - } - } - fun blockPerson( form: BlockPerson, ctx: Context, - ) { - viewModelScope.launch { - blockPersonRes = ApiState.Loading - blockPersonRes = API.getInstance().blockPerson(form).toApiState() - showBlockPersonToast(blockPersonRes, ctx) - } - } + ) = blockPerson(viewModelScope, form, ctx) fun markAllAsRead(onComplete: () -> Unit) { viewModelScope.launch { diff --git a/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt b/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt index db6474214..b3f3f83aa 100644 --- a/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt @@ -1,6 +1,5 @@ package com.jerboa.model -import android.content.Context import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -17,25 +16,20 @@ import com.jerboa.api.toApiState import com.jerboa.findAndUpdateComment import com.jerboa.findAndUpdatePost import com.jerboa.getDeduplicateMerge -import com.jerboa.showBlockCommunityToast -import com.jerboa.showBlockPersonToast +import com.jerboa.model.helper.CommentsHelper +import com.jerboa.model.helper.PostsHelper import it.vercruysse.lemmyapi.dto.SortType import it.vercruysse.lemmyapi.v0x19.datatypes.* import kotlinx.coroutines.launch -class PersonProfileViewModel(personArg: Either, savedMode: Boolean) : ViewModel() { +class PersonProfileViewModel( + personArg: Either, + savedMode: Boolean, +) : ViewModel(), CommentsHelper, PostsHelper { var personDetailsRes: ApiState by mutableStateOf(ApiState.Empty) private set - private var likePostRes: ApiState by mutableStateOf(ApiState.Empty) - private var savePostRes: ApiState by mutableStateOf(ApiState.Empty) - private var deletePostRes: ApiState by mutableStateOf(ApiState.Empty) - private var blockCommunityRes: ApiState by mutableStateOf(ApiState.Empty) - private var blockPersonRes: ApiState by mutableStateOf(ApiState.Empty) - - private var likeCommentRes: ApiState by mutableStateOf(ApiState.Empty) - private var saveCommentRes: ApiState by mutableStateOf(ApiState.Empty) - private var deleteCommentRes: ApiState by mutableStateOf(ApiState.Empty) + override val scope = viewModelScope private var markPostRes: ApiState by mutableStateOf(ApiState.Empty) @@ -95,12 +89,11 @@ class PersonProfileViewModel(personArg: Either, savedMode: Boo fun appendData(profileId: PersonId) { viewModelScope.launch { val oldRes = personDetailsRes - personDetailsRes = - when (oldRes) { - is ApiState.Appending -> return@launch - is ApiState.Holder -> ApiState.Appending(oldRes.data) - else -> return@launch - } + personDetailsRes = when (oldRes) { + is ApiState.Appending -> return@launch + is ApiState.Holder -> ApiState.Appending(oldRes.data) + else -> return@launch + } nextPage() val form = @@ -137,140 +130,25 @@ class PersonProfileViewModel(personArg: Either, savedMode: Boo } } - fun likePost(form: CreatePostLike) { - viewModelScope.launch { - likePostRes = ApiState.Loading - likePostRes = API.getInstance().createPostLike(form).toApiState() - - when (val likeRes = likePostRes) { - is ApiState.Success -> { - updatePost(likeRes.data.post_view) - } - - else -> {} - } - } - } - - fun savePost(form: SavePost) { - viewModelScope.launch { - savePostRes = ApiState.Loading - savePostRes = API.getInstance().savePost(form).toApiState() - when (val saveRes = savePostRes) { - is ApiState.Success -> { - updatePost(saveRes.data.post_view) - } - - else -> {} - } - } - } - - fun deletePost(form: DeletePost) { - viewModelScope.launch { - deletePostRes = ApiState.Loading - deletePostRes = API.getInstance().deletePost(form).toApiState() - when (val deletePost = deletePostRes) { - is ApiState.Success -> { - updatePost(deletePost.data.post_view) - } - - else -> {} - } - } - } - - fun blockCommunity( - form: BlockCommunity, - ctx: Context, - ) { - viewModelScope.launch { - blockCommunityRes = ApiState.Loading - blockCommunityRes = API.getInstance().blockCommunity(form).toApiState() - showBlockCommunityToast(blockCommunityRes, ctx) - } - } - - fun blockPerson( - form: BlockPerson, - ctx: Context, - ) { - viewModelScope.launch { - blockPersonRes = ApiState.Loading - blockPersonRes = API.getInstance().blockPerson(form).toApiState() - showBlockPersonToast(blockPersonRes, ctx) - } - } - - fun likeComment(form: CreateCommentLike) { - viewModelScope.launch { - likeCommentRes = ApiState.Loading - likeCommentRes = API.getInstance().createCommentLike(form).toApiState() - - when (val likeRes = likeCommentRes) { - is ApiState.Success -> { - updateComment(likeRes.data.comment_view) - } - - else -> {} - } - } - } - - fun deleteComment(form: DeleteComment) { - viewModelScope.launch { - deleteCommentRes = ApiState.Loading - deleteCommentRes = API.getInstance().deleteComment(form).toApiState() - - when (val deleteRes = deleteCommentRes) { - is ApiState.Success -> { - updateComment(deleteRes.data.comment_view) - } - - else -> {} - } - } - } - - fun saveComment(form: SaveComment) { - viewModelScope.launch { - saveCommentRes = ApiState.Loading - saveCommentRes = API.getInstance().saveComment(form).toApiState() - - when (val saveRes = saveCommentRes) { - is ApiState.Success -> { - updateComment(saveRes.data.comment_view) - } - - else -> {} - } - } - } - - fun updatePost(postView: PostView) { + override fun updatePost(postView: PostView) { when (val existing = personDetailsRes) { is ApiState.Success -> { - val newPosts = - findAndUpdatePost(existing.data.posts, postView) - val newRes = ApiState.Success(existing.data.copy(posts = newPosts)) - personDetailsRes = newRes + val newPosts = findAndUpdatePost(existing.data.posts, postView) + personDetailsRes = ApiState.Success(existing.data.copy(posts = newPosts)) } else -> {} } } - fun updateComment(commentView: CommentView) { + override fun updateComment(commentView: CommentView) { when (val existing = personDetailsRes) { is ApiState.Success -> { - val newComments = - findAndUpdateComment( - existing.data.comments, - commentView, - ) - val newRes = - ApiState.Success(existing.data.copy(comments = newComments)) - personDetailsRes = newRes + val newComments = findAndUpdateComment( + existing.data.comments, + commentView, + ) + personDetailsRes = ApiState.Success(existing.data.copy(comments = newComments)) } else -> {} @@ -282,9 +160,7 @@ class PersonProfileViewModel(personArg: Either, savedMode: Boo is ApiState.Success -> { val mutable = existing.data.comments.toMutableList() mutable.add(0, commentView) - val newRes = - ApiState.Success(existing.data.copy(comments = mutable.toList())) - personDetailsRes = newRes + personDetailsRes = ApiState.Success(existing.data.copy(comments = mutable.toList())) } else -> {} diff --git a/app/src/main/java/com/jerboa/model/PostViewModel.kt b/app/src/main/java/com/jerboa/model/PostViewModel.kt index a721a9781..710fa1492 100644 --- a/app/src/main/java/com/jerboa/model/PostViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostViewModel.kt @@ -1,6 +1,5 @@ package com.jerboa.model -import android.content.Context import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf @@ -10,20 +9,23 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.CreationExtras import arrow.core.Either +import com.jerboa.COMMENTS_DEPTH_MAX import com.jerboa.api.API import com.jerboa.api.ApiState import com.jerboa.api.toApiState import com.jerboa.appendData import com.jerboa.findAndUpdateComment -import com.jerboa.showBlockPersonToast +import com.jerboa.model.helper.CommentsHelper +import com.jerboa.model.helper.PostsHelper import it.vercruysse.lemmyapi.dto.CommentSortType import it.vercruysse.lemmyapi.dto.ListingType import it.vercruysse.lemmyapi.v0x19.datatypes.* +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -const val COMMENTS_DEPTH_MAX = 6 - -class PostViewModel(val id: Either) : ViewModel() { +class PostViewModel( + val id: Either, +) : ViewModel(), CommentsHelper, PostsHelper { var postRes: ApiState by mutableStateOf(ApiState.Empty) private set @@ -32,18 +34,11 @@ class PostViewModel(val id: Either) : ViewModel() { var sortType by mutableStateOf(CommentSortType.Hot) private set - private var likeCommentRes: ApiState by mutableStateOf(ApiState.Empty) - private var saveCommentRes: ApiState by mutableStateOf(ApiState.Empty) - private var deleteCommentRes: ApiState by mutableStateOf(ApiState.Empty) - - private var likePostRes: ApiState by mutableStateOf(ApiState.Empty) - private var savePostRes: ApiState by mutableStateOf(ApiState.Empty) - private var deletePostRes: ApiState by mutableStateOf(ApiState.Empty) - private var blockPersonRes: ApiState by mutableStateOf(ApiState.Empty) - val unExpandedComments = mutableStateListOf() val commentsWithToggledActionBar = mutableStateListOf() + override val scope: CoroutineScope = viewModelScope + init { this.getData() } @@ -126,106 +121,7 @@ class PostViewModel(val id: Either) : ViewModel() { } } - fun likeComment(form: CreateCommentLike) { - viewModelScope.launch { - likeCommentRes = ApiState.Loading - likeCommentRes = API.getInstance().createCommentLike(form).toApiState() - - when (val likeRes = likeCommentRes) { - is ApiState.Success -> { - updateComment(likeRes.data.comment_view) - } - - else -> {} - } - } - } - - fun deleteComment(form: DeleteComment) { - viewModelScope.launch { - deleteCommentRes = ApiState.Loading - deleteCommentRes = API.getInstance().deleteComment(form).toApiState() - - when (val deleteRes = deleteCommentRes) { - is ApiState.Success -> { - updateComment(deleteRes.data.comment_view) - } - - else -> {} - } - } - } - - fun saveComment(form: SaveComment) { - viewModelScope.launch { - saveCommentRes = ApiState.Loading - saveCommentRes = API.getInstance().saveComment(form).toApiState() - - when (val saveRes = saveCommentRes) { - is ApiState.Success -> { - updateComment(saveRes.data.comment_view) - } - - else -> {} - } - } - } - - fun likePost(form: CreatePostLike) { - viewModelScope.launch { - likePostRes = ApiState.Loading - likePostRes = API.getInstance().createPostLike(form).toApiState() - - when (val likeRes = likePostRes) { - is ApiState.Success -> { - updatePost(likeRes.data.post_view) - } - - else -> {} - } - } - } - - fun savePost(form: SavePost) { - viewModelScope.launch { - savePostRes = ApiState.Loading - savePostRes = API.getInstance().savePost(form).toApiState() - when (val saveRes = savePostRes) { - is ApiState.Success -> { - updatePost(saveRes.data.post_view) - } - - else -> {} - } - } - } - - fun deletePost(form: DeletePost) { - viewModelScope.launch { - deletePostRes = ApiState.Loading - deletePostRes = API.getInstance().deletePost(form).toApiState() - when (val deletePost = deletePostRes) { - is ApiState.Success -> { - updatePost(deletePost.data.post_view) - } - - else -> {} - } - } - } - - fun blockPerson( - form: BlockPerson, - ctx: Context, - ) { - viewModelScope.launch { - blockPersonRes = ApiState.Loading - blockPersonRes = API.getInstance().blockPerson(form).toApiState() - showBlockPersonToast(blockPersonRes, ctx) - } - } - - fun updateComment(commentView: CommentView) { + override fun updateComment(commentView: CommentView) { when (val existing = commentsRes) { is ApiState.Success -> { val newComments = @@ -233,9 +129,7 @@ class PostViewModel(val id: Either) : ViewModel() { existing.data.comments, commentView, ) - val newRes = - ApiState.Success(existing.data.copy(comments = newComments)) - commentsRes = newRes + commentsRes = ApiState.Success(existing.data.copy(comments = newComments)) } else -> {} @@ -256,7 +150,7 @@ class PostViewModel(val id: Either) : ViewModel() { } } - fun updatePost(postView: PostView) { + override fun updatePost(postView: PostView) { when (val existing = postRes) { is ApiState.Success -> { val newRes = ApiState.Success(existing.data.copy(post_view = postView)) diff --git a/app/src/main/java/com/jerboa/model/PostsViewModel.kt b/app/src/main/java/com/jerboa/model/PostsViewModel.kt index bd8034ad1..c0d452a96 100644 --- a/app/src/main/java/com/jerboa/model/PostsViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostsViewModel.kt @@ -12,19 +12,17 @@ import com.jerboa.api.ApiState import com.jerboa.api.toApiState import com.jerboa.findAndUpdatePost import com.jerboa.mergePosts +import com.jerboa.model.helper.PostsHelper import it.vercruysse.lemmyapi.dto.ListingType import it.vercruysse.lemmyapi.dto.SortType -import it.vercruysse.lemmyapi.v0x19.datatypes.CreatePostLike -import it.vercruysse.lemmyapi.v0x19.datatypes.DeletePost import it.vercruysse.lemmyapi.v0x19.datatypes.GetPosts import it.vercruysse.lemmyapi.v0x19.datatypes.GetPostsResponse import it.vercruysse.lemmyapi.v0x19.datatypes.MarkPostAsRead import it.vercruysse.lemmyapi.v0x19.datatypes.PaginationCursor import it.vercruysse.lemmyapi.v0x19.datatypes.PostView -import it.vercruysse.lemmyapi.v0x19.datatypes.SavePost import kotlinx.coroutines.launch -open class PostsViewModel : ViewModel() { +open class PostsViewModel : ViewModel(), PostsHelper { var postsRes: ApiState by mutableStateOf(ApiState.Empty) private set private var page by mutableIntStateOf(1) @@ -35,6 +33,8 @@ open class PostsViewModel : ViewModel() { var listingType by mutableStateOf(ListingType.Local) private set + override val scope = viewModelScope + protected fun nextPage() { page += 1 } @@ -98,7 +98,7 @@ open class PostsViewModel : ViewModel() { } } - fun updatePost(postView: PostView) { + override fun updatePost(postView: PostView) { when (val existing = postsRes) { is ApiState.Success -> { val newPosts = findAndUpdatePost(existing.data.posts, postView) @@ -153,28 +153,4 @@ open class PostsViewModel : ViewModel() { fun updateListingType(listingType: ListingType) { this.listingType = listingType } - - fun likePost(form: CreatePostLike) { - viewModelScope.launch { - API.getInstance().createPostLike(form).onSuccess { - updatePost(it.post_view) - } - } - } - - fun savePost(form: SavePost) { - viewModelScope.launch { - API.getInstance().savePost(form).onSuccess { - updatePost(it.post_view) - } - } - } - - fun deletePost(form: DeletePost) { - viewModelScope.launch { - API.getInstance().deletePost(form).onSuccess { - updatePost(it.post_view) - } - } - } } diff --git a/app/src/main/java/com/jerboa/model/SearchListViewModel.kt b/app/src/main/java/com/jerboa/model/SearchListViewModel.kt new file mode 100644 index 000000000..90577aa46 --- /dev/null +++ b/app/src/main/java/com/jerboa/model/SearchListViewModel.kt @@ -0,0 +1,206 @@ +package com.jerboa.model + +import android.content.Context +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import com.jerboa.DEBOUNCE_DELAY +import com.jerboa.api.API +import com.jerboa.api.ApiState +import com.jerboa.api.toApiState +import com.jerboa.findAndUpdateComment +import com.jerboa.findAndUpdatePost +import com.jerboa.model.helper.CommentsHelper +import com.jerboa.model.helper.PostsHelper +import com.jerboa.ui.components.common.apiErrorToast +import it.vercruysse.lemmyapi.dto.ListingType +import it.vercruysse.lemmyapi.dto.SearchType +import it.vercruysse.lemmyapi.dto.SortType +import it.vercruysse.lemmyapi.dto.SubscribedType +import it.vercruysse.lemmyapi.v0x19.datatypes.CommentView +import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityAggregates +import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityFollowerView +import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityView +import it.vercruysse.lemmyapi.v0x19.datatypes.PostView +import it.vercruysse.lemmyapi.v0x19.datatypes.Search +import it.vercruysse.lemmyapi.v0x19.datatypes.SearchResponse +import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class SearchListViewModel( + communities: ImmutableList, + selectCommunityMode: Boolean, +) : ViewModel(), CommentsHelper, PostsHelper { + private var fetchSearchJob: Job? = null + var q by mutableStateOf("") + + var searchRes: ApiState by mutableStateOf(ApiState.Empty) + private set + + var currentSearchType by mutableStateOf(SearchType.All) + var currentListing by mutableStateOf(ListingType.All) + var currentSort by mutableStateOf(SortType.TopAll) + var loading by mutableStateOf(false) + + var page by mutableIntStateOf(1) + + override val scope = viewModelScope + + override fun updatePost(postView: PostView) { + when (val existing = searchRes) { + is ApiState.Success -> { + val newPosts = + findAndUpdatePost( + existing.data.posts, + postView, + ) + searchRes = ApiState.Success(existing.data.copy(posts = newPosts)) + } + + else -> {} + } + } + + override fun updateComment(commentView: CommentView) { + when (val existing = searchRes) { + is ApiState.Success -> { + val newComments = + findAndUpdateComment( + existing.data.comments, + commentView, + ) + searchRes = ApiState.Success(existing.data.copy(comments = newComments)) + } + + else -> {} + } + } + + init { + if (selectCommunityMode) { + setCommunityListFromFollowed(communities) + currentSearchType = SearchType.Communities + currentSort = SortType.TopAll + currentListing = ListingType.Local + } + } + + fun updateSearch() { + fetchSearchJob?.cancel() + fetchSearchJob = + viewModelScope.launch { + delay(DEBOUNCE_DELAY) + search() + } + } + + fun onSearchChange(q: String) { + this.q = q + this.page = 1 + updateSearch() + } + + private fun search() { + viewModelScope.launch { + searchRes = ApiState.Loading + searchRes = API.getInstance().search(getForm()).toApiState() + } + } + + private fun getForm(): Search { + return Search( + q = q, + sort = currentSort, + type_ = currentSearchType, + listing_type = currentListing, + page = page, + ) + } + + private fun setCommunityListFromFollowed(myFollows: ImmutableList) { + // A hack to convert communityFollowerView into CommunityView + val followsIntoCommunityViews = + myFollows.map { cfv -> + CommunityView( + community = cfv.community, + subscribed = SubscribedType.Subscribed, + blocked = false, + counts = + CommunityAggregates( + community_id = cfv.community.id, + subscribers = 0, + posts = 0, + comments = 0, + published = "", + users_active_day = 0, + users_active_week = 0, + users_active_month = 0, + users_active_half_year = 0, + ), + ) + } + + searchRes = + ApiState.Success( + SearchResponse( + type_ = SearchType.Communities, + communities = followsIntoCommunityViews, + comments = emptyList(), + posts = emptyList(), + users = emptyList(), + ), + ) + } + + fun searchNextPage(ctx: Context) { + viewModelScope.launch { + page++ + loading = true + val res = API.getInstance().search(getForm()) + + res.onSuccess { + when (val oldRes = searchRes) { + is ApiState.Success -> { + searchRes = ApiState.Success( + SearchResponse( + type_ = SearchType.Communities, + communities = oldRes.data.communities + it.communities, + comments = oldRes.data.comments + it.comments, + posts = oldRes.data.posts + it.posts, + users = oldRes.data.users + it.users, + ), + ) + } + + else -> {} + } + }.onFailure { + page-- + apiErrorToast(ctx, it) + } + loading = false + } + } + + companion object { + class Factory( + private val followedCommunities: ImmutableList, + private val selectCommunityMode: Boolean, + ) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create( + modelClass: Class, + extras: CreationExtras, + ): T { + return SearchListViewModel(followedCommunities, selectCommunityMode) as T + } + } + } +} diff --git a/app/src/main/java/com/jerboa/model/helper/CommentsHelper.kt b/app/src/main/java/com/jerboa/model/helper/CommentsHelper.kt new file mode 100644 index 000000000..54cbe8f8c --- /dev/null +++ b/app/src/main/java/com/jerboa/model/helper/CommentsHelper.kt @@ -0,0 +1,59 @@ +package com.jerboa.model.helper + +import android.content.Context +import com.jerboa.api.API +import com.jerboa.api.toApiState +import com.jerboa.showBlockPersonToast +import it.vercruysse.lemmyapi.v0x19.datatypes.BlockPerson +import it.vercruysse.lemmyapi.v0x19.datatypes.CommentView +import it.vercruysse.lemmyapi.v0x19.datatypes.CreateCommentLike +import it.vercruysse.lemmyapi.v0x19.datatypes.DeleteComment +import it.vercruysse.lemmyapi.v0x19.datatypes.SaveComment +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +interface CommentsHelper { + val scope: CoroutineScope + + fun updateComment(commentView: CommentView) + + fun likeComment(form: CreateCommentLike) { + scope.launch { + val res = API.getInstance().createCommentLike(form) + + res.onSuccess { + updateComment(it.comment_view) + } + } + } + + fun deleteComment(form: DeleteComment) { + scope.launch { + API.getInstance().deleteComment(form).onSuccess { + updateComment(it.comment_view) + } + } + } + + fun saveComment(form: SaveComment) { + scope.launch { + API.getInstance().saveComment(form).onSuccess { + updateComment(it.comment_view) + } + } + } + + fun blockPerson( + form: BlockPerson, + ctx: Context, + ) { + scope.launch { + val blockPersonRes = API.getInstance().blockPerson(form).toApiState() + withContext(Dispatchers.Main) { + showBlockPersonToast(blockPersonRes, ctx) + } + } + } +} diff --git a/app/src/main/java/com/jerboa/model/helper/PostsHelper.kt b/app/src/main/java/com/jerboa/model/helper/PostsHelper.kt new file mode 100644 index 000000000..0952d815f --- /dev/null +++ b/app/src/main/java/com/jerboa/model/helper/PostsHelper.kt @@ -0,0 +1,39 @@ +package com.jerboa.model.helper + +import com.jerboa.api.API +import it.vercruysse.lemmyapi.v0x19.datatypes.CreatePostLike +import it.vercruysse.lemmyapi.v0x19.datatypes.DeletePost +import it.vercruysse.lemmyapi.v0x19.datatypes.PostView +import it.vercruysse.lemmyapi.v0x19.datatypes.SavePost +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +interface PostsHelper { + val scope: CoroutineScope + + fun updatePost(postView: PostView) + + fun likePost(form: CreatePostLike) { + scope.launch { + API.getInstance().createPostLike(form).onSuccess { + updatePost(it.post_view) + } + } + } + + fun savePost(form: SavePost) { + scope.launch { + API.getInstance().savePost(form).onSuccess { + updatePost(it.post_view) + } + } + } + + fun deletePost(form: DeletePost) { + scope.launch { + API.getInstance().deletePost(form).onSuccess { + updatePost(it.post_view) + } + } + } +} diff --git a/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt b/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt index 1a7a61fb2..29f126b7d 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt @@ -181,7 +181,6 @@ fun LazyListScope.commentNodeItem( onDownvoteClick: (commentView: CommentView) -> Unit, onReplyClick: (commentView: CommentView) -> Unit, onSaveClick: (commentView: CommentView) -> Unit, - onMarkAsReadClick: (commentView: CommentView) -> Unit, onCommentClick: (commentView: CommentView) -> Unit, onEditCommentClick: (commentView: CommentView) -> Unit, onDeleteCommentClick: (commentView: CommentView) -> Unit, @@ -391,7 +390,6 @@ fun LazyListScope.commentNodeItem( onUpvoteClick = onUpvoteClick, onDownvoteClick = onDownvoteClick, onSaveClick = onSaveClick, - onMarkAsReadClick = onMarkAsReadClick, onCommentClick = onCommentClick, onEditCommentClick = onEditCommentClick, onDeleteCommentClick = onDeleteCommentClick, @@ -429,7 +427,6 @@ fun LazyListScope.missingCommentNodeItem( onDownvoteClick: (commentView: CommentView) -> Unit, onReplyClick: (commentView: CommentView) -> Unit, onSaveClick: (commentView: CommentView) -> Unit, - onMarkAsReadClick: (commentView: CommentView) -> Unit, onCommentClick: (commentView: CommentView) -> Unit, onEditCommentClick: (commentView: CommentView) -> Unit, onDeleteCommentClick: (commentView: CommentView) -> Unit, @@ -527,7 +524,6 @@ fun LazyListScope.missingCommentNodeItem( onUpvoteClick = onUpvoteClick, onDownvoteClick = onDownvoteClick, onSaveClick = onSaveClick, - onMarkAsReadClick = onMarkAsReadClick, onCommentClick = onCommentClick, onEditCommentClick = onEditCommentClick, onDeleteCommentClick = onDeleteCommentClick, @@ -783,7 +779,6 @@ fun CommentNodesPreview() { onReplyClick = {}, onFetchChildrenClick = {}, onSaveClick = {}, - onMarkAsReadClick = {}, onCommentClick = {}, onEditCommentClick = {}, onDeleteCommentClick = {}, diff --git a/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt b/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt index 0c52ceb11..69f62e75f 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt @@ -31,7 +31,6 @@ fun CommentNodes( onDownvoteClick: (commentView: CommentView) -> Unit, onReplyClick: (commentView: CommentView) -> Unit, onSaveClick: (commentView: CommentView) -> Unit, - onMarkAsReadClick: (commentView: CommentView) -> Unit, onCommentClick: (commentView: CommentView) -> Unit, onEditCommentClick: (commentView: CommentView) -> Unit, onDeleteCommentClick: (commentView: CommentView) -> Unit, @@ -68,7 +67,6 @@ fun CommentNodes( onReplyClick = onReplyClick, onSaveClick = onSaveClick, account = account, - onMarkAsReadClick = onMarkAsReadClick, onCommentClick = onCommentClick, onPersonClick = onPersonClick, onHeaderClick = onHeaderClick, @@ -108,7 +106,6 @@ fun LazyListScope.commentNodeItems( onDownvoteClick: (commentView: CommentView) -> Unit, onReplyClick: (commentView: CommentView) -> Unit, onSaveClick: (commentView: CommentView) -> Unit, - onMarkAsReadClick: (commentView: CommentView) -> Unit, onCommentClick: (commentView: CommentView) -> Unit, onEditCommentClick: (commentView: CommentView) -> Unit, onDeleteCommentClick: (commentView: CommentView) -> Unit, @@ -147,7 +144,6 @@ fun LazyListScope.commentNodeItems( onReplyClick = onReplyClick, onSaveClick = onSaveClick, account = account, - onMarkAsReadClick = onMarkAsReadClick, onCommentClick = onCommentClick, onPersonClick = onPersonClick, onHeaderClick = onHeaderClick, @@ -184,7 +180,6 @@ fun LazyListScope.commentNodeItems( onReplyClick = onReplyClick, onSaveClick = onSaveClick, account = account, - onMarkAsReadClick = onMarkAsReadClick, onCommentClick = onCommentClick, onPersonClick = onPersonClick, onHeaderClick = onHeaderClick, diff --git a/app/src/main/java/com/jerboa/ui/components/common/DropdownMenu.kt b/app/src/main/java/com/jerboa/ui/components/common/DropdownMenu.kt index 965f3e08f..d999cc78f 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/DropdownMenu.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/DropdownMenu.kt @@ -46,10 +46,12 @@ fun SortOptionsDropdown( onDismissRequest: () -> Unit, onClickSortType: (SortType) -> Unit, selectedSortType: SortType, + fixedWidth: Dp = 196.dp, ) { CascadeDropdownMenu( expanded = expanded, onDismissRequest = onDismissRequest, + fixedWidth = fixedWidth, modifier = Modifier.semantics { testTagsAsResourceId = true }, ) { getSupportedEntries(API.version).filter { !isTopSort(it) }.forEach { diff --git a/app/src/main/java/com/jerboa/ui/components/common/FormComponents.kt b/app/src/main/java/com/jerboa/ui/components/common/FormComponents.kt new file mode 100644 index 000000000..46988e1b2 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/common/FormComponents.kt @@ -0,0 +1,83 @@ +package com.jerboa.ui.components.common + +import androidx.compose.foundation.clickable +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun ClickableOutlinedTextField( + value: String, + onClick: () -> Unit, + label: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, +) { + OutlinedTextField( + value = value, + enabled = false, + readOnly = true, + singleLine = true, + label = label, + onValueChange = {}, + trailingIcon = trailingIcon, + modifier = Modifier.clickable { onClick() }, + colors = + OutlinedTextFieldDefaults.colors( + disabledTextColor = MaterialTheme.colorScheme.onSurface, + disabledBorderColor = MaterialTheme.colorScheme.outline, + disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant, + disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant, + disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant, + disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant, + ), + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ReadOnlyDropdown( + expanded: Boolean, + setExpanded: ((Boolean) -> Unit), + states: List, + state: T, + setState: ((T) -> Unit), + stringTransform: (T) -> String = { it.toString() }, + label: String, +) { + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = setExpanded, + ) { + OutlinedTextField( + modifier = Modifier.menuAnchor(), + value = stringTransform(state), + onValueChange = {}, + label = { Text(label) }, + readOnly = true, + singleLine = true, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, + ) + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { setExpanded(false) }, + ) { + states.forEach { selectionOption -> + DropdownMenuItem( + text = { Text(stringTransform(selectionOption)) }, + onClick = { + setState(selectionOption) + setExpanded(false) + }, + contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, + ) + } + } + } +} diff --git a/app/src/main/java/com/jerboa/ui/components/community/Community.kt b/app/src/main/java/com/jerboa/ui/components/community/Community.kt index d37b7bc43..bb92faa68 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/Community.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/Community.kt @@ -172,7 +172,7 @@ fun CommunityHeader( }) { Icon( Icons.Outlined.Sort, - contentDescription = stringResource(R.string.community_sortBy), + contentDescription = stringResource(R.string.selectSort), ) } diff --git a/app/src/main/java/com/jerboa/ui/components/community/CommunityLink.kt b/app/src/main/java/com/jerboa/ui/components/community/CommunityLink.kt index 2d4b36bf8..bc5f1ec5b 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/CommunityLink.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/CommunityLink.kt @@ -83,7 +83,7 @@ fun CommunityLink( horizontalArrangement = Arrangement.spacedBy(spacing), modifier = if (clickable) { - modifier.clickable { onClick(community) } + Modifier.clickable { onClick(community) }.then(modifier) } else { modifier }, @@ -123,6 +123,7 @@ fun CommunityLinkLarger( onClick: (community: Community) -> Unit, showDefaultIcon: Boolean, blurNSFW: Int, + usersPerMonth: Int? = null, ) { CommunityLink( community = community, @@ -138,6 +139,7 @@ fun CommunityLinkLarger( onClick = onClick, showDefaultIcon = showDefaultIcon, blurNSFW = blurNSFW, + usersPerMonth = usersPerMonth, ) } @@ -159,7 +161,6 @@ fun CommunityLinkLargerWithUserCount( Modifier .padding(LARGE_PADDING) .fillMaxWidth(), - style = MaterialTheme.typography.titleLarge, onClick = onClick, showDefaultIcon = showDefaultIcon, blurNSFW = blurNSFW, diff --git a/app/src/main/java/com/jerboa/ui/components/community/list/CommunityListActivity.kt b/app/src/main/java/com/jerboa/ui/components/community/list/CommunityListActivity.kt deleted file mode 100644 index b6fb02463..000000000 --- a/app/src/main/java/com/jerboa/ui/components/community/list/CommunityListActivity.kt +++ /dev/null @@ -1,119 +0,0 @@ -package com.jerboa.ui.components.community.list - -import android.util.Log -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.DrawerState -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.lifecycle.viewmodel.compose.viewModel -import com.jerboa.DEBOUNCE_DELAY -import com.jerboa.JerboaAppState -import com.jerboa.api.ApiState -import com.jerboa.model.CommunityListViewModel -import com.jerboa.ui.components.common.ApiEmptyText -import com.jerboa.ui.components.common.ApiErrorText -import com.jerboa.ui.components.common.LoadingBar -import it.vercruysse.lemmyapi.dto.SearchType -import it.vercruysse.lemmyapi.dto.SortType -import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityFollowerView -import it.vercruysse.lemmyapi.v0x19.datatypes.Search -import kotlinx.collections.immutable.ImmutableList -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch - -private var fetchCommunitiesJob: Job? = null - -object CommunityListReturn { - const val COMMUNITY = "community-list::return(community)" -} - -@Composable -fun CommunityListActivity( - appState: JerboaAppState, - selectMode: Boolean = false, - followList: ImmutableList, - blurNSFW: Int, - drawerState: DrawerState, -) { - Log.d("jerboa", "got to community list activity") - - val communityListViewModel: CommunityListViewModel = - viewModel(factory = CommunityListViewModel.Companion.Factory(followList)) - - var search by rememberSaveable { mutableStateOf("") } - - val scope = rememberCoroutineScope() - - Surface(color = MaterialTheme.colorScheme.background) { - Scaffold( - topBar = { - CommunityListHeader( - openDrawer = { - scope.launch { - drawerState.open() - } - }, - search = search, - onSearchChange = { - search = it - fetchCommunitiesJob?.cancel() - fetchCommunitiesJob = - scope.launch { - delay(DEBOUNCE_DELAY) - communityListViewModel.searchCommunities( - form = - Search( - q = search, - type_ = SearchType.Communities, - sort = SortType.TopAll, - ), - ) - } - }, - ) - }, - content = { padding -> - when (val communitiesRes = communityListViewModel.searchRes) { - ApiState.Empty -> ApiEmptyText() - is ApiState.Failure -> ApiErrorText(communitiesRes.msg) - ApiState.Loading -> { - LoadingBar(padding) - } - - is ApiState.Success -> { - CommunityListings( - communities = communitiesRes.data.communities, - onClickCommunity = { cs -> - if (selectMode) { - appState.apply { - addReturn(CommunityListReturn.COMMUNITY, cs) - navigateUp() - } - } else { - appState.toCommunity(id = cs.id) - } - }, - modifier = - Modifier - .padding(padding) - .imePadding(), - blurNSFW = blurNSFW, - ) - } - - else -> {} - } - }, - ) - } -} diff --git a/app/src/main/java/com/jerboa/ui/components/drawer/Drawer.kt b/app/src/main/java/com/jerboa/ui/components/drawer/Drawer.kt index 1fd5db82c..ecaccaac9 100644 --- a/app/src/main/java/com/jerboa/ui/components/drawer/Drawer.kt +++ b/app/src/main/java/com/jerboa/ui/components/drawer/Drawer.kt @@ -362,7 +362,7 @@ fun DrawerHeader( accountViewModel: AccountViewModel, myPerson: Person?, onClickShowAccountAddMode: () -> Unit, - showAccountAddMode: Boolean = false, + showAccountAddMode: Boolean, showAvatar: Boolean, ) { val account = getCurrentAccount(accountViewModel) @@ -470,5 +470,6 @@ fun DrawerHeaderPreview() { onClickShowAccountAddMode = {}, showAvatar = true, accountViewModel = accountViewModel, + showAccountAddMode = false, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/home/BottomNavActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/BottomNavActivity.kt index da58ce013..11dd50368 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/BottomNavActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/BottomNavActivity.kt @@ -50,10 +50,10 @@ import com.jerboa.model.SiteViewModel import com.jerboa.ui.components.common.BottomAppBarAll import com.jerboa.ui.components.common.JerboaSnackbarHost import com.jerboa.ui.components.common.getCurrentAccount -import com.jerboa.ui.components.community.list.CommunityListActivity import com.jerboa.ui.components.drawer.MainDrawer import com.jerboa.ui.components.inbox.InboxActivity import com.jerboa.ui.components.person.PersonProfileActivity +import com.jerboa.ui.components.search.SearchActivity import kotlinx.coroutines.launch enum class NavTab( @@ -220,12 +220,14 @@ fun BottomNavActivity( } composable(route = NavTab.Search.name) { - CommunityListActivity( + SearchActivity( appState = appState, - selectMode = false, + selectCommunityMode = false, followList = siteViewModel.getFollowList(), - blurNSFW = appSettings.blurNSFW, + appSettings = appSettings, drawerState = drawerState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt b/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt index 52826fc9d..dfa3c2e6f 100644 --- a/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt @@ -244,15 +244,7 @@ fun InboxTabs( // act when end of list reached if (endOfListReached) { LaunchedEffect(Unit) { - account.doIfReadyElseDisplayInfo( - appState, - ctx, - snackbarHostState, - scope, - siteViewModel, - ) { - inboxViewModel.appendReplies() - } + inboxViewModel.appendReplies() } } @@ -461,15 +453,7 @@ fun InboxTabs( // act when end of list reached if (endOfListReached) { LaunchedEffect(Unit) { - account.doIfReadyElseDisplayInfo( - appState, - ctx, - snackbarHostState, - scope, - siteViewModel, - ) { - inboxViewModel.appendMentions() - } + inboxViewModel.appendMentions() } } @@ -668,15 +652,7 @@ fun InboxTabs( // act when end of list reached if (endOfListReached) { LaunchedEffect(Unit) { - account.doIfReadyElseDisplayInfo( - appState, - ctx, - snackbarHostState, - scope, - siteViewModel, - ) { - inboxViewModel.appendMessages() - } + inboxViewModel.appendMessages() } } diff --git a/app/src/main/java/com/jerboa/ui/components/login/Login.kt b/app/src/main/java/com/jerboa/ui/components/login/Login.kt index 873d31f66..3b487d48c 100644 --- a/app/src/main/java/com/jerboa/ui/components/login/Login.kt +++ b/app/src/main/java/com/jerboa/ui/components/login/Login.kt @@ -58,6 +58,7 @@ import it.vercruysse.lemmyapi.v0x19.datatypes.Login import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +@OptIn(ExperimentalComposeUiApi::class) @Composable fun MyTextField( modifier: Modifier = Modifier, diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfile.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfile.kt index ac96d58bb..8396bc3fa 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfile.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfile.kt @@ -200,7 +200,7 @@ fun PersonProfileHeader( }) { Icon( Icons.Outlined.Sort, - contentDescription = stringResource(R.string.community_sortBy), + contentDescription = stringResource(R.string.selectSort), ) } diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt index 8b1208f92..639a62d63 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt @@ -33,6 +33,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -138,6 +139,7 @@ fun PersonProfileActivity( personProfileViewModel.insertComment(cv) } } + else -> {} } } @@ -171,6 +173,7 @@ fun PersonProfileActivity( matrixId = null, ) } + is ApiState.Holder -> { val person = profileRes.data.person_view.person PersonProfileHeader( @@ -235,6 +238,7 @@ fun PersonProfileActivity( matrixId = person.matrix_user_id, ) } + else -> {} } }, @@ -327,6 +331,7 @@ fun UserTabs( ApiState.Refreshing, ) } + else -> {} } }, @@ -420,6 +425,7 @@ fun UserTabs( } } } + else -> {} } } @@ -571,6 +577,7 @@ fun UserTabs( showPostAppendRetry = personProfileViewModel.personDetailsRes is ApiState.AppendingFailure, ) } + else -> {} } } @@ -583,7 +590,7 @@ fun UserTabs( ApiState.Loading -> LoadingBar() ApiState.Refreshing -> LoadingBar() is ApiState.Holder -> { - val nodes = commentsToFlatNodes(profileRes.data.comments) + val nodes by remember { mutableStateOf(commentsToFlatNodes(profileRes.data.comments)) } val listState = rememberLazyListState() @@ -651,7 +658,6 @@ fun UserTabs( listState = listState, toggleExpanded = { commentId -> toggleExpanded(commentId) }, toggleActionBar = { commentId -> toggleActionBar(commentId) }, - onMarkAsReadClick = {}, onCommentClick = { cv -> appState.toComment(id = cv.comment.id) }, diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostActivity.kt b/app/src/main/java/com/jerboa/ui/components/post/PostActivity.kt index ed29e3c9f..384409d32 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostActivity.kt @@ -480,7 +480,6 @@ fun PostActivity( }, toggleExpanded = toggleExpanded, toggleActionBar = toggleActionBar, - onMarkAsReadClick = {}, onCommentClick = { commentView -> toggleExpanded(commentView.comment.id) }, onUpvoteClick = { cv -> account.doIfReadyElseDisplayInfo( @@ -607,6 +606,7 @@ fun PostActivity( account = account, enableDownVotes = siteViewModel.enableDownvotes(), showAvatar = siteViewModel.showAvatar(), + showScores = siteViewModel.showScores(), isCollapsedByParent = false, showCollapsedCommentContent = showCollapsedCommentContent, showActionBar = { commentId -> @@ -616,7 +616,6 @@ fun PostActivity( ) }, blurNSFW = blurNSFW, - showScores = siteViewModel.showScores(), ) item { Spacer(modifier = Modifier.height(100.dp)) diff --git a/app/src/main/java/com/jerboa/ui/components/post/create/CreatePostActivity.kt b/app/src/main/java/com/jerboa/ui/components/post/create/CreatePostActivity.kt index 9b49d175b..4fb76cc85 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/create/CreatePostActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/create/CreatePostActivity.kt @@ -35,9 +35,9 @@ import com.jerboa.padUrlWithHttps import com.jerboa.ui.components.common.ActionTopBar import com.jerboa.ui.components.common.LoadingBar import com.jerboa.ui.components.common.getCurrentAccount -import com.jerboa.ui.components.community.list.CommunityListReturn import com.jerboa.ui.components.post.composables.CreateEditPostBody import com.jerboa.ui.components.post.composables.PostCommunitySelector +import com.jerboa.ui.components.search.SearchListReturn import com.jerboa.validatePostName import com.jerboa.validateUrl import it.vercruysse.lemmyapi.v0x19.datatypes.Community @@ -77,7 +77,7 @@ fun CreatePostActivity( } // On return from the community picker - appState.ConsumeReturn(CommunityListReturn.COMMUNITY) { community -> + appState.ConsumeReturn(SearchListReturn.COMMUNITY) { community -> selectedCommunity = community } diff --git a/app/src/main/java/com/jerboa/ui/components/search/SearchActivity.kt b/app/src/main/java/com/jerboa/ui/components/search/SearchActivity.kt new file mode 100644 index 000000000..97f150bc1 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/search/SearchActivity.kt @@ -0,0 +1,476 @@ +package com.jerboa.ui.components.search + +import android.util.Log +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.Divider +import androidx.compose.material3.DrawerState +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.viewmodel.compose.viewModel +import com.jerboa.JerboaAppState +import com.jerboa.PostViewMode +import com.jerboa.VoteType +import com.jerboa.api.ApiState +import com.jerboa.commentsToFlatNodes +import com.jerboa.db.entity.AppSettings +import com.jerboa.feat.doIfReadyElseDisplayInfo +import com.jerboa.isScrolledToEnd +import com.jerboa.model.AccountViewModel +import com.jerboa.model.ReplyItem +import com.jerboa.model.SearchListViewModel +import com.jerboa.model.SiteViewModel +import com.jerboa.newVote +import com.jerboa.ui.components.comment.commentNodeItems +import com.jerboa.ui.components.common.ApiErrorText +import com.jerboa.ui.components.common.JerboaSnackbarHost +import com.jerboa.ui.components.common.LoadingBar +import com.jerboa.ui.components.common.getCurrentAccount +import com.jerboa.ui.components.common.simpleVerticalScrollbar +import com.jerboa.ui.components.post.PostListing +import it.vercruysse.lemmyapi.v0x19.datatypes.BlockPerson +import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityFollowerView +import it.vercruysse.lemmyapi.v0x19.datatypes.CreateCommentLike +import it.vercruysse.lemmyapi.v0x19.datatypes.CreatePostLike +import it.vercruysse.lemmyapi.v0x19.datatypes.DeleteComment +import it.vercruysse.lemmyapi.v0x19.datatypes.DeletePost +import it.vercruysse.lemmyapi.v0x19.datatypes.SaveComment +import it.vercruysse.lemmyapi.v0x19.datatypes.SavePost +import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.launch + +object SearchListReturn { + const val COMMUNITY = "search-list::return(community)" +} + +@Composable +fun SearchActivity( + appState: JerboaAppState, + selectCommunityMode: Boolean = false, + followList: ImmutableList, + appSettings: AppSettings, + drawerState: DrawerState, + accountViewModel: AccountViewModel, + siteViewModel: SiteViewModel, +) { + Log.d("SearchActivity", "Arrived") + + val ctx = LocalContext.current + val account = getCurrentAccount(accountViewModel) + val snackbarHostState = remember { SnackbarHostState() } + val scope = rememberCoroutineScope() + + var showSearchOptions by rememberSaveable { mutableStateOf(false) } + val searchListViewModel: SearchListViewModel = + viewModel(factory = SearchListViewModel.Companion.Factory(followList, selectCommunityMode)) + + Surface(color = MaterialTheme.colorScheme.background) { + Scaffold( + topBar = { + SearchListHeader( + showSearchOptions = showSearchOptions, + setShowSearchOptions = { showSearchOptions = it }, + openDrawer = { + appState.coroutineScope.launch { + drawerState.open() + } + }, + search = searchListViewModel.q, + onSearchChange = searchListViewModel::onSearchChange, + ) + }, + snackbarHost = { JerboaSnackbarHost(snackbarHostState) }, + content = { padding -> + + Column( + Modifier + .padding(padding) + .imePadding(), + ) { + if (searchListViewModel.loading || searchListViewModel.searchRes is ApiState.Loading) { + LoadingBar() + } + + AnimatedVisibility( + visible = showSearchOptions, + enter = expandVertically(), + exit = shrinkVertically(), + ) { + Column( + Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + SearchParametersField( + currentSort = searchListViewModel.currentSort, + setCurrentSort = { + searchListViewModel.currentSort = it + searchListViewModel.updateSearch() + }, + currentSearchType = searchListViewModel.currentSearchType, + setCurrentSearchType = { + searchListViewModel.currentSearchType = it + searchListViewModel.updateSearch() + }, + currentListing = searchListViewModel.currentListing, + setCurrentListing = { + searchListViewModel.currentListing = it + searchListViewModel.updateSearch() + }, + ) + } + } + + when (val communitiesRes = searchListViewModel.searchRes) { + is ApiState.Failure -> ApiErrorText(communitiesRes.msg) + is ApiState.Success -> { + val listState = rememberLazyListState() + + val endOfListReached by remember { + derivedStateOf { + listState.isScrolledToEnd() + } + } + + if (endOfListReached) { + LaunchedEffect(Unit) { + searchListViewModel.searchNextPage(ctx) + } + } + + val nodes by remember { mutableStateOf(commentsToFlatNodes(communitiesRes.data.comments)) } + + // Holds the un-expanded comment ids + val unExpandedComments = remember { mutableStateListOf() } + val commentsWithToggledActionBar = remember { mutableStateListOf() } + + val toggleExpanded = { commentId: Int -> + if (unExpandedComments.contains(commentId)) { + unExpandedComments.remove(commentId) + } else { + unExpandedComments.add(commentId) + } + } + + val toggleActionBar = { commentId: Int -> + if (commentsWithToggledActionBar.contains(commentId)) { + commentsWithToggledActionBar.remove(commentId) + } else { + commentsWithToggledActionBar.add(commentId) + } + } + + val showActionBarByDefault = true + + LazyColumn( + state = listState, + modifier = Modifier.simpleVerticalScrollbar(listState), + ) { + searchPersonListings( + personViews = communitiesRes.data.users, + onPersonClick = { personView -> + appState.toProfile(id = personView.person.id) + }, + ) + searchCommunityListings( + communities = communitiesRes.data.communities, + onClickCommunity = { cs -> + if (selectCommunityMode) { + appState.apply { + addReturn(SearchListReturn.COMMUNITY, cs) + navigateUp() + } + } else { + appState.toCommunity(id = cs.id) + } + }, + blurNSFW = appSettings.blurNSFW, + ) + + commentNodeItems( + nodes = nodes, + increaseLazyListIndexTracker = {}, + addToParentIndexes = {}, + isFlat = true, + isExpanded = { commentId -> !unExpandedComments.contains(commentId) }, + toggleExpanded = { commentId -> toggleExpanded(commentId) }, + toggleActionBar = { commentId -> toggleActionBar(commentId) }, + onCommentClick = { cv -> + appState.toComment(id = cv.comment.id) + }, + onUpvoteClick = { cv -> + account.doIfReadyElseDisplayInfo( + appState, + ctx, + snackbarHostState, + scope, + loginAsToast = true, + ) { + searchListViewModel.likeComment( + CreateCommentLike( + comment_id = cv.comment.id, + score = newVote(cv.my_vote, VoteType.Upvote), + ), + ) + } + }, + onDownvoteClick = { cv -> + account.doIfReadyElseDisplayInfo( + appState, + ctx, + snackbarHostState, + scope, + loginAsToast = true, + ) { + account.doIfReadyElseDisplayInfo( + appState, + ctx, + snackbarHostState, + scope, + loginAsToast = true, + ) { + searchListViewModel.likeComment( + CreateCommentLike( + comment_id = cv.comment.id, + score = newVote(cv.my_vote, VoteType.Downvote), + ), + ) + } + } + }, + onReplyClick = { cv -> + appState.toCommentReply( + replyItem = ReplyItem.CommentItem(cv), + ) + }, + onSaveClick = { cv -> + account.doIfReadyElseDisplayInfo( + appState, + ctx, + snackbarHostState, + scope, + loginAsToast = true, + ) { + searchListViewModel.saveComment( + SaveComment( + comment_id = cv.comment.id, + save = !cv.saved, + ), + ) + } + }, + onPersonClick = appState::toProfile, + onHeaderClick = {}, + onHeaderLongClick = { commentView -> toggleActionBar(commentView.comment.id) }, + onCommunityClick = { community -> + appState.toCommunity(id = community.id) + }, + onPostClick = { postId -> + appState.toPost(id = postId) + }, + onEditCommentClick = { cv -> + appState.toCommentEdit( + commentView = cv, + ) + }, + onDeleteCommentClick = { cv -> + account.doIfReadyElseDisplayInfo( + appState, + ctx, + snackbarHostState, + scope, + loginAsToast = true, + ) { + searchListViewModel.deleteComment( + DeleteComment( + comment_id = cv.comment.id, + deleted = !cv.comment.deleted, + ), + ) + } + }, + onReportClick = { cv -> + appState.toCommentReport(id = cv.comment.id) + }, + onCommentLinkClick = { cv -> + appState.toComment(id = cv.comment.id) + }, + onFetchChildrenClick = {}, + onBlockCreatorClick = { person -> + account.doIfReadyElseDisplayInfo( + appState, + ctx, + snackbarHostState, + scope, + loginAsToast = true, + ) { + searchListViewModel.blockPerson( + BlockPerson( + person_id = person.id, + block = true, + ), + ctx, + ) + } + }, + showPostAndCommunityContext = true, + showCollapsedCommentContent = true, + isCollapsedByParent = false, + showActionBar = { commentId -> + showActionBarByDefault xor commentsWithToggledActionBar.contains(commentId) + }, + account = account, + enableDownVotes = siteViewModel.enableDownvotes(), + showAvatar = siteViewModel.showAvatar(), + showScores = siteViewModel.showScores(), + blurNSFW = appSettings.blurNSFW, + ) + items(communitiesRes.data.posts) { postView -> + PostListing( + postView = postView, + onUpvoteClick = { pv -> + account.doIfReadyElseDisplayInfo( + appState, + ctx, + snackbarHostState, + scope, + siteViewModel, + accountViewModel, + ) { + searchListViewModel.likePost( + CreatePostLike( + post_id = pv.post.id, + score = + newVote( + postView.my_vote, + VoteType.Upvote, + ), + ), + ) + } + }, + onDownvoteClick = { pv -> + account.doIfReadyElseDisplayInfo( + appState, + ctx, + snackbarHostState, + scope, + siteViewModel, + accountViewModel, + ) { + searchListViewModel.likePost( + CreatePostLike( + post_id = pv.post.id, + score = + newVote( + postView.my_vote, + VoteType.Downvote, + ), + ), + ) + } + }, + onReplyClick = { pv -> + appState.toCommentReply( + replyItem = ReplyItem.PostItem(pv), + ) + }, + onPostClick = { + appState.toPost(id = it.post.id) + }, + onSaveClick = { pv -> + account.doIfReadyElseDisplayInfo( + appState, + ctx, + snackbarHostState, + scope, + siteViewModel, + accountViewModel, + ) { + searchListViewModel.savePost( + SavePost( + post_id = pv.post.id, + save = !pv.saved, + ), + ) + } + }, + onCommunityClick = { community -> + appState.toCommunity(id = community.id) + }, + onEditPostClick = { pv -> + appState.toPostEdit( + postView = pv, + ) + }, + onDeletePostClick = { pv -> + account.doIfReadyElseDisplayInfo( + appState, + ctx, + snackbarHostState, + scope, + siteViewModel, + accountViewModel, + ) { + searchListViewModel.deletePost( + DeletePost( + post_id = pv.post.id, + deleted = !pv.post.deleted, + ), + ) + } + }, + onReportClick = { pv -> + appState.toPostReport(id = pv.post.id) + }, + onPersonClick = appState::toProfile, + showReply = true, // Do nothing + fullBody = true, + account = account, + postViewMode = PostViewMode.List, + enableDownVotes = siteViewModel.enableDownvotes(), + showAvatar = siteViewModel.showAvatar(), + showScores = siteViewModel.showScores(), + appState = appState, + showIfRead = false, + blurNSFW = appSettings.blurNSFW, + showPostLinkPreview = appSettings.showPostLinkPreviews, + postActionbarMode = appSettings.postActionbarMode, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + ) + Divider() + } + } + } + + else -> {} + } + } + }, + ) + } +} diff --git a/app/src/main/java/com/jerboa/ui/components/community/list/CommunityList.kt b/app/src/main/java/com/jerboa/ui/components/search/SearchList.kt similarity index 60% rename from app/src/main/java/com/jerboa/ui/components/community/list/CommunityList.kt rename to app/src/main/java/com/jerboa/ui/components/search/SearchList.kt index d41d13bd8..67bee4dd3 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/list/CommunityList.kt +++ b/app/src/main/java/com/jerboa/ui/components/search/SearchList.kt @@ -1,12 +1,11 @@ -package com.jerboa.ui.components.community.list +package com.jerboa.ui.components.search import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -19,38 +18,33 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Preview import com.jerboa.R -import com.jerboa.datatypes.sampleCommunityView -import com.jerboa.ui.components.common.simpleVerticalScrollbar import com.jerboa.ui.components.community.CommunityLinkLarger -import com.jerboa.ui.components.community.CommunityLinkLargerWithUserCount import it.vercruysse.lemmyapi.v0x19.datatypes.* @OptIn(ExperimentalMaterial3Api::class) @Composable -fun CommunityListHeader( +fun SearchListHeader( openDrawer: () -> Unit, search: String, onSearchChange: (search: String) -> Unit, + showSearchOptions: Boolean, + setShowSearchOptions: (Boolean) -> Unit, ) { TopAppBar( title = { - CommunityTopBarSearchView( + TopBarSearchField( search = search, onSearchChange = onSearchChange, ) }, actions = { - // TODO: disabled until ever implemented IconButton( - enabled = false, - onClick = { - }, + onClick = { setShowSearchOptions(!showSearchOptions) }, ) { Icon( - imageVector = Icons.Outlined.MoreVert, + imageVector = if (showSearchOptions) Icons.Outlined.ExpandMore else Icons.Outlined.ExpandLess, contentDescription = stringResource(R.string.moreOptions), ) } @@ -66,57 +60,34 @@ fun CommunityListHeader( ) } -@Composable -fun CommunityListings( +fun LazyListScope.searchCommunityListings( communities: List, onClickCommunity: (community: Community) -> Unit, - modifier: Modifier = Modifier, blurNSFW: Int, ) { - val listState = rememberLazyListState() - - LazyColumn( - state = listState, - modifier = modifier.simpleVerticalScrollbar(listState), - ) { - items( - communities, - key = { it.community.id }, - contentType = { "communitylink" }, - ) { item -> + items( + communities, + contentType = { "communitylink" }, + ) { item -> + CommunityLinkLarger( + community = item.community, + onClick = onClickCommunity, + showDefaultIcon = true, + blurNSFW = blurNSFW, // A hack for the community follower views that were coerced into community views without counts - if (item.counts.users_active_month == 0) { - CommunityLinkLarger( - community = item.community, - onClick = onClickCommunity, - showDefaultIcon = true, - blurNSFW = blurNSFW, - ) - } else { - CommunityLinkLargerWithUserCount( - communityView = item, - onClick = onClickCommunity, - showDefaultIcon = true, - blurNSFW = blurNSFW, - ) - } - } + usersPerMonth = + if (item.counts.users_active_month == 0) { + null + } else { + item.counts.users_active_month + }, + ) + Divider() } } -@Preview -@Composable -fun CommunityListingsPreview() { - val communities = listOf(sampleCommunityView, sampleCommunityView) - CommunityListings( - communities = communities, - onClickCommunity = {}, - blurNSFW = 1, - ) -} - @Composable -fun CommunityTopBarSearchView( +fun TopBarSearchField( search: String, onSearchChange: (search: String) -> Unit, ) { @@ -127,9 +98,7 @@ fun CommunityTopBarSearchView( placeholder = { Text(stringResource(R.string.community_list_search)) }, - modifier = - Modifier - .fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), trailingIcon = { if (search.isNotEmpty()) { IconButton( @@ -153,14 +122,14 @@ fun CommunityTopBarSearchView( unfocusedIndicatorColor = Color.Transparent, disabledIndicatorColor = Color.Transparent, ), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + // keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), ) } @Preview(showBackground = true) @Composable fun SearchViewPreview() { - CommunityTopBarSearchView( + TopBarSearchField( search = "", onSearchChange = {}, ) diff --git a/app/src/main/java/com/jerboa/ui/components/search/SearchListPersonItem.kt b/app/src/main/java/com/jerboa/ui/components/search/SearchListPersonItem.kt new file mode 100644 index 000000000..48f1948ad --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/search/SearchListPersonItem.kt @@ -0,0 +1,88 @@ +package com.jerboa.ui.components.search + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Person +import androidx.compose.material3.Icon +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.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import com.jerboa.personNameShown +import com.jerboa.ui.components.common.CircularIcon +import com.jerboa.ui.components.person.CommentsAndPosts +import com.jerboa.ui.theme.DRAWER_ITEM_SPACING +import com.jerboa.ui.theme.LARGER_ICON_THUMBNAIL_SIZE +import com.jerboa.ui.theme.LARGE_PADDING +import com.jerboa.ui.theme.LINK_ICON_SIZE +import it.vercruysse.lemmyapi.v0x19.datatypes.PersonView + +@Composable +fun SearchListPersonItem( + personView: PersonView, + onClickPerson: (PersonView) -> Unit, + spacing: Dp = DRAWER_ITEM_SPACING, + size: Dp = LINK_ICON_SIZE, + thumbnailSize: Int = LARGER_ICON_THUMBNAIL_SIZE, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(spacing), + modifier = + Modifier + .clickable { onClickPerson(personView) } + .padding(LARGE_PADDING) + .fillMaxWidth(), + ) { + val avatar = personView.person.avatar + + if (avatar != null) { + CircularIcon( + icon = avatar, + contentDescription = null, + size = size, + thumbnailSize = thumbnailSize, + ) + } else { + Icon( + imageVector = Icons.Outlined.Person, + contentDescription = "", + modifier = Modifier.size(size), + ) + } + + Column { + PersonName(personView) + CommentsAndPosts(personView) + } + } +} + +@Composable +fun PersonName( + person: PersonView, + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.primary, + style: TextStyle = MaterialTheme.typography.bodyMedium, + overflow: TextOverflow = TextOverflow.Ellipsis, +) { + Text( + text = personNameShown(person.person, true), + style = style, + color = color, + modifier = modifier, + overflow = overflow, + maxLines = 1, + ) +} diff --git a/app/src/main/java/com/jerboa/ui/components/search/SearchListings.kt b/app/src/main/java/com/jerboa/ui/components/search/SearchListings.kt new file mode 100644 index 000000000..90f313163 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/search/SearchListings.kt @@ -0,0 +1,16 @@ +package com.jerboa.ui.components.search + +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Divider +import it.vercruysse.lemmyapi.v0x19.datatypes.PersonView + +fun LazyListScope.searchPersonListings( + personViews: List, + onPersonClick: (PersonView) -> Unit, +) { + items(personViews) { personView -> + SearchListPersonItem(personView = personView, onClickPerson = onPersonClick) + Divider() + } +} diff --git a/app/src/main/java/com/jerboa/ui/components/search/SearchParametersField.kt b/app/src/main/java/com/jerboa/ui/components/search/SearchParametersField.kt new file mode 100644 index 000000000..cbf84ace2 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/search/SearchParametersField.kt @@ -0,0 +1,89 @@ +package com.jerboa.ui.components.search + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.jerboa.R +import com.jerboa.datatypes.data +import com.jerboa.datatypes.getLocalizedListingTypeName +import com.jerboa.ui.components.common.ClickableOutlinedTextField +import com.jerboa.ui.components.common.ReadOnlyDropdown +import com.jerboa.ui.components.common.SortOptionsDropdown +import it.vercruysse.lemmyapi.dto.ListingType +import it.vercruysse.lemmyapi.dto.SearchType +import it.vercruysse.lemmyapi.dto.SortType + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SearchParametersField( + currentSort: SortType, + setCurrentSort: (SortType) -> Unit, + currentSearchType: SearchType, + setCurrentSearchType: (SearchType) -> Unit, + currentListing: ListingType, + setCurrentListing: (ListingType) -> Unit, +) { + val ctx = LocalContext.current + + var expandedListing by rememberSaveable { mutableStateOf(false) } + var expandedSearchType by rememberSaveable { mutableStateOf(false) } + var expandedSort by rememberSaveable { mutableStateOf(false) } + + Column( + modifier = Modifier.padding(16.dp, 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Box { + ClickableOutlinedTextField( + value = stringResource(currentSort.data.shortForm), + label = { Text(stringResource(R.string.selectSort)) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expandedSort) }, + onClick = { expandedSort = true }, + ) + + SortOptionsDropdown( + expanded = expandedSort, + onDismissRequest = { expandedSort = false }, + onClickSortType = { + expandedSort = false + setCurrentSort(it) + }, + selectedSortType = currentSort, + fixedWidth = OutlinedTextFieldDefaults.MinWidth, + ) + } + + ReadOnlyDropdown( + expanded = expandedSearchType, + setExpanded = { expandedSearchType = it }, + states = SearchType.entries, + state = currentSearchType, + setState = setCurrentSearchType, + label = stringResource(R.string.search_in), + stringTransform = { it.name }, + ) + ReadOnlyDropdown( + expanded = expandedListing, + setExpanded = { expandedListing = it }, + states = ListingType.entries, + state = currentListing, + setState = setCurrentListing, + label = stringResource(R.string.limit_to), + stringTransform = { getLocalizedListingTypeName(ctx, it) }, + ) + } +} diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index cb8b0d812..278957180 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -82,7 +82,6 @@ %1$s مستخدم \ شهر ابحث… انتظر الإذن - رتِّب حسب اشترك %1$s مستخدم \ شهر سجلَّات التعطُّل diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index e255502b1..cc8dc90ae 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -80,7 +80,6 @@ %1$s istifadəçi / ay Axtar… Gözlə - Çeşidlə Abunə ol %1$s istifadəçi / ay Çökmə Jurnalları diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 6da5f4959..95f679aa5 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -82,7 +82,6 @@ %1$s потребители / месец Търсене… В очакване - Сортиране по Абониране %1$s потребители / месец Дневник на сривовете diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 359730dfc..63818a098 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -175,7 +175,6 @@ Vis flere muligheder Gem redigeret kommentar Send svar - Sorter efter Opret indlæg Fællesskabets ikon Indsend diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b49627f69..ae8e1e9c7 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -183,7 +183,6 @@ Zeige mehr Optionen Kommentarbearbeitung speichern Antwort senden - Sortieren nach Beitrag erstellen Community Symbol Abschicken diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 5393ba35c..0bc4e3793 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -80,7 +80,6 @@ %1$s χρήστες / μήνα Αναζήτηση… Εκκρεμεί - Ταξινόμηση κατά Εγγραφή %1$s χρήστες / μήνα Επιλογή κοινότητας από τη λίστα diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ec8e33f9a..3caa4ad35 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -193,7 +193,6 @@ Mostrar más opciones Guardar ediciones del comentario Enviar respuesta - Ordenar por La versión del servidor (%1$s) se encuentra por debajo de la versión mínima soportada (%2$s). La versión del servidor (%1$s) es muy baja. Crear post diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index d585ac372..fa8cec389 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -191,7 +191,6 @@ Plus d’options Enregistrer les modifications Envoyer la réponse - Trier par La version du serveur (%1$s) est inférieure à la version minimale prise en charge (%2$s). Veuillez informer l’administrateur de l’instance et vous connecter à une autre instance, ou vous déconnecter et utiliser l’instance par défaut. Créer publication Icône de la communauté diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4ac21a5f1..85fc54156 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -80,7 +80,6 @@ %1$s utenti / mese Cerca… In attesa - Ordina per Partecipa %1$s utenti / mese Seleziona comunità dalla lista diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 93040190d..87c881290 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -176,7 +176,6 @@ 追加オプションを表示 コメント編集を保存 返信を送る - 並び替え 投稿を作成 コミュニティアイコン 確定 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index f319f8920..ada6bfbf2 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -80,7 +80,6 @@ %1$s 사용자 / 월 검색… 보류 중 - 정렬 구독 %1$s 사용자 / 월 크래시 기록 diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 00c645471..e5757d44d 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -65,7 +65,6 @@ %1$s gebruikers / maand Community icoon Lid geworden - Sorteer op Abonneren In behandeling Bewaar reactie diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index cee5743a9..9e6cdcee2 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -81,8 +81,7 @@ %1$s brukarar/månad Søk … Ventar - Sorter etter - Abonner + Abonner %1$s brukarar/månad Krasjloggar Alle krasjloggar vart sletta diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 02ef2480d..ad989eceb 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -176,7 +176,6 @@ Pokaż więcej opcji Zapisz edytowany komentarz Wyślij odpowiedź - Sortuj po Stwórz post Ikonka Społeczności Prześlij diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 26f8d6964..a9f597176 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -24,7 +24,6 @@ %1$s пользователей / месяц Поиск… В ожидании - Отсортировать по Подписаться %1$s пользователей / месяц Журналы сбоев diff --git a/app/src/main/res/values-se/strings.xml b/app/src/main/res/values-se/strings.xml index 3cd21f800..664b7e0e4 100644 --- a/app/src/main/res/values-se/strings.xml +++ b/app/src/main/res/values-se/strings.xml @@ -175,7 +175,6 @@ Visa fler alternativ Spara redigerad kommentar Skicka svar - Sortera efter Skapa inlägg Gemenskapens profilbild Skicka in diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 16b9ced8b..c65042d45 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -81,7 +81,6 @@ %1$s користувачів за місяць Що ви шукаєте? Очікує на розгляд - Впорядкувати за Передплатити %1$s користувачів за місяць Обрати спільноту з переліку diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 338f563e4..42f9f74b9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -82,7 +82,6 @@ %1$s users / month Search… Pending - Sort by Subscribe %1$s users / month Crash Logs @@ -403,4 +402,6 @@ Failed to parse datetime There is no record of this comment The version (%s) is not supported + Search in + Limit to