diff --git a/app/build.gradle.kts b/app/build.gradle.kts index eedbaa578..d2a77eaa5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -136,6 +136,12 @@ dependencies { implementation("androidx.appcompat:appcompat:1.7.1") testImplementation("androidx.arch.core:core-testing:2.2.0") + // Adaptive layouts + implementation("androidx.compose.material3.adaptive:adaptive:1.2.0") + implementation("androidx.compose.material3.adaptive:adaptive-layout:1.2.0") + implementation("androidx.compose.material3.adaptive:adaptive-navigation:1.2.0") + implementation("androidx.compose.material3:material3-adaptive-navigation-suite") + implementation("me.zhanghai.compose.preference:library:1.1.1") // Freedroidwarn @@ -215,9 +221,12 @@ dependencies { implementation("androidx.profileinstaller:profileinstaller:1.4.1") baselineProfile(project(":benchmarks")) - implementation("it.vercruysse.lemmyapi:lemmy-api:0.4.1") + implementation("it.vercruysse.lemmyapi:lemmy-api:0.6.0-beta.7") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0") - // For custom logging plugin - implementation("io.ktor:ktor-client-logging:3.5.1") + val ktor = "3.5.2" + implementation("io.ktor:ktor-client-core:$ktor") + implementation("io.ktor:ktor-client-okhttp:$ktor") + implementation("io.ktor:ktor-client-logging:$ktor") + } diff --git a/app/src/main/java/com/jerboa/JerboaAppState.kt b/app/src/main/java/com/jerboa/JerboaAppState.kt index 0269f8ff7..1736e68ff 100644 --- a/app/src/main/java/com/jerboa/JerboaAppState.kt +++ b/app/src/main/java/com/jerboa/JerboaAppState.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController +import com.jerboa.datatypes.BanData import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.model.ReplyItem import com.jerboa.state.VideoAppState @@ -34,6 +35,7 @@ import it.vercruysse.lemmyapi.datatypes.CommunityId import it.vercruysse.lemmyapi.datatypes.GetCommunityResponse import it.vercruysse.lemmyapi.datatypes.Person import it.vercruysse.lemmyapi.datatypes.PersonId +import it.vercruysse.lemmyapi.datatypes.PersonView import it.vercruysse.lemmyapi.datatypes.Post import it.vercruysse.lemmyapi.datatypes.PostId import it.vercruysse.lemmyapi.datatypes.PostView @@ -95,8 +97,8 @@ class JerboaAppState( navController.navigate(Route.COMMENT_REMOVE) } - fun toBanPerson(person: Person) { - sendReturnForwards(BanPersonReturn.PERSON_SEND, person) + fun toBanPerson(banData: BanData) { + sendReturnForwards(BanPersonReturn.BAN_DATA_SEND, banData) navController.navigate(Route.BAN_PERSON) } diff --git a/app/src/main/java/com/jerboa/JerboaApplication.kt b/app/src/main/java/com/jerboa/JerboaApplication.kt index 314ffe59d..9661d4bf8 100644 --- a/app/src/main/java/com/jerboa/JerboaApplication.kt +++ b/app/src/main/java/com/jerboa/JerboaApplication.kt @@ -27,7 +27,7 @@ class JerboaApplication : imageLoader = ImageLoader .Builder(this) - .okHttpClient(API.httpClient) + .okHttpClient(API.okHttpClient) .crossfade(true) .error(R.drawable.error_placeholder) .placeholder(R.drawable.ic_launcher_mono) diff --git a/app/src/main/java/com/jerboa/MainActivity.kt b/app/src/main/java/com/jerboa/MainActivity.kt index 38cbecc7e..ec0c272c0 100644 --- a/app/src/main/java/com/jerboa/MainActivity.kt +++ b/app/src/main/java/com/jerboa/MainActivity.kt @@ -16,13 +16,16 @@ import androidx.compose.animation.ExitTransition import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.DrawerValue +import androidx.compose.material3.Surface import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.core.content.getSystemService import androidx.navigation.compose.NavHost @@ -31,6 +34,7 @@ import androidx.navigation.navArgument import androidx.navigation.navDeepLink import arrow.core.Either import coil.Coil +import com.jerboa.api.toOpt import com.jerboa.db.APP_SETTINGS_DEFAULT import com.jerboa.feat.BackConfirmation.addConfirmationDialog import com.jerboa.feat.BackConfirmation.addConfirmationToast @@ -44,6 +48,7 @@ import com.jerboa.model.AccountViewModel import com.jerboa.model.AccountViewModelFactory import com.jerboa.model.AppSettingsViewModel import com.jerboa.model.AppSettingsViewModelFactory +import com.jerboa.model.MyUserInfoViewModel import com.jerboa.model.SiteViewModel import com.jerboa.ui.components.ban.BanFromCommunityScreen import com.jerboa.ui.components.ban.BanPersonScreen @@ -56,7 +61,7 @@ import com.jerboa.ui.components.common.SwipeToNavigateBack import com.jerboa.ui.components.community.CommunityScreen import com.jerboa.ui.components.community.list.CommunityListScreen import com.jerboa.ui.components.community.sidebar.CommunitySidebarScreen -import com.jerboa.ui.components.home.BottomNavScreen +import com.jerboa.ui.components.home.HomeNavScreen import com.jerboa.ui.components.home.ShowAppStartupDialogs import com.jerboa.ui.components.home.legal.SiteLegalScreen import com.jerboa.ui.components.home.sidebar.SiteSidebarScreen @@ -64,7 +69,7 @@ import com.jerboa.ui.components.imageviewer.ImageViewerScreen import com.jerboa.ui.components.inbox.InboxScreen import com.jerboa.ui.components.login.LoginScreen import com.jerboa.ui.components.person.PersonProfileScreen -import com.jerboa.ui.components.post.PostScreen +import com.jerboa.ui.components.post.PostPane import com.jerboa.ui.components.post.create.CreatePostScreen import com.jerboa.ui.components.post.edit.PostEditScreen import com.jerboa.ui.components.privatemessage.CreatePrivateMessageScreen @@ -92,6 +97,7 @@ import org.woheller69.freeDroidWarn.FreeDroidWarn class MainActivity : AppCompatActivity() { val siteViewModel by viewModels(factoryProducer = { SiteViewModel.Factory }) val accountViewModel by viewModels(factoryProducer = { AccountViewModelFactory.Factory }) + val myUserInfoViewModel by viewModels(factoryProducer = { AccountViewModelFactory.Factory }) private val appSettingsViewModel by viewModels(factoryProducer = { AppSettingsViewModelFactory.Factory }) private val accountSettingsViewModel by viewModels( factoryProducer = { AccountSettingsViewModelFactory.Factory }, @@ -107,16 +113,14 @@ class MainActivity : AppCompatActivity() { val ctx = LocalContext.current val appSettings by appSettingsViewModel.appSettings.observeAsState(APP_SETTINGS_DEFAULT) + val myUserInfo = myUserInfoViewModel.myUserRes.toOpt() + val siteRes = siteViewModel.siteRes.toOpt() @Suppress("SENSELESS_COMPARISON") if (appSettings == null) { triggerRebirth(ctx) } - val connectivityManager = ctx.getSystemService() - val lowBandwidthMode = - appSettings.lowBandwidthMode.toEnum().isActive(connectivityManager) - if (appSettings.autoPlayGifs) { Coil.setImageLoader((ctx.applicationContext as JerboaApplication).imageGifLoader) } else { @@ -126,756 +130,754 @@ class MainActivity : AppCompatActivity() { JerboaTheme( appSettings = appSettings, ) { - val appState = rememberJerboaAppState() + Surface(modifier = Modifier.fillMaxSize()) { + val appState = rememberJerboaAppState() + DisposableEffect(Unit) { + onDispose { + appState.release() + } + } - DisposableEffect(Unit) { - onDispose { - appState.release() + val showConfirmationDialog = remember { mutableStateOf(false) } + + if (showConfirmationDialog.value) { + ShowConfirmationDialog({ showConfirmationDialog.value = false }, ::finish) } - } - val showConfirmationDialog = remember { mutableStateOf(false) } + DisposableEffect(appSettings.backConfirmationMode) { + when (appSettings.backConfirmationMode.toEnum()) { + BackConfirmationMode.Toast -> { + this@MainActivity.addConfirmationToast(appState.navController, ctx) + } - if (showConfirmationDialog.value) { - ShowConfirmationDialog({ showConfirmationDialog.value = false }, ::finish) - } + BackConfirmationMode.Dialog -> { + this@MainActivity.addConfirmationDialog(appState.navController) { showConfirmationDialog.value = true } + } - DisposableEffect(appSettings.backConfirmationMode) { - when (appSettings.backConfirmationMode.toEnum()) { - BackConfirmationMode.Toast -> { - this@MainActivity.addConfirmationToast(appState.navController, ctx) + BackConfirmationMode.None -> {} } - BackConfirmationMode.Dialog -> { - this@MainActivity.addConfirmationDialog(appState.navController) { showConfirmationDialog.value = true } + onDispose { + disposeConfirmation() } - - BackConfirmationMode.None -> {} } - onDispose { - disposeConfirmation() - } - } - - MarkdownHelper.init( - appState, - appSettings.useCustomTabs, - appSettings.usePrivateTabs, - lowBandwidthMode, - object : BetterLinkMovementMethod.OnLinkLongClickListener { - override fun onLongClick( - textView: TextView, - url: String, - ): Boolean { - appState.showLinkPopup(url) - return true - } - }, - ) - - LinkDropDownMenu( - appState.linkDropdownExpanded.value, - appState::hideLinkPopup, - appState, - appSettings.useCustomTabs, - appSettings.usePrivateTabs, - ) - - ShowAppStartupDialogs( - appSettingsViewModel = appSettingsViewModel, - siteViewModel = siteViewModel, - ) - - val drawerState = rememberDrawerState(DrawerValue.Closed) - - NavHost( - route = Route.Graph.ROOT, - navController = appState.navController, - startDestination = Route.HOME, - enterTransition = { - slideInHorizontally { it } - }, - exitTransition = - { + MarkdownHelper.init( + appState = appState, + appSettings = appSettings, + ctx = ctx, + onLongClick = object : BetterLinkMovementMethod.OnLinkLongClickListener { + override fun onLongClick( + textView: TextView, + url: String, + ): Boolean { + appState.showLinkPopup(url) + return true + } + }, + ) + + LinkDropDownMenu( + link = appState.linkDropdownExpanded.value, + onDismissRequest = appState::hideLinkPopup, + appState = appState, + appSettings = appSettings, + ) + + ShowAppStartupDialogs( + appSettingsViewModel = appSettingsViewModel, + myUserInfo= myUserInfo, + ) + + val drawerState = rememberDrawerState(DrawerValue.Closed) + + NavHost( + route = Route.Graph.ROOT, + navController = appState.navController, + startDestination = Route.HOME, + enterTransition = { + slideInHorizontally { it } + }, + exitTransition = + { + // No animation for image viewer + if (this.targetState.destination.route == Route.IMAGE_VIEW) { + ExitTransition.None + } else { + slideOutHorizontally { -it } + } + }, + popEnterTransition = { // No animation for image viewer - if (this.targetState.destination.route == Route.IMAGE_VIEW) { - ExitTransition.None + if (this.initialState.destination.route == Route.IMAGE_VIEW) { + EnterTransition.None } else { - slideOutHorizontally { -it } + slideInHorizontally { -it } + } + }, + popExitTransition = { + slideOutHorizontally { + it } }, - popEnterTransition = { - // No animation for image viewer - if (this.initialState.destination.route == Route.IMAGE_VIEW) { - EnterTransition.None - } else { - slideInHorizontally { -it } - } - }, - popExitTransition = { - slideOutHorizontally { - it - } - }, - ) { - composable( - route = Route.LOGIN, - deepLinks = - DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/login" } - }, ) { - LoginScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - ) - } + composable( + route = Route.LOGIN, + deepLinks = + DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "$instance/login" } + }, + ) { + LoginScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + myUserInfoViewModel = myUserInfoViewModel, + ) + } - composable(route = Route.HOME) { - BottomNavScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - appSettingsViewModel = appSettingsViewModel, - appSettings = appSettings, - drawerState = drawerState, - lowBandwidthMode = lowBandwidthMode, - ) - } + composable(route = Route.HOME) { + HomeNavScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + myUserInfoViewModel = myUserInfoViewModel, + appSettingsViewModel = appSettingsViewModel, + appSettings = appSettings, + drawerState = drawerState, + lowBandwidthMode = lowBandwidthMode, + ) + } - composable( - route = Route.COMMUNITY_FROM_ID, - arguments = - listOf( - navArgument(Route.CommunityFromIdArgs.ID) { - type = Route.CommunityFromIdArgs.ID_TYPE - }, - ), - ) { - val args = Route.CommunityFromIdArgs(it) - - CommunityScreen( - communityArg = Either.Left(args.id), - appState = appState, - accountViewModel = accountViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), - lowBandwidthMode = lowBandwidthMode, - ) - } + composable( + route = Route.COMMUNITY_FROM_ID, + arguments = + listOf( + navArgument(Route.CommunityFromIdArgs.ID) { + type = Route.CommunityFromIdArgs.ID_TYPE + }, + ), + ) { + val args = Route.CommunityFromIdArgs(it) - // Only necessary for community deeplinks - composable( - route = Route.COMMUNITY_FROM_URL, - deepLinks = - listOf( - navDeepLink { uriPattern = Route.COMMUNITY_FROM_URL }, - ), - arguments = - listOf( - navArgument(Route.CommunityFromUrlArgs.NAME) { - type = Route.CommunityFromUrlArgs.NAME_TYPE - }, - navArgument(Route.CommunityFromUrlArgs.INSTANCE) { - type = Route.CommunityFromUrlArgs.INSTANCE_TYPE - }, - ), - ) { - val args = Route.CommunityFromUrlArgs(it) - // Could be instance/c/community@otherinstance ({instance}/c/{name}) - // Name could contain its instance already, thus we check for it - val qualifiedName = - if (args.name.contains("@")) { - args.name - } else { - "${args.name}@${args.instance}" - } + CommunityScreen( + communityArg = Either.Left(args.id), + appState = appState, + accountViewModel = accountViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + siteViewModel = siteViewModel, + myUserInfoViewModel = myUserInfoViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreviews = appSettings.showPostLinkPreviews, + markAsReadOnScroll = appSettings.markAsReadOnScroll, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), + lowBandwidthMode = lowBandwidthMode, + ) + } - CommunityScreen( - communityArg = Either.Right(qualifiedName), - appState = appState, - accountViewModel = accountViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), - lowBandwidthMode = lowBandwidthMode, - ) - } + // Only necessary for community deeplinks + composable( + route = Route.COMMUNITY_FROM_URL, + deepLinks = + listOf( + navDeepLink { uriPattern = Route.COMMUNITY_FROM_URL }, + ), + arguments = + listOf( + navArgument(Route.CommunityFromUrlArgs.NAME) { + type = Route.CommunityFromUrlArgs.NAME_TYPE + }, + navArgument(Route.CommunityFromUrlArgs.INSTANCE) { + type = Route.CommunityFromUrlArgs.INSTANCE_TYPE + }, + ), + ) { + val args = Route.CommunityFromUrlArgs(it) + // Could be instance/c/community@otherinstance ({instance}/c/{name}) + // Name could contain its instance already, thus we check for it + val qualifiedName = + if (args.name.contains("@")) { + args.name + } else { + "${args.name}@${args.instance}" + } + + CommunityScreen( + communityArg = Either.Right(qualifiedName), + appState = appState, + accountViewModel = accountViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + siteViewModel = siteViewModel, + myUserInfoViewModel = myUserInfoViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreviews = appSettings.showPostLinkPreviews, + markAsReadOnScroll = appSettings.markAsReadOnScroll, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), + lowBandwidthMode = lowBandwidthMode, + ) + } - composable(route = Route.COMMUNITY_SIDEBAR) { - CommunitySidebarScreen( - appState = appState, - onClickBack = appState::popBackStack, - showAvatar = siteViewModel.showAvatar() && !lowBandwidthMode, - ) - } + composable(route = Route.COMMUNITY_SIDEBAR) { + CommunitySidebarScreen( + appState = appState, + onClickBack = appState::popBackStack, + myUserInfoViewModel = myUserInfoViewModel, + lowBandwidthMode = lowBandwidthMode, + ) + } - composable( - route = Route.PROFILE_FROM_ID, - arguments = - listOf( - navArgument(Route.ProfileFromIdArgs.ID) { - type = Route.ProfileFromIdArgs.ID_TYPE - }, - navArgument(Route.ProfileFromIdArgs.SAVED) { - defaultValue = Route.ProfileFromIdArgs.SAVED_DEFAULT - type = Route.ProfileFromIdArgs.SAVED_TYPE - }, - ), - ) { - val args = Route.ProfileFromIdArgs(it) - PersonProfileScreen( - personArg = Either.Left(args.id), - savedMode = args.saved, - appState = appState, - accountViewModel = accountViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - drawerState = drawerState, - onBack = appState::popBackStack, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), - lowBandwidthMode = lowBandwidthMode, - ) - } + composable( + route = Route.PROFILE_FROM_ID, + arguments = + listOf( + navArgument(Route.ProfileFromIdArgs.ID) { + type = Route.ProfileFromIdArgs.ID_TYPE + }, + navArgument(Route.ProfileFromIdArgs.SAVED) { + defaultValue = Route.ProfileFromIdArgs.SAVED_DEFAULT + type = Route.ProfileFromIdArgs.SAVED_TYPE + }, + ), + ) { + val args = Route.ProfileFromIdArgs(it) + PersonProfileScreen( + personArg = Either.Left(args.id), + savedMode = args.saved, + appState = appState, + accountViewModel = accountViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + siteViewModel = siteViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreviews = appSettings.showPostLinkPreviews, + drawerState = drawerState, + onBack = appState::popBackStack, + markAsReadOnScroll = appSettings.markAsReadOnScroll, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), + lowBandwidthMode = lowBandwidthMode, + ) + } - // Necessary for deep links - composable( - route = Route.PROFILE_FROM_URL, - deepLinks = - listOf( - navDeepLink { uriPattern = Route.PROFILE_FROM_URL }, - ), - arguments = - listOf( - navArgument(Route.ProfileFromUrlArgs.NAME) { - type = Route.ProfileFromUrlArgs.NAME_TYPE - }, - navArgument(Route.ProfileFromUrlArgs.INSTANCE) { - type = Route.ProfileFromUrlArgs.INSTANCE_TYPE - }, - ), - ) { - val args = Route.ProfileFromUrlArgs(it) - val qualifiedName = "${args.name}@${args.instance}" - PersonProfileScreen( - personArg = Either.Right(qualifiedName), - savedMode = false, - appState = appState, - accountViewModel = accountViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - drawerState = drawerState, - onBack = appState::popBackStack, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), - lowBandwidthMode = lowBandwidthMode, - ) - } + // Necessary for deep links + composable( + route = Route.PROFILE_FROM_URL, + deepLinks = + listOf( + navDeepLink { uriPattern = Route.PROFILE_FROM_URL }, + ), + arguments = + listOf( + navArgument(Route.ProfileFromUrlArgs.NAME) { + type = Route.ProfileFromUrlArgs.NAME_TYPE + }, + navArgument(Route.ProfileFromUrlArgs.INSTANCE) { + type = Route.ProfileFromUrlArgs.INSTANCE_TYPE + }, + ), + ) { + val args = Route.ProfileFromUrlArgs(it) + val qualifiedName = "${args.name}@${args.instance}" + PersonProfileScreen( + personArg = Either.Right(qualifiedName), + savedMode = false, + appState = appState, + accountViewModel = accountViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + siteViewModel = siteViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreviews = appSettings.showPostLinkPreviews, + drawerState = drawerState, + onBack = appState::popBackStack, + markAsReadOnScroll = appSettings.markAsReadOnScroll, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), + lowBandwidthMode = lowBandwidthMode, + ) + } - composable( - route = Route.COMMUNITY_LIST, - arguments = - listOf( - navArgument(Route.CommunityListArgs.SELECT) { - defaultValue = Route.CommunityListArgs.SELECT_DEFAULT - type = Route.CommunityListArgs.SELECT_TYPE - }, - ), - ) { - val args = Route.CommunityListArgs(it) - CommunityListScreen( - appState = appState, - selectMode = args.select, - blurNSFW = appSettings.blurNSFW.toEnum(), - drawerState = drawerState, - followList = siteViewModel.getFollowList(), - showAvatar = siteViewModel.showAvatar() && !lowBandwidthMode, - ) - } + composable( + route = Route.COMMUNITY_LIST, + arguments = + listOf( + navArgument(Route.CommunityListArgs.SELECT) { + defaultValue = Route.CommunityListArgs.SELECT_DEFAULT + type = Route.CommunityListArgs.SELECT_TYPE + }, + ), + ) { + val args = Route.CommunityListArgs(it) + CommunityListScreen( + appState = appState, + selectMode = args.select, + appSettings = appSettings, + myUserInfo = myUserInfo, + drawerState = drawerState, + ) + } - composable( - route = Route.CREATE_POST, - deepLinks = - listOf( - navDeepLink { mimeType = "text/plain" }, - navDeepLink { mimeType = "image/*" }, - ), - ) { - val activity = ctx.findActivity() - val text = activity?.intent?.getStringExtra(Intent.EXTRA_TEXT) ?: "" - val image = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - activity?.intent?.getParcelableExtra( - Intent.EXTRA_STREAM, - Uri::class.java, - ) + composable( + route = Route.CREATE_POST, + deepLinks = + listOf( + navDeepLink { mimeType = "text/plain" }, + navDeepLink { mimeType = "image/*" }, + ), + ) { + val activity = ctx.findActivity() + val text = activity?.intent?.getStringExtra(Intent.EXTRA_TEXT) ?: "" + val image = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + activity?.intent?.getParcelableExtra( + Intent.EXTRA_STREAM, + Uri::class.java, + ) + } else { + @Suppress("DEPRECATION") + activity?.intent?.getParcelableExtra(Intent.EXTRA_STREAM) + } + // url and body will be empty everytime except when there is EXTRA TEXT in the intent + var url = "" + var body = "" + if (Patterns.WEB_URL.matcher(text).matches()) { + url = text.padUrlWithHttps() } else { - @Suppress("DEPRECATION") - activity?.intent?.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri + body = text } - // url and body will be empty everytime except when there is EXTRA TEXT in the intent - var url = "" - var body = "" - if (Patterns.WEB_URL.matcher(text).matches()) { - url = text.padUrlWithHttps() - } else { - body = text - } - CreatePostScreen( - appState = appState, - accountViewModel = accountViewModel, - initialUrl = url, - initialBody = body, - initialImage = image, - ) - activity?.intent?.replaceExtras(Bundle()) - } + CreatePostScreen( + appState = appState, + accountViewModel = accountViewModel, + initialUrl = url, + initialBody = body, + initialImage = image, + ) + activity?.intent?.replaceExtras(Bundle()) + } - composable( - route = Route.INBOX, - deepLinks = - DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/inbox" } - }, - ) { - InboxScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - blurNSFW = appSettings.blurNSFW.toEnum(), - lowBandwidthMode = lowBandwidthMode, - drawerState = drawerState, - ) - } + composable( + route = Route.INBOX, + deepLinks = + DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "$instance/inbox" } + }, + ) { + InboxScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + blurNSFW = appSettings.blurNSFW.toEnum(), + lowBandwidthMode = lowBandwidthMode, + drawerState = drawerState, + ) + } - composable( - route = Route.REGISTRATION_APPLICATIONS, - ) { - RegistrationApplicationsScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - drawerState = drawerState, - lowBandwidthMode = lowBandwidthMode, - ) - } + composable( + route = Route.REGISTRATION_APPLICATIONS, + ) { + RegistrationApplicationsScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + drawerState = drawerState, + lowBandwidthMode = lowBandwidthMode, + ) + } - composable( - route = Route.REGISTRATION_APPLICATIONS, - ) { - RegistrationApplicationsScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - drawerState = drawerState, - lowBandwidthMode = lowBandwidthMode, - ) - } + composable( + route = Route.REGISTRATION_APPLICATIONS, + ) { + RegistrationApplicationsScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + drawerState = drawerState, + lowBandwidthMode = lowBandwidthMode, + ) + } - composable( - route = Route.REPORTS, - ) { - ReportsScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - drawerState = drawerState, - blurNSFW = appSettings.blurNSFW.toEnum(), - lowBandwidthMode = lowBandwidthMode, - ) - } + composable( + route = Route.REPORTS, + ) { + ReportsScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + drawerState = drawerState, + blurNSFW = appSettings.blurNSFW.toEnum(), + lowBandwidthMode = lowBandwidthMode, + ) + } - composable( - route = Route.POST, - deepLinks = - DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/post/{${Route.PostArgs.ID}}" } - }, - arguments = - listOf( - navArgument(Route.PostArgs.ID) { - type = Route.PostArgs.ID_TYPE + composable( + route = Route.POST, + deepLinks = + DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "$instance/post/{${Route.PostArgs.ID}}" } }, - ), - ) { - val args = Route.PostArgs(it) + arguments = + listOf( + navArgument(Route.PostArgs.ID) { + type = Route.PostArgs.ID_TYPE + }, + ), + ) { + val args = Route.PostArgs(it) + + SwipeToNavigateBack( + appSettings.postNavigationGestureMode.toEnumSafe(), + appState::navigateUp, + ) { + if (siteRes != null) { + PostPane( + postOrCommentId = Either.Left(args.id), + accountViewModel = accountViewModel, + appState = appState, + appSettings = appSettings, + siteRes = siteRes, + myUserInfo = myUserInfo, + onClickBack = appState::popBackStack, + ) + } + } + } - SwipeToNavigateBack( - appSettings.postNavigationGestureMode.toEnumSafe(), - appState::navigateUp, + composable( + route = Route.COMMENT, + deepLinks = + DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "$instance/comment/{${Route.CommentArgs.ID}}" } + }, + arguments = + listOf( + navArgument(Route.CommentArgs.ID) { + type = Route.CommentArgs.ID_TYPE + }, + ), ) { - PostScreen( - id = Either.Left(args.id), + val args = Route.CommentArgs(it) + PostPane( + postOrCommentId = Either.Right(args.id), accountViewModel = accountViewModel, appState = appState, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, showCollapsedCommentContent = appSettings.showCollapsedCommentContent, showActionBarByDefault = appSettings.showCommentActionBarByDefault, showVotingArrowsInListView = appSettings.showVotingArrowsInListView, showParentCommentNavigationButtons = appSettings.showParentCommentNavigationButtons, navigateParentCommentsWithVolumeButtons = appSettings.navigateParentCommentsWithVolumeButtons, siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, blurNSFW = appSettings.blurNSFW.toEnum(), showPostLinkPreview = appSettings.showPostLinkPreviews, postActionBarMode = appSettings.postActionBarMode.toEnum(), swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, + onClickBack = appState::popBackStack, ) } - } - - composable( - route = Route.COMMENT, - deepLinks = - DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/comment/{${Route.CommentArgs.ID}}" } - }, - arguments = - listOf( - navArgument(Route.CommentArgs.ID) { - type = Route.CommentArgs.ID_TYPE - }, - ), - ) { - val args = Route.CommentArgs(it) - PostScreen( - id = Either.Right(args.id), - accountViewModel = accountViewModel, - appState = appState, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - showCollapsedCommentContent = appSettings.showCollapsedCommentContent, - showActionBarByDefault = appSettings.showCommentActionBarByDefault, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - showParentCommentNavigationButtons = appSettings.showParentCommentNavigationButtons, - navigateParentCommentsWithVolumeButtons = appSettings.navigateParentCommentsWithVolumeButtons, - siteViewModel = siteViewModel, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreview = appSettings.showPostLinkPreviews, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), - lowBandwidthMode = lowBandwidthMode, - ) - } - composable( - route = Route.COMMENT_REPLY, - ) { - CommentReplyScreen( - accountViewModel = accountViewModel, - appState = appState, - siteViewModel = siteViewModel, - lowBandwidthMode = lowBandwidthMode, - ) - } + composable( + route = Route.COMMENT_REPLY, + ) { + CommentReplyScreen( + accountViewModel = accountViewModel, + appState = appState, + siteViewModel = siteViewModel, + lowBandwidthMode = lowBandwidthMode, + ) + } - composable(route = Route.SITE_SIDEBAR) { - SiteSidebarScreen( - appState = appState, - siteViewModel = siteViewModel, - lowBandwidthMode = lowBandwidthMode, - ) - } + composable(route = Route.SITE_SIDEBAR) { + SiteSidebarScreen( + appState = appState, + siteViewModel = siteViewModel, + lowBandwidthMode = lowBandwidthMode, + ) + } - composable(route = Route.SITE_LEGAL) { - SiteLegalScreen( - siteViewModel = siteViewModel, - onBackClick = appState::popBackStack, - ) - } + composable(route = Route.SITE_LEGAL) { + SiteLegalScreen( + siteViewModel = siteViewModel, + onBackClick = appState::popBackStack, + ) + } - composable(route = Route.COMMENT_EDIT) { - CommentEditScreen( - appState = appState, - accountViewModel = accountViewModel, - ) - } + composable(route = Route.COMMENT_EDIT) { + CommentEditScreen( + appState = appState, + accountViewModel = accountViewModel, + ) + } - composable(route = Route.POST_EDIT) { - PostEditScreen( - accountViewModel = accountViewModel, - appState = appState, - ) - } + composable(route = Route.POST_EDIT) { + PostEditScreen( + accountViewModel = accountViewModel, + appState = appState, + ) + } - composable(route = Route.PRIVATE_MESSAGE_REPLY) { - PrivateMessageReplyScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - lowBandwidthMode = lowBandwidthMode, - onBack = appState::popBackStack, - onProfile = appState::toProfile, - ) - } + composable(route = Route.PRIVATE_MESSAGE_REPLY) { + PrivateMessageReplyScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + lowBandwidthMode = lowBandwidthMode, + onBack = appState::popBackStack, + onProfile = appState::toProfile, + ) + } - composable( - route = Route.POST_REMOVE, - ) { - PostRemoveScreen( - appState = appState, - accountViewModel = accountViewModel, - ) - } + composable( + route = Route.POST_REMOVE, + ) { + PostRemoveScreen( + appState = appState, + accountViewModel = accountViewModel, + ) + } - composable( - route = Route.POST_REMOVE, - ) { - PostRemoveScreen( - appState = appState, - accountViewModel = accountViewModel, - ) - } + composable( + route = Route.POST_REMOVE, + ) { + PostRemoveScreen( + appState = appState, + accountViewModel = accountViewModel, + ) + } - composable( - route = Route.COMMENT_REMOVE, - ) { - CommentRemoveScreen( - appState = appState, - accountViewModel = accountViewModel, - ) - } + composable( + route = Route.COMMENT_REMOVE, + ) { + CommentRemoveScreen( + appState = appState, + accountViewModel = accountViewModel, + ) + } - composable( - route = Route.BAN_PERSON, - ) { - BanPersonScreen( - appState = appState, - accountViewModel = accountViewModel, - ) - } + composable( + route = Route.BAN_PERSON, + ) { + BanPersonScreen( + appState = appState, + accountViewModel = accountViewModel, + ) + } - composable( - route = Route.BAN_FROM_COMMUNITY, - ) { - BanFromCommunityScreen( - appState = appState, - accountViewModel = accountViewModel, - ) - } + composable( + route = Route.BAN_FROM_COMMUNITY, + ) { + BanFromCommunityScreen( + appState = appState, + accountViewModel = accountViewModel, + ) + } - composable( - route = Route.POST_REPORT, - arguments = - listOf( - navArgument(Route.PostReportArgs.ID) { - type = Route.PostReportArgs.ID_TYPE - }, - ), - ) { - val args = Route.PostReportArgs(it) - CreatePostReportScreen( - postId = args.id, - accountViewModel = accountViewModel, - onBack = appState::navigateUp, - ) - } + composable( + route = Route.POST_REPORT, + arguments = + listOf( + navArgument(Route.PostReportArgs.ID) { + type = Route.PostReportArgs.ID_TYPE + }, + ), + ) { + val args = Route.PostReportArgs(it) + CreatePostReportScreen( + postId = args.id, + accountViewModel = accountViewModel, + onBack = appState::navigateUp, + ) + } - composable( - route = Route.POST_LIKES, - arguments = - listOf( - navArgument(Route.PostLikesArgs.ID) { - type = Route.PostLikesArgs.ID_TYPE - }, - ), - ) { - val args = Route.PostLikesArgs(it) - PostLikesScreen( - appState = appState, - postId = args.id, - onBack = appState::navigateUp, - ) - } + composable( + route = Route.POST_LIKES, + arguments = + listOf( + navArgument(Route.PostLikesArgs.ID) { + type = Route.PostLikesArgs.ID_TYPE + }, + ), + ) { + val args = Route.PostLikesArgs(it) + PostLikesScreen( + appState = appState, + postId = args.id, + onBack = appState::navigateUp, + ) + } - composable( - route = Route.COMMENT_LIKES, - arguments = - listOf( - navArgument(Route.CommentLikesArgs.ID) { - type = Route.CommentLikesArgs.ID_TYPE - }, - ), - ) { - val args = Route.CommentLikesArgs(it) - CommentLikesScreen( - appState = appState, - commentId = args.id, - onBack = appState::navigateUp, - ) - } + composable( + route = Route.COMMENT_LIKES, + arguments = + listOf( + navArgument(Route.CommentLikesArgs.ID) { + type = Route.CommentLikesArgs.ID_TYPE + }, + ), + ) { + val args = Route.CommentLikesArgs(it) + CommentLikesScreen( + appState = appState, + commentId = args.id, + onBack = appState::navigateUp, + ) + } - composable( - route = Route.COMMENT_REPORT, - arguments = - listOf( - navArgument(Route.CommentReportArgs.ID) { - type = Route.CommentReportArgs.ID_TYPE - }, - ), - ) { - val args = Route.CommentReportArgs(it) - CreateCommentReportScreen( - commentId = args.id, - accountViewModel = accountViewModel, - onBack = appState::navigateUp, - ) - } + composable( + route = Route.COMMENT_REPORT, + arguments = + listOf( + navArgument(Route.CommentReportArgs.ID) { + type = Route.CommentReportArgs.ID_TYPE + }, + ), + ) { + val args = Route.CommentReportArgs(it) + CreateCommentReportScreen( + commentId = args.id, + accountViewModel = accountViewModel, + onBack = appState::navigateUp, + ) + } - composable(route = Route.SETTINGS) { - SettingsScreen( - accountViewModel = accountViewModel, - onBack = appState::popBackStack, - onClickAbout = appState::toAbout, - onClickAccountSettings = appState::toAccountSettings, - onClickBlocks = appState::toBlockView, - onClickLookAndFeel = appState::toLookAndFeel, - onClickBackupAndRestore = appState::toBackupAndRestore, - ) - } + composable(route = Route.SETTINGS) { + SettingsScreen( + accountViewModel = accountViewModel, + onBack = appState::popBackStack, + onClickAbout = appState::toAbout, + onClickAccountSettings = appState::toAccountSettings, + onClickBlocks = appState::toBlockView, + onClickLookAndFeel = appState::toLookAndFeel, + onClickBackupAndRestore = appState::toBackupAndRestore, + ) + } - composable(route = Route.LOOK_AND_FEEL) { - LookAndFeelScreen( - appSettingsViewModel = appSettingsViewModel, - onBack = appState::popBackStack, - ) - } + composable(route = Route.LOOK_AND_FEEL) { + LookAndFeelScreen( + appSettingsViewModel = appSettingsViewModel, + onBack = appState::popBackStack, + ) + } - composable( - route = Route.ACCOUNT_SETTINGS, - deepLinks = - DEFAULT_LEMMY_INSTANCES.map { instance -> - navDeepLink { uriPattern = "$instance/settings" } - }, - ) { - AccountSettingsScreen( - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - accountSettingsViewModel = accountSettingsViewModel, - onBack = appState::popBackStack, - ) - } + composable( + route = Route.ACCOUNT_SETTINGS, + deepLinks = + DEFAULT_LEMMY_INSTANCES.map { instance -> + navDeepLink { uriPattern = "$instance/settings" } + }, + ) { + AccountSettingsScreen( + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + accountSettingsViewModel = accountSettingsViewModel, + onBack = appState::popBackStack, + ) + } - composable(route = Route.BACKUP_AND_RESTORE) { - BackupAndRestoreScreen( - onBack = appState::popBackStack, - ) - } + composable(route = Route.BACKUP_AND_RESTORE) { + BackupAndRestoreScreen( + onBack = appState::popBackStack, + ) + } - composable(route = Route.ABOUT) { - AboutScreen( - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - onBack = appState::popBackStack, - onClickCrashLogs = appState::toCrashLogs, - openLinkRaw = appState::openLinkRaw, - ) - } + composable(route = Route.ABOUT) { + AboutScreen( + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + onBack = appState::popBackStack, + onClickCrashLogs = appState::toCrashLogs, + openLinkRaw = appState::openLinkRaw, + ) + } - composable(route = Route.BLOCK_VIEW) { - BlocksScreen( - siteViewModel = siteViewModel, - onBack = appState::popBackStack, - ) - } + composable(route = Route.BLOCK_VIEW) { + BlocksScreen( + myUserInfoViewModel = myUserInfoViewModel, + onBack = appState::popBackStack, + ) + } - composable(route = Route.CRASH_LOGS) { - CrashLogsScreen( - onClickBack = appState::popBackStack, - ) - } + composable(route = Route.CRASH_LOGS) { + CrashLogsScreen( + onClickBack = appState::popBackStack, + ) + } - composable( - route = Route.IMAGE_VIEW, - arguments = - listOf( - navArgument(Route.ImageViewArgs.URL) { - type = Route.ImageViewArgs.URL_TYPE - }, - ), - enterTransition = { EnterTransition.None }, - exitTransition = { ExitTransition.None }, - popEnterTransition = { EnterTransition.None }, - popExitTransition = { ExitTransition.None }, - ) { - val args = Route.ImageViewArgs(it) - ImageViewerScreen(url = args.url, appState = appState) - } + composable( + route = Route.IMAGE_VIEW, + arguments = + listOf( + navArgument(Route.ImageViewArgs.URL) { + type = Route.ImageViewArgs.URL_TYPE + }, + ), + enterTransition = { EnterTransition.None }, + exitTransition = { ExitTransition.None }, + popEnterTransition = { EnterTransition.None }, + popExitTransition = { ExitTransition.None }, + ) { + val args = Route.ImageViewArgs(it) + ImageViewerScreen(url = args.url, appState = appState) + } - composable( - route = Route.VIDEO_VIEW, - arguments = - listOf( - navArgument(Route.VideoViewArgs.URL) { - type = Route.VideoViewArgs.URL_TYPE - }, - ), - enterTransition = { EnterTransition.None }, - exitTransition = { ExitTransition.None }, - popEnterTransition = { EnterTransition.None }, - popExitTransition = { ExitTransition.None }, - ) { - val args = Route.VideoViewArgs(it) - VideoViewerScreen(url = args.url, appState = appState) - } + composable( + route = Route.VIDEO_VIEW, + arguments = + listOf( + navArgument(Route.VideoViewArgs.URL) { + type = Route.VideoViewArgs.URL_TYPE + }, + ), + enterTransition = { EnterTransition.None }, + exitTransition = { ExitTransition.None }, + popEnterTransition = { EnterTransition.None }, + popExitTransition = { ExitTransition.None }, + ) { + val args = Route.VideoViewArgs(it) + VideoViewerScreen(url = args.url, appState = appState) + } - composable( - route = Route.CREATE_PRIVATE_MESSAGE, - arguments = - listOf( - navArgument(Route.CreatePrivateMessageArgs.PERSON_ID) { - type = Route.CreatePrivateMessageArgs.PERSON_ID_TYPE - }, - navArgument(Route.CreatePrivateMessageArgs.PERSON_NAME) { - type = Route.CreatePrivateMessageArgs.PERSON_NAME_TYPE - }, - ), - ) { - val args = Route.CreatePrivateMessageArgs(it) - - CreatePrivateMessageScreen( - args.personId, - args.personName, - accountViewModel, - appState::popBackStack, - ) + composable( + route = Route.CREATE_PRIVATE_MESSAGE, + arguments = + listOf( + navArgument(Route.CreatePrivateMessageArgs.PERSON_ID) { + type = Route.CreatePrivateMessageArgs.PERSON_ID_TYPE + }, + navArgument(Route.CreatePrivateMessageArgs.PERSON_NAME) { + type = Route.CreatePrivateMessageArgs.PERSON_NAME_TYPE + }, + ), + ) { + val args = Route.CreatePrivateMessageArgs(it) + + if (myUserInfo != null) { + CreatePrivateMessageScreen( + args.personId, + args.personName, + myUserInfo = myUserInfo, + onBack = appState::popBackStack, + ) + } + } } } } diff --git a/app/src/main/java/com/jerboa/Utils.kt b/app/src/main/java/com/jerboa/Utils.kt index f0eea0673..f316746f3 100644 --- a/app/src/main/java/com/jerboa/Utils.kt +++ b/app/src/main/java/com/jerboa/Utils.kt @@ -1,6 +1,5 @@ package com.jerboa -import android.annotation.SuppressLint import android.app.Activity import android.content.ActivityNotFoundException import android.content.Context @@ -49,9 +48,11 @@ import coil.annotation.ExperimentalCoilApi import coil.imageLoader import com.jerboa.api.API import com.jerboa.datatypes.BanFromCommunityData +import com.jerboa.datatypes.UserViewType import com.jerboa.datatypes.getDisplayName import com.jerboa.db.APP_SETTINGS_DEFAULT import com.jerboa.db.entity.AppSettings +import com.jerboa.db.entity.lowBandwidthMode import com.jerboa.ui.components.common.Route import com.jerboa.ui.components.videoviewer.hosts.DirectFileVideoHost import com.jerboa.ui.theme.SMALL_PADDING @@ -67,9 +68,13 @@ import java.io.InputStream import java.net.MalformedURLException import java.net.URL import java.text.DecimalFormat +import java.time.Instant +import java.time.OffsetDateTime +import java.time.temporal.ChronoUnit import java.util.* import kotlin.math.abs import kotlin.math.pow +import kotlin.time.Clock // / This should be done in a UI wrapper fun toastException( @@ -146,7 +151,7 @@ fun buildCommentsTree( val depthOffset = if (isCommentView && firstComment != null) { - getCommentIdDepthFromPath(firstComment.path, rootCommentId!!) + getCommentIdDepthFromPath(firstComment.path, rootCommentId) } else { 0 } @@ -308,7 +313,7 @@ suspend fun openLink( useCustomTab: Boolean, usePrivateTab: Boolean, ) { - val baseUrl = API.getInstance().baseUrl + val baseUrl = API.getInstance().instance.url val (formatted, parsedUrl) = parseUrl(baseUrl, url) ?: return val userUrl = looksLikeUserUrl(parsedUrl) @@ -384,6 +389,8 @@ fun formatDuration( } } +fun nowBoolean(bool: Boolean): String? = if (bool) Clock.System.now().toString() else null + fun prettyTimeShortener(timeString: String): String = if (prettyTime.locale.language == "en") { if (timeString.isEmpty()) { @@ -454,7 +461,7 @@ fun personNameShown( if (person.local) { name } else { - "$name@${hostName(person.actor_id)}" + "$name@${hostName(person.ap_id)}" } } } @@ -464,15 +471,15 @@ fun personNameShown( */ fun communityNameShown(community: Community): String = if (community.local) { - community.title + community.title ?: community.name } else { - "${community.title}@${hostName(community.actor_id)}" + "${community.title}@${hostName(community.ap_id)}" } fun hostName(url: String): String? = try { URL(url).host - } catch (e: MalformedURLException) { + } catch (_: MalformedURLException) { null } @@ -869,142 +876,183 @@ fun findAndUpdatePrivateMessage( messages: List, updated: PrivateMessageView, ): List { - val foundIndex = - messages.indexOfFirst { - it.private_message.id == updated.private_message.id + val newMessages = messages.toMutableList() + newMessages.replaceAll { + if (it.private_message.id == updated.private_message.id) { + updated + } else { + it } - return if (foundIndex != -1) { - val mutable = messages.toMutableList() - mutable[foundIndex] = updated - mutable.toList() - } else { - messages } + return newMessages } fun findAndUpdateApplication( applications: List, updatedApplication: RegistrationApplicationView, ): List { - val foundIndex = - applications.indexOfFirst { - it.registration_application.id == updatedApplication.registration_application.id + val newApplications = applications.toMutableList() + newApplications.replaceAll { + if (it.registration_application.id == updatedApplication.registration_application.id) { + updatedApplication + } else { + it } - return if (foundIndex != -1) { - val mutable = applications.toMutableList() - mutable[foundIndex] = updatedApplication - mutable.toList() - } else { - applications } + return newApplications } fun findAndUpdatePostReport( - reports: List, + reports: List, updatedReport: PostReportView, -): List { - val foundIndex = - reports.indexOfFirst { - it.post_report.id == updatedReport.post_report.id +): List { + val newReports = reports.toMutableList() + newReports.replaceAll { + when (it) { + is PostReportView -> { + if (it.post_report.id == updatedReport.post_report.id) { + updatedReport + } else { + it + } + } + + else -> { + it + } } - return if (foundIndex != -1) { - val mutable = reports.toMutableList() - mutable[foundIndex] = updatedReport - mutable.toList() - } else { - reports } + return newReports } fun findAndUpdateCommentReport( - reports: List, + reports: List, updatedReport: CommentReportView, -): List { - val foundIndex = - reports.indexOfFirst { - it.comment_report.id == updatedReport.comment_report.id +): List { + val newReports = reports.toMutableList() + newReports.replaceAll { + when (it) { + is CommentReportView -> { + if (it.comment_report.id == updatedReport.comment_report.id) { + updatedReport + } else { + it + } + } + + else -> { + it + } } - return if (foundIndex != -1) { - val mutable = reports.toMutableList() - mutable[foundIndex] = updatedReport - mutable.toList() - } else { - reports } + return newReports } fun findAndUpdatePrivateMessageReport( - reports: List, + reports: List, updatedReport: PrivateMessageReportView, -): List { - val foundIndex = - reports.indexOfFirst { - it.private_message_report.id == updatedReport.private_message_report.id - } - return if (foundIndex != -1) { - val mutable = reports.toMutableList() - mutable[foundIndex] = updatedReport - mutable.toList() - } else { - reports - } -} +): List { + val newReports = reports.toMutableList() + newReports.replaceAll { + when (it) { + is PrivateMessageReportView -> { + if (it.private_message_report.id == updatedReport.private_message_report.id) { + updatedReport + } else { + it + } + } -fun findAndUpdatePersonMention( - mentions: List, - updatedCommentView: CommentView, -): List { - val foundIndex = - mentions.indexOfFirst { - it.person_mention.comment_id == updatedCommentView.comment.id + else -> { + it + } } - return if (foundIndex != -1) { - val mutable = mentions.toMutableList() - mutable[foundIndex] = - mentions[foundIndex].copy( - my_vote = updatedCommentView.my_vote, - counts = updatedCommentView.counts, - saved = updatedCommentView.saved, - comment = updatedCommentView.comment, - ) - mutable.toList() - } else { - mentions } + return newReports } -fun findAndUpdateMention( - mentions: List, - updated: PersonMentionView, -): List { - val foundIndex = - mentions.indexOfFirst { - it.person_mention.id == updated.person_mention.id +fun findAndUpdateCommunityReport( + reports: List, + updatedReport: CommunityReportView, +): List { + val newReports = reports.toMutableList() + newReports.replaceAll { + when (it) { + is CommunityReportView -> { + if (it.community_report.id == updatedReport.community_report.id) { + updatedReport + } else { + it + } + } + + else -> { + it + } } - return if (foundIndex != -1) { - val mutable = mentions.toMutableList() - mutable[foundIndex] = updated - mutable.toList() - } else { - mentions } + return newReports } fun findAndUpdateComment( comments: List, updated: CommentView, ): List { - val foundIndex = - comments.indexOfFirst { - it.comment.id == updated.comment.id + val newComments = comments.toMutableList() + newComments.replaceAll { + if (it.comment.id == updated.comment.id) { + updated + } else { + it + } + } + return newComments +} + +fun findAndUpdateCommentInPostCommentCombined( + items: List, + updated: CommentView, +): List { + val newItems = items.toMutableList() + newItems.replaceAll { + when (it) { + is CommentView -> { + if (it.comment.id == updated.comment.id) { + updated + } else { + it + } + } + + is PostView -> { + it + } } - return if (foundIndex != -1) { - val mutable = comments.toMutableList() - mutable[foundIndex] = updated - mutable.toList() - } else { - comments } + return newItems +} + +fun findAndUpdateCommentInNotificationView( + items: List, + updated: CommentView, +): List { + val newItems = items.toMutableList() + newItems.replaceAll { + when (val data = it.data) { + is CommentView -> { + if (data.comment.id == updated.comment.id) { + it.copy(data = updated) + } else { + it + } + } + + else -> { + it + } + } + } + return newItems } fun findAndUpdatePostCreator( @@ -1022,14 +1070,43 @@ fun findAndUpdatePostCreator( return newPosts } +fun findAndUpdateCreatorInPostCommentCombined( + items: List, + person: Person, +): List { + val newItems = items.toMutableList() + + newItems.replaceAll { + when (it) { + is CommentView -> { + if (it.creator.id == person.id) { + it.copy(creator = person) + } else { + it + } + } + + is PostView -> { + if (it.creator.id == person.id) { + it.copy(creator = person) + } else { + it + } + } + } + } + return newItems +} + fun findAndUpdatePostHidden( posts: List, form: HidePost, ): List { val newPosts = posts.toMutableList() newPosts.replaceAll { - if (form.post_ids.contains(it.post.id)) { - it.copy(hidden = form.hide) + if (form.post_id == it.post.id) { + val newPostActions = it.post_actions?.copy(hidden_at = nowBoolean(form.hide)) + it.copy(post_actions = newPostActions) } else { it } @@ -1052,6 +1129,33 @@ fun findAndUpdatePostCreatorBannedFromCommunity( return newPosts } +fun findAndUpdateCreatorBannedFromCommunityInPostCommentCombined( + items: List, + banData: BanFromCommunityData, +): List { + val newItems = items.toMutableList() + newItems.replaceAll { + when (it) { + is CommentView -> { + if (it.creator.id == banData.person.id && it.community.id == banData.community.id) { + it.copy(creator_banned_from_community = banData.banned) + } else { + it + } + } + + is PostView -> { + if (it.creator.id == banData.person.id && it.community.id == banData.community.id) { + it.copy(creator_banned_from_community = banData.banned) + } else { + it + } + } + } + } + return newItems +} + fun findAndUpdateCommentCreator( comments: List, person: Person, @@ -1082,29 +1186,6 @@ fun findAndUpdateCommentCreatorBannedFromCommunity( return newComments } -fun findAndUpdateCommentReply( - replies: List, - updatedCommentView: CommentView, -): List { - val foundIndex = - replies.indexOfFirst { - it.comment_reply.comment_id == updatedCommentView.comment.id - } - return if (foundIndex != -1) { - val mutable = replies.toMutableList() - mutable[foundIndex] = - replies[foundIndex].copy( - my_vote = updatedCommentView.my_vote, - counts = updatedCommentView.counts, - saved = updatedCommentView.saved, - comment = updatedCommentView.comment, - ) - mutable.toList() - } else { - replies - } -} - fun calculateCommentOffset( depth: Int, padding: Dp = SMALL_PADDING, @@ -1119,17 +1200,38 @@ fun findAndUpdatePost( posts: List, updatedPostView: PostView, ): List { - val foundIndex = - posts.indexOfFirst { - it.post.id == updatedPostView.post.id + val newPosts = posts.toMutableList() + newPosts.replaceAll { + if (it.post.id == updatedPostView.post.id) { + updatedPostView + } else { + it + } + } + return newPosts +} + +fun findAndUpdatePostInPostCommentCombined( + items: List, + updatedPostView: PostView, +): List { + val newItems = items.toMutableList() + newItems.replaceAll { + when (it) { + is PostView -> { + if (it.post.id == updatedPostView.post.id) { + updatedPostView + } else { + it + } + } + + else -> { + it + } } - return if (foundIndex != -1) { - val mutable = posts.toMutableList() - mutable[foundIndex] = updatedPostView - mutable.toList() - } else { - posts } + return newItems } fun scrollToNextParentComment( @@ -1246,8 +1348,8 @@ inline fun > Int.toEnumSafe(): T { fun matchLoginErrorMsgToStringRes( resources: Resources, e: Throwable, -): String { - return when (e.message) { +): String = + when (e.message) { "incorrect_login" -> { resources.getString(R.string.login_view_model_incorrect_login) } @@ -1273,7 +1375,7 @@ fun matchLoginErrorMsgToStringRes( } else -> { - return if (e.message?.contains("timeout") == true) { + if (e.message?.contains("timeout") == true) { resources.getString(R.string.login_view_model_timeout) } else { Log.d("login", "failed", e) @@ -1281,7 +1383,6 @@ fun matchLoginErrorMsgToStringRes( } } } -} fun ConnectivityManager?.isCurrentlyConnected(): Boolean = this @@ -1319,10 +1420,8 @@ fun ComponentActivity.registerActivityResultLauncher( fun Context.getInputStream(url: String): InputStream { val snapshot = this.imageLoader.diskCache?.openSnapshot(url) - return if (snapshot != null) { - snapshot.data.toFile().inputStream() - } else { - API.httpClient + return snapshot?.data?.toFile()?.inputStream() + ?: API.okHttpClient .newCall( Request .Builder() @@ -1330,8 +1429,7 @@ fun Context.getInputStream(url: String): InputStream { .build(), ).execute() .body - ?.byteStream() ?: throw IOException("Failed to get input stream") - } + .byteStream() } val nonMediaExt = setOf("html", "htm", "xhtml", "") @@ -1375,11 +1473,6 @@ fun getDeduplicateMerge( getId: (T) -> PostId, ): List = appendData(oldItems, getDeduplicatedList(oldItems, newItems, getId)) -fun mergePosts( - old: List, - new: List, -): List = appendData(old, getDeduplicatedList(old, new) { it.post.id }) - /** * This function rewrites HTTP URLs to HTTPS */ @@ -1413,3 +1506,65 @@ fun Context.getVersionCode(): Int = } fun Int.toBool() = this > 0 + +sealed interface SelectionVisibilityState { + object NoSelection : SelectionVisibilityState + + data class ShowSelection( + val selectedItem: Item, + ) : SelectionVisibilityState +} + +fun futureDaysToUnixTime(days: Long?): Long? = + days?.let { + Instant.now().plus(it, ChronoUnit.DAYS).epochSecond + } + +fun MyUserInfo?.showAvatar( + appSettings: AppSettings, + ctx: Context, +): Boolean { + val showAvatar = this + ?.local_user_view + ?.local_user + ?.show_avatars ?: true + return showAvatar && !appSettings.lowBandwidthMode(ctx) +} + +fun MyUserInfo?.moderatedCommunities(): List? = + this + ?.moderates + ?.map { it.community.id } + +fun MyUserInfo?.shouldShowDonation(): Boolean { + val lastDonationNotification = this + ?.local_user_view + ?.local_user + ?.last_donation_notification_at + ?: return false + + return try { + val lastDonationTime = OffsetDateTime.parse(lastDonationNotification) + val oneYearAgo = OffsetDateTime.now().minusYears(1) + lastDonationTime.isBefore(oneYearAgo) + } catch (e: Exception) { + Log.e("MyUserInfoViewModel", "Failed to parse donation time", e) + false + } +} + +fun MyUserInfo?.amAdmin(): Boolean { + return this?.local_user_view?.local_user?.admin ?: false +} + +fun MyUserInfo?.userViewType(): UserViewType { + return if (this?.local_user_view?.local_user?.admin == true) { + UserViewType.Admin + } else if (this?.moderates?.isNotEmpty() == true) { + UserViewType.Mod + } else if (this != null){ + UserViewType.Normal + } else { + UserViewType.NotLoggedIn + } +} diff --git a/app/src/main/java/com/jerboa/api/ApiAction.kt b/app/src/main/java/com/jerboa/api/ApiAction.kt index 0f8a2bcbe..5a03455f1 100644 --- a/app/src/main/java/com/jerboa/api/ApiAction.kt +++ b/app/src/main/java/com/jerboa/api/ApiAction.kt @@ -1,5 +1,6 @@ package com.jerboa.api +// TODO how did this get added? Its a complete dupe of ApiState sealed class ApiAction( val data: T, ) { diff --git a/app/src/main/java/com/jerboa/api/ApiState.kt b/app/src/main/java/com/jerboa/api/ApiState.kt index a26ae75f9..a169263f5 100644 --- a/app/src/main/java/com/jerboa/api/ApiState.kt +++ b/app/src/main/java/com/jerboa/api/ApiState.kt @@ -27,3 +27,9 @@ sealed class ApiState { data object Empty : ApiState() } + +fun ApiState.toOpt(): T? = + when (this) { + is ApiState.Success -> this.data + else -> null + } diff --git a/app/src/main/java/com/jerboa/api/Http.kt b/app/src/main/java/com/jerboa/api/Http.kt index 413ec7c4e..f4a3ccf6f 100644 --- a/app/src/main/java/com/jerboa/api/Http.kt +++ b/app/src/main/java/com/jerboa/api/Http.kt @@ -1,29 +1,29 @@ package com.jerboa.api -import android.content.Context import android.net.TrafficStats import android.os.Build.VERSION.SDK_INT import android.util.Log import com.jerboa.DEFAULT_LEMMY_INSTANCES -import com.jerboa.toastException -import io.ktor.client.plugins.UserAgent +import io.ktor.client.HttpClient +import io.ktor.client.engine.okhttp.OkHttp import io.ktor.client.plugins.logging.* import io.ktor.http.HttpHeaders -import it.vercruysse.lemmyapi.LemmyApi import it.vercruysse.lemmyapi.LemmyApiBaseController -import it.vercruysse.lemmyapi.pictrs.datatypes.UploadImage -import it.vercruysse.lemmyapi.setDefaultClientConfig +import it.vercruysse.lemmyapi.LemmyApiClient +import it.vercruysse.lemmyapi.LemmyApiOptions +import it.vercruysse.lemmyapi.LemmyAuth +import it.vercruysse.lemmyapi.LemmyInstance +import it.vercruysse.lemmyapi.LemmyVersion import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withContext import okhttp3.OkHttpClient -import java.io.InputStream import java.net.MalformedURLException import java.net.URL -import java.util.concurrent.TimeUnit +// TODO get rid of a default instance, use a list of defaults and pick a random one const val DEFAULT_INSTANCE = "lemmy.ml" const val DEFAULT_VERSION = "0.19.0" @@ -43,12 +43,9 @@ object API { // TODO add check for this val apiFailState: StateFlow = _apiFailState - val httpClient: OkHttpClient = + val okHttpClient: OkHttpClient = OkHttpClient .Builder() - .connectTimeout(30, TimeUnit.SECONDS) - .writeTimeout(30, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) .addNetworkInterceptor { chain -> if (SDK_INT >= 36) { TrafficStats.setThreadStatsTag(Thread.currentThread().threadId().toInt()) @@ -64,28 +61,30 @@ object API { .let(chain::proceed) }.build() - init { - LemmyApi.setDefaultClientConfig { - install(Logging) { - logger = object : Logger { - override fun log(message: String) { - Log.d("LemmyAPI", message) - } + val httpClient = HttpClient(OkHttp) { + install(Logging) { + logger = object : Logger { + override fun log(message: String) { + Log.d("LemmyAPI", message) } - level = LogLevel.ALL - sanitizeHeader { header -> header == HttpHeaders.Authorization } - } - - install(UserAgent) { - agent = "Jerboa" } + level = LogLevel.ALL + sanitizeHeader { header -> header == HttpHeaders.Authorization } + } - engine { - preconfigured = httpClient - } + engine { + preconfigured = okHttpClient } + } + val lemmyApiClient = LemmyApiClient( + httpClient = httpClient, + options = LemmyApiOptions( + userAgent = "Jerboa", + ), + ) + suspend fun getInstance(): LemmyApiBaseController { initialized.await() return newApi @@ -123,17 +122,19 @@ object API { instance: String, auth: String? = null, ): LemmyApiBaseController { - setLemmyInstance(LemmyApi.getLemmyApi(instance, auth)) + setLemmyInstance(createTempInstanceSafe(instance, auth)) return newApi } fun setLemmyInstance(api: LemmyApiBaseController) { - Log.d("setLemmyInstance", "Setting lemmy instance to ${api.baseUrl}") - version = api.version.toString() + Log.d("setLemmyInstance", "Setting lemmy instance to ${api.instance.url}") + version = api.version.value newApi = api initialized.complete(Unit) } + // TODO: Not ideal + // We need like loading/setup/verification startup screen to properly init or handle this stuff suspend fun createTempInstanceSafe( host: String, auth: String? = null, @@ -149,13 +150,26 @@ object API { suspend fun createTempInstance( host: String, auth: String? = null, - ): LemmyApiBaseController = LemmyApi.getLemmyApi(host, auth) + ): LemmyApiBaseController { + return lemmyApiClient.connect( + LemmyInstance(host), + LemmyAuth.fromToken(auth), + ).getOrThrow() + } fun createTempInstanceVersion( host: String, version: String, auth: String? = null, - ): LemmyApiBaseController = LemmyApi.getLemmyApi(host, version, auth) + ): LemmyApiBaseController { + // Can still fail if the given version is unsupported by LemmyApi + // only really possible by having an old instance account (pre v0.18.0) + return lemmyApiClient.connectForVersion( + LemmyInstance(host), + LemmyAuth.fromToken(auth), + LemmyVersion(version), + ).getOrThrow() + } suspend fun checkIfLemmyInstance(url: String): Boolean { try { @@ -167,7 +181,7 @@ object API { return false } else { return withContext(Dispatchers.IO) { - return@withContext if (LemmyApi.isLemmyInstance(url)) { + return@withContext if (lemmyApiClient.nodeInfoClient.isLemmyInstance(LemmyInstance(host)).getOrDefault(false)) { TEMP_RECOGNISED_AS_LEMMY_INSTANCES.add(host) true } else { @@ -181,30 +195,6 @@ object API { } } - suspend fun uploadPictrsImage( - imageIs: InputStream, - ctx: Context, - ): String { - Log.d("jerboa", "Uploading image....") - val resp = getInstance().uploadImage(UploadImage(listOf(imageIs.readBytes()))) - Log.d("jerboa", "Uploading done.") - - return resp.fold( - onSuccess = { - if (it.files.isEmpty()) { - toastException(ctx, Exception("Upload image failed")) - return "" - } else { - it.files[0].url.orEmpty() - } - }, - onFailure = { - toastException(ctx, it as? Exception ?: Exception("Unknown error")) - "" - }, - ) - } - fun apiFailureHandled() { _apiFailState.value = false } diff --git a/app/src/main/java/com/jerboa/datatypes/Others.kt b/app/src/main/java/com/jerboa/datatypes/Others.kt index 729fb3321..b10c44d96 100644 --- a/app/src/main/java/com/jerboa/datatypes/Others.kt +++ b/app/src/main/java/com/jerboa/datatypes/Others.kt @@ -1,6 +1,5 @@ package com.jerboa.datatypes -import android.content.Context import android.content.res.Resources import androidx.annotation.StringRes import androidx.compose.material.icons.Icons @@ -20,10 +19,10 @@ import com.jerboa.ui.components.person.UserTab import it.vercruysse.lemmyapi.datatypes.Community import it.vercruysse.lemmyapi.datatypes.Person import it.vercruysse.lemmyapi.datatypes.Post -import it.vercruysse.lemmyapi.dto.CommentSortType -import it.vercruysse.lemmyapi.dto.ListingType -import it.vercruysse.lemmyapi.dto.PostFeatureType -import it.vercruysse.lemmyapi.dto.SortType +import it.vercruysse.lemmyapi.enums.CommentSortType +import it.vercruysse.lemmyapi.enums.ListingType +import it.vercruysse.lemmyapi.enums.PostFeatureType +import it.vercruysse.lemmyapi.enums.SortType import kotlinx.serialization.Serializable data class CommentSortData( @@ -144,6 +143,7 @@ fun getLocalizedListingTypeName( ListingType.Local -> resources.getString(R.string.home_local) ListingType.Subscribed -> resources.getString(R.string.home_subscribed) ListingType.ModeratorView -> resources.getString(R.string.home_moderator_view) + ListingType.Suggested -> resources.getString(R.string.home_suggested) } /** @@ -175,6 +175,7 @@ fun getLocalizedUnreadOrAllName( /** * A container to store extra community ban info + * TODO you should be able to get rid of this */ @Serializable data class BanFromCommunityData( @@ -183,6 +184,16 @@ data class BanFromCommunityData( val banned: Boolean, ) +/** + * A container to store extra ban data + * TODO you should be able to get rid of this + */ +@Serializable +data class BanData( + val person: Person, + val banned: Boolean, +) + /** * A container to store extra post feature info */ @@ -197,6 +208,7 @@ data class PostFeatureData( */ enum class UserViewType { Normal, - AdminOnly, - AdminOrMod, + Admin, + Mod, + NotLoggedIn, } diff --git a/app/src/main/java/com/jerboa/datatypes/SampleData.kt b/app/src/main/java/com/jerboa/datatypes/SampleData.kt index 319826569..f74810a1f 100644 --- a/app/src/main/java/com/jerboa/datatypes/SampleData.kt +++ b/app/src/main/java/com/jerboa/datatypes/SampleData.kt @@ -2,26 +2,20 @@ package com.jerboa.datatypes import com.jerboa.feat.InstantScores import it.vercruysse.lemmyapi.datatypes.Comment -import it.vercruysse.lemmyapi.datatypes.CommentAggregates -import it.vercruysse.lemmyapi.datatypes.CommentReply -import it.vercruysse.lemmyapi.datatypes.CommentReplyView import it.vercruysse.lemmyapi.datatypes.CommentReport import it.vercruysse.lemmyapi.datatypes.CommentReportView import it.vercruysse.lemmyapi.datatypes.CommentView import it.vercruysse.lemmyapi.datatypes.Community -import it.vercruysse.lemmyapi.datatypes.CommunityAggregates import it.vercruysse.lemmyapi.datatypes.CommunityView import it.vercruysse.lemmyapi.datatypes.GetSiteResponse import it.vercruysse.lemmyapi.datatypes.LocalSite import it.vercruysse.lemmyapi.datatypes.LocalSiteRateLimit import it.vercruysse.lemmyapi.datatypes.LocalUser +import it.vercruysse.lemmyapi.datatypes.LocalUserView +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.Person -import it.vercruysse.lemmyapi.datatypes.PersonAggregates -import it.vercruysse.lemmyapi.datatypes.PersonMention -import it.vercruysse.lemmyapi.datatypes.PersonMentionView import it.vercruysse.lemmyapi.datatypes.PersonView import it.vercruysse.lemmyapi.datatypes.Post -import it.vercruysse.lemmyapi.datatypes.PostAggregates import it.vercruysse.lemmyapi.datatypes.PostReport import it.vercruysse.lemmyapi.datatypes.PostReportView import it.vercruysse.lemmyapi.datatypes.PostView @@ -32,14 +26,18 @@ import it.vercruysse.lemmyapi.datatypes.PrivateMessageView import it.vercruysse.lemmyapi.datatypes.RegistrationApplication import it.vercruysse.lemmyapi.datatypes.RegistrationApplicationView import it.vercruysse.lemmyapi.datatypes.Site -import it.vercruysse.lemmyapi.datatypes.SiteAggregates import it.vercruysse.lemmyapi.datatypes.SiteView -import it.vercruysse.lemmyapi.dto.CommunityVisibility -import it.vercruysse.lemmyapi.dto.ListingType -import it.vercruysse.lemmyapi.dto.PostListingMode -import it.vercruysse.lemmyapi.dto.RegistrationMode -import it.vercruysse.lemmyapi.dto.SortType -import it.vercruysse.lemmyapi.dto.SubscribedType +import it.vercruysse.lemmyapi.datatypes.UnreadCountsResponse +import it.vercruysse.lemmyapi.enums.CommentSortType +import it.vercruysse.lemmyapi.enums.CommunityVisibility +import it.vercruysse.lemmyapi.enums.FederationMode +import it.vercruysse.lemmyapi.enums.ImageMode +import it.vercruysse.lemmyapi.enums.ListingType +import it.vercruysse.lemmyapi.enums.PostListingMode +import it.vercruysse.lemmyapi.enums.RegistrationMode +import it.vercruysse.lemmyapi.enums.SortType +import it.vercruysse.lemmyapi.enums.VoteAction +import it.vercruysse.lemmyapi.enums.VoteShow val samplePost = Post( @@ -51,8 +49,8 @@ val samplePost = community_id = 14681, removed = false, locked = false, - published = "2022-01-01T09:53:46.904077Z", - updated = null, + published_at = "2022-01-01T09:53:46.904077Z", + updated_at = null, deleted = false, nsfw = false, featured_local = false, @@ -64,6 +62,19 @@ val samplePost = language_id = 0, ap_id = "https://lemmy.ml/post/135129", local = true, + url_content_type = null, + alt_text = null, + scheduled_publish_time_at = null, + newest_comment_time_at = null, + comments = 0, + score = 0, + upvotes = 0, + downvotes = 0, + report_count = 0, + unresolved_report_count = 0, + federation_pending = false, + embed_video_width = null, + embed_video_height = null, ) val sampleLinkPost = @@ -76,8 +87,8 @@ val sampleLinkPost = community_id = 14681, removed = false, locked = true, - published = "2022-01-01T09:53:46.904077Z", - updated = null, + published_at = "2022-01-01T09:53:46.904077Z", + updated_at = null, deleted = false, nsfw = false, featured_local = false, @@ -89,6 +100,19 @@ val sampleLinkPost = ap_id = "https://lemmy.ml/post/135129", local = true, language_id = 0, + url_content_type = null, + alt_text = null, + scheduled_publish_time_at = null, + newest_comment_time_at = null, + comments = 0, + score = 0, + upvotes = 0, + downvotes = 0, + report_count = 0, + unresolved_report_count = 0, + federation_pending = false, + embed_video_width = null, + embed_video_height = null, ) val sampleLinkNoThumbnailPost = @@ -101,8 +125,8 @@ val sampleLinkNoThumbnailPost = community_id = 14681, removed = false, locked = false, - published = "2022-01-01T09:53:46.904077Z", - updated = null, + published_at = "2022-01-01T09:53:46.904077Z", + updated_at = null, deleted = false, nsfw = false, featured_local = false, @@ -114,6 +138,19 @@ val sampleLinkNoThumbnailPost = ap_id = "https://lemmy.ml/post/135129", local = true, language_id = 0, + url_content_type = null, + alt_text = null, + scheduled_publish_time_at = null, + newest_comment_time_at = null, + comments = 0, + score = 0, + upvotes = 0, + downvotes = 0, + report_count = 0, + unresolved_report_count = 0, + federation_pending = false, + embed_video_width = null, + embed_video_height = null, ) val sampleImagePost = @@ -126,8 +163,8 @@ val sampleImagePost = community_id = 14681, removed = false, locked = false, - published = "2022-01-01T09:53:46.904077Z", - updated = null, + published_at = "2022-01-01T09:53:46.904077Z", + updated_at = null, deleted = false, nsfw = false, featured_local = false, @@ -139,6 +176,19 @@ val sampleImagePost = ap_id = "https://lemmy.ml/post/135129", local = true, language_id = 0, + url_content_type = null, + alt_text = null, + scheduled_publish_time_at = null, + newest_comment_time_at = null, + comments = 0, + score = 0, + upvotes = 0, + downvotes = 0, + report_count = 0, + unresolved_report_count = 0, + federation_pending = false, + embed_video_width = null, + embed_video_height = null, ) val sampleMarkdownPost = @@ -151,8 +201,8 @@ val sampleMarkdownPost = community_id = 14681, removed = false, locked = false, - published = "2022-01-01T09:53:46.904077Z", - updated = null, + published_at = "2022-01-01T09:53:46.904077Z", + updated_at = null, deleted = false, nsfw = false, featured_local = false, @@ -164,6 +214,19 @@ val sampleMarkdownPost = language_id = 0, ap_id = "https://lemmy.ml/post/135129", local = true, + url_content_type = null, + alt_text = null, + scheduled_publish_time_at = null, + newest_comment_time_at = null, + comments = 0, + score = 0, + upvotes = 0, + downvotes = 0, + report_count = 0, + unresolved_report_count = 0, + federation_pending = false, + embed_video_width = null, + embed_video_height = null, ) val samplePerson = @@ -172,10 +235,9 @@ val samplePerson = name = "homeless", display_name = "No longer Homeless", avatar = "https://lemmy.ml/pictrs/image/LqURxPzFNW.jpg", - banned = false, - published = "2021-08-08T01:47:44.437708Z", - updated = "2021-10-11T07:14:53.548707Z", - actor_id = "https://lemmy.ml/u/homeless", + published_at = "2021-08-08T01:47:44.437708Z", + updated_at = "2021-10-11T07:14:53.548707Z", + ap_id = "https://lemmy.ml/u/homeless", bio = "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 " + @@ -185,8 +247,10 @@ val samplePerson = deleted = false, matrix_user_id = null, bot_account = false, - ban_expires = null, instance_id = 0, + post_count = 28, + comment_count = 98, + last_refreshed_at = "2021-08-08T01:47:44.437708Z", ) val samplePerson2 = @@ -195,18 +259,19 @@ val samplePerson2 = name = "gary_host_laptop", display_name = null, avatar = "https://lemmy.ml/pictrs/image/kykidJ1ssM.jpg", - banned = false, - published = "2021-08-08T01:47:44.437708Z", - updated = "2021-10-11T07:14:53.548707Z", - actor_id = "https://lemmy.ml/u/homeless", + published_at = "2021-08-08T01:47:44.437708Z", + updated_at = "2021-10-11T07:14:53.548707Z", + ap_id = "https://lemmy.ml/u/homeless", bio = null, local = false, banner = null, deleted = false, matrix_user_id = null, bot_account = false, - ban_expires = null, instance_id = 0, + post_count = 0, + comment_count = 0, + last_refreshed_at = "2021-08-08T01:47:44.437708Z", ) val samplePerson3 = @@ -214,18 +279,19 @@ val samplePerson3 = id = 33478, name = "witch_power", display_name = null, - banned = false, - published = "2021-08-08T01:47:44.437708Z", - updated = "2021-10-11T07:14:53.548707Z", - actor_id = "https://lemmy.ml/u/witch_power", + published_at = "2021-08-08T01:47:44.437708Z", + updated_at = "2021-10-11T07:14:53.548707Z", + ap_id = "https://lemmy.ml/u/witch_power", bio = null, local = true, banner = null, deleted = false, matrix_user_id = null, bot_account = false, - ban_expires = null, instance_id = 0, + post_count = 0, + comment_count = 0, + last_refreshed_at = "2021-08-08T01:47:44.437708Z", ) val sampleLocalUser = LocalUser( @@ -233,27 +299,55 @@ val sampleLocalUser = LocalUser( person_id = 82, show_nsfw = false, theme = "none", - default_sort_type = SortType.Active, + default_post_sort_type = SortType.Active, default_listing_type = ListingType.Local, interface_language = "en", show_avatars = false, send_notifications_to_email = false, - show_scores = false, show_bot_accounts = false, show_read_posts = false, email_verified = false, accepted_application = false, open_links_in_new_tab = false, blur_nsfw = false, - auto_expand = false, infinite_scroll_enabled = false, admin = false, post_listing_mode = PostListingMode.List, totp_2fa_enabled = false, - enable_keyboard_navigation = false, - enable_animated_images = false, collapse_bot_comments = false, - last_donation_notification = "2019-04-30T13:28:35.965035Z", + last_donation_notification_at = "2019-04-30T13:28:35.965035Z", + email = null, + animated_images_enabled = false, + private_messages_enabled = false, + default_comment_sort_type = CommentSortType.Hot, + auto_mark_fetched_posts_as_read = false, + hide_posts_with_media = false, + show_media = true, + default_post_time_range_seconds = null, + show_score = true, + show_upvotes = true, + show_downvotes = VoteShow.Show, + show_upvote_percentage = true, + show_person_votes = true, + default_items_per_page = 20, +) + +val sampleLocalUserView = LocalUserView( + local_user = sampleLocalUser, + person = samplePerson, + banned = false, +) + +val sampleMyUserInfo = MyUserInfo( + local_user_view = sampleLocalUserView, + follows = emptyList(), + moderates = emptyList(), + multi_community_follows = emptyList(), + community_blocks = emptyList(), + instance_communities_blocks = emptyList(), + instance_persons_blocks = emptyList(), + person_blocks = emptyList(), + discussion_languages = emptyList(), ) val sampleCommunity = @@ -261,35 +355,36 @@ val sampleCommunity = id = 14681, name = "socialism", title = "Socialism", - description = "This is the r/socialism community", + sidebar = "This is the r/socialism community", removed = false, - published = "2019-04-30T13:28:35.965035Z", - updated = "2021-01-25T16:27:15.804739Z", + published_at = "2019-04-30T13:28:35.965035Z", + updated_at = "2021-01-25T16:27:15.804739Z", deleted = false, nsfw = false, - actor_id = "https://lemmy.ml/c/socialism", + ap_id = "https://lemmy.ml/c/socialism", local = true, icon = "https://lemmy.ml/pictrs/image/QtiqDmp9XY.png", banner = "https://lemmy.ml/pictrs/image/386rk5OYWS.jpg", instance_id = 0, - hidden = false, posting_restricted_to_mods = false, visibility = CommunityVisibility.Public, + last_refreshed_at = "2021-01-25T16:27:15.804739Z", + summary = null, + subscribers = 52, + subscribers_local = 21, + posts = 82, + comments = 987, + users_active_day = 28, + users_active_week = 98, + users_active_month = 82, + users_active_half_year = 91, + report_count = 0, + unresolved_report_count = 0, + local_removed = false, ) val sampleCommunityFederated = sampleCommunity.copy(local = false) -val samplePostAggregates = - PostAggregates( - post_id = 135129, - comments = 4, - score = 5, - upvotes = 8, - downvotes = 3, - published = "2022-01-02T04:02:44.592929Z", - newest_comment_time = "2022-01-02T04:02:44.592929Z", - ) - val samplePostView = PostView( post = samplePost, @@ -298,15 +393,15 @@ val samplePostView = creator_banned_from_community = false, creator_is_moderator = false, creator_is_admin = false, - counts = samplePostAggregates, - subscribed = SubscribedType.NotSubscribed, - saved = false, - read = false, - creator_blocked = false, - unread_comments = 1, - my_vote = 0, - banned_from_community = false, - hidden = false, + image_details = null, + community_actions = null, + person_actions = null, + post_actions = null, + tags = emptyList(), + can_mod = false, + creator_banned = false, + creator_ban_expires_at = null, + creator_community_ban_expires_at = null, ) val sampleLinkPostView = @@ -317,15 +412,15 @@ val sampleLinkPostView = creator_banned_from_community = false, creator_is_moderator = false, creator_is_admin = false, - counts = samplePostAggregates, - subscribed = SubscribedType.NotSubscribed, - saved = false, - read = false, - creator_blocked = false, - unread_comments = 1, - my_vote = 0, - banned_from_community = false, - hidden = false, + image_details = null, + community_actions = null, + person_actions = null, + post_actions = null, + tags = emptyList(), + can_mod = false, + creator_banned = false, + creator_ban_expires_at = null, + creator_community_ban_expires_at = null, ) val sampleLinkNoThumbnailPostView = @@ -336,15 +431,15 @@ val sampleLinkNoThumbnailPostView = creator_banned_from_community = false, creator_is_moderator = false, creator_is_admin = false, - counts = samplePostAggregates, - subscribed = SubscribedType.NotSubscribed, - saved = false, - read = false, - creator_blocked = false, - unread_comments = 1, - my_vote = 0, - banned_from_community = false, - hidden = false, + image_details = null, + community_actions = null, + person_actions = null, + post_actions = null, + tags = emptyList(), + can_mod = false, + creator_banned = false, + creator_ban_expires_at = null, + creator_community_ban_expires_at = null, ) val sampleImagePostView = @@ -355,15 +450,15 @@ val sampleImagePostView = creator_banned_from_community = false, creator_is_moderator = false, creator_is_admin = false, - counts = samplePostAggregates, - subscribed = SubscribedType.NotSubscribed, - saved = false, - read = false, - creator_blocked = false, - unread_comments = 1, - my_vote = 0, - banned_from_community = false, - hidden = false, + image_details = null, + community_actions = null, + person_actions = null, + post_actions = null, + tags = emptyList(), + can_mod = false, + creator_banned = false, + creator_ban_expires_at = null, + creator_community_ban_expires_at = null, ) val sampleMarkdownPostView = @@ -374,15 +469,15 @@ val sampleMarkdownPostView = creator_banned_from_community = false, creator_is_moderator = false, creator_is_admin = false, - counts = samplePostAggregates, - subscribed = SubscribedType.NotSubscribed, - saved = false, - read = false, - creator_blocked = false, - unread_comments = 1, - my_vote = 0, - banned_from_community = false, - hidden = false, + image_details = null, + community_actions = null, + person_actions = null, + post_actions = null, + tags = emptyList(), + can_mod = false, + creator_banned = false, + creator_ban_expires_at = null, + creator_community_ban_expires_at = null, ) val sampleComment = @@ -396,14 +491,22 @@ val sampleComment = "work" + ".\n\nIts kind of a long comment\n\nbut I don't want...", removed = false, - published = "2022-01-07T03:12:26.398434Z", - updated = "2022-01-07T03:15:37.360888Z", + published_at = "2022-01-07T03:12:26.398434Z", + updated_at = "2022-01-07T03:15:37.360888Z", deleted = false, ap_id = "https://midwest.social/comment/24621", local = false, distinguished = false, language_id = 0, path = "0.1", + score = 0, + upvotes = 0, + downvotes = 0, + child_count = 0, + report_count = 0, + unresolved_report_count = 0, + federation_pending = false, + locked = false, ) val sampleReplyComment = @@ -414,13 +517,21 @@ val sampleReplyComment = path = "0.1.2", content = "This is a reply comment.\n\n# This is a header\n\n- list one\n\n- list two", removed = false, - published = "2022-01-07T04:12:26.398434Z", - updated = "2022-01-07T03:15:37.360888Z", + published_at = "2022-01-07T04:12:26.398434Z", + updated_at = "2022-01-07T03:15:37.360888Z", deleted = false, ap_id = "https://midwest.social/comment/24622", local = false, distinguished = false, language_id = 0, + score = 0, + upvotes = 0, + downvotes = 0, + child_count = 0, + report_count = 0, + unresolved_report_count = 0, + federation_pending = false, + locked = false, ) val sampleSecondReplyComment = @@ -431,23 +542,21 @@ val sampleSecondReplyComment = path = "0.1.2.3", content = "This is a sub-reply comment, mmmmk", removed = false, - published = "2022-01-07T04:12:26.398434Z", - updated = "2022-01-07T03:15:37.360888Z", + published_at = "2022-01-07T04:12:26.398434Z", + updated_at = "2022-01-07T03:15:37.360888Z", deleted = false, ap_id = "https://midwest.social/comment/24622", local = false, distinguished = false, language_id = 0, - ) - -val sampleCommentAggregates = - CommentAggregates( - comment_id = 24, - score = 8, - upvotes = 12, - downvotes = 4, - published = "2022-01-02T04:02:44.592929Z", + score = 0, + upvotes = 0, + downvotes = 0, child_count = 0, + report_count = 0, + unresolved_report_count = 0, + federation_pending = false, + locked = false, ) val sampleCommentView = @@ -456,14 +565,12 @@ val sampleCommentView = creator = samplePerson, post = samplePost, community = sampleCommunity, - counts = sampleCommentAggregates, creator_banned_from_community = false, - subscribed = SubscribedType.NotSubscribed, - saved = false, - creator_blocked = false, creator_is_moderator = false, creator_is_admin = false, - banned_from_community = false, + tags = emptyList(), + can_mod = false, + creator_banned = false, ) val sampleSecondReplyCommentView = @@ -472,14 +579,12 @@ val sampleSecondReplyCommentView = creator = samplePerson, post = samplePost, community = sampleCommunity, - counts = sampleCommentAggregates, creator_banned_from_community = false, - subscribed = SubscribedType.NotSubscribed, - saved = false, - creator_blocked = false, creator_is_moderator = false, creator_is_admin = false, - banned_from_community = false, + tags = emptyList(), + can_mod = false, + creator_banned = false, ) val sampleReplyCommentView = @@ -488,105 +593,26 @@ val sampleReplyCommentView = creator = samplePerson2, post = samplePost, community = sampleCommunity, - counts = sampleCommentAggregates, creator_banned_from_community = false, - subscribed = SubscribedType.NotSubscribed, - saved = false, - creator_blocked = false, creator_is_moderator = false, creator_is_admin = false, - banned_from_community = false, - ) - -val sampleCommentReply = - CommentReply( - id = 30, - recipient_id = 20, - comment_id = 42, - read = false, - published = "2022-01-01T09:53:46.904077Z", - ) - -val samplePersonMention = - PersonMention( - id = 30, - recipient_id = 20, - comment_id = 42, - read = false, - published = "2022-01-01T09:53:46.904077Z", - ) - -val sampleCommentReplyView = - CommentReplyView( - comment_reply = sampleCommentReply, - comment = sampleReplyComment, - creator = samplePerson2, - recipient = samplePerson, - post = samplePost, - community = sampleCommunity, - counts = sampleCommentAggregates, - creator_banned_from_community = false, - subscribed = SubscribedType.NotSubscribed, - saved = false, - creator_blocked = false, - creator_is_moderator = false, - creator_is_admin = false, - banned_from_community = false, - ) - -val samplePersonMentionView = - PersonMentionView( - person_mention = samplePersonMention, - comment = sampleReplyComment, - creator = samplePerson2, - recipient = samplePerson, - post = samplePost, - community = sampleCommunity, - counts = sampleCommentAggregates, - creator_banned_from_community = false, - subscribed = SubscribedType.NotSubscribed, - saved = false, - creator_blocked = false, - creator_is_moderator = false, - creator_is_admin = false, - banned_from_community = false, - ) - -val sampleCommunityAggregates = - CommunityAggregates( - published = "2022-01-02T04:02:44.592929Z", - community_id = 834, - subscribers = 52, - subscribers_local = 21, - posts = 82, - comments = 987, - users_active_day = 28, - users_active_week = 98, - users_active_month = 82, - users_active_half_year = 91, + tags = emptyList(), + can_mod = false, + creator_banned = false, ) val sampleCommunityView = CommunityView( community = sampleCommunity, - subscribed = SubscribedType.NotSubscribed, - blocked = false, - counts = sampleCommunityAggregates, - banned_from_community = false, - ) - -val samplePersonAggregates = - PersonAggregates( - person_id = 54, - post_count = 28, - comment_count = 98, + can_mod = false, + tags = emptyList(), ) val samplePersonView = PersonView( person = samplePerson, - counts = samplePersonAggregates, is_admin = false, + banned = false, ) val samplePrivateMessage = @@ -597,10 +623,12 @@ val samplePrivateMessage = content = "A message from *me* to **you**", deleted = false, read = false, - published = "2022-01-07T04:12:26.398434Z", - updated = "2022-01-07T03:15:37.360888Z", + published_at = "2022-01-07T04:12:26.398434Z", + updated_at = "2022-01-07T03:15:37.360888Z", ap_id = "https://midwest.social/comment/24622", local = false, + removed = false, + deleted_by_recipient = false, ) val samplePrivateMessageView = @@ -615,12 +643,12 @@ val sampleSite = id = 23, name = "Lemmy.ml", sidebar = "# Hello!\n\n**This is** lemmy's sidebar", - description = "A general purpose instance for lemmy", - published = "2022-01-07T04:12:26.398434Z", - updated = "2022-01-07T03:15:37.360888Z", + summary = "A general purpose instance for lemmy", + published_at = "2022-01-07T04:12:26.398434Z", + updated_at = "2022-01-07T03:15:37.360888Z", icon = "https://lemmy.ml/pictrs/image/LqURxPzFNW.jpg", banner = "https://lemmy.ml/pictrs/image/386rk5OYWS.jpg", - actor_id = "https://lemmy.ml", + ap_id = "https://lemmy.ml", inbox_url = "https://lemmy.ml", last_refreshed_at = "2023-01-01", instance_id = 0, @@ -628,85 +656,92 @@ val sampleSite = val sampleLocalSite = LocalSite( - enable_downvotes = true, registration_mode = RegistrationMode.Open, - enable_nsfw = true, community_creation_admin_only = true, - require_email_verification = false, application_question = null, private_instance = false, - actor_name_max_length = 30, application_email_admins = false, - captcha_difficulty = "easy", - captcha_enabled = false, default_post_listing_type = ListingType.All, default_theme = "main", federation_enabled = true, id = 1, legal_information = null, - published = "2023-01-01", + published_at = "2023-01-01", site_setup = true, site_id = 1, slur_filter_regex = null, - updated = null, - hide_modlog_mod_names = true, federation_signed_fetch = false, reports_email_admins = false, - default_sort_type = SortType.Active, + default_post_sort_type = SortType.Active, default_post_listing_mode = PostListingMode.Card, - ) - -val sampleSiteAggregates = - SiteAggregates( - site_id = 84, - users = 8092, - posts = 888929, - comments = 9882, - communities = 89, - users_active_day = 21, - users_active_week = 82, - users_active_month = 208, - users_active_half_year = 689, - ) - -val local_site_rate_limit = + email_verification_required = false, + default_comment_sort_type = CommentSortType.Hot, + oauth_registration = false, + post_upvotes = FederationMode.All, + post_downvotes = FederationMode.All, + comment_upvotes = FederationMode.All, + comment_downvotes = FederationMode.All, + nsfw_content_disallowed = false, + users = 22, + posts = 67, + comments = 322, + communities = 6, + users_active_day = 3, + users_active_week = 6, + users_active_month = 9, + users_active_half_year = 12, + email_notifications_disabled = false, + default_items_per_page = 20, + image_mode = ImageMode.ProxyAllImages, + image_upload_timeout_seconds = 180, + image_max_thumbnail_size = 256, + image_max_avatar_size = 256, + image_max_banner_size = 1024, + image_max_upload_size = 5096, + image_allow_video_uploads = true, + image_upload_disabled = false, + max_invites_per_user_allowed = 15, + ) + +val sampleLocalSiteRateLimit = LocalSiteRateLimit( local_site_id = 2, - message = 2, - message_per_second = 2, - post = 2, - post_per_second = 2, - register = 2, - register_per_second = 2, - image = 2, - image_per_second = 2, - comment = 2, - comment_per_second = 2, - search = 2, - search_per_second = 2, - import_user_settings = 2, - import_user_settings_per_second = 2, - published = "2022-01-01T09:53:46.904077", + message_max_requests = 2, + message_interval_seconds = 2, + post_max_requests = 2, + post_interval_seconds = 2, + register_max_requests = 2, + register_interval_seconds = 2, + image_max_requests = 2, + image_interval_seconds = 2, + comment_max_requests = 2, + comment_interval_seconds = 2, + search_max_requests = 2, + search_interval_seconds = 2, + import_user_settings_max_requests = 2, + import_user_settings_interval_seconds = 2, + published_at = "2022-01-01T09:53:46.904077", ) val sampleSiteView = SiteView( site = sampleSite, - counts = sampleSiteAggregates, local_site = sampleLocalSite, - local_site_rate_limit = local_site_rate_limit, + local_site_rate_limit = sampleLocalSiteRateLimit, + instance = null, ) val sampleGetSiteRes = GetSiteResponse( site_view = sampleSiteView, admins = listOf(samplePersonView), - my_user = null, all_languages = emptyList(), - custom_emojis = emptyList(), - taglines = emptyList(), discussion_languages = emptyList(), version = "0.0.1", blocked_urls = emptyList(), + oauth_providers = emptyList(), + admin_oauth_providers = emptyList(), + active_plugins = emptyList(), + captcha_enabled = false, ) val samplePendingRegistrationApplication = @@ -714,7 +749,7 @@ val samplePendingRegistrationApplication = id = 23, local_user_id = 28, answer = "**Please** let me in", - published = "2022-01-01T09:53:46.904077", + published_at = "2022-01-01T09:53:46.904077", ) val samplePendingRegistrationApplicationView = @@ -729,7 +764,7 @@ val sampleApprovedRegistrationApplication = id = 24, local_user_id = 29, answer = "**Please** let me in", - published = "2022-01-01T09:53:46.904077", + published_at = "2022-01-01T09:53:46.904077", admin_id = samplePerson2.id, ) @@ -746,7 +781,7 @@ val sampleDeniedRegistrationApplication = id = 24, local_user_id = 29, answer = "**Please** let me in", - published = "2022-01-01T09:53:46.904077", + published_at = "2022-01-01T09:53:46.904077", admin_id = samplePerson2.id, deny_reason = "I'm not letting you in, sorry.", ) @@ -765,10 +800,11 @@ val samplePostReport = id = 89, original_post_name = samplePost.name, post_id = samplePost.id, - published = samplePost.published, + published_at = samplePost.published_at, reason = "This post is *peak* **cringe**", resolved = true, resolver_id = samplePerson3.id, + violates_instance_rules = false, ) val samplePostReportView = @@ -779,16 +815,10 @@ val samplePostReportView = post = samplePost, post_report = samplePostReport, community = sampleCommunity, - counts = samplePostAggregates, creator_banned_from_community = false, - hidden = false, - read = false, creator_is_admin = false, - subscribed = SubscribedType.NotSubscribed, creator_is_moderator = false, - saved = false, - creator_blocked = false, - unread_comments = 1, + creator_banned = false, ) val sampleCommentReport = @@ -797,7 +827,7 @@ val sampleCommentReport = id = 89, original_comment_text = sampleComment.content, comment_id = sampleComment.id, - published = sampleComment.published, + published_at = sampleComment.published_at, reason = "This is a bad comment, remove it plz.", resolved = true, resolver_id = samplePerson3.id, @@ -812,13 +842,10 @@ val sampleCommentReportView = comment = sampleComment, comment_report = sampleCommentReport, community = sampleCommunity, - counts = sampleCommentAggregates, creator_banned_from_community = false, - subscribed = SubscribedType.NotSubscribed, - saved = false, creator_is_admin = false, creator_is_moderator = false, - creator_blocked = false, + creator_banned = false, ) val samplePrivateMessageReport = @@ -827,7 +854,7 @@ val samplePrivateMessageReport = id = 89, original_pm_text = samplePrivateMessage.content, private_message_id = samplePrivateMessage.id, - published = sampleComment.published, + published_at = sampleComment.published_at, reason = "This PM is from a spammer", resolved = true, resolver_id = samplePerson3.id, @@ -840,12 +867,21 @@ val samplePrivateMessageReportView = private_message_creator = samplePerson, creator = samplePerson2, resolver = samplePerson3, + creator_is_admin = false, + creator_banned = false, ) val sampleInstantScores = InstantScores( - myVote = samplePostView.my_vote, - score = samplePostView.counts.score, - upvotes = samplePostView.counts.upvotes, - downvotes = samplePostView.counts.downvotes, - ) + myVote = VoteAction.UpVote, + score = 4, + upvotes = 5, + downvotes = 1, + ) + +val sampleUnreadCountsResponse = UnreadCountsResponse( + notification_count = 2, + report_count = 5, + pending_follow_count = null, + registration_application_count = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/jerboa/db/entity/Account.kt b/app/src/main/java/com/jerboa/db/entity/Account.kt index 83d84b4e8..acbd58004 100644 --- a/app/src/main/java/com/jerboa/db/entity/Account.kt +++ b/app/src/main/java/com/jerboa/db/entity/Account.kt @@ -13,6 +13,7 @@ data class Account( @ColumnInfo(name = "instance") val instance: String, @ColumnInfo(name = "name") val name: String, @ColumnInfo(name = "jwt") val jwt: String, + // TODO everything below here should be deleted, and should be retrieved from MyUserInfo @ColumnInfo( name = "default_listing_type", defaultValue = "0", @@ -47,15 +48,7 @@ val AnonAccount = isMod = false, ) -fun Account.isAnon(): Boolean = this.id == -1L +// TODO this should be removed. MyUserInfo != null should replace it +//fun Account.isAnon(): Boolean = this.id == -1L fun Account.isReady(): Boolean = this.verificationState == AccountVerificationState.CHECKS_COMPLETE.ordinal - -fun Account.userViewType(): UserViewType = - if (isAdmin) { - UserViewType.AdminOnly - } else if (isMod) { - UserViewType.AdminOrMod - } else { - UserViewType.Normal - } diff --git a/app/src/main/java/com/jerboa/db/entity/AppSettings.kt b/app/src/main/java/com/jerboa/db/entity/AppSettings.kt index e38f8e8a4..740b2a8db 100644 --- a/app/src/main/java/com/jerboa/db/entity/AppSettings.kt +++ b/app/src/main/java/com/jerboa/db/entity/AppSettings.kt @@ -1,5 +1,8 @@ package com.jerboa.db.entity +import android.content.Context +import android.net.ConnectivityManager +import androidx.core.content.getSystemService import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey @@ -27,6 +30,8 @@ import com.jerboa.db.DEFAULT_THEME import com.jerboa.db.DEFAULT_THEME_COLOR import com.jerboa.db.DEFAULT_USE_CUSTOM_TABS import com.jerboa.db.DEFAULT_USE_PRIVATE_TABS +import com.jerboa.feat.LowBandwidthMode +import com.jerboa.toEnumSafe import com.jerboa.ui.theme.DEFAULT_FONT_SIZE @Entity @@ -159,3 +164,8 @@ data class AppSettings( ) val lowBandwidthMode: Int, ) + +fun AppSettings.lowBandwidthMode(ctx: Context): Boolean { + val connectivityManager = ctx.getSystemService() + return this.lowBandwidthMode.toEnumSafe().isActive(connectivityManager) +} \ No newline at end of file diff --git a/app/src/main/java/com/jerboa/feat/AccountVerificationState.kt b/app/src/main/java/com/jerboa/feat/AccountVerificationState.kt index 6c8f7a1a9..a40a69c51 100644 --- a/app/src/main/java/com/jerboa/feat/AccountVerificationState.kt +++ b/app/src/main/java/com/jerboa/feat/AccountVerificationState.kt @@ -1,5 +1,4 @@ package com.jerboa.feat -import android.annotation.SuppressLint import android.content.Context import android.content.res.Resources import android.net.ConnectivityManager @@ -20,13 +19,13 @@ import com.jerboa.db.entity.isReady import com.jerboa.isCurrentlyConnected import com.jerboa.loginFirstToast import com.jerboa.model.AccountViewModel -import com.jerboa.model.SiteViewModel +import com.jerboa.model.MyUserInfoViewModel import com.jerboa.toEnum import com.jerboa.ui.components.common.apiErrorToast import it.vercruysse.lemmyapi.LemmyApiBaseController import it.vercruysse.lemmyapi.datatypes.GetPersonDetails import it.vercruysse.lemmyapi.datatypes.GetPersonDetailsResponse -import it.vercruysse.lemmyapi.datatypes.GetSiteResponse +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.exception.LemmyBadRequestException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -93,7 +92,7 @@ suspend fun checkInstance(instance: String): CheckState = withContext(Dispatchers.IO) { try { val response = - API.httpClient + API.okHttpClient .newCall(Request.Builder().url("https://$instance").build()) .execute() response.close() @@ -131,11 +130,11 @@ suspend fun checkIfAccountIsDeleted( .equals(account.name, true) && !body.person_view.person.deleted ) { - Pair(CheckState.Passed, ApiState.Success(body)) + Pair(CheckState.Passed, ApiState.Success(body)) } else { Pair(CheckState.Failed, null) } - } else if ((res.exceptionOrNull() as? LemmyBadRequestException)?.code == 404) { + } else if ((res.exceptionOrNull() as? LemmyBadRequestException)?.statusCode == 404) { return@withContext Pair(CheckState.Failed, null) } else { return@withContext Pair(CheckState.ConnectionFailed, null) @@ -144,8 +143,8 @@ suspend fun checkIfAccountIsDeleted( } fun checkIfAccountIsBanned(userRes: GetPersonDetailsResponse): CheckState = - if (userRes.person_view.person.banned) { - CheckState.FailedMsg(userRes.person_view.person.ban_expires ?: "TIME_NOT_SPECIFIED") + if (userRes.person_view.banned) { + CheckState.FailedMsg(userRes.person_view.ban_expires_at ?: "TIME_NOT_SPECIFIED") } else { CheckState.Passed } @@ -158,7 +157,7 @@ suspend fun checkIfJWTValid(api: LemmyApiBaseController): CheckState { return@withContext if (resp.isSuccess) { CheckState.Passed // Could check for exact body response `{"error":"not_logged_in"}` but could change over time and is unneeded - } else if ((resp.exceptionOrNull() as? LemmyBadRequestException)?.code in 400..499) { + } else if ((resp.exceptionOrNull() as? LemmyBadRequestException)?.statusCode in 400..499) { CheckState.Failed } else { CheckState.ConnectionFailed @@ -166,28 +165,28 @@ suspend fun checkIfJWTValid(api: LemmyApiBaseController): CheckState { } } -suspend fun checkIfSiteRetrievalSucceeded( - siteViewModel: SiteViewModel, +suspend fun checkIfMyUserRetrievalSucceeded( + myUserInfoViewModel: MyUserInfoViewModel, account: Account, -): Pair?> = - when (val res = siteViewModel.siteRes) { +): Pair?> = + when (val res = myUserInfoViewModel.myUserRes) { is ApiState.Success -> { // Contains information for the wrong person - if (res.data.my_user - ?.local_user_view - ?.local_user - ?.person_id == account.id + if (res.data + .local_user_view + .local_user + .person_id == account.id ) { Pair(CheckState.Passed, res) } else { - siteViewModel.siteRes = ApiState.Empty - checkIfSiteRetrievalSucceeded(siteViewModel, account) + myUserInfoViewModel.myUserRes = ApiState.Empty + checkIfMyUserRetrievalSucceeded(myUserInfoViewModel, account) } } else -> { - siteViewModel.getSite().join() - when (val res2 = siteViewModel.siteRes) { + myUserInfoViewModel.getMyUser().join() + when (val res2 = myUserInfoViewModel.myUserRes) { is ApiState.Success -> Pair(CheckState.Passed, res2) else -> Pair(CheckState.Failed, null) } @@ -216,7 +215,7 @@ sealed class CheckState { suspend fun Account.checkAccountVerification( ctx: Context, - siteViewModel: SiteViewModel, + myUserInfoViewModel: MyUserInfoViewModel, accountViewModel: AccountViewModel, ): Pair { Log.d("verification", "Verification started") @@ -267,8 +266,8 @@ suspend fun Account.checkAccountVerification( } AccountVerificationState.SITE_RETRIEVAL_SUCCEEDED -> { - checkIfSiteRetrievalSucceeded( - siteViewModel, + checkIfMyUserRetrievalSucceeded( + myUserInfoViewModel, this, ).first } @@ -418,7 +417,7 @@ suspend fun Account.isReadyAndIfNotDisplayInfo( ctx: Context, resources: Resources, snackbarHostState: SnackbarHostState, - siteViewModel: SiteViewModel? = null, + myUserInfoViewModel: MyUserInfoViewModel? = null, accountViewModel: AccountViewModel? = null, loginAsToast: Boolean = true, ): Boolean { @@ -429,10 +428,10 @@ suspend fun Account.isReadyAndIfNotDisplayInfo( return false } else { lockAccount.add(this) - val siteVM = siteViewModel ?: (ctx as MainActivity).siteViewModel + val myUserInfoVM = myUserInfoViewModel ?: (ctx as MainActivity).myUserInfoViewModel val accountVM = accountViewModel ?: (ctx as MainActivity).accountViewModel - checkAccountVerification(ctx, siteVM, accountVM).let { + checkAccountVerification(ctx, myUserInfoVM, accountVM).let { lockAccount.remove(this) it.showSnackbarForVerificationInfo( @@ -467,7 +466,7 @@ suspend fun Account.isReadyAndIfNotDisplayInfo( ctx, resources, snackbarHostState, - siteVM, + myUserInfoVM, accountVM, loginAsToast, ) @@ -488,7 +487,7 @@ fun Account.doIfReadyElseDisplayInfo( resources: Resources, snackbarHostState: SnackbarHostState, scope: CoroutineScope, - siteViewModel: SiteViewModel? = null, + myUserInfoViewModel: MyUserInfoViewModel? = null, accountViewModel: AccountViewModel? = null, loginAsToast: Boolean = true, doAction: (Account) -> Unit, @@ -499,7 +498,7 @@ fun Account.doIfReadyElseDisplayInfo( ctx, resources, snackbarHostState, - siteViewModel, + myUserInfoViewModel, accountViewModel, loginAsToast, ) diff --git a/app/src/main/java/com/jerboa/feat/Block.kt b/app/src/main/java/com/jerboa/feat/Block.kt index f80d3f53c..cfd469edc 100644 --- a/app/src/main/java/com/jerboa/feat/Block.kt +++ b/app/src/main/java/com/jerboa/feat/Block.kt @@ -6,10 +6,9 @@ import android.widget.Toast import com.jerboa.R import com.jerboa.api.API import it.vercruysse.lemmyapi.datatypes.BlockCommunity -import it.vercruysse.lemmyapi.datatypes.BlockCommunityResponse -import it.vercruysse.lemmyapi.datatypes.BlockInstanceResponse import it.vercruysse.lemmyapi.datatypes.BlockPerson -import it.vercruysse.lemmyapi.datatypes.BlockPersonResponse +import it.vercruysse.lemmyapi.datatypes.CommunityResponse +import it.vercruysse.lemmyapi.datatypes.PersonResponse import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -44,14 +43,15 @@ fun blockPerson( } fun showBlockInstanceToast( - blockInstanceResp: Result, + blockInstanceResp: Result, instance: String, + blocked: Boolean, ctx: Context, ) { blockInstanceResp .onSuccess { makeSuccessfulBlockMessage( - it.blocked, + blocked, instance, ctx, ) @@ -67,13 +67,13 @@ fun showBlockInstanceToast( } fun showBlockPersonToast( - blockPersonRes: Result, + blockPersonRes: Result, ctx: Context, ) { blockPersonRes .onSuccess { makeSuccessfulBlockMessage( - it.blocked, + it.person_view.person_actions?.blocked_at != null, it.person_view.person.name, ctx, ) @@ -89,13 +89,13 @@ fun showBlockPersonToast( } fun showBlockCommunityToast( - blockCommunityRes: Result, + blockCommunityRes: Result, ctx: Context, ) { blockCommunityRes .onSuccess { makeSuccessfulBlockMessage( - it.blocked, + it.community_view.community_actions?.blocked_at != null, it.community_view.community.name, ctx, ) diff --git a/app/src/main/java/com/jerboa/feat/ModActions.kt b/app/src/main/java/com/jerboa/feat/ModActions.kt index ce1ddc528..86ce8e1c2 100644 --- a/app/src/main/java/com/jerboa/feat/ModActions.kt +++ b/app/src/main/java/com/jerboa/feat/ModActions.kt @@ -1,52 +1,51 @@ package com.jerboa.feat -import android.content.Context -import com.jerboa.MainActivity -import com.jerboa.db.entity.Account -import com.jerboa.findActivity -import it.vercruysse.lemmyapi.datatypes.CommunityId +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.PersonId import it.vercruysse.lemmyapi.datatypes.PersonView -import java.time.Instant -import java.time.temporal.ChronoUnit /** - * Determines whether someone can moderate an item. Uses a hierarchy of admins then mods. + * Determines whether someone can admin an item. Uses a hierarchy of admins from the API. */ -fun canMod( +fun canAdmin( creatorId: PersonId, + myUserInfo: MyUserInfo?, admins: List?, - moderators: List?, - myId: PersonId, onSelf: Boolean = false, -): Boolean { - // You can do moderator actions only on the mods added after you. - val adminIds = admins?.map { a -> a.person.id }.orEmpty() - val modIds = moderators.orEmpty() + ): Boolean { + val myId = myUserInfo?.local_user_view?.local_user?.person_id - val adminsThenMods = adminIds.toMutableList() - adminsThenMods.addAll(modIds) + return myId?.let { myId -> + if (onSelf && creatorId != myId) { + false + } else { + // You can only do actions on admins created after you + val firstId = admins?.find { pv -> pv.person.id == creatorId || pv.person.id == myId }?.person?.id - val myIndex = adminsThenMods.indexOf(myId) - return if (myIndex == -1) { + // IE you should come before them + val amFirst = firstId == myId + amFirst + } + } ?: run { false - } else { - // onSelf +1 on mod actions not for yourself, IE ban, remove, etc - val subList = adminsThenMods.subList(0, myIndex.plus(if (onSelf) 0 else 1)) - - !subList.contains(creatorId) } + } + +fun canModOrAdmin( + canMod: Boolean, + creatorId: PersonId, + myUserInfo: MyUserInfo?, + admins: List?, + onSelf: Boolean = false, +): Boolean { + return canMod || canAdmin(creatorId, myUserInfo, admins, onSelf) } -fun futureDaysToUnixTime(days: Long?): Long? = - days?.let { - Instant.now().plus(it, ChronoUnit.DAYS).epochSecond - } -fun amMod( - moderators: List?, - myId: PersonId, -): Boolean = moderators?.contains(myId) ?: false +// fun amMod( +// moderators: List?, +// myId: PersonId, +// ): Boolean = moderators?.contains(myId) ?: false /** * In screens with posts from different communities we don't have access to moderators of those communities @@ -54,18 +53,19 @@ fun amMod( * * So this is QoL were we simulate the mods of the community * It is not completely accurate as it doesn't take into account the hierarchy of mods + * TODO can probably get rid because of canMod */ -fun simulateModerators( - ctx: Context, - account: Account, - forCommunity: CommunityId, -): List { - if (account.isMod) { - val siteVM = (ctx.findActivity() as MainActivity).siteViewModel - val canModerate = siteVM.moderatedCommunities().orEmpty().contains(forCommunity) - if (canModerate) { - return listOf(account.id) - } - } - return emptyList() -} +// fun simulateModerators( +// ctx: Context, +// account: Account, +// forCommunity: CommunityId, +// ): List { +// if (account.isMod) { +// val siteVM = (ctx.findActivity() as MainActivity).siteViewModel +// val canModerate = siteVM.moderatedCommunities().orEmpty().contains(forCommunity) +// if (canModerate) { +// return listOf(account.id) +// } +// } +// return emptyList() +// } diff --git a/app/src/main/java/com/jerboa/feat/Voting.kt b/app/src/main/java/com/jerboa/feat/Voting.kt index 78823c4e7..94720b865 100644 --- a/app/src/main/java/com/jerboa/feat/Voting.kt +++ b/app/src/main/java/com/jerboa/feat/Voting.kt @@ -1,12 +1,10 @@ package com.jerboa.feat -import it.vercruysse.lemmyapi.datatypes.LocalUserVoteDisplayMode +import it.vercruysse.lemmyapi.enums.VoteAction -enum class VoteType( - val value: Int, -) { - Upvote(1), - Downvote(-1), +enum class PostOrCommentType { + Post, + Comment, } /** @@ -14,17 +12,19 @@ enum class VoteType( * for an API result */ data class InstantScores( - val myVote: Int, + val myVote: VoteAction?, val score: Long, val upvotes: Long, val downvotes: Long, ) { - fun update(voteAction: VoteType): InstantScores { + fun update(voteAction: VoteAction): InstantScores { val newVote = newVote(this.myVote, voteAction) + // get original (up/down)votes, add (up/down)vote if (up/down)voted - val upvotes = this.upvotes - (if (this.myVote == 1) 1 else 0) + (if (newVote == 1) 1 else 0) + + val upvotes = this.upvotes - (if (this.myVote == VoteAction.UpVote) 1 else 0) + (if (newVote == VoteAction.UpVote) 1 else 0) val downvotes = - this.downvotes - (if (this.myVote == -1) 1 else 0) + (if (newVote == -1) 1 else 0) + this.downvotes - (if (this.myVote == VoteAction.DownVote) 1 else 0) + (if (newVote == VoteAction.DownVote) 1 else 0) return InstantScores( myVote = newVote, @@ -33,57 +33,11 @@ data class InstantScores( score = upvotes - downvotes, ) } - - fun scoreOrPctStr(voteDisplayMode: LocalUserVoteDisplayMode): String? = - scoreOrPctStr( - score = score, - upvotes = upvotes, - downvotes = downvotes, - voteDisplayMode = voteDisplayMode, - ) } // Set myVote to given action unless it was already set to that action, in which case we reset to 0 +// TODO this should really be private to the vote button components fun newVote( - oldVote: Int, - voteAction: VoteType, -): Int = if (voteAction.value == oldVote) 0 else voteAction.value - -fun upvotePercent( - upvotes: Long, - downvotes: Long, -): Float = (upvotes.toFloat() / (upvotes + downvotes)) - -fun formatPercent(pct: Float): String = "%.0f".format(pct * 100F) - -private fun scoreOrPctStr( - score: Long, - upvotes: Long, - downvotes: Long, - voteDisplayMode: LocalUserVoteDisplayMode, -): String? = - if (voteDisplayMode.upvote_percentage) { - formatPercent(upvotePercent(upvotes, downvotes)) - } else if (voteDisplayMode.score || voteDisplayMode.upvotes || voteDisplayMode.downvotes) { - score.toString() - } else { - null - } - -fun LocalUserVoteDisplayMode.Companion.default(score: Boolean? = false) = - LocalUserVoteDisplayMode( - local_user_id = -1, - upvotes = true, - downvotes = true, - score = score ?: false, - upvote_percentage = false, - ) - -fun LocalUserVoteDisplayMode.Companion.allHidden() = - LocalUserVoteDisplayMode( - local_user_id = -1, - upvotes = false, - downvotes = false, - score = false, - upvote_percentage = false, - ) + oldVote: VoteAction?, + voteAction: VoteAction, +): VoteAction = if (voteAction == oldVote) VoteAction.NoVote else voteAction diff --git a/app/src/main/java/com/jerboa/feed/PostController.kt b/app/src/main/java/com/jerboa/feed/PostController.kt index b092b8e51..b797d09d6 100644 --- a/app/src/main/java/com/jerboa/feed/PostController.kt +++ b/app/src/main/java/com/jerboa/feed/PostController.kt @@ -1,6 +1,7 @@ package com.jerboa.feed import com.jerboa.datatypes.BanFromCommunityData +import com.jerboa.nowBoolean import it.vercruysse.lemmyapi.datatypes.HidePost import it.vercruysse.lemmyapi.datatypes.Person import it.vercruysse.lemmyapi.datatypes.PostView @@ -27,16 +28,22 @@ open class PostController : UniqueFeedController() { postView.creator.id == banData.person.id && postView.community.id == banData.community.id } }, - ) { it.copy(banned_from_community = banData.banned, creator_banned_from_community = banData.banned, creator = banData.person) } + ) { + it.copy( + community_actions = it.community_actions?.copy(received_ban_at = nowBoolean(banData.banned)), + creator_banned_from_community = banData.banned, + creator = banData.person, + ) + } } fun findAndUpdatePostHidden(hidePost: HidePost) { updateAll( { it.indexesOf { postView -> - hidePost.post_ids.contains(postView.post.id) + hidePost.post_id == postView.post.id } }, - ) { it.copy(hidden = hidePost.hide) } + ) { it.copy(post_actions = it.post_actions?.copy(hidden_at = nowBoolean(hidePost.hide))) } } } diff --git a/app/src/main/java/com/jerboa/model/AccountSettingsViewModel.kt b/app/src/main/java/com/jerboa/model/AccountSettingsViewModel.kt index 47ee81b3e..718048654 100644 --- a/app/src/main/java/com/jerboa/model/AccountSettingsViewModel.kt +++ b/app/src/main/java/com/jerboa/model/AccountSettingsViewModel.kt @@ -61,7 +61,7 @@ class AccountSettingsViewModel( val newAccount = account.copy( defaultListingType = form.default_listing_type?.ordinal ?: account.defaultListingType, - defaultSortType = form.default_sort_type?.ordinal ?: account.defaultSortType, + defaultSortType = form.default_post_sort_type?.ordinal ?: account.defaultSortType, ) if (newAccount != account) { accountRepository.update(newAccount) diff --git a/app/src/main/java/com/jerboa/model/BanFromCommunityViewModel.kt b/app/src/main/java/com/jerboa/model/BanFromCommunityViewModel.kt index 560fa7cc6..8d5b9ca33 100644 --- a/app/src/main/java/com/jerboa/model/BanFromCommunityViewModel.kt +++ b/app/src/main/java/com/jerboa/model/BanFromCommunityViewModel.kt @@ -1,6 +1,5 @@ package com.jerboa.model -import android.annotation.SuppressLint import android.content.Context import android.content.res.Resources import android.util.Log @@ -17,24 +16,24 @@ import com.jerboa.api.ApiState import com.jerboa.api.toApiState import com.jerboa.communityNameShown import com.jerboa.datatypes.BanFromCommunityData -import com.jerboa.feat.futureDaysToUnixTime +import com.jerboa.futureDaysToUnixTime import com.jerboa.personNameShown import com.jerboa.ui.components.common.apiErrorToast import it.vercruysse.lemmyapi.datatypes.BanFromCommunity -import it.vercruysse.lemmyapi.datatypes.BanFromCommunityResponse import it.vercruysse.lemmyapi.datatypes.Community import it.vercruysse.lemmyapi.datatypes.PersonId +import it.vercruysse.lemmyapi.datatypes.PersonResponse import kotlinx.coroutines.launch class BanFromCommunityViewModel : ViewModel() { - var banFromCommunityRes: ApiState by mutableStateOf(ApiState.Empty) + var banFromCommunityRes: ApiState by mutableStateOf(ApiState.Empty) private set fun banOrUnbanFromCommunity( personId: PersonId, community: Community, ban: Boolean, - removeData: Boolean? = null, + removeOrRestoreData: Boolean? = null, reason: String, expireDays: Long? = null, ctx: Context, @@ -48,9 +47,9 @@ class BanFromCommunityViewModel : ViewModel() { person_id = personId, community_id = community.id, ban = ban, - remove_data = removeData, + remove_or_restore_data = removeOrRestoreData, reason = reason, - expires = futureDaysToUnixTime(expireDays), + expires_at = futureDaysToUnixTime(expireDays), ) banFromCommunityRes = ApiState.Loading @@ -87,7 +86,7 @@ class BanFromCommunityViewModel : ViewModel() { BanFromCommunityData( person = res.data.person_view.person, community = community, - banned = res.data.banned, + banned = ban, ), ) } diff --git a/app/src/main/java/com/jerboa/model/BanPersonViewModel.kt b/app/src/main/java/com/jerboa/model/BanPersonViewModel.kt index 5ba7f8aa3..005a0cd16 100644 --- a/app/src/main/java/com/jerboa/model/BanPersonViewModel.kt +++ b/app/src/main/java/com/jerboa/model/BanPersonViewModel.kt @@ -1,6 +1,5 @@ package com.jerboa.model -import android.annotation.SuppressLint import android.content.Context import android.content.res.Resources import android.util.Log @@ -15,23 +14,23 @@ import com.jerboa.R import com.jerboa.api.API import com.jerboa.api.ApiState import com.jerboa.api.toApiState -import com.jerboa.feat.futureDaysToUnixTime +import com.jerboa.futureDaysToUnixTime import com.jerboa.personNameShown import com.jerboa.ui.components.common.apiErrorToast import it.vercruysse.lemmyapi.datatypes.BanPerson -import it.vercruysse.lemmyapi.datatypes.BanPersonResponse import it.vercruysse.lemmyapi.datatypes.PersonId +import it.vercruysse.lemmyapi.datatypes.PersonResponse import it.vercruysse.lemmyapi.datatypes.PersonView import kotlinx.coroutines.launch class BanPersonViewModel : ViewModel() { - var banPersonRes: ApiState by mutableStateOf(ApiState.Empty) + var banPersonRes: ApiState by mutableStateOf(ApiState.Empty) private set fun banOrUnbanPerson( personId: PersonId, ban: Boolean, - removeData: Boolean? = null, + removeOrRestoreData: Boolean? = null, reason: String, expireDays: Long? = null, ctx: Context, @@ -44,9 +43,9 @@ class BanPersonViewModel : ViewModel() { BanPerson( person_id = personId, ban = ban, - remove_data = removeData, + remove_or_restore_data = removeOrRestoreData, reason = reason, - expires = futureDaysToUnixTime(expireDays), + expires_at = futureDaysToUnixTime(expireDays), ) banPersonRes = ApiState.Loading diff --git a/app/src/main/java/com/jerboa/model/BlockViewModel.kt b/app/src/main/java/com/jerboa/model/BlockViewModel.kt index 0057fec99..df2d3e5f2 100644 --- a/app/src/main/java/com/jerboa/model/BlockViewModel.kt +++ b/app/src/main/java/com/jerboa/model/BlockViewModel.kt @@ -11,48 +11,83 @@ import com.jerboa.feat.showBlockInstanceToast import com.jerboa.feat.showBlockPersonToast import com.jerboa.feed.ApiActionController import it.vercruysse.lemmyapi.datatypes.BlockCommunity -import it.vercruysse.lemmyapi.datatypes.BlockInstance import it.vercruysse.lemmyapi.datatypes.BlockPerson import it.vercruysse.lemmyapi.datatypes.Community import it.vercruysse.lemmyapi.datatypes.Instance import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.Person +import it.vercruysse.lemmyapi.datatypes.UserBlockInstanceCommunitiesParams +import it.vercruysse.lemmyapi.datatypes.UserBlockInstancePersonsParams import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class BlockViewModel : ViewModel() { - private val instanceBlockController = ApiActionController { it.id } + private val instancePersonsBlockController = ApiActionController { it.id } + private val instanceCommunitiesBlockController = ApiActionController { it.id } private val communityBlockController = ApiActionController { it.id } private val personBlockController = ApiActionController { it.id } - val instanceBlocks = derivedStateOf { instanceBlockController.feed } + val instancePersonsBlocks = derivedStateOf { instancePersonsBlockController.feed } + val instanceCommunitiesBlocks = derivedStateOf { instanceCommunitiesBlockController.feed } val communityBlocks = derivedStateOf { communityBlockController.feed } val personBlocks = derivedStateOf { personBlockController.feed } fun initData(userInfo: MyUserInfo) { Log.d("BlockViewModel", "initData") - instanceBlockController.init(userInfo.instance_blocks.map { it.instance }) - communityBlockController.init(userInfo.community_blocks.map { it.community }) - personBlockController.init(userInfo.person_blocks.map { it.target }) + instancePersonsBlockController.init(userInfo.instance_persons_blocks.map { it }) + instanceCommunitiesBlockController.init(userInfo.instance_communities_blocks.map { it }) + communityBlockController.init(userInfo.community_blocks.map { it }) + personBlockController.init(userInfo.person_blocks.map { it }) } - fun unBlockInstance( + fun unBlockInstancePersons( instance: Instance, ctx: Context, ) { - instanceBlockController.setLoading(instance) + instancePersonsBlockController.setLoading(instance) viewModelScope.launch { API .getInstance() - .blockInstance(BlockInstance(instance.id, false)) + .userBlockInstancePersons(UserBlockInstancePersonsParams(instance.id, false)) .onSuccess { - instanceBlockController.removeItem(instance) + instancePersonsBlockController.removeItem(instance) }.onFailure { - instanceBlockController.setFailed(instance, it) + instancePersonsBlockController.setFailed(instance, it) withContext(Dispatchers.Main) { - showBlockInstanceToast(Result.failure(it), instance.domain, ctx) + showBlockInstanceToast( + blockInstanceResp = Result.failure(it), + instance = instance.domain, + blocked = false, + ctx = ctx, + ) + } + } + } + } + + fun unBlockInstanceCommunities( + instance: Instance, + ctx: Context, + ) { + instanceCommunitiesBlockController.setLoading(instance) + + viewModelScope.launch { + API + .getInstance() + .userBlockInstanceCommunities(UserBlockInstanceCommunitiesParams(instance.id, false)) + .onSuccess { + instanceCommunitiesBlockController.removeItem(instance) + }.onFailure { + instanceCommunitiesBlockController.setFailed(instance, it) + withContext(Dispatchers.Main) { + showBlockInstanceToast( + blockInstanceResp = Result.failure(it), + instance = instance.domain, + blocked = false, + ctx = ctx, + ) } } } diff --git a/app/src/main/java/com/jerboa/model/CommentLikesViewModel.kt b/app/src/main/java/com/jerboa/model/CommentLikesViewModel.kt index abb0b8bbf..b718031e3 100644 --- a/app/src/main/java/com/jerboa/model/CommentLikesViewModel.kt +++ b/app/src/main/java/com/jerboa/model/CommentLikesViewModel.kt @@ -12,39 +12,50 @@ import com.jerboa.VIEW_VOTES_LIMIT import com.jerboa.api.API import com.jerboa.api.ApiState import com.jerboa.api.toApiState +import com.jerboa.api.toOpt +import com.jerboa.feed.PaginationController import com.jerboa.getDeduplicateMerge import it.vercruysse.lemmyapi.datatypes.CommentId import it.vercruysse.lemmyapi.datatypes.ListCommentLikes -import it.vercruysse.lemmyapi.datatypes.ListCommentLikesResponse +import it.vercruysse.lemmyapi.datatypes.PagedResponse +import it.vercruysse.lemmyapi.datatypes.VoteView import kotlinx.coroutines.launch class CommentLikesViewModel( val id: CommentId, ) : ViewModel() { - var likesRes: ApiState by mutableStateOf(ApiState.Empty) + var likesRes: ApiState> by mutableStateOf(ApiState.Empty) private set - private var page by mutableLongStateOf(1) + private val pageController = PaginationController() init { getLikes() } fun resetPage() { - page = 1 + pageController.reset() } - fun getLikes(state: ApiState = ApiState.Loading) { + fun getLikes(state: ApiState> = ApiState.Loading) { viewModelScope.launch { likesRes = state likesRes = API.getInstance().listCommentLikes(getForm()).toApiState() } + when (val res = likesRes) { + is ApiState.Success -> { + pageController.nextPage(res.data.next_page) + } + + else -> {} + } } private fun getForm(): ListCommentLikes = ListCommentLikes( comment_id = id, limit = VIEW_VOTES_LIMIT, - page = page, + page = pageController.page, + page_cursor = pageController.pageCursor, ) fun appendLikes() { @@ -55,7 +66,6 @@ class CommentLikesViewModel( else -> return@launch } - page += 1 val newRes = API.getInstance().listCommentLikes(getForm()).toApiState() likesRes = @@ -63,11 +73,11 @@ class CommentLikesViewModel( is ApiState.Success -> { val appended = getDeduplicateMerge( - oldRes.data.comment_likes, - newRes.data.comment_likes, + oldRes.data.items, + newRes.data.items, ) { it.creator.id } - ApiState.Success(oldRes.data.copy(comment_likes = appended)) + ApiState.Success(oldRes.data.copy(items = appended)) } else -> { diff --git a/app/src/main/java/com/jerboa/model/CommentReplyViewModel.kt b/app/src/main/java/com/jerboa/model/CommentReplyViewModel.kt index 8c18a38c8..101c73c6e 100644 --- a/app/src/main/java/com/jerboa/model/CommentReplyViewModel.kt +++ b/app/src/main/java/com/jerboa/model/CommentReplyViewModel.kt @@ -11,11 +11,9 @@ import com.jerboa.api.API import com.jerboa.api.ApiState import com.jerboa.api.toApiState import com.jerboa.ui.components.common.apiErrorToast -import it.vercruysse.lemmyapi.datatypes.CommentReplyView import it.vercruysse.lemmyapi.datatypes.CommentResponse import it.vercruysse.lemmyapi.datatypes.CommentView import it.vercruysse.lemmyapi.datatypes.CreateComment -import it.vercruysse.lemmyapi.datatypes.PersonMentionView import it.vercruysse.lemmyapi.datatypes.PostView import kotlinx.coroutines.launch import kotlinx.serialization.Serializable @@ -31,16 +29,6 @@ sealed class ReplyItem { class CommentItem( val item: CommentView, ) : ReplyItem() - - @Serializable - class CommentReplyItem( - val item: CommentReplyView, - ) : ReplyItem() - - @Serializable - class MentionReplyItem( - val item: PersonMentionView, - ) : ReplyItem() } class CommentReplyViewModel : ViewModel() { @@ -65,20 +53,6 @@ class CommentReplyViewModel : ViewModel() { reply.item.comment.id, ) } - - is ReplyItem.CommentReplyItem -> { - Pair( - reply.item.post.id, - reply.item.comment.id, - ) - } - - is ReplyItem.MentionReplyItem -> { - Pair( - reply.item.post.id, - reply.item.comment.id, - ) - } } viewModelScope.launch { diff --git a/app/src/main/java/com/jerboa/model/CommunityListViewModel.kt b/app/src/main/java/com/jerboa/model/CommunityListViewModel.kt index b96f6953f..a3fad5767 100644 --- a/app/src/main/java/com/jerboa/model/CommunityListViewModel.kt +++ b/app/src/main/java/com/jerboa/model/CommunityListViewModel.kt @@ -11,30 +11,31 @@ import com.jerboa.DEBOUNCE_DELAY import com.jerboa.api.API import com.jerboa.api.ApiState import com.jerboa.api.toApiState -import it.vercruysse.lemmyapi.datatypes.CommunityAggregates +import com.jerboa.nowBoolean +import it.vercruysse.lemmyapi.datatypes.CommunityActions import it.vercruysse.lemmyapi.datatypes.CommunityFollowerView import it.vercruysse.lemmyapi.datatypes.CommunityView -import it.vercruysse.lemmyapi.datatypes.Search -import it.vercruysse.lemmyapi.datatypes.SearchResponse -import it.vercruysse.lemmyapi.dto.SearchType -import it.vercruysse.lemmyapi.dto.SubscribedType +import it.vercruysse.lemmyapi.datatypes.ListCommunities +import it.vercruysse.lemmyapi.datatypes.PagedResponse +import it.vercruysse.lemmyapi.enums.CommunityFollowerState import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.milliseconds class CommunityListViewModel( communities: List, ) : ViewModel() { - var searchRes: ApiState by mutableStateOf(ApiState.Empty) + var listRes: ApiState> by mutableStateOf(ApiState.Empty) private set private var fetchCommunitiesJob: Job? = null - fun searchCommunities(form: Search) { + fun listCommunities(form: ListCommunities) { fetchCommunitiesJob?.cancel() fetchCommunitiesJob = viewModelScope.launch { - delay(DEBOUNCE_DELAY) - searchRes = ApiState.Loading - searchRes = API.getInstance().search(form).toApiState() + delay(DEBOUNCE_DELAY.milliseconds) + listRes = ApiState.Loading + listRes = API.getInstance().listCommunities(form).toApiState() } } @@ -48,33 +49,19 @@ class CommunityListViewModel( myFollows.map { cfv -> CommunityView( community = cfv.community, - subscribed = SubscribedType.Subscribed, - blocked = false, - counts = - CommunityAggregates( - community_id = cfv.community.id, - subscribers = 0, - subscribers_local = 0, - posts = 0, - comments = 0, - published = "", - users_active_day = 0, - users_active_week = 0, - users_active_month = 0, - users_active_half_year = 0, - ), - banned_from_community = false, + community_actions = CommunityActions( + followed_at = nowBoolean(true), + follow_state = CommunityFollowerState.Accepted, + ), + can_mod = false, + tags = emptyList(), ) } - searchRes = + listRes = ApiState.Success( - SearchResponse( - type_ = SearchType.Communities, - communities = followsIntoCommunityViews, - comments = emptyList(), - posts = emptyList(), - users = emptyList(), + PagedResponse( + items = followsIntoCommunityViews, ), ) } diff --git a/app/src/main/java/com/jerboa/model/CommunityViewModel.kt b/app/src/main/java/com/jerboa/model/CommunityViewModel.kt index 8d6b5acc4..ec576f2ef 100644 --- a/app/src/main/java/com/jerboa/model/CommunityViewModel.kt +++ b/app/src/main/java/com/jerboa/model/CommunityViewModel.kt @@ -16,7 +16,6 @@ import com.jerboa.db.repository.AccountRepository import com.jerboa.feat.showBlockCommunityToast import com.jerboa.jerboaApplication import it.vercruysse.lemmyapi.datatypes.BlockCommunity -import it.vercruysse.lemmyapi.datatypes.BlockCommunityResponse import it.vercruysse.lemmyapi.datatypes.CommunityId import it.vercruysse.lemmyapi.datatypes.CommunityResponse import it.vercruysse.lemmyapi.datatypes.FollowCommunity @@ -33,7 +32,7 @@ class CommunityViewModel( private set private var followCommunityRes: ApiState by mutableStateOf(ApiState.Empty) - private var blockCommunityRes: ApiState by mutableStateOf(ApiState.Empty) + private var blockCommunityRes: ApiState by mutableStateOf(ApiState.Empty) private var communityId: CommunityId? by mutableStateOf(null) private var communityName: String? by mutableStateOf(null) diff --git a/app/src/main/java/com/jerboa/model/CreateReportViewModel.kt b/app/src/main/java/com/jerboa/model/CreateReportViewModel.kt index 4a79b976c..4ce2ce921 100644 --- a/app/src/main/java/com/jerboa/model/CreateReportViewModel.kt +++ b/app/src/main/java/com/jerboa/model/CreateReportViewModel.kt @@ -1,6 +1,5 @@ package com.jerboa.model -import android.annotation.SuppressLint import android.content.Context import android.content.res.Resources import android.widget.Toast diff --git a/app/src/main/java/com/jerboa/model/InboxViewModel.kt b/app/src/main/java/com/jerboa/model/InboxViewModel.kt deleted file mode 100644 index a7d00a8de..000000000 --- a/app/src/main/java/com/jerboa/model/InboxViewModel.kt +++ /dev/null @@ -1,539 +0,0 @@ -package com.jerboa.model - -import android.content.Context -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableLongStateOf -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 com.jerboa.db.entity.Account -import com.jerboa.db.entity.isAnon -import com.jerboa.feat.showBlockPersonToast -import com.jerboa.findAndUpdateCommentReply -import com.jerboa.findAndUpdateMention -import com.jerboa.findAndUpdatePersonMention -import com.jerboa.findAndUpdatePrivateMessage -import com.jerboa.getDeduplicateMerge -import it.vercruysse.lemmyapi.datatypes.* -import it.vercruysse.lemmyapi.dto.CommentSortType -import kotlinx.coroutines.launch - -class InboxViewModel( - account: Account, - siteViewModel: SiteViewModel, -) : ViewModel() { - var repliesRes: ApiState by mutableStateOf( - ApiState.Empty, - ) - private set - var mentionsRes: ApiState by mutableStateOf( - ApiState.Empty, - ) - private set - var messagesRes: ApiState by mutableStateOf( - ApiState.Empty, - ) - private set - - private var likeReplyRes: ApiState by mutableStateOf(ApiState.Empty) - - private var saveReplyRes: ApiState by mutableStateOf(ApiState.Empty) - - private var likeMentionRes: ApiState by mutableStateOf(ApiState.Empty) - - private var saveMentionRes: ApiState by mutableStateOf(ApiState.Empty) - - private var markReplyAsReadRes: ApiState by mutableStateOf(ApiState.Empty) - - private var markMentionAsReadRes: ApiState by mutableStateOf(ApiState.Empty) - - private var markMessageAsReadRes: ApiState by mutableStateOf(ApiState.Empty) - - private var markAllAsReadRes: ApiState by mutableStateOf(ApiState.Empty) - - private var blockCommunityRes: ApiState by - mutableStateOf(ApiState.Empty) - - private var blockPersonRes: ApiState by - mutableStateOf(ApiState.Empty) - - private var pageReplies by mutableLongStateOf(1) - private var pageMentions by mutableLongStateOf(1) - private var pageMessages by mutableLongStateOf(1) - var unreadOnly by mutableStateOf(true) - private set - - fun resetPageMentions() { - pageMentions = 1 - } - - fun resetPageMessages() { - pageMessages = 1 - } - - fun resetPageReplies() { - pageReplies = 1 - } - - fun resetPages() { - resetPageMentions() - resetPageMessages() - resetPageReplies() - } - - fun updateUnreadOnly(unreadOnly: Boolean) { - this.unreadOnly = unreadOnly - } - - fun getReplies( - form: GetReplies, - state: ApiState = ApiState.Loading, - ) { - viewModelScope.launch { - repliesRes = state - repliesRes = API.getInstance().getReplies(form).toApiState() - } - } - - fun appendReplies() { - viewModelScope.launch { - val oldRes = repliesRes - when (oldRes) { - is ApiState.Success -> repliesRes = ApiState.Appending(oldRes.data) - else -> return@launch - } - - pageReplies += 1 - val newRes = API.getInstance().getReplies(getFormReplies()).toApiState() - - repliesRes = - when (newRes) { - is ApiState.Success -> { - val mergedReplies = - getDeduplicateMerge( - oldRes.data.replies, - newRes.data.replies, - ) { it.comment_reply.id } - - ApiState.Success(oldRes.data.copy(replies = mergedReplies)) - } - - else -> { - pageReplies -= 1 - oldRes - } - } - } - } - - fun getMentions( - form: GetPersonMentions, - state: ApiState = ApiState.Loading, - ) { - viewModelScope.launch { - mentionsRes = state - mentionsRes = API.getInstance().getPersonMentions(form).toApiState() - } - } - - fun appendMentions() { - viewModelScope.launch { - val oldRes = mentionsRes - when (oldRes) { - is ApiState.Success -> mentionsRes = ApiState.Appending(oldRes.data) - else -> return@launch - } - - pageMentions += 1 - val form = - GetPersonMentions( - unread_only = unreadOnly, - sort = CommentSortType.New, - page = pageMentions, - ) - - val newRes = API.getInstance().getPersonMentions(form).toApiState() - - mentionsRes = - when (newRes) { - is ApiState.Success -> { - val mergedMentions = - getDeduplicateMerge( - oldRes.data.mentions, - newRes.data.mentions, - ) { it.person_mention.id } - - ApiState.Success(oldRes.data.copy(mentions = mergedMentions)) - } - - else -> { - pageMentions -= 1 - oldRes - } - } - } - } - - fun getMessages( - form: GetPrivateMessages, - state: ApiState = ApiState.Loading, - ) { - viewModelScope.launch { - messagesRes = state - messagesRes = API.getInstance().getPrivateMessages(form).toApiState() - } - } - - fun appendMessages() { - viewModelScope.launch { - val oldRes = messagesRes - when (oldRes) { - is ApiState.Success -> messagesRes = ApiState.Appending(oldRes.data) - else -> return@launch - } - - pageMessages += 1 - val form = - GetPrivateMessages( - unread_only = unreadOnly, - page = pageMessages, - ) - - val newRes = API.getInstance().getPrivateMessages(form).toApiState() - - messagesRes = - when (newRes) { - is ApiState.Success -> { - // see 1211, one can get a message between two pages, (especially noticeable if you dm yourself) - // This makes it so it shifts one message up and the next page will have a duplicate message - // This crashes because you can't have duplicate messages, as we use the id as id for the item - val mergedMessages = - getDeduplicateMerge( - oldRes.data.private_messages, - newRes.data.private_messages, - ) { it.private_message.id } - - ApiState.Success(oldRes.data.copy(private_messages = mergedMessages)) - } - - else -> { - pageMessages -= 1 - oldRes - } - } - } - } - - fun likeReply(form: CreateCommentLike) { - viewModelScope.launch { - likeReplyRes = ApiState.Loading - likeReplyRes = API.getInstance().createCommentLike(form).toApiState() - - when (val likeRes = likeReplyRes) { - is ApiState.Success -> { - when (val existing = repliesRes) { - is ApiState.Success -> { - val newReplies = - findAndUpdateCommentReply( - existing.data.replies, - likeRes.data.comment_view, - ) - val newRes = ApiState.Success(existing.data.copy(replies = newReplies)) - repliesRes = newRes - } - - else -> {} - } - } - - else -> {} - } - } - } - - fun saveReply(form: SaveComment) { - viewModelScope.launch { - saveReplyRes = ApiState.Loading - saveReplyRes = API.getInstance().saveComment(form).toApiState() - - when (val saveRes = saveReplyRes) { - is ApiState.Success -> { - when (val existing = repliesRes) { - is ApiState.Success -> { - val newReplies = - findAndUpdateCommentReply( - existing.data.replies, - saveRes.data.comment_view, - ) - val newRes = ApiState.Success(existing.data.copy(replies = newReplies)) - repliesRes = newRes - } - - else -> {} - } - } - - else -> {} - } - } - } - - fun likeMention(form: CreateCommentLike) { - viewModelScope.launch { - likeMentionRes = ApiState.Loading - likeMentionRes = API.getInstance().createCommentLike(form).toApiState() - - when (val likeRes = likeMentionRes) { - is ApiState.Success -> { - when (val existing = mentionsRes) { - is ApiState.Success -> { - val newMentions = - findAndUpdatePersonMention( - existing.data.mentions, - likeRes.data.comment_view, - ) - val newRes = - ApiState.Success(existing.data.copy(mentions = newMentions)) - mentionsRes = newRes - } - - else -> {} - } - } - - else -> {} - } - } - } - - fun saveMention(form: SaveComment) { - viewModelScope.launch { - saveReplyRes = ApiState.Loading - saveReplyRes = API.getInstance().saveComment(form).toApiState() - - when (val saveRes = saveMentionRes) { - is ApiState.Success -> { - when (val existing = mentionsRes) { - is ApiState.Success -> { - val newMentions = - findAndUpdatePersonMention( - existing.data.mentions, - saveRes.data.comment_view, - ) - val newRes = - ApiState.Success(existing.data.copy(mentions = newMentions)) - mentionsRes = newRes - } - - else -> {} - } - } - - else -> {} - } - } - } - - fun markReplyAsRead( - form: MarkCommentReplyAsRead, - onSuccess: () -> Unit, - ) { - viewModelScope.launch { - markReplyAsReadRes = ApiState.Loading - markReplyAsReadRes = API.getInstance().markCommentReplyAsRead(form).toApiState() - - when (val readRes = markReplyAsReadRes) { - is ApiState.Success -> { - when (val existing = repliesRes) { - is ApiState.Success -> { - val mutable = existing.data.replies.toMutableList() - val foundIndex = - mutable.indexOfFirst { - it.comment_reply.comment_id == readRes.data.comment_reply_view.comment.id - } - val cr = mutable[foundIndex].comment_reply - val newCr = cr.copy(read = !cr.read) - mutable[foundIndex] = mutable[foundIndex].copy(comment_reply = newCr) - - val newRes = - ApiState.Success(existing.data.copy(replies = mutable.toList())) - repliesRes = newRes - onSuccess() - } - - else -> {} - } - } - - else -> {} - } - } - } - - fun markPersonMentionAsRead( - form: MarkPersonMentionAsRead, - onSuccess: () -> Unit, - ) { - viewModelScope.launch { - markMentionAsReadRes = ApiState.Loading - markMentionAsReadRes = API.getInstance().markPersonMentionAsRead(form).toApiState() - - when (val readRes = markMentionAsReadRes) { - is ApiState.Success -> { - when (val existing = mentionsRes) { - is ApiState.Success -> { - val newMentions = - findAndUpdateMention( - existing.data.mentions, - readRes.data.person_mention_view, - ) - val newRes = ApiState.Success(existing.data.copy(mentions = newMentions)) - mentionsRes = newRes - onSuccess() - } - - else -> {} - } - } - - else -> {} - } - } - } - - fun markPrivateMessageAsRead( - form: MarkPrivateMessageAsRead, - onSuccess: () -> Unit, - ) { - viewModelScope.launch { - markMessageAsReadRes = ApiState.Loading - markMessageAsReadRes = API.getInstance().markPrivateMessageAsRead(form).toApiState() - - when (val readRes = markMessageAsReadRes) { - is ApiState.Success -> { - when (val existing = messagesRes) { - is ApiState.Success -> { - val newMessages = - findAndUpdatePrivateMessage( - existing.data.private_messages, - readRes.data.private_message_view, - ) - val newRes = ApiState.Success(existing.data.copy(private_messages = newMessages)) - messagesRes = newRes - onSuccess() - } - - else -> {} - } - } - - else -> {} - } - } - } - - fun blockPerson( - form: BlockPerson, - ctx: Context, - ) { - viewModelScope.launch { - blockPersonRes = ApiState.Loading - val res = API.getInstance().blockPerson(form) - blockPersonRes = res.toApiState() - showBlockPersonToast(res, ctx) - } - } - - fun markAllAsRead(onComplete: () -> Unit) { - viewModelScope.launch { - markAllAsReadRes = ApiState.Loading - markAllAsReadRes = API.getInstance().markAllAsRead().toApiState() - - when (val replies = repliesRes) { - is ApiState.Success -> { - val mutable = replies.data.replies.toMutableList() - mutable.replaceAll { it.copy(comment_reply = it.comment_reply.copy(read = true)) } - repliesRes = ApiState.Success(replies.data.copy(replies = mutable.toList())) - } - - else -> {} - } - - when (val mentions = mentionsRes) { - is ApiState.Success -> { - val mutable = mentions.data.mentions.toMutableList() - mutable.replaceAll { it.copy(person_mention = it.person_mention.copy(read = true)) } - mentionsRes = ApiState.Success(mentions.data.copy(mentions = mutable.toList())) - } - - else -> {} - } - - when (val messages = messagesRes) { - is ApiState.Success -> { - val mutable = messages.data.private_messages.toMutableList() - mutable.replaceAll { it.copy(private_message = it.private_message.copy(read = true)) } - messagesRes = ApiState.Success(messages.data.copy(private_messages = mutable.toList())) - } - - else -> {} - } - onComplete() - } - } - - fun getFormReplies(): GetReplies = - GetReplies( - unread_only = unreadOnly, - sort = CommentSortType.New, - page = pageReplies, - ) - - fun getFormMentions(): GetPersonMentions = - GetPersonMentions( - unread_only = unreadOnly, - sort = CommentSortType.New, - page = pageMentions, - ) - - fun getFormMessages(): GetPrivateMessages = - GetPrivateMessages( - unread_only = unreadOnly, - page = pageMessages, - ) - - init { - if (!account.isAnon()) { - this.resetPages() - this.getReplies( - this.getFormReplies(), - ) - this.getMentions( - this.getFormMentions(), - ) - this.getMessages( - this.getFormMessages(), - ) - siteViewModel.fetchUnreadCounts() - } - } - - companion object { - class Factory( - private val account: Account, - private val siteViewModel: SiteViewModel, - ) : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create( - modelClass: Class, - extras: CreationExtras, - ): T = InboxViewModel(account, siteViewModel) as T - } - } -} diff --git a/app/src/main/java/com/jerboa/model/LoginViewModel.kt b/app/src/main/java/com/jerboa/model/LoginViewModel.kt index f609a8003..e8a866bed 100644 --- a/app/src/main/java/com/jerboa/model/LoginViewModel.kt +++ b/app/src/main/java/com/jerboa/model/LoginViewModel.kt @@ -1,6 +1,5 @@ package com.jerboa.model -import android.annotation.SuppressLint import android.content.Context import android.content.res.Resources import android.util.Log @@ -16,8 +15,9 @@ import com.jerboa.api.ApiState import com.jerboa.api.toApiState import com.jerboa.db.entity.Account import com.jerboa.matchLoginErrorMsgToStringRes -import it.vercruysse.lemmyapi.LemmyApi import it.vercruysse.lemmyapi.LemmyApiBaseController +import it.vercruysse.lemmyapi.LemmyAuth +import it.vercruysse.lemmyapi.LemmyInstance import it.vercruysse.lemmyapi.datatypes.Login import it.vercruysse.lemmyapi.exception.NotSupportedException import kotlinx.coroutines.launch @@ -33,22 +33,25 @@ class LoginViewModel : ViewModel() { onGoHome: () -> Unit, accountViewModel: AccountViewModel, siteViewModel: SiteViewModel, + myUserInfoViewModel: MyUserInfoViewModel, ctx: Context, resources: Resources, ) { viewModelScope.launch { loading = true - lateinit var tempInstance: LemmyApiBaseController + val tempInstance: LemmyApiBaseController + val auth: String? try { - val nodeInfo = LemmyApi.getNodeInfo(instance).getOrThrow() + val nodeInfo = API.lemmyApiClient.nodeInfoClient.getNodeInfoSafe(LemmyInstance(instance)).getOrThrow() - if (!LemmyApi.isLemmyInstance(nodeInfo)) { + if (!API.lemmyApiClient.nodeInfoClient.isLemmyInstance(nodeInfo)) { throw UnknownHostException() } - tempInstance = API.createTempInstanceVersion(instance, LemmyApi.getVersion(nodeInfo)) + tempInstance = API.createTempInstanceVersion(instance, API.lemmyApiClient.nodeInfoClient.getVersion(nodeInfo)) val resp = tempInstance.login(form = form).getOrThrow() - tempInstance.auth = resp.jwt + auth = resp.jwt + tempInstance.updateAuth(LemmyAuth.fromToken(auth)) } catch (e: Throwable) { loading = false @@ -74,19 +77,19 @@ class LoginViewModel : ViewModel() { return@launch } - // Fetch the site to get your name and id - siteViewModel.siteRes = tempInstance.getSite().toApiState() + // Fetch your user to get your name and id + myUserInfoViewModel.myUserRes = tempInstance.getMyUser().toApiState() try { - when (val siteRes = siteViewModel.siteRes) { + when (val muiRes = myUserInfoViewModel.myUserRes) { is ApiState.Failure -> { - val txt = siteRes.msg.message ?: "FAILURE: NO MESSAGE, probably that version not supported" + val txt = muiRes.msg.message ?: "FAILURE: NO MESSAGE, probably that version not supported" Toast.makeText(ctx, txt, Toast.LENGTH_SHORT).show() throw Exception(txt) } is ApiState.Success -> { - val mui = siteRes.data.my_user!! + val mui = muiRes.data val luv = mui.local_user_view if (accountViewModel.allAccounts.value?.any { @@ -106,9 +109,9 @@ class LoginViewModel : ViewModel() { name = luv.person.name, current = true, instance = instance, - jwt = tempInstance.auth!!, + jwt = auth!!, defaultListingType = luv.local_user.default_listing_type.ordinal, - defaultSortType = luv.local_user.default_sort_type.ordinal, + defaultSortType = luv.local_user.default_post_sort_type.ordinal, verificationState = 0, isAdmin = luv.local_user.admin, isMod = mui.moderates.isNotEmpty(), @@ -118,6 +121,10 @@ class LoginViewModel : ViewModel() { accountViewModel.insert(account) API.setLemmyInstance(tempInstance) loading = false + + // Now re-fetch the site + siteViewModel.getSite() + onGoHome() } diff --git a/app/src/main/java/com/jerboa/model/MyUserInfoViewModel.kt b/app/src/main/java/com/jerboa/model/MyUserInfoViewModel.kt new file mode 100644 index 000000000..67a14496f --- /dev/null +++ b/app/src/main/java/com/jerboa/model/MyUserInfoViewModel.kt @@ -0,0 +1,109 @@ +package com.jerboa.model + +import android.util.Log +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.asFlow +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.jerboa.api.API +import com.jerboa.api.ApiState +import com.jerboa.api.toApiState +import com.jerboa.api.toOpt +import com.jerboa.db.entity.AnonAccount +import com.jerboa.db.entity.isAnon +import com.jerboa.db.repository.AccountRepository +import com.jerboa.jerboaApplication +import it.vercruysse.lemmyapi.datatypes.CommunityFollowerView +import it.vercruysse.lemmyapi.datatypes.CommunityId +import it.vercruysse.lemmyapi.datatypes.MyUserInfo +import it.vercruysse.lemmyapi.datatypes.SaveUserSettings +import it.vercruysse.lemmyapi.datatypes.UnreadCountsResponse +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import java.time.OffsetDateTime +import kotlin.collections.map + +@Stable +class MyUserInfoViewModel( + private val accountRepository: AccountRepository, +) : ViewModel() { + // Can't be private, because it needs to be set by the login viewmodel + var myUserRes: ApiState by mutableStateOf(ApiState.Empty) + + var unreadCountsRes: ApiState by mutableStateOf(ApiState.Empty) + private set + + lateinit var saveUserSettings: SaveUserSettings + + init { + viewModelScope.launch { + + // Makes sure that the user is fetched when the account is changed + accountRepository.currentAccount + .asFlow() + .map { it ?: AnonAccount } + .collect { + Log.d("MyUserInfoViewModel", "acc init for id: ${it.id}") + getMyUser() + + if (it.isAnon()) { // Reset the unread counts if we're anonymous + unreadCountsRes = ApiState.Empty + } else { + fetchUnreadCounts() + } + } + } + } + + fun getMyUser(loadingState: ApiState = ApiState.Loading): Job = + viewModelScope.launch { + myUserRes = loadingState + myUserRes = API.getInstance().getMyUser().toApiState() + + when (val res = myUserRes) { + is ApiState.Success -> { + val mui = res.data + val currAcc = accountRepository.currentAccount.value + val localUser = mui.local_user_view.local_user + if (currAcc != null) { + val newAccount = + currAcc.copy( + defaultListingType = localUser.default_listing_type.ordinal, + defaultSortType = localUser.default_post_sort_type.ordinal, + isAdmin = localUser.admin, + isMod = mui.moderates.isNotEmpty(), + ) + + if (currAcc != newAccount) { + accountRepository.update(newAccount) + } + } + } + + else -> {} + } + } + + fun fetchUnreadCounts() { + viewModelScope.launch { + unreadCountsRes = ApiState.Loading + unreadCountsRes = API.getInstance().getUnreadCounts().toApiState() + } + } + + companion object { + val Factory = + viewModelFactory { + initializer { + MyUserInfoViewModel(jerboaApplication().container.accountRepository) + } + } + } +} + diff --git a/app/src/main/java/com/jerboa/model/NotificationViewModel.kt b/app/src/main/java/com/jerboa/model/NotificationViewModel.kt new file mode 100644 index 000000000..29b15c40c --- /dev/null +++ b/app/src/main/java/com/jerboa/model/NotificationViewModel.kt @@ -0,0 +1,269 @@ +package com.jerboa.model + +import android.content.Context +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 com.jerboa.db.entity.Account +import com.jerboa.db.entity.isAnon +import com.jerboa.feat.showBlockPersonToast +import com.jerboa.feed.PaginationController +import com.jerboa.findAndUpdateCommentInNotificationView +import com.jerboa.getDeduplicateMerge +import it.vercruysse.lemmyapi.datatypes.* +import it.vercruysse.lemmyapi.enums.NotificationDataType +import kotlinx.coroutines.launch + +class NotificationViewModel( + account: Account, + myUserInfoViewModel: MyUserInfoViewModel, +) : ViewModel() { + var notifsRes: ApiState> by mutableStateOf( + ApiState.Empty, + ) + private set + + private var likeCommentRes: ApiState by mutableStateOf(ApiState.Empty) + + private var saveCommentRes: ApiState by mutableStateOf(ApiState.Empty) + + private var markNotificationAsReadRes: ApiState by mutableStateOf(ApiState.Empty) + + private var markAllAsReadRes: ApiState by mutableStateOf(ApiState.Empty) + + private var blockPersonRes: ApiState by + mutableStateOf(ApiState.Empty) + + private val pageController = PaginationController() + + var unreadOnly by mutableStateOf(true) + private set + + var notificationDataType by mutableStateOf(NotificationDataType.All) + private set + + fun resetPage() { + pageController.reset() + } + + fun updateUnreadOnly(unreadOnly: Boolean) { + this.unreadOnly = unreadOnly + } + + fun listNotifications( + form: ListNotifications, + state: ApiState> = ApiState.Loading, + ) { + viewModelScope.launch { + notifsRes = state + notifsRes = API.getInstance().listNotifications(form).toApiState() + } + + when (val res = notifsRes) { + is ApiState.Success -> { + pageController.nextPage(res.data.next_page) + } + + else -> {} + } + } + + fun appendNotifications() { + viewModelScope.launch { + val oldRes = notifsRes + when (oldRes) { + is ApiState.Success -> notifsRes = ApiState.Appending(oldRes.data) + else -> return@launch + } + + val newRes = API.getInstance().listNotifications(listNotificationsForm()).toApiState() + + notifsRes = + when (newRes) { + is ApiState.Success -> { + val mergedItems = + getDeduplicateMerge( + oldRes.data.items, + newRes.data.items, + ) { + it.notification.id + } + + ApiState.Success(oldRes.data.copy(items = mergedItems)) + } + + else -> { + ApiState.AppendingFailure(oldRes.data) + } + } + } + } + + fun likeComment(form: CreateCommentLike) { + viewModelScope.launch { + likeCommentRes = ApiState.Loading + likeCommentRes = API.getInstance().createCommentLike(form).toApiState() + + when (val likeRes = likeCommentRes) { + is ApiState.Success -> { + when (val existing = notifsRes) { + is ApiState.Success -> { + val newReplies = + findAndUpdateCommentInNotificationView( + existing.data.items, + likeRes.data.comment_view, + ) + val newRes = ApiState.Success(existing.data.copy(items = newReplies)) + notifsRes = newRes + } + + else -> {} + } + } + + else -> {} + } + } + } + + fun saveComment(form: SaveComment) { + viewModelScope.launch { + saveCommentRes = ApiState.Loading + saveCommentRes = API.getInstance().saveComment(form).toApiState() + + when (val saveRes = saveCommentRes) { + is ApiState.Success -> { + when (val existing = notifsRes) { + is ApiState.Success -> { + val newReplies = + findAndUpdateCommentInNotificationView( + existing.data.items, + saveRes.data.comment_view, + ) + val newRes = ApiState.Success(existing.data.copy(items = newReplies)) + notifsRes = newRes + } + + else -> {} + } + } + + else -> {} + } + } + } + + fun markNotificationsAsRead( + form: MarkNotificationAsRead, + onSuccess: () -> Unit, + ) { + viewModelScope.launch { + markNotificationAsReadRes = ApiState.Loading + markNotificationAsReadRes = API.getInstance().markNotificationAsRead(form).toApiState() + + when (markNotificationAsReadRes) { + is ApiState.Success -> { + when (val existing = notifsRes) { + is ApiState.Success -> { + val newNotifs = existing.data.items.toMutableList() + newNotifs.replaceAll { + if (it.notification.id == form.notification_id) { + it.copy(notification = it.notification.copy(read = form.read)) + } else { + it + } + } + + val newRes = + ApiState.Success(existing.data.copy(items = newNotifs)) + notifsRes = newRes + onSuccess() + } + + else -> {} + } + } + + else -> {} + } + } + } + + fun blockPerson( + form: BlockPerson, + ctx: Context, + ) { + viewModelScope.launch { + blockPersonRes = ApiState.Loading + val res = API.getInstance().blockPerson(form) + blockPersonRes = res.toApiState() + showBlockPersonToast(res, ctx) + } + } + + fun markAllAsRead(onComplete: () -> Unit) { + viewModelScope.launch { + markAllAsReadRes = ApiState.Loading + markAllAsReadRes = API.getInstance().markAllNotificationsAsRead().toApiState() + + when (markAllAsReadRes) { + is ApiState.Success -> { + when (val existing = notifsRes) { + is ApiState.Success -> { + val newNotifs = existing.data.items.toMutableList() + newNotifs.replaceAll { + it.copy(notification = it.notification.copy(read = true)) + } + + val newRes = + ApiState.Success(existing.data.copy(items = newNotifs)) + notifsRes = newRes + } + + else -> {} + } + } + + else -> {} + } + + onComplete() + } + } + + fun listNotificationsForm(): ListNotifications = + ListNotifications( + type_ = notificationDataType, + unread_only = unreadOnly, + page = pageController.page, + page_cursor = pageController.pageCursor, + ) + + init { + if (!account.isAnon()) { + this.resetPage() + this.listNotifications(listNotificationsForm()) + myUserInfoViewModel.fetchUnreadCounts() + } + } + + companion object { + class Factory( + private val account: Account, + private val myUserInfoViewModel: MyUserInfoViewModel, + ) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create( + modelClass: Class, + extras: CreationExtras, + ): T = NotificationViewModel(account, myUserInfoViewModel) as T + } + } +} diff --git a/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt b/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt index 541c33187..191b3a01b 100644 --- a/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt @@ -15,45 +15,54 @@ import com.jerboa.JerboaAppState import com.jerboa.R import com.jerboa.api.API import com.jerboa.api.ApiState +import com.jerboa.api.ApiState.* import com.jerboa.api.toApiState import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.feat.showBlockCommunityToast import com.jerboa.feat.showBlockPersonToast -import com.jerboa.findAndUpdateComment -import com.jerboa.findAndUpdateCommentCreator -import com.jerboa.findAndUpdateCommentCreatorBannedFromCommunity -import com.jerboa.findAndUpdatePost -import com.jerboa.findAndUpdatePostCreator -import com.jerboa.findAndUpdatePostCreatorBannedFromCommunity -import com.jerboa.findAndUpdatePostHidden +import com.jerboa.feed.PaginationController +import com.jerboa.findAndUpdateCommentInPostCommentCombined +import com.jerboa.findAndUpdateCreatorBannedFromCommunityInPostCommentCombined +import com.jerboa.findAndUpdateCreatorInPostCommentCombined +import com.jerboa.findAndUpdatePostInPostCommentCombined import com.jerboa.getDeduplicateMerge import it.vercruysse.lemmyapi.datatypes.* -import it.vercruysse.lemmyapi.dto.SortType +import it.vercruysse.lemmyapi.enums.* import kotlinx.coroutines.launch class PersonProfileViewModel( personArg: Either, - savedMode: Boolean, ) : ViewModel() { - var personDetailsRes: ApiState by mutableStateOf(ApiState.Empty) + var personDetailsRes: ApiState by mutableStateOf(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 hidePostRes: ApiState<(Unit)> by mutableStateOf(ApiState.Empty) - private var lockPostRes: ApiState by mutableStateOf(ApiState.Empty) - private var featurePostRes: 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) - private var distinguishCommentRes: ApiState by mutableStateOf(ApiState.Empty) - - private var markPostRes: ApiState by mutableStateOf(ApiState.Empty) - + var personContentRes: ApiState> by mutableStateOf(value = Empty) + + // TODO need to add all these yet + var personSavedRes: ApiState> by mutableStateOf(value = Empty) + var personLikedRes: ApiState> by mutableStateOf(value = Empty) + var personReadRes: ApiState> by mutableStateOf(value = Empty) + var personHiddenRes: ApiState> by mutableStateOf(value = Empty) + var uploadsRes: ApiState> by mutableStateOf(value = Empty) + + private val pageController = PaginationController() + + private var likePostRes: ApiState by mutableStateOf(Empty) + private var savePostRes: ApiState by mutableStateOf(Empty) + private var deletePostRes: ApiState by mutableStateOf(Empty) + private var hidePostRes: ApiState by mutableStateOf(Empty) + private var lockPostRes: ApiState by mutableStateOf(Empty) + private var featurePostRes: ApiState by mutableStateOf(Empty) + private var blockCommunityRes: ApiState by mutableStateOf(Empty) + private var blockPersonRes: ApiState by mutableStateOf(Empty) + + private var likeCommentRes: ApiState by mutableStateOf(Empty) + private var saveCommentRes: ApiState by mutableStateOf(Empty) + private var deleteCommentRes: ApiState by mutableStateOf(Empty) + private var distinguishCommentRes: ApiState by mutableStateOf(Empty) + + private var markPostRes: ApiState by mutableStateOf(Empty) + + // TODO this is probably gone var sortType by mutableStateOf(SortType.New) private set var page by mutableLongStateOf(1) @@ -65,14 +74,10 @@ class PersonProfileViewModel( val personId = personArg.fold({ it }, { null }) val personName = personArg.fold({ null }, { it }) - this.resetPage() - this.updateSavedOnly(savedMode) this.getPersonDetails( GetPersonDetails( person_id = personId, username = personName, - sort = SortType.New, - saved_only = savedMode, ), ) } @@ -81,34 +86,14 @@ class PersonProfileViewModel( this.sortType = sortType } - fun resetPage() { - page = 1 - } - - private fun nextPage() { - page += 1 - } - - private fun prevPage() { - page -= 1 - } - - private fun updateSavedOnly(savedOnly: Boolean) { - this.savedOnly = savedOnly - } - fun refresh() { when (val profileRes = personDetailsRes) { - is ApiState.Success -> { - resetPage() + is Success -> { getPersonDetails( GetPersonDetails( person_id = profileRes.data.person_view.person.id, - sort = sortType, - page = page, - saved_only = savedOnly, ), - ApiState.Refreshing, + Refreshing, ) } @@ -118,7 +103,7 @@ class PersonProfileViewModel( fun getPersonDetails( form: GetPersonDetails, - state: ApiState = ApiState.Loading, + state: ApiState = Loading, ) { viewModelScope.launch { personDetailsRes = state @@ -126,47 +111,59 @@ class PersonProfileViewModel( } } - fun appendData(profileId: PersonId) { + fun listPersonContent( + form: ListPersonContent, + state: ApiState> = Loading, + ) { + viewModelScope.launch { + personContentRes = state + personContentRes = API.getInstance().listPersonContent(form).toApiState() + when (val res = personContentRes) { + is Success -> { + pageController.nextPage(res.data.next_page) + } + + else -> {} + } + } + } + + fun appendPersonContent(profileId: PersonId) { viewModelScope.launch { - val oldRes = personDetailsRes - personDetailsRes = + val oldRes = personContentRes + personContentRes = when (oldRes) { - is ApiState.Appending -> return@launch - is ApiState.Holder -> ApiState.Appending(oldRes.data) + is Success -> Appending(oldRes.data) else -> return@launch } - nextPage() val form = - GetPersonDetails( + ListPersonContent( person_id = profileId, - sort = sortType, - page = page, - saved_only = savedOnly, + page_cursor = pageController.pageCursor, + page = pageController.page, ) - val newRes = API.getInstance().getPersonDetails(form).toApiState() + val newRes = API.getInstance().listPersonContent(form).toApiState() - personDetailsRes = + personContentRes = when (newRes) { - is ApiState.Success -> { - val appendedPosts = getDeduplicateMerge(oldRes.data.posts, newRes.data.posts) { it.post.id } - val appendedComments = - getDeduplicateMerge( - oldRes.data.comments, - newRes.data.comments, - ) { it.comment.id } - - ApiState.Success( + is Success -> { + val appendedItems = getDeduplicateMerge(oldRes.data.items, newRes.data.items) { + when (val item = it) { + is CommentView -> item.comment.id + is PostView -> item.post.id + } + } + + Success( oldRes.data.copy( - posts = appendedPosts, - comments = appendedComments, + items = appendedItems, ), ) } else -> { - prevPage() - ApiState.AppendingFailure(oldRes.data) + AppendingFailure(oldRes.data) } } } @@ -174,11 +171,11 @@ class PersonProfileViewModel( fun likePost(form: CreatePostLike) { viewModelScope.launch { - likePostRes = ApiState.Loading + likePostRes = Loading likePostRes = API.getInstance().createPostLike(form).toApiState() when (val likeRes = likePostRes) { - is ApiState.Success -> { + is Success -> { updatePost(likeRes.data.post_view) } @@ -189,10 +186,10 @@ class PersonProfileViewModel( fun savePost(form: SavePost) { viewModelScope.launch { - savePostRes = ApiState.Loading + savePostRes = Loading savePostRes = API.getInstance().savePost(form).toApiState() when (val saveRes = savePostRes) { - is ApiState.Success -> { + is Success -> { updatePost(saveRes.data.post_view) } @@ -203,10 +200,10 @@ class PersonProfileViewModel( fun deletePost(form: DeletePost) { viewModelScope.launch { - deletePostRes = ApiState.Loading + deletePostRes = Loading deletePostRes = API.getInstance().deletePost(form).toApiState() when (val deletePost = deletePostRes) { - is ApiState.Success -> { + is Success -> { updatePost(deletePost.data.post_view) } @@ -220,12 +217,12 @@ class PersonProfileViewModel( ctx: Context, ) { viewModelScope.launch { - hidePostRes = ApiState.Loading + hidePostRes = Loading hidePostRes = API.getInstance().hidePost(form).toApiState() val msg = if (form.hide) R.string.post_hidden else R.string.post_unhidden - when (hidePostRes) { - is ApiState.Success -> { - updatePostHidden(form) + when (val res = hidePostRes) { + is Success -> { + updatePost(res.data.post_view) Toast.makeText(ctx, msg, Toast.LENGTH_SHORT).show() } @@ -236,10 +233,10 @@ class PersonProfileViewModel( fun lockPost(form: LockPost) { viewModelScope.launch { - lockPostRes = ApiState.Loading + lockPostRes = Loading lockPostRes = API.getInstance().lockPost(form).toApiState() when (val lockPost = lockPostRes) { - is ApiState.Success -> { + is Success -> { updatePost(lockPost.data.post_view) } @@ -250,10 +247,10 @@ class PersonProfileViewModel( fun featurePost(form: FeaturePost) { viewModelScope.launch { - featurePostRes = ApiState.Loading + featurePostRes = Loading featurePostRes = API.getInstance().featurePost(form).toApiState() when (val featurePost = featurePostRes) { - is ApiState.Success -> { + is Success -> { updatePost(featurePost.data.post_view) } @@ -267,7 +264,7 @@ class PersonProfileViewModel( ctx: Context, ) { viewModelScope.launch { - blockCommunityRes = ApiState.Loading + blockCommunityRes = Loading val res = API.getInstance().blockCommunity(form) blockCommunityRes = res.toApiState() showBlockCommunityToast(res, ctx) @@ -279,7 +276,7 @@ class PersonProfileViewModel( ctx: Context, ) { viewModelScope.launch { - blockPersonRes = ApiState.Loading + blockPersonRes = Loading val res = API.getInstance().blockPerson(form) blockPersonRes = res.toApiState() showBlockPersonToast(res, ctx) @@ -288,11 +285,11 @@ class PersonProfileViewModel( fun likeComment(form: CreateCommentLike) { viewModelScope.launch { - likeCommentRes = ApiState.Loading + likeCommentRes = Loading likeCommentRes = API.getInstance().createCommentLike(form).toApiState() when (val likeRes = likeCommentRes) { - is ApiState.Success -> { + is Success -> { updateComment(likeRes.data.comment_view) } @@ -303,11 +300,11 @@ class PersonProfileViewModel( fun deleteComment(form: DeleteComment) { viewModelScope.launch { - deleteCommentRes = ApiState.Loading + deleteCommentRes = Loading deleteCommentRes = API.getInstance().deleteComment(form).toApiState() when (val deleteRes = deleteCommentRes) { - is ApiState.Success -> { + is Success -> { updateComment(deleteRes.data.comment_view) } @@ -318,11 +315,11 @@ class PersonProfileViewModel( fun distinguishComment(form: DistinguishComment) { viewModelScope.launch { - distinguishCommentRes = ApiState.Loading + distinguishCommentRes = Loading distinguishCommentRes = API.getInstance().distinguishComment(form).toApiState() when (val distinguishRes = distinguishCommentRes) { - is ApiState.Success -> { + is Success -> { updateComment(distinguishRes.data.comment_view) } @@ -333,11 +330,11 @@ class PersonProfileViewModel( fun saveComment(form: SaveComment) { viewModelScope.launch { - saveCommentRes = ApiState.Loading + saveCommentRes = Loading saveCommentRes = API.getInstance().saveComment(form).toApiState() when (val saveRes = saveCommentRes) { - is ApiState.Success -> { + is Success -> { updateComment(saveRes.data.comment_view) } @@ -347,43 +344,36 @@ class PersonProfileViewModel( } 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 + when (val existing = personContentRes) { + is Success -> { + val newItems = + findAndUpdatePostInPostCommentCombined(existing.data.items, postView) + val newRes = Success(existing.data.copy(items = newItems)) + personContentRes = newRes } else -> {} } } - fun updatePostHidden(form: HidePost) { + fun updateBanned(personView: PersonView) { + // Update the person details when (val existing = personDetailsRes) { - is ApiState.Success -> { - val newPosts = findAndUpdatePostHidden(existing.data.posts, form) - val newRes = ApiState.Success(existing.data.copy(posts = newPosts)) + is Success -> { + val newRes = Success(existing.data.copy(person_view = personView)) personDetailsRes = newRes } else -> {} } - } - fun updateBanned(personView: PersonView) { - when (val existing = personDetailsRes) { - is ApiState.Success -> { - val data = existing.data - - // Replace all the post creators - val posts = findAndUpdatePostCreator(posts = data.posts, person = personView.person) + // Update their content + when (val existing = personContentRes) { + is Success -> { + val newItems = findAndUpdateCreatorInPostCommentCombined(existing.data.items, personView.person) - // Replace all the comment creators - val comments = findAndUpdateCommentCreator(comments = data.comments, person = personView.person) - - val newRes = ApiState.Success(data.copy(person_view = personView, posts = posts, comments = comments)) - personDetailsRes = newRes + val newRes = Success(existing.data.copy(items = newItems)) + personContentRes = newRes } else -> {} @@ -391,26 +381,12 @@ class PersonProfileViewModel( } fun updateBannedFromCommunity(banData: BanFromCommunityData) { - when (val existing = personDetailsRes) { - is ApiState.Success -> { - val data = existing.data - - // Replace all the post creators - val posts = - findAndUpdatePostCreatorBannedFromCommunity( - posts = data.posts, - banData = banData, - ) - - // Replace all the comment creators - val comments = - findAndUpdateCommentCreatorBannedFromCommunity( - comments = data.comments, - banData = banData, - ) - - val newRes = ApiState.Success(data.copy(posts = posts, comments = comments)) - personDetailsRes = newRes + when (val existing = personContentRes) { + is Success -> { + // Replace all the items + val newItems = findAndUpdateCreatorBannedFromCommunityInPostCommentCombined(existing.data.items, banData) + val newRes = Success(existing.data.copy(items = newItems)) + personContentRes = newRes } else -> {} @@ -418,16 +394,16 @@ class PersonProfileViewModel( } fun updateComment(commentView: CommentView) { - when (val existing = personDetailsRes) { - is ApiState.Success -> { - val newComments = - findAndUpdateComment( - existing.data.comments, + when (val existing = personContentRes) { + is Success -> { + val newItems = + findAndUpdateCommentInPostCommentCombined( + existing.data.items, commentView, ) val newRes = - ApiState.Success(existing.data.copy(comments = newComments)) - personDetailsRes = newRes + Success(existing.data.copy(items = newItems)) + personContentRes = newRes } else -> {} @@ -435,13 +411,13 @@ class PersonProfileViewModel( } fun insertComment(commentView: CommentView) { - when (val existing = personDetailsRes) { - is ApiState.Success -> { - val mutable = existing.data.comments.toMutableList() + when (val existing = personContentRes) { + is Success -> { + val mutable = existing.data.items.toMutableList() mutable.add(0, commentView) val newRes = - ApiState.Success(existing.data.copy(comments = mutable.toList())) - personDetailsRes = newRes + Success(existing.data.copy(items = mutable)) + personContentRes = newRes } else -> {} @@ -450,16 +426,15 @@ class PersonProfileViewModel( fun markPostAsRead( form: MarkPostAsRead, - post: PostView, appState: JerboaAppState, ) { appState.coroutineScope.launch { - markPostRes = ApiState.Loading + markPostRes = Loading markPostRes = API.getInstance().markPostAsRead(form).toApiState() - when (markPostRes) { - is ApiState.Success -> { - updatePost(post.copy(read = form.read)) + when (val res = markPostRes) { + is Success -> { + updatePost(res.data.post_view) } else -> {} @@ -470,13 +445,12 @@ class PersonProfileViewModel( companion object { class Factory( private val personArg: Either, - private val savedMode: Boolean, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( modelClass: Class, extras: CreationExtras, - ): T = PersonProfileViewModel(personArg, savedMode) as T + ): T = PersonProfileViewModel(personArg) as T } } } diff --git a/app/src/main/java/com/jerboa/model/PostLikesViewModel.kt b/app/src/main/java/com/jerboa/model/PostLikesViewModel.kt index ac202cfaf..506acf183 100644 --- a/app/src/main/java/com/jerboa/model/PostLikesViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostLikesViewModel.kt @@ -1,7 +1,6 @@ package com.jerboa.model import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel @@ -12,39 +11,49 @@ import com.jerboa.VIEW_VOTES_LIMIT import com.jerboa.api.API import com.jerboa.api.ApiState import com.jerboa.api.toApiState +import com.jerboa.feed.PaginationController import com.jerboa.getDeduplicateMerge import it.vercruysse.lemmyapi.datatypes.ListPostLikes -import it.vercruysse.lemmyapi.datatypes.ListPostLikesResponse +import it.vercruysse.lemmyapi.datatypes.PagedResponse import it.vercruysse.lemmyapi.datatypes.PostId +import it.vercruysse.lemmyapi.datatypes.VoteView import kotlinx.coroutines.launch class PostLikesViewModel( val id: PostId, ) : ViewModel() { - var likesRes: ApiState by mutableStateOf(ApiState.Empty) + var likesRes: ApiState> by mutableStateOf(ApiState.Empty) private set - private var page by mutableLongStateOf(1) + private val pageController = PaginationController() init { getLikes() } fun resetPage() { - page = 1 + pageController.reset() } - fun getLikes(state: ApiState = ApiState.Loading) { + fun getLikes(state: ApiState> = ApiState.Loading) { viewModelScope.launch { likesRes = state likesRes = API.getInstance().listPostLikes(getForm()).toApiState() } + when (val res = likesRes) { + is ApiState.Success -> { + pageController.nextPage(res.data.next_page) + } + + else -> {} + } } private fun getForm(): ListPostLikes = ListPostLikes( post_id = id, limit = VIEW_VOTES_LIMIT, - page = page, + page = pageController.page, + page_cursor = pageController.pageCursor, ) fun appendLikes() { @@ -55,7 +64,6 @@ class PostLikesViewModel( else -> return@launch } - page += 1 val newRes = API.getInstance().listPostLikes(getForm()).toApiState() likesRes = @@ -63,11 +71,11 @@ class PostLikesViewModel( is ApiState.Success -> { val appended = getDeduplicateMerge( - oldRes.data.post_likes, - newRes.data.post_likes, + oldRes.data.items, + newRes.data.items, ) { it.creator.id } - ApiState.Success(oldRes.data.copy(post_likes = appended)) + ApiState.Success(oldRes.data.copy(items = appended)) } else -> { diff --git a/app/src/main/java/com/jerboa/model/PostViewModel.kt b/app/src/main/java/com/jerboa/model/PostViewModel.kt index f616fac74..afd63db0f 100644 --- a/app/src/main/java/com/jerboa/model/PostViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostViewModel.kt @@ -5,7 +5,6 @@ import android.widget.Toast import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -23,7 +22,6 @@ import com.jerboa.findAndUpdateComment import com.jerboa.findAndUpdateCommentCreator import com.jerboa.findAndUpdateCommentCreatorBannedFromCommunity import it.vercruysse.lemmyapi.datatypes.BlockPerson -import it.vercruysse.lemmyapi.datatypes.BlockPersonResponse import it.vercruysse.lemmyapi.datatypes.CommentId import it.vercruysse.lemmyapi.datatypes.CommentResponse import it.vercruysse.lemmyapi.datatypes.CommentView @@ -34,28 +32,29 @@ import it.vercruysse.lemmyapi.datatypes.DeletePost import it.vercruysse.lemmyapi.datatypes.DistinguishComment import it.vercruysse.lemmyapi.datatypes.FeaturePost import it.vercruysse.lemmyapi.datatypes.GetComments -import it.vercruysse.lemmyapi.datatypes.GetCommentsResponse import it.vercruysse.lemmyapi.datatypes.GetPost import it.vercruysse.lemmyapi.datatypes.GetPostResponse import it.vercruysse.lemmyapi.datatypes.HidePost import it.vercruysse.lemmyapi.datatypes.LockPost +import it.vercruysse.lemmyapi.datatypes.PagedResponse +import it.vercruysse.lemmyapi.datatypes.PersonResponse import it.vercruysse.lemmyapi.datatypes.PersonView import it.vercruysse.lemmyapi.datatypes.PostId import it.vercruysse.lemmyapi.datatypes.PostResponse import it.vercruysse.lemmyapi.datatypes.PostView import it.vercruysse.lemmyapi.datatypes.SaveComment import it.vercruysse.lemmyapi.datatypes.SavePost -import it.vercruysse.lemmyapi.dto.CommentSortType -import it.vercruysse.lemmyapi.dto.ListingType +import it.vercruysse.lemmyapi.enums.CommentSortType +import it.vercruysse.lemmyapi.enums.ListingType import kotlinx.coroutines.launch class PostViewModel( - val id: Either, + var id: Either, ) : ViewModel() { var postRes: ApiState by mutableStateOf(ApiState.Empty) private set - var commentsRes: ApiState by mutableStateOf(ApiState.Empty) + var commentsRes: ApiState> by mutableStateOf(ApiState.Empty) private set var sortType by mutableStateOf(CommentSortType.Hot) private set @@ -68,10 +67,10 @@ class PostViewModel( 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 hidePostRes: ApiState<(Unit)> by mutableStateOf(ApiState.Empty) + private var hidePostRes: ApiState by mutableStateOf(ApiState.Empty) private var lockPostRes: ApiState by mutableStateOf(ApiState.Empty) private var featurePostRes: ApiState by mutableStateOf(ApiState.Empty) - private var blockPersonRes: ApiState by mutableStateOf(ApiState.Empty) + private var blockPersonRes: ApiState by mutableStateOf(ApiState.Empty) val unExpandedComments = mutableStateListOf() val commentsWithToggledActionBar = mutableStateListOf() @@ -81,6 +80,11 @@ class PostViewModel( this.getData() } + fun reInitializeWithNewId(id: Either) { + this.id = id + this.getData() + } + fun updateSortType(sortType: CommentSortType) { this.sortType = sortType } @@ -169,12 +173,12 @@ class PostViewModel( // Remove the first comment, since it is a parent // Actually since a bug in 18.3 that is no longer a guarantee // see https://github.com/LemmyNet/lemmy/issues/3767 - val newComments = moreComments.data.comments.toMutableList() + val newComments = moreComments.data.items.toMutableList() newComments.removeIf { it.comment.id == commentView.comment.id } - val appended = appendData(existing.data.comments, newComments.toList()) + val appended = appendData(existing.data.items, newComments.toList()) - commentsRes = ApiState.Success(existing.data.copy(comments = appended)) + commentsRes = ApiState.Success(existing.data.copy(items = appended)) } else -> {} @@ -293,9 +297,9 @@ class PostViewModel( hidePostRes = ApiState.Loading hidePostRes = API.getInstance().hidePost(form).toApiState() val msg = if (form.hide) R.string.post_hidden else R.string.post_unhidden - when (hidePostRes) { + when (val res = hidePostRes) { is ApiState.Success -> { - updatePostHidden(form.hide) + updatePost(res.data.post_view) Toast.makeText(ctx, msg, Toast.LENGTH_SHORT).show() } @@ -349,11 +353,11 @@ class PostViewModel( is ApiState.Success -> { val newComments = findAndUpdateComment( - existing.data.comments, + existing.data.items, commentView, ) val newRes = - ApiState.Success(existing.data.copy(comments = newComments)) + ApiState.Success(existing.data.copy(items = newComments)) commentsRes = newRes } @@ -364,10 +368,10 @@ class PostViewModel( fun appendComment(commentView: CommentView) { when (val existing = commentsRes) { is ApiState.Success -> { - val mutable = existing.data.comments.toMutableList() + val mutable = existing.data.items.toMutableList() mutable.add(commentView) val newRes = - ApiState.Success(existing.data.copy(comments = mutable.toList())) + ApiState.Success(existing.data.copy(items = mutable.toList())) commentsRes = newRes } @@ -386,18 +390,6 @@ class PostViewModel( } } - fun updatePostHidden(hidden: Boolean) { - when (val existing = postRes) { - is ApiState.Success -> { - val newPostView = existing.data.post_view.copy(hidden = hidden) - val newRes = ApiState.Success(existing.data.copy(post_view = newPostView)) - postRes = newRes - } - - else -> {} - } - } - fun updateBanned(personView: PersonView) { when (val existing = postRes) { is ApiState.Success -> { @@ -419,8 +411,8 @@ class PostViewModel( // Also do all the comments when (val existing = commentsRes) { is ApiState.Success -> { - val comments = findAndUpdateCommentCreator(existing.data.comments, personView.person) - val newRes = ApiState.Success(existing.data.copy(comments = comments)) + val comments = findAndUpdateCommentCreator(existing.data.items, personView.person) + val newRes = ApiState.Success(existing.data.copy(items = comments)) commentsRes = newRes } @@ -449,8 +441,8 @@ class PostViewModel( // Also do all the comments when (val existing = commentsRes) { is ApiState.Success -> { - val comments = findAndUpdateCommentCreatorBannedFromCommunity(existing.data.comments, banData) - val newRes = ApiState.Success(existing.data.copy(comments = comments)) + val comments = findAndUpdateCommentCreatorBannedFromCommunity(existing.data.items, banData) + val newRes = ApiState.Success(existing.data.copy(items = comments)) commentsRes = newRes } diff --git a/app/src/main/java/com/jerboa/model/PostsViewModel.kt b/app/src/main/java/com/jerboa/model/PostsViewModel.kt index 0822e802c..2e609270a 100644 --- a/app/src/main/java/com/jerboa/model/PostsViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostsViewModel.kt @@ -20,7 +20,6 @@ import com.jerboa.db.entity.AnonAccount import com.jerboa.db.repository.AccountRepository import com.jerboa.feed.PaginationController import com.jerboa.feed.PostController -import com.jerboa.findAndUpdatePostHidden import com.jerboa.toEnumSafe import it.vercruysse.lemmyapi.datatypes.CreatePostLike import it.vercruysse.lemmyapi.datatypes.DeletePost @@ -32,8 +31,8 @@ import it.vercruysse.lemmyapi.datatypes.MarkPostAsRead import it.vercruysse.lemmyapi.datatypes.PersonView import it.vercruysse.lemmyapi.datatypes.PostView import it.vercruysse.lemmyapi.datatypes.SavePost -import it.vercruysse.lemmyapi.dto.ListingType -import it.vercruysse.lemmyapi.dto.SortType +import it.vercruysse.lemmyapi.enums.ListingType +import it.vercruysse.lemmyapi.enums.SortType import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -74,7 +73,7 @@ open class PostsViewModel( postsRes = API.getInstance().getPosts(form).fold( onSuccess = { pageController.nextPage(it.next_page) - postController.addAll(it.posts) + postController.addAll(it.items) ApiState.Success(postController.feed) }, onFailure = { ApiState.Failure(it) }, @@ -95,7 +94,7 @@ open class PostsViewModel( when (val newRes = API.getInstance().getPosts(getForm()).toApiState()) { is ApiState.Success -> { pageController.nextPage(newRes.data.next_page) - postController.addAll(newRes.data.posts) + postController.addAll(newRes.data.items) postsRes = ApiState.Success(oldRes.data) } @@ -135,12 +134,11 @@ open class PostsViewModel( fun markPostAsRead( form: MarkPostAsRead, - post: PostView, appState: JerboaAppState, ) { appState.coroutineScope.launch { API.getInstance().markPostAsRead(form).onSuccess { - updatePost(post.copy(read = form.read)) + updatePost(it.post_view) } } } diff --git a/app/src/main/java/com/jerboa/model/RegistrationApplicationsViewModel.kt b/app/src/main/java/com/jerboa/model/RegistrationApplicationsViewModel.kt index b42812143..89b5aa6a1 100644 --- a/app/src/main/java/com/jerboa/model/RegistrationApplicationsViewModel.kt +++ b/app/src/main/java/com/jerboa/model/RegistrationApplicationsViewModel.kt @@ -1,7 +1,6 @@ package com.jerboa.model import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel @@ -13,6 +12,7 @@ import com.jerboa.api.ApiState import com.jerboa.api.toApiState import com.jerboa.db.entity.Account import com.jerboa.db.entity.isAnon +import com.jerboa.feed.PaginationController import com.jerboa.findAndUpdateApplication import com.jerboa.getDeduplicateMerge import it.vercruysse.lemmyapi.datatypes.* @@ -20,44 +20,52 @@ import kotlinx.coroutines.launch class RegistrationApplicationsViewModel( account: Account, - siteViewModel: SiteViewModel, + myUserInfoViewModel: MyUserInfoViewModel, ) : ViewModel() { - var applicationsRes: ApiState by mutableStateOf( + var applicationsRes: ApiState> by mutableStateOf( ApiState.Empty, ) private set + private val pageController = PaginationController() + private var approveRes: ApiState by mutableStateOf( ApiState.Empty, ) - private var page by mutableLongStateOf(1) - var unreadOnly by mutableStateOf(true) private set - fun resetPage() { - page = 1 - } - fun updateUnreadOnly(unreadOnly: Boolean) { this.unreadOnly = unreadOnly } + fun resetPage() { + pageController.reset() + } + fun getFormApplications(): ListRegistrationApplications = ListRegistrationApplications( unread_only = this.unreadOnly, - page = this.page, + page = pageController.page, + page_cursor = pageController.pageCursor, ) fun listApplications( form: ListRegistrationApplications, - state: ApiState = ApiState.Loading, + state: ApiState> = ApiState.Loading, ) { viewModelScope.launch { applicationsRes = state applicationsRes = API.getInstance().listRegistrationApplications(form).toApiState() } + when (val res = applicationsRes) { + is ApiState.Success -> { + pageController.nextPage(res.data.next_page) + } + + else -> {} + } } fun appendApplications() { @@ -68,24 +76,22 @@ class RegistrationApplicationsViewModel( else -> return@launch } - page += 1 val newRes = API.getInstance().listRegistrationApplications(getFormApplications()).toApiState() applicationsRes = when (newRes) { is ApiState.Success -> { - val mergedReplies = + val appended = getDeduplicateMerge( - oldRes.data.registration_applications, - newRes.data.registration_applications, + oldRes.data.items, + newRes.data.items, ) { it.registration_application.id } - ApiState.Success(oldRes.data.copy(registration_applications = mergedReplies)) + ApiState.Success(oldRes.data.copy(items = appended)) } else -> { - page -= 1 - oldRes + ApiState.AppendingFailure(oldRes.data) } } } @@ -102,10 +108,10 @@ class RegistrationApplicationsViewModel( is ApiState.Success -> { val newApps = findAndUpdateApplication( - existing.data.registration_applications, + existing.data.items, approveRes.data.registration_application, ) - val newRes = ApiState.Success(existing.data.copy(registration_applications = newApps)) + val newRes = ApiState.Success(existing.data.copy(items = newApps)) applicationsRes = newRes } @@ -120,24 +126,24 @@ class RegistrationApplicationsViewModel( init { if (!account.isAnon()) { - this.resetPage() + pageController.reset() this.listApplications( this.getFormApplications(), ) - siteViewModel.fetchUnreadAppCount() + myUserInfoViewModel.fetchUnreadCounts() } } companion object { class Factory( private val account: Account, - private val siteViewModel: SiteViewModel, + private val myUserInfoViewModel: MyUserInfoViewModel, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( modelClass: Class, extras: CreationExtras, - ): T = RegistrationApplicationsViewModel(account, siteViewModel) as T + ): T = RegistrationApplicationsViewModel(account, myUserInfoViewModel) as T } } } diff --git a/app/src/main/java/com/jerboa/model/ReportsViewModel.kt b/app/src/main/java/com/jerboa/model/ReportsViewModel.kt index 42bc3e830..e9dc72dbb 100644 --- a/app/src/main/java/com/jerboa/model/ReportsViewModel.kt +++ b/app/src/main/java/com/jerboa/model/ReportsViewModel.kt @@ -1,7 +1,6 @@ package com.jerboa.model import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel @@ -13,28 +12,21 @@ import com.jerboa.api.ApiState import com.jerboa.api.toApiState import com.jerboa.db.entity.Account import com.jerboa.db.entity.isAnon +import com.jerboa.feed.PaginationController import com.jerboa.findAndUpdateCommentReport +import com.jerboa.findAndUpdateCommunityReport import com.jerboa.findAndUpdatePostReport import com.jerboa.findAndUpdatePrivateMessageReport import com.jerboa.getDeduplicateMerge import it.vercruysse.lemmyapi.datatypes.* +import it.vercruysse.lemmyapi.enums.ReportType import kotlinx.coroutines.launch class ReportsViewModel( account: Account, - siteViewModel: SiteViewModel, + myUserInfoViewModel: MyUserInfoViewModel, ) : ViewModel() { - var postReportsRes: ApiState by mutableStateOf( - ApiState.Empty, - ) - private set - - var commentReportsRes: ApiState by mutableStateOf( - ApiState.Empty, - ) - private set - - var messageReportsRes: ApiState by mutableStateOf( + var reportsRes: ApiState> by mutableStateOf( ApiState.Empty, ) private set @@ -51,171 +43,83 @@ class ReportsViewModel( ApiState.Empty, ) - private var pagePostReports by mutableLongStateOf(1) - private var pageCommentReports by mutableLongStateOf(1) - private var pageMessageReports by mutableLongStateOf(1) + private var resolveCommunityReportRes: ApiState by mutableStateOf( + ApiState.Empty, + ) + + private val pageController = PaginationController() var unresolvedOnly by mutableStateOf(true) private set - fun resetPagePostReports() { - pagePostReports = 1 - } - - fun resetPageCommentReports() { - pageCommentReports = 1 - } + var reportType by mutableStateOf(ReportType.All) + private set - fun resetPageMessageReports() { - pageMessageReports = 1 + fun resetPage() { + pageController.reset() } fun updateUnresolvedOnly(unresolvedOnly: Boolean) { this.unresolvedOnly = unresolvedOnly } - fun resetPages() { - resetPagePostReports() - resetPageCommentReports() - resetPageMessageReports() - } - - fun getFormPostReports(): ListPostReports = - ListPostReports( - unresolved_only = unresolvedOnly, - page = pagePostReports, - ) - - fun getFormCommentReports(): ListCommentReports = - ListCommentReports( - unresolved_only = unresolvedOnly, - page = pageCommentReports, - ) - - fun getFormMessageReports(): ListPrivateMessageReports = - ListPrivateMessageReports( + fun getFormReports(): ListReports = + ListReports( unresolved_only = unresolvedOnly, - page = pageMessageReports, + type_ = reportType, + page = pageController.page, + page_cursor = pageController.pageCursor, ) - fun listPostReports( - form: ListPostReports, - state: ApiState = ApiState.Loading, + fun listReports( + form: ListReports, + state: ApiState> = ApiState.Loading, ) { viewModelScope.launch { - postReportsRes = state - postReportsRes = API.getInstance().listPostReports(form).toApiState() + reportsRes = state + reportsRes = API.getInstance().listReports(form).toApiState() } - } - fun appendPostReports() { - viewModelScope.launch { - val oldRes = postReportsRes - when (oldRes) { - is ApiState.Success -> postReportsRes = ApiState.Appending(oldRes.data) - else -> return@launch + when (val res = reportsRes) { + is ApiState.Success -> { + pageController.nextPage(res.data.next_page) } - pagePostReports += 1 - val newRes = API.getInstance().listPostReports(getFormPostReports()).toApiState() - - postReportsRes = - when (newRes) { - is ApiState.Success -> { - val mergedReplies = - getDeduplicateMerge( - oldRes.data.post_reports, - newRes.data.post_reports, - ) { it.post_report.id } - - ApiState.Success(oldRes.data.copy(post_reports = mergedReplies)) - } - - else -> { - pagePostReports -= 1 - oldRes - } - } - } - } - - fun listCommentReports( - form: ListCommentReports, - state: ApiState = ApiState.Loading, - ) { - viewModelScope.launch { - commentReportsRes = state - commentReportsRes = API.getInstance().listCommentReports(form).toApiState() + else -> {} } } - fun appendCommentReports() { + fun appendReports() { viewModelScope.launch { - val oldRes = commentReportsRes + val oldRes = reportsRes when (oldRes) { - is ApiState.Success -> commentReportsRes = ApiState.Appending(oldRes.data) + is ApiState.Success -> reportsRes = ApiState.Appending(oldRes.data) else -> return@launch } - pageCommentReports += 1 - val newRes = API.getInstance().listCommentReports(getFormCommentReports()).toApiState() + val newRes = API.getInstance().listReports(getFormReports()).toApiState() - commentReportsRes = + reportsRes = when (newRes) { is ApiState.Success -> { - val mergedReplies = + val mergedItems = getDeduplicateMerge( - oldRes.data.comment_reports, - newRes.data.comment_reports, - ) { it.comment_report.id } - - ApiState.Success(oldRes.data.copy(comment_reports = mergedReplies)) + oldRes.data.items, + newRes.data.items, + ) { + when (it) { + is CommentReportView -> it.comment_report.id + is CommunityReportView -> it.community_report.id + is PostReportView -> it.post_report.id + is PrivateMessageReportView -> it.private_message_report.id + } + } + + ApiState.Success(oldRes.data.copy(items = mergedItems)) } else -> { - pageCommentReports -= 1 - oldRes - } - } - } - } - - fun listMessageReports( - form: ListPrivateMessageReports, - state: ApiState = ApiState.Loading, - ) { - viewModelScope.launch { - messageReportsRes = state - messageReportsRes = API.getInstance().listPrivateMessageReports(form).toApiState() - } - } - - fun appendMessageReports() { - viewModelScope.launch { - val oldRes = messageReportsRes - when (oldRes) { - is ApiState.Success -> messageReportsRes = ApiState.Appending(oldRes.data) - else -> return@launch - } - - pageMessageReports += 1 - val newRes = API.getInstance().listPrivateMessageReports(getFormMessageReports()).toApiState() - - messageReportsRes = - when (newRes) { - is ApiState.Success -> { - val mergedReplies = - getDeduplicateMerge( - oldRes.data.private_message_reports, - newRes.data.private_message_reports, - ) { it.private_message_report.id } - - ApiState.Success(oldRes.data.copy(private_message_reports = mergedReplies)) - } - - else -> { - pageMessageReports -= 1 - oldRes + ApiState.AppendingFailure(oldRes.data) } } } @@ -228,15 +132,15 @@ class ReportsViewModel( when (val resolveRes = resolvePostReportRes) { is ApiState.Success -> { - when (val existing = postReportsRes) { + when (val existing = reportsRes) { is ApiState.Success -> { val newReports = findAndUpdatePostReport( - existing.data.post_reports, + existing.data.items, resolveRes.data.post_report_view, ) - val newRes = ApiState.Success(existing.data.copy(post_reports = newReports)) - postReportsRes = newRes + val newRes = ApiState.Success(existing.data.copy(items = newReports)) + reportsRes = newRes } else -> {} @@ -255,15 +159,15 @@ class ReportsViewModel( when (val resolveRes = resolveCommentReportRes) { is ApiState.Success -> { - when (val existing = commentReportsRes) { + when (val existing = reportsRes) { is ApiState.Success -> { val newReports = findAndUpdateCommentReport( - existing.data.comment_reports, + existing.data.items, resolveRes.data.comment_report_view, ) - val newRes = ApiState.Success(existing.data.copy(comment_reports = newReports)) - commentReportsRes = newRes + val newRes = ApiState.Success(existing.data.copy(items = newReports)) + reportsRes = newRes } else -> {} @@ -282,15 +186,42 @@ class ReportsViewModel( when (val resolveRes = resolveMessageReportRes) { is ApiState.Success -> { - when (val existing = messageReportsRes) { + when (val existing = reportsRes) { is ApiState.Success -> { val newReports = findAndUpdatePrivateMessageReport( - existing.data.private_message_reports, + existing.data.items, resolveRes.data.private_message_report_view, ) - val newRes = ApiState.Success(existing.data.copy(private_message_reports = newReports)) - messageReportsRes = newRes + val newRes = ApiState.Success(existing.data.copy(items = newReports)) + reportsRes = newRes + } + + else -> {} + } + } + + else -> {} + } + } + } + + fun resolveCommunityReport(form: ResolveCommunityReport) { + viewModelScope.launch { + resolveCommunityReportRes = ApiState.Loading + resolveCommunityReportRes = API.getInstance().resolveCommunityReport(form).toApiState() + + when (val resolveRes = resolveCommunityReportRes) { + is ApiState.Success -> { + when (val existing = reportsRes) { + is ApiState.Success -> { + val newReports = + findAndUpdateCommunityReport( + existing.data.items, + resolveRes.data.community_report_view, + ) + val newRes = ApiState.Success(existing.data.copy(items = newReports)) + reportsRes = newRes } else -> {} @@ -304,30 +235,22 @@ class ReportsViewModel( init { if (!account.isAnon()) { - this.resetPages() - this.listPostReports( - this.getFormPostReports(), - ) - this.listCommentReports( - this.getFormCommentReports(), - ) - this.listMessageReports( - this.getFormMessageReports(), - ) - siteViewModel.fetchUnreadReportCount() + pageController.reset() + listReports(getFormReports()) + myUserInfoViewModel.fetchUnreadCounts() } } companion object { class Factory( private val account: Account, - private val siteViewModel: SiteViewModel, + private val myUserInfoViewModel: MyUserInfoViewModel, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( modelClass: Class, extras: CreationExtras, - ): T = ReportsViewModel(account, siteViewModel) as T + ): T = ReportsViewModel(account, myUserInfoViewModel) as T } } } diff --git a/app/src/main/java/com/jerboa/model/SiteViewModel.kt b/app/src/main/java/com/jerboa/model/SiteViewModel.kt index 414e7bc49..e8113f300 100644 --- a/app/src/main/java/com/jerboa/model/SiteViewModel.kt +++ b/app/src/main/java/com/jerboa/model/SiteViewModel.kt @@ -2,7 +2,6 @@ package com.jerboa.model import android.util.Log import androidx.compose.runtime.Stable -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -16,21 +15,9 @@ import com.jerboa.api.ApiState import com.jerboa.api.DEFAULT_INSTANCE import com.jerboa.api.toApiState import com.jerboa.db.entity.AnonAccount -import com.jerboa.db.entity.isAnon import com.jerboa.db.repository.AccountRepository -import com.jerboa.feat.allHidden -import com.jerboa.feat.default import com.jerboa.jerboaApplication -import it.vercruysse.lemmyapi.datatypes.CommunityFollowerView -import it.vercruysse.lemmyapi.datatypes.CommunityId -import it.vercruysse.lemmyapi.datatypes.GetReportCount -import it.vercruysse.lemmyapi.datatypes.GetReportCountResponse import it.vercruysse.lemmyapi.datatypes.GetSiteResponse -import it.vercruysse.lemmyapi.datatypes.GetUnreadCountResponse -import it.vercruysse.lemmyapi.datatypes.GetUnreadRegistrationApplicationCountResponse -import it.vercruysse.lemmyapi.datatypes.LocalUserVoteDisplayMode -import it.vercruysse.lemmyapi.datatypes.PersonView -import it.vercruysse.lemmyapi.datatypes.SaveUserSettings import kotlinx.coroutines.Job import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -39,21 +26,8 @@ import kotlinx.coroutines.launch class SiteViewModel( private val accountRepository: AccountRepository, ) : ViewModel() { - // Can't be private, because it needs to be set by the login viewmodel var siteRes: ApiState by mutableStateOf(ApiState.Empty) - private var unreadCountRes: ApiState by mutableStateOf(ApiState.Empty) - private var unreadAppCountRes: ApiState by mutableStateOf( - ApiState.Empty, - ) - private var unreadReportCountRes: ApiState by mutableStateOf(ApiState.Empty) - - val unreadCount by derivedStateOf { getUnreadCountTotal() } - val unreadAppCount by derivedStateOf { getUnreadAppCountTotal() } - val unreadReportCount by derivedStateOf { getUnreadReportCountTotal() } - - lateinit var saveUserSettings: SaveUserSettings - init { viewModelScope.launch { @@ -66,30 +40,14 @@ class SiteViewModel( } // Makes sure that the site is fetched when the account is changed + // TODO instead, both the siteViewModel and myUserInfoViewModel + // should be moved into the accountViewModel accountRepository.currentAccount .asFlow() .map { it ?: AnonAccount } .collect { Log.d("SiteViewModel", "acc init for id: ${it.id}") getSite() - - if (it.isAnon()) { // Reset the unread counts if we're anonymous - unreadCountRes = ApiState.Empty - unreadAppCountRes = ApiState.Empty - unreadReportCountRes = ApiState.Empty - } else { - fetchUnreadCounts() - - // if you're an admin, fetch the unread registration counts - if (it.isAdmin) { - fetchUnreadAppCount() - } - - // if you're an admin or a mod, fetch the report counts - if (it.isAdmin || it.isMod) { - fetchUnreadReportCount() - } - } } } } @@ -98,201 +56,6 @@ class SiteViewModel( viewModelScope.launch { siteRes = loadingState siteRes = API.getInstance().getSite().toApiState() - - when (val res = siteRes) { - is ApiState.Success -> { - res.data.my_user?.let { mui -> - val currAcc = accountRepository.currentAccount.value - val localUser = mui.local_user_view.local_user - if (currAcc != null) { - val newAccount = - currAcc.copy( - defaultListingType = localUser.default_listing_type.ordinal, - defaultSortType = localUser.default_sort_type.ordinal, - isAdmin = localUser.admin, - isMod = mui.moderates.isNotEmpty(), - ) - - if (currAcc != newAccount) { - accountRepository.update(newAccount) - } - } - } - } - - else -> {} - } - } - - fun fetchUnreadCounts() { - viewModelScope.launch { - unreadCountRes = ApiState.Loading - unreadCountRes = API.getInstance().getUnreadCount().toApiState() - } - } - - fun fetchUnreadAppCount() { - viewModelScope.launch { - unreadAppCountRes = ApiState.Loading - unreadAppCountRes = - API.getInstance().getUnreadRegistrationApplicationCount().toApiState() - } - } - - fun fetchUnreadReportCount() { - viewModelScope.launch { - unreadReportCountRes = ApiState.Loading - unreadReportCountRes = API.getInstance().getReportCount(GetReportCount()).toApiState() - } - } - - private fun getUnreadCountTotal(): Long = - when (val res = unreadCountRes) { - is ApiState.Success -> { - val unreads = res.data - unreads.mentions + unreads.private_messages + unreads.replies - } - - else -> { - 0 - } - } - - private fun getUnreadAppCountTotal(): Long? = - when (val res = unreadAppCountRes) { - is ApiState.Success -> { - res.data.registration_applications - } - - else -> { - null - } - } - - private fun getUnreadReportCountTotal(): Long? = - when (val res = unreadReportCountRes) { - is ApiState.Success -> { - val unreads = res.data - unreads.post_reports + unreads.comment_reports + ( - unreads.private_message_reports - ?: 0 - ) - } - - else -> { - null - } - } - - fun updateUnreadCounts( - dReplies: Int = 0, - dMentions: Int = 0, - dMessages: Int = 0, - ) { - when (val res = unreadCountRes) { - is ApiState.Success -> { - unreadCountRes = - ApiState.Success( - GetUnreadCountResponse( - private_messages = res.data.private_messages + dMessages, - mentions = res.data.mentions + dMentions, - replies = res.data.replies + dReplies, - ), - ) - } - - else -> {} - } - } - - fun showAvatar(): Boolean = - when (val res = siteRes) { - is ApiState.Success -> { - res.data.my_user - ?.local_user_view - ?.local_user - ?.show_avatars - ?: true - } - - else -> { - true - } - } - - fun moderatedCommunities(): List? = - when (val res = siteRes) { - is ApiState.Success -> { - res.data.my_user - ?.moderates - ?.map { it.community.id } - } - - else -> { - null - } - } - - fun enableDownvotes(): Boolean = - when (val res = siteRes) { - is ApiState.Success -> res.data.site_view.local_site.enable_downvotes - else -> true - } - - // For the current default, just use Upvotes / Downvotes - fun voteDisplayMode(): LocalUserVoteDisplayMode { - val api = API.getInstanceOrNull() - - return if (api == null || api.FF.hidePost()) { - newVoteDisplayMode() - } else { - legacyVoteDisplayMode() - } - } - - private fun newVoteDisplayMode(): LocalUserVoteDisplayMode = - when (val res = siteRes) { - is ApiState.Success -> { - res.data.my_user - ?.local_user_view - ?.local_user_vote_display_mode ?: LocalUserVoteDisplayMode.default() - } - - else -> { - LocalUserVoteDisplayMode.default() - } - } - - private fun legacyVoteDisplayMode(): LocalUserVoteDisplayMode = - when (val res = siteRes) { - is ApiState.Success -> { - // Legacy behaviour is hide all scores if show_scores is false - if (res.data.my_user - ?.local_user_view - ?.local_user - ?.show_scores == false - ) { - LocalUserVoteDisplayMode.allHidden() - } else { - LocalUserVoteDisplayMode.default(false) - } - } - - else -> { - LocalUserVoteDisplayMode.default(true) - } // Legacy default - } - - fun getFollowList(): List = - when (val res = siteRes) { - is ApiState.Success -> res.data.my_user?.follows ?: emptyList() - else -> emptyList() - } - - fun admins(): List = - when (val res = siteRes) { - is ApiState.Success -> res.data.admins - else -> emptyList() } companion object { diff --git a/app/src/main/java/com/jerboa/ui/components/ban/BanFromCommunityScreen.kt b/app/src/main/java/com/jerboa/ui/components/ban/BanFromCommunityScreen.kt index 1e6d971b4..d36c0fdef 100644 --- a/app/src/main/java/com/jerboa/ui/components/ban/BanFromCommunityScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/ban/BanFromCommunityScreen.kt @@ -90,7 +90,7 @@ fun BanFromCommunityScreen( personId = person.id, community = community, ban = isBan, - removeData = if (isBan) removeData else false, + removeOrRestoreData = if (isBan) removeData else false, expireDays = if (!isBan or permaBan) null else expireDays, reason = reason.text, ctx = ctx, diff --git a/app/src/main/java/com/jerboa/ui/components/ban/BanPerson.kt b/app/src/main/java/com/jerboa/ui/components/ban/BanPerson.kt index bf585aeb0..751c28019 100644 --- a/app/src/main/java/com/jerboa/ui/components/ban/BanPerson.kt +++ b/app/src/main/java/com/jerboa/ui/components/ban/BanPerson.kt @@ -11,11 +11,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue import com.jerboa.R -import com.jerboa.db.entity.Account import com.jerboa.ui.components.common.CheckboxField import com.jerboa.ui.components.common.ExpiresField import com.jerboa.ui.components.common.MarkdownTextField import com.jerboa.ui.theme.MEDIUM_PADDING +import it.vercruysse.lemmyapi.datatypes.MyUserInfo @Composable fun BanPersonBody( @@ -29,7 +29,7 @@ fun BanPersonBody( removeData: Boolean, onRemoveDataChange: (Boolean) -> Unit, isValid: Boolean, - account: Account, + myUserInfo: MyUserInfo?, padding: PaddingValues, ) { val scrollState = rememberScrollState() @@ -46,7 +46,7 @@ fun BanPersonBody( MarkdownTextField( text = reason, onTextChange = onReasonChange, - account = account, + myUserInfo = myUserInfo, placeholder = stringResource(R.string.type_your_reason), ) diff --git a/app/src/main/java/com/jerboa/ui/components/ban/BanPersonScreen.kt b/app/src/main/java/com/jerboa/ui/components/ban/BanPersonScreen.kt index b53c53ffd..e6d6c2141 100644 --- a/app/src/main/java/com/jerboa/ui/components/ban/BanPersonScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/ban/BanPersonScreen.kt @@ -18,32 +18,29 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.jerboa.JerboaAppState import com.jerboa.R import com.jerboa.api.ApiState -import com.jerboa.db.entity.isAnon -import com.jerboa.model.AccountViewModel +import com.jerboa.datatypes.BanData import com.jerboa.model.BanPersonViewModel import com.jerboa.personNameShown import com.jerboa.ui.components.common.ActionTopBar -import com.jerboa.ui.components.common.getCurrentAccount -import it.vercruysse.lemmyapi.datatypes.Person +import it.vercruysse.lemmyapi.datatypes.MyUserInfo object BanPersonReturn { - const val PERSON_VIEW = "ban-person::return(person-view)" - const val PERSON_SEND = "ban-person::send(person-view)" + const val BAN_DATA_VIEW = "ban-person::return(ban-data-view)" + const val BAN_DATA_SEND = "ban-person::send(ban-data-view)" } @Composable fun BanPersonScreen( appState: JerboaAppState, - accountViewModel: AccountViewModel, + myUserInfo: MyUserInfo?, ) { Log.d("jerboa", "got to ban person screen") val ctx = LocalContext.current val resources = LocalResources.current - val account = getCurrentAccount(accountViewModel = accountViewModel) val banPersonViewModel: BanPersonViewModel = viewModel() - val person = appState.getPrevReturn(key = BanPersonReturn.PERSON_SEND) + val banData = appState.getPrevReturn(key = BanPersonReturn.BAN_DATA_SEND) var removeData by rememberSaveable { mutableStateOf(false) } var permaBan by rememberSaveable { mutableStateOf(false) } @@ -62,9 +59,10 @@ fun BanPersonScreen( } val focusManager = LocalFocusManager.current - val title = stringResource(if (person.banned) R.string.unban_person else R.string.ban_person, personNameShown(person, true)) + val title = + stringResource(if (banData.banned) R.string.unban_person else R.string.ban_person, personNameShown(banData.person, true)) - val isBan = !person.banned + val isBan = !banData.banned // Make sure the form is valid only if permaban is checked or expireDays is not null val isValid = !isBan or permaBan or (expireDays !== null) @@ -76,11 +74,11 @@ fun BanPersonScreen( title = title, loading = loading, onActionClick = { - if (!account.isAnon()) { + if (myUserInfo != null) { banPersonViewModel.banOrUnbanPerson( - personId = person.id, + personId = banData.person.id, ban = isBan, - removeData = if (isBan) removeData else false, + removeOrRestoreData = if (isBan) removeData else false, expireDays = if (!isBan or permaBan) null else expireDays, reason = reason.text, ctx = ctx, @@ -88,7 +86,7 @@ fun BanPersonScreen( focusManager = focusManager, ) { personView -> appState.apply { - addReturn(BanPersonReturn.PERSON_VIEW, personView) + addReturn(BanPersonReturn.BAN_DATA_VIEW, personView) navigateUp() } } @@ -117,7 +115,7 @@ fun BanPersonScreen( removeData = removeData, onRemoveDataChange = { removeData = it }, isValid = isValid, - account = account, + myUserInfo = myUserInfo, padding = padding, ) }, 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 3eb7e8096..19a8c8c76 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 @@ -37,6 +37,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.res.stringResource @@ -51,25 +52,29 @@ import com.jerboa.R import com.jerboa.border import com.jerboa.buildCommentsTree import com.jerboa.calculateCommentOffset +import com.jerboa.datatypes.BanData import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.datatypes.getContent import com.jerboa.datatypes.sampleCommentView import com.jerboa.datatypes.sampleCommunity +import com.jerboa.datatypes.sampleLocalSite +import com.jerboa.datatypes.sampleMyUserInfo import com.jerboa.datatypes.samplePost import com.jerboa.datatypes.sampleReplyCommentView import com.jerboa.datatypes.sampleSecondReplyCommentView +import com.jerboa.db.APP_SETTINGS_DEFAULT import com.jerboa.db.entity.Account import com.jerboa.db.entity.AnonAccount +import com.jerboa.db.entity.AppSettings import com.jerboa.feat.BlurNSFW import com.jerboa.feat.InstantScores +import com.jerboa.feat.PostOrCommentType import com.jerboa.feat.SwipeToActionPreset import com.jerboa.feat.SwipeToActionType -import com.jerboa.feat.VoteType -import com.jerboa.feat.amMod -import com.jerboa.feat.canMod -import com.jerboa.feat.default import com.jerboa.feat.isReadyAndIfNotShowSimplifiedInfoToast import com.jerboa.isPostCreator +import com.jerboa.showAvatar +import com.jerboa.toEnumSafe import com.jerboa.ui.components.common.ActionBarButton import com.jerboa.ui.components.common.CommentOrPostNodeHeader import com.jerboa.ui.components.common.MarkdownHelper @@ -78,6 +83,8 @@ import com.jerboa.ui.components.common.SwipeToAction import com.jerboa.ui.components.common.UpvotePercentage import com.jerboa.ui.components.common.VoteGeneric import com.jerboa.ui.components.common.VoteScore +import com.jerboa.ui.components.common.enableDownvotes +import com.jerboa.ui.components.common.enableUpvotes import com.jerboa.ui.components.common.rememberSwipeActionState import com.jerboa.ui.components.community.CommunityLink import com.jerboa.ui.theme.BORDER_WIDTH @@ -86,40 +93,55 @@ import com.jerboa.ui.theme.MEDIUM_PADDING import com.jerboa.ui.theme.SMALL_PADDING import com.jerboa.ui.theme.XXL_PADDING import com.jerboa.ui.theme.colorList +import it.vercruysse.lemmyapi.datatypes.BanFromCommunity +import it.vercruysse.lemmyapi.datatypes.BanPerson +import it.vercruysse.lemmyapi.datatypes.BlockPerson import it.vercruysse.lemmyapi.datatypes.Comment import it.vercruysse.lemmyapi.datatypes.CommentId import it.vercruysse.lemmyapi.datatypes.CommentView import it.vercruysse.lemmyapi.datatypes.Community -import it.vercruysse.lemmyapi.datatypes.LocalUserVoteDisplayMode +import it.vercruysse.lemmyapi.datatypes.CommunityId +import it.vercruysse.lemmyapi.datatypes.CreateComment +import it.vercruysse.lemmyapi.datatypes.CreateCommentLike +import it.vercruysse.lemmyapi.datatypes.CreateCommentReport +import it.vercruysse.lemmyapi.datatypes.DeleteComment +import it.vercruysse.lemmyapi.datatypes.DistinguishComment +import it.vercruysse.lemmyapi.datatypes.EditComment +import it.vercruysse.lemmyapi.datatypes.GetComments +import it.vercruysse.lemmyapi.datatypes.GetSiteResponse +import it.vercruysse.lemmyapi.datatypes.LocalSite +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.Person import it.vercruysse.lemmyapi.datatypes.PersonId import it.vercruysse.lemmyapi.datatypes.PersonView import it.vercruysse.lemmyapi.datatypes.Post import it.vercruysse.lemmyapi.datatypes.PostId +import it.vercruysse.lemmyapi.datatypes.RemoveComment +import it.vercruysse.lemmyapi.datatypes.SaveComment +import it.vercruysse.lemmyapi.enums.VoteAction @Composable fun CommentNodeHeader( commentView: CommentView, - onPersonClick: (personId: PersonId) -> Unit, - collapsedCommentsCount: Long, isExpanded: Boolean, + onPersonClick: (personId: PersonId) -> Unit, onClick: () -> Unit, onLongClick: () -> Unit, - showAvatar: Boolean, + myUserInfo: MyUserInfo?, ) { CommentOrPostNodeHeader( creator = commentView.creator, - published = commentView.comment.published, - updated = commentView.comment.updated, + published = commentView.comment.published_at, + updated = commentView.comment.updated_at, deleted = commentView.comment.deleted, onPersonClick = onPersonClick, isPostCreator = isPostCreator(commentView), isCommunityBanned = commentView.creator_banned_from_community, - collapsedCommentsCount = collapsedCommentsCount, + collapsedCommentsCount = commentView.comment.child_count, isExpanded = isExpanded, onClick = onClick, onLongCLick = onLongClick, - showAvatar = showAvatar, + myUserInfo = myUserInfo, isDistinguished = commentView.comment.distinguished, isNsfw = false, ) @@ -133,9 +155,8 @@ fun CommentNodeHeaderPreview() { onPersonClick = {}, onClick = {}, onLongClick = {}, - collapsedCommentsCount = 5, isExpanded = false, - showAvatar = true, + myUserInfo = sampleMyUserInfo, ) } @@ -178,50 +199,48 @@ fun CommentBodyPreview() { @OptIn(ExperimentalMaterial3Api::class) fun LazyListScope.commentNodeItem( node: CommentNode, - admins: List, - moderators: List?, - increaseLazyListIndexTracker: () -> Unit, - addToParentIndexes: () -> Unit, isFlat: Boolean, - isExpanded: (commentId: CommentId) -> Boolean, - toggleExpanded: (commentId: CommentId) -> Unit, - toggleActionBar: (commentId: CommentId) -> Unit, - onUpvoteClick: (commentView: CommentView) -> Unit, - 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, - onPersonClick: (personId: PersonId) -> Unit, - onViewVotesClick: (CommentId) -> Unit, - onHeaderClick: (commentView: CommentView) -> Unit, - onHeaderLongClick: (commentView: CommentView) -> Unit, - onCommunityClick: (community: Community) -> Unit, - onPostClick: (postId: PostId) -> Unit, - onReportClick: (commentView: CommentView) -> Unit, - onRemoveClick: (commentView: CommentView) -> Unit, - onDistinguishClick: (commentView: CommentView) -> Unit, - onBanPersonClick: (person: Person) -> Unit, - onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, - onCommentLinkClick: (commentView: CommentView) -> Unit, - onBlockCreatorClick: (creator: Person) -> Unit, - onFetchChildrenClick: (commentView: CommentView) -> Unit, showCollapsedCommentContent: Boolean, - showPostAndCommunityContext: Boolean = false, - account: Account, + showPostAndCommunityContext: Boolean, isCollapsedByParent: Boolean, - showActionBar: (commentId: CommentId) -> Boolean, - enableDownVotes: Boolean, - showAvatar: Boolean, - blurNSFW: BlurNSFW, - voteDisplayMode: LocalUserVoteDisplayMode, - swipeToActionPreset: SwipeToActionPreset, + siteRes: GetSiteResponse, + myUserInfo: MyUserInfo?, + appSettings: AppSettings, + increaseLazyListIndexTracker: () -> Unit, + addToParentIndexes: () -> Unit, + isExpanded: (CommentId) -> Boolean, + toggleExpanded: (CommentId) -> Unit, + toggleActionBar: (CommentId) -> Unit, + onVoteClick: (CreateCommentLike) -> Unit, + onReplyClick: (CommentView) -> Unit, + onSaveClick: (SaveComment) -> Unit, + onMarkAsReadClick: (commentId: CommentId, read: Boolean) -> Unit, + onCommentClick: (CommentId) -> Unit, + onEditCommentClick: (CommentView) -> Unit, + onDeleteCommentClick: (DeleteComment) -> Unit, + onPersonClick: (PersonId) -> Unit, + onViewVotesClick: (CommentId) -> Unit, + onHeaderClick: (CommentId) -> Unit, + onHeaderLongClick: (CommentId) -> Unit, + onCommunityClick: (CommunityId) -> Unit, + onPostClick: (PostId) -> Unit, + onReportClick: (CommentId) -> Unit, + onRemoveClick: (CommentView) -> Unit, + onDistinguishClick: (DistinguishComment) -> Unit, + onBanPersonClick: (PersonView) -> Unit, + onBanFromCommunityClick: (BanFromCommunityData) -> Unit, + onCommentLinkClick: (CommentId) -> Unit, + onBlockCreatorClick: (BlockPerson) -> Unit, + onFetchChildrenClick: (GetComments) -> Unit, + showActionBar: (CommentId) -> Boolean, ) { val commentView = node.commentView val commentId = commentView.comment.id + val localSite = siteRes.site_view.local_site + val enableDownVotes = enableDownvotes(localSite, PostOrCommentType.Comment) + val enableUpvotes = enableUpvotes(localSite, PostOrCommentType.Comment) + val offset = calculateCommentOffset(node.depth) // The ones with a border on val offset2 = if (node.depth == 0) { @@ -237,7 +256,7 @@ fun LazyListScope.commentNodeItem( val showMoreChildren = isExpanded(commentId) && node.children.isEmpty() && - commentView.counts.child_count > 0 && + commentView.comment.child_count > 0 && !isFlat increaseLazyListIndexTracker() @@ -257,31 +276,43 @@ fun LazyListScope.commentNodeItem( remember { mutableStateOf( InstantScores( - myVote = commentView.my_vote, - score = commentView.counts.score, - upvotes = commentView.counts.upvotes, - downvotes = commentView.counts.downvotes, + myVote = commentView.comment_actions?.vote, + score = commentView.comment.score, + upvotes = commentView.comment.upvotes, + downvotes = commentView.comment.downvotes, ), ) } val swipeState = rememberSwipeActionState( - swipeToActionPreset = swipeToActionPreset, + swipeToActionPreset = appSettings.swipeToActionPreset.toEnumSafe(), enableDownVotes = enableDownVotes, + enableUpVotes = enableUpvotes, rememberKey = commentView, ) { - if (account.isReadyAndIfNotShowSimplifiedInfoToast(ctx, resources)) { when (it) { SwipeToActionType.Upvote -> { + val voteAction = VoteAction.UpVote instantScores = - instantScores.update(VoteType.Upvote) - onUpvoteClick(commentView) + instantScores.update(voteAction) + onVoteClick( + CreateCommentLike( + comment_id = commentId, + vote = voteAction, + ) + ) } SwipeToActionType.Downvote -> { + val voteAction = VoteAction.DownVote instantScores = - instantScores.update(VoteType.Downvote) - onDownvoteClick(commentView) + instantScores.update(voteAction) + onVoteClick( + CreateCommentLike( + comment_id = commentId, + vote = voteAction, + ) + ) } SwipeToActionType.Reply -> { @@ -289,10 +320,14 @@ fun LazyListScope.commentNodeItem( } SwipeToActionType.Save -> { - onSaveClick(commentView) + onSaveClick( + SaveComment( + comment_id = commentId, + save = commentView.comment_actions?.saved_at != null, + ) + ) } } - } } val swipeableContent: @Composable RowScope.() -> Unit = { AnimatedVisibility( @@ -321,22 +356,21 @@ fun LazyListScope.commentNodeItem( community = commentView.community, onCommunityClick = onCommunityClick, onPostClick = onPostClick, - blurNSFW = blurNSFW, - showAvatar = showAvatar, + myUserInfo = myUserInfo, + appSettings = appSettings, ) } CommentNodeHeader( commentView = commentView, onPersonClick = onPersonClick, onClick = { - onHeaderClick(commentView) + onHeaderClick(commentId) }, onLongClick = { - onHeaderLongClick(commentView) + onHeaderLongClick(commentId) }, - collapsedCommentsCount = commentView.counts.child_count, isExpanded = isExpanded(commentId), - showAvatar = showAvatar, + myUserInfo = myUserInfo, ) AnimatedVisibility( visible = isExpanded(commentId) || showCollapsedCommentContent, @@ -347,7 +381,7 @@ fun LazyListScope.commentNodeItem( CommentBody( comment = commentView.comment, viewSource = viewSource, - onClick = { onCommentClick(commentView) }, + onClick = { onCommentClick(commentId) }, onLongClick = { v -> if (v is TextView) { // Also triggers for long click on links, so we check if link was hit @@ -366,19 +400,12 @@ fun LazyListScope.commentNodeItem( ) { CommentFooterLine( commentView = commentView, - admins = admins, - moderators = moderators, + admins = siteRes.admins, instantScores = instantScores, - voteDisplayMode = voteDisplayMode, - onUpvoteClick = { - instantScores = - instantScores.update(VoteType.Upvote) - onUpvoteClick(commentView) - }, - onDownvoteClick = { + onVoteClick = { instantScores = - instantScores.update(VoteType.Downvote) - onDownvoteClick(commentView) + instantScores.update(it.vote) + onVoteClick(it) }, onViewSourceClick = { viewSource = !viewSource @@ -398,7 +425,7 @@ fun LazyListScope.commentNodeItem( onBlockCreatorClick = onBlockCreatorClick, onClick = if (isFlat) { { - onCommentClick(commentView) + onCommentClick(commentId) } } else { { @@ -408,9 +435,9 @@ fun LazyListScope.commentNodeItem( onLongClick = { toggleActionBar(commentId) }, - account = account, - enableDownVotes = enableDownVotes, viewSource = viewSource, + localSite = localSite, + myUserInfo = myUserInfo, ) } } @@ -447,6 +474,9 @@ fun LazyListScope.commentNodeItem( commentNodeItems( nodes = node.children.toList(), + admins = admins, + localSite = localSite, + myUserInfo = myUserInfo, increaseLazyListIndexTracker = increaseLazyListIndexTracker, addToParentIndexes = addToParentIndexes, isFlat = isFlat, @@ -481,19 +511,17 @@ fun LazyListScope.commentNodeItem( isCollapsedByParent = isCollapsedByParent || !isExpanded(commentId), showActionBar = showActionBar, enableDownVotes = enableDownVotes, - showAvatar = showAvatar, + showAvatar = myUserInfo.showAvatar(lowBandwidthMode), blurNSFW = blurNSFW, - voteDisplayMode = voteDisplayMode, - admins = admins, - moderators = moderators, - swipeToActionPreset = swipeToActionPreset, + swipeToActionPreset = swipeToActionPreset,,, ) } fun LazyListScope.missingCommentNodeItem( node: MissingCommentNode, admins: List, - moderators: List?, + localSite: LocalSite, + myUserInfo: MyUserInfo?, increaseLazyListIndexTracker: () -> Unit, addToParentIndexes: () -> Unit, isFlat: Boolean, @@ -530,7 +558,6 @@ fun LazyListScope.missingCommentNodeItem( enableDownVotes: Boolean, showAvatar: Boolean, blurNSFW: BlurNSFW, - voteDisplayMode: LocalUserVoteDisplayMode, swipeToActionPreset: SwipeToActionPreset, ) { val commentId = node.missingCommentView.commentId @@ -595,7 +622,8 @@ fun LazyListScope.missingCommentNodeItem( commentNodeItems( nodes = node.children.toList(), admins = admins, - moderators = moderators, + localSite = localSite, + myUserInfo = myUserInfo, increaseLazyListIndexTracker = increaseLazyListIndexTracker, addToParentIndexes = addToParentIndexes, isFlat = isFlat, @@ -632,8 +660,7 @@ fun LazyListScope.missingCommentNodeItem( enableDownVotes = enableDownVotes, showAvatar = showAvatar, blurNSFW = blurNSFW, - voteDisplayMode = voteDisplayMode, - swipeToActionPreset = swipeToActionPreset, + swipeToActionPreset = swipeToActionPreset,,, ) } @@ -690,10 +717,10 @@ private fun ShowMoreChildrenNode( fun PostAndCommunityContextHeader( post: Post, community: Community, - onCommunityClick: (community: Community) -> Unit, - onPostClick: (postId: PostId) -> Unit, - blurNSFW: BlurNSFW, - showAvatar: Boolean, + myUserInfo: MyUserInfo?, + appSettings: AppSettings, + onCommunityClick: (CommunityId) -> Unit, + onPostClick: (PostId) -> Unit, ) { Column( modifier = Modifier @@ -716,8 +743,8 @@ fun PostAndCommunityContextHeader( community = community, onClick = onCommunityClick, showDefaultIcon = false, - blurNSFW = blurNSFW, - showAvatar = showAvatar, + myUserInfo = myUserInfo, + appSettings = appSettings, ) } } @@ -731,8 +758,8 @@ fun PostAndCommunityContextHeaderPreview() { community = sampleCommunity, onCommunityClick = {}, onPostClick = {}, - blurNSFW = BlurNSFW.NSFW, - showAvatar = true, + myUserInfo = sampleMyUserInfo, + appSettings = APP_SETTINGS_DEFAULT, ) } @@ -741,52 +768,35 @@ fun PostAndCommunityContextHeaderPreview() { fun CommentFooterLine( commentView: CommentView, admins: List, - moderators: List?, - enableDownVotes: Boolean, + localSite: LocalSite, + myUserInfo: MyUserInfo?, instantScores: InstantScores, - voteDisplayMode: LocalUserVoteDisplayMode, - onUpvoteClick: () -> Unit, - onDownvoteClick: () -> Unit, - onReplyClick: (commentView: CommentView) -> Unit, - onSaveClick: (commentView: CommentView) -> Unit, + onVoteClick: (CreateCommentLike) -> Unit, + onReplyClick: (CommentView) -> Unit, + onSaveClick: (SaveComment) -> Unit, onViewSourceClick: () -> Unit, - onEditCommentClick: (commentView: CommentView) -> Unit, - onDeleteCommentClick: (commentView: CommentView) -> Unit, - onReportClick: (commentView: CommentView) -> Unit, - onRemoveClick: (commentView: CommentView) -> Unit, - onDistinguishClick: (commentView: CommentView) -> Unit, - onBanPersonClick: (person: Person) -> Unit, - onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, + onEditCommentClick: (CommentView) -> Unit, + onDeleteCommentClick: (DeleteComment) -> Unit, + onReportClick: (CommentId) -> Unit, + onRemoveClick: (CommentView) -> Unit, + onDistinguishClick: (DistinguishComment) -> Unit, + onBanPersonClick: (BanData) -> Unit, + onBanFromCommunityClick: (BanFromCommunityData) -> Unit, onCommentLinkClick: (commentView: CommentView) -> Unit, onBlockCreatorClick: (creator: Person) -> Unit, onPersonClick: (personId: PersonId) -> Unit, onViewVotesClick: (CommentId) -> Unit, onClick: () -> Unit, onLongClick: () -> Unit, - account: Account, viewSource: Boolean, ) { var showMoreOptions by rememberSaveable { mutableStateOf(false) } - val amMod = remember(moderators) { - amMod( - moderators = moderators, - myId = account.id, - ) - } - - val canMod = remember(admins) { - canMod( - creatorId = commentView.comment.creator_id, - admins = admins, - moderators = moderators, - myId = account.id, - ) - } - if (showMoreOptions) { CommentOptionsDropdown( commentView = commentView, + admins = admins, + myUserInfo = myUserInfo, onDismissRequest = { showMoreOptions = false }, onViewSourceClick = onViewSourceClick, onEditCommentClick = onEditCommentClick, @@ -800,10 +810,6 @@ fun CommentFooterLine( onCommentLinkClick = onCommentLinkClick, onPersonClick = onPersonClick, onViewVotesClick = onViewVotesClick, - isCreator = account.id == commentView.creator.id, - canMod = canMod, - amMod = amMod, - amAdmin = account.isAdmin, viewSource = viewSource, ) } @@ -825,57 +831,88 @@ fun CommentFooterLine( ) { VoteScore( instantScores = instantScores, - onVoteClick = onUpvoteClick, - voteDisplayMode = voteDisplayMode, + onVoteClick = { + onVoteClick( + CreateCommentLike( + comment_id = commentView.comment.id, + vote = VoteAction.UpVote, + ) + ) + }, + localSite = localSite, + myUserInfo = myUserInfo, + voteContentType = PostOrCommentType.Comment, account = account, ) UpvotePercentage( instantScores = instantScores, - voteDisplayMode = voteDisplayMode, + localSite = localSite, + myUserInfo = myUserInfo, + voteContentType = PostOrCommentType.Comment, account = account, ) VoteGeneric( instantScores = instantScores, - voteDisplayMode = voteDisplayMode, - type = VoteType.Upvote, - onVoteClick = onUpvoteClick, + voteAction = VoteAction.UpVote, + localSite = localSite, + myUserInfo = myUserInfo, + voteContentType = PostOrCommentType.Comment, + onVoteClick = { + onVoteClick( + CreateCommentLike( + comment_id = commentView.comment.id, + vote = VoteAction.UpVote, + ) + ) + }, + creatorId = commentView.comment.creator_id, account = account, ) - if (enableDownVotes) { VoteGeneric( instantScores = instantScores, - voteDisplayMode = voteDisplayMode, - type = VoteType.Downvote, - onVoteClick = onDownvoteClick, + voteAction = VoteAction.DownVote, + localSite = localSite, + myUserInfo = myUserInfo, + voteContentType = PostOrCommentType.Comment, + onVoteClick = { + onVoteClick( + CreateCommentLike( + comment_id = commentView.comment.id, + vote = VoteAction.DownVote, + ) + ) + }, + creatorId = commentView.comment.creator_id, account = account, ) - } ActionBarButton( icon = Icons.AutoMirrored.Outlined.Comment, onClick = { onReplyClick(commentView) }, contentDescription = stringResource(R.string.commentFooter_reply), account = account, ) + + data class BarItems(val icon: ImageVector, val contentStr: String, val color: Color) + + val (icon, contentStr, color) = if (commentView.comment_actions?.saved_at != null) { + BarItems( + icon = Icons.Filled.Bookmark, + contentStr = stringResource(R.string.removeBookmark), + color = MaterialTheme.colorScheme.primary + ) + } else { + BarItems( + icon = Icons.Outlined.BookmarkBorder, + contentStr = stringResource(R.string.addBookmark), + color = MaterialTheme.colorScheme.outline + ) + } + ActionBarButton( - icon = - if (commentView.saved) { - Icons.Filled.Bookmark - } else { - Icons.Outlined.BookmarkBorder - }, - contentDescription = - if (commentView.saved) { - stringResource(R.string.removeBookmark) - } else { - stringResource(R.string.addBookmark) - }, + icon = icon, + contentDescription = contentStr, onClick = { onSaveClick(commentView) }, - contentColor = - if (commentView.saved) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.outline - }, + contentColor = color, account = account, ) ActionBarButton( @@ -903,7 +940,8 @@ fun CommentNodesPreview() { CommentNodes( nodes = tree, admins = emptyList(), - moderators = emptyList(), + localSite = sampleLocalSite, + myUserInfo = sampleMyUserInfo, increaseLazyListIndexTracker = {}, addToParentIndexes = {}, isFlat = false, @@ -940,7 +978,6 @@ fun CommentNodesPreview() { showAvatar = true, blurNSFW = BlurNSFW.NSFW, account = AnonAccount, - voteDisplayMode = LocalUserVoteDisplayMode.default(), swipeToActionPreset = SwipeToActionPreset.TwoSides, ) } @@ -952,7 +989,7 @@ fun ShowMoreChildren( ) { TextButton( content = { - Text(stringResource(R.string.comment_node_more_replies, commentView.counts.child_count)) + Text(stringResource(R.string.comment_node_more_replies, commentView.comment.child_count)) }, onClick = { onFetchChildrenClick(commentView) }, ) 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 374ef609c..ad92cd6ca 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 @@ -11,14 +11,18 @@ import androidx.compose.ui.unit.dp import com.jerboa.CommentNode import com.jerboa.CommentNodeData import com.jerboa.MissingCommentNode +import com.jerboa.datatypes.BanData import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.db.entity.Account +import com.jerboa.db.entity.AppSettings import com.jerboa.feat.BlurNSFW import com.jerboa.feat.SwipeToActionPreset import it.vercruysse.lemmyapi.datatypes.CommentId import it.vercruysse.lemmyapi.datatypes.CommentView import it.vercruysse.lemmyapi.datatypes.Community -import it.vercruysse.lemmyapi.datatypes.LocalUserVoteDisplayMode +import it.vercruysse.lemmyapi.datatypes.GetSiteResponse +import it.vercruysse.lemmyapi.datatypes.LocalSite +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.Person import it.vercruysse.lemmyapi.datatypes.PersonId import it.vercruysse.lemmyapi.datatypes.PersonView @@ -28,7 +32,8 @@ import it.vercruysse.lemmyapi.datatypes.PostId fun CommentNodes( nodes: List, admins: List, - moderators: List?, + localSite: LocalSite, + myUserInfo: MyUserInfo?, increaseLazyListIndexTracker: () -> Unit, addToParentIndexes: () -> Unit, isFlat: Boolean, @@ -66,14 +71,14 @@ fun CommentNodes( enableDownVotes: Boolean, showAvatar: Boolean, blurNSFW: BlurNSFW, - voteDisplayMode: LocalUserVoteDisplayMode, swipeToActionPreset: SwipeToActionPreset, ) { LazyColumn(state = listState) { commentNodeItems( nodes = nodes, admins = admins, - moderators = moderators, + localSite = localSite, + myUserInfo = myUserInfo, increaseLazyListIndexTracker = increaseLazyListIndexTracker, addToParentIndexes = addToParentIndexes, isFlat = isFlat, @@ -110,8 +115,7 @@ fun CommentNodes( enableDownVotes = enableDownVotes, showAvatar = showAvatar, blurNSFW = blurNSFW, - voteDisplayMode = voteDisplayMode, - swipeToActionPreset = swipeToActionPreset, + swipeToActionPreset = swipeToActionPreset,,, ) item { Spacer(modifier = Modifier.height(100.dp)) @@ -121,27 +125,28 @@ fun CommentNodes( fun LazyListScope.commentNodeItems( nodes: List, - admins: List, - moderators: List?, + siteRes: GetSiteResponse, + appSettings: AppSettings, + myUserInfo: MyUserInfo?, increaseLazyListIndexTracker: () -> Unit, addToParentIndexes: () -> Unit, isFlat: Boolean, - isExpanded: (commentId: CommentId) -> Boolean, - toggleExpanded: (commentId: CommentId) -> Unit, - toggleActionBar: (commentId: CommentId) -> Unit, + isExpanded: (CommentId) -> Boolean, + toggleExpanded: (CommentId) -> Unit, + toggleActionBar: (CommentId) -> Unit, onUpvoteClick: (commentView: CommentView) -> Unit, onDownvoteClick: (commentView: CommentView) -> Unit, onReplyClick: (commentView: CommentView) -> Unit, onSaveClick: (commentView: CommentView) -> Unit, - onMarkAsReadClick: (commentView: CommentView) -> Unit, - onCommentClick: (commentView: CommentView) -> Unit, + onMarkAsReadClick: (commentId: CommentId, read: Boolean) -> Unit, + onCommentClick: (CommentId) -> Unit, onEditCommentClick: (commentView: CommentView) -> Unit, onDeleteCommentClick: (commentView: CommentView) -> Unit, onReportClick: (commentView: CommentView) -> Unit, onRemoveClick: (commentView: CommentView) -> Unit, onDistinguishClick: (commentView: CommentView) -> Unit, - onBanPersonClick: (person: Person) -> Unit, - onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, + onBanPersonClick: (BanData) -> Unit, + onBanFromCommunityClick: (BanFromCommunityData) -> Unit, onCommentLinkClick: (commentView: CommentView) -> Unit, onFetchChildrenClick: (commentView: CommentView) -> Unit, onPersonClick: (personId: PersonId) -> Unit, @@ -156,11 +161,6 @@ fun LazyListScope.commentNodeItems( showCollapsedCommentContent: Boolean, isCollapsedByParent: Boolean, showActionBar: (commentId: CommentId) -> Boolean, - enableDownVotes: Boolean, - showAvatar: Boolean, - blurNSFW: BlurNSFW, - voteDisplayMode: LocalUserVoteDisplayMode, - swipeToActionPreset: SwipeToActionPreset, ) { nodes.forEach { node -> when (node) { @@ -168,7 +168,6 @@ fun LazyListScope.commentNodeItems( commentNodeItem( node = node, admins = admins, - moderators = moderators, increaseLazyListIndexTracker = increaseLazyListIndexTracker, addToParentIndexes = addToParentIndexes, isFlat = isFlat, @@ -202,11 +201,10 @@ fun LazyListScope.commentNodeItems( showCollapsedCommentContent = showCollapsedCommentContent, isCollapsedByParent = isCollapsedByParent, showActionBar = showActionBar, - enableDownVotes = enableDownVotes, - showAvatar = showAvatar, blurNSFW = blurNSFW, - voteDisplayMode = voteDisplayMode, swipeToActionPreset = swipeToActionPreset, + localSite = localSite, + myUserInfo = myUserInfo, ) } @@ -214,7 +212,6 @@ fun LazyListScope.commentNodeItems( missingCommentNodeItem( node = node, admins = admins, - moderators = moderators, increaseLazyListIndexTracker = increaseLazyListIndexTracker, addToParentIndexes = addToParentIndexes, isFlat = isFlat, @@ -251,8 +248,9 @@ fun LazyListScope.commentNodeItems( enableDownVotes = enableDownVotes, showAvatar = showAvatar, blurNSFW = blurNSFW, - voteDisplayMode = voteDisplayMode, swipeToActionPreset = swipeToActionPreset, + localSite = localSite, + myUserInfo = myUserInfo, ) } } diff --git a/app/src/main/java/com/jerboa/ui/components/comment/CommentOptionsDropdown.kt b/app/src/main/java/com/jerboa/ui/components/comment/CommentOptionsDropdown.kt index 80d6fd7ed..c644093b9 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/CommentOptionsDropdown.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/CommentOptionsDropdown.kt @@ -22,9 +22,12 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import com.jerboa.R +import com.jerboa.amAdmin import com.jerboa.api.API +import com.jerboa.datatypes.BanData import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.datatypes.getContent +import com.jerboa.feat.canModOrAdmin import com.jerboa.feat.copyTextToClipboard import com.jerboa.ui.components.common.BanFromCommunityPopupMenuItem import com.jerboa.ui.components.common.BanPersonPopupMenuItem @@ -32,32 +35,43 @@ import com.jerboa.ui.components.common.PopupMenuItem import com.jerboa.util.cascade.CascadeCenteredDropdownMenu import it.vercruysse.lemmyapi.datatypes.CommentId import it.vercruysse.lemmyapi.datatypes.CommentView +import it.vercruysse.lemmyapi.datatypes.DeleteComment +import it.vercruysse.lemmyapi.datatypes.DistinguishComment +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.Person import it.vercruysse.lemmyapi.datatypes.PersonId +import it.vercruysse.lemmyapi.datatypes.PersonView @Composable fun CommentOptionsDropdown( commentView: CommentView, + admins: List, + myUserInfo: MyUserInfo?, onDismissRequest: () -> Unit, onCommentLinkClick: (CommentView) -> Unit, onPersonClick: (PersonId) -> Unit, onViewSourceClick: () -> Unit, onEditCommentClick: (CommentView) -> Unit, - onDeleteCommentClick: (CommentView) -> Unit, + onDeleteCommentClick: (DeleteComment) -> Unit, onBlockCreatorClick: (Person) -> Unit, - onReportClick: (CommentView) -> Unit, + onReportClick: (CommentId) -> Unit, onRemoveClick: (CommentView) -> Unit, - onDistinguishClick: (CommentView) -> Unit, + onDistinguishClick: (DistinguishComment) -> Unit, onViewVotesClick: (CommentId) -> Unit, - onBanPersonClick: (person: Person) -> Unit, - onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, - isCreator: Boolean, - canMod: Boolean, - amMod: Boolean, - amAdmin: Boolean, + onBanPersonClick: (BanData) -> Unit, + onBanFromCommunityClick: (BanFromCommunityData) -> Unit, viewSource: Boolean, ) { val ctx = LocalContext.current + val commentId = commentView.comment.id + + val isCreator = commentView.comment.creator_id == myUserInfo?.local_user_view?.local_user?.person_id + val canModOrAdmin = canModOrAdmin( + canMod = commentView.can_mod, + creatorId = commentView.comment.creator_id, + myUserInfo = myUserInfo, + admins = admins, + ) CascadeCenteredDropdownMenu( expanded = true, @@ -131,13 +145,19 @@ fun CommentOptionsDropdown( }, ) + val deleteForm = + DeleteComment( + comment_id = commentId, + deleted = !commentView.comment.deleted, + ) if (commentView.comment.deleted) { + PopupMenuItem( text = stringResource(R.string.restore), icon = Icons.Outlined.Restore, onClick = { onDismissRequest() - onDeleteCommentClick(commentView) + onDeleteCommentClick(deleteForm) }, ) } else { @@ -146,13 +166,13 @@ fun CommentOptionsDropdown( icon = Icons.Outlined.Delete, onClick = { onDismissRequest() - onDeleteCommentClick(commentView) + onDeleteCommentClick(deleteForm) }, ) } } else { PopupMenuItem( - text = stringResource(R.string.block_person, commentView.creator.name), + text = stringResource(R.string.block_x, commentView.creator.name), icon = Icons.Outlined.Block, onClick = { onDismissRequest() @@ -164,18 +184,17 @@ fun CommentOptionsDropdown( icon = Icons.Outlined.Flag, onClick = { onDismissRequest() - onReportClick(commentView) + onReportClick(commentId) }, ) } // The moderation subfield - if (amMod || amAdmin) { + if (canModOrAdmin) { PopupMenuItem( text = stringResource(R.string.moderation), icon = Icons.Outlined.Shield, ) { - if (canMod) { HorizontalDivider() val (removeText, removeIcon) = if (commentView.comment.removed) { @@ -192,8 +211,15 @@ fun CommentOptionsDropdown( onRemoveClick(commentView) }, ) - if (amAdmin) { - BanPersonPopupMenuItem(commentView.creator, onDismissRequest, onBanPersonClick) + if (myUserInfo.amAdmin()) { + BanPersonPopupMenuItem( + banData = BanData( + person = commentView.creator, + banned = commentView.creator_banned, + ), + onDismissRequest = onDismissRequest, + onBanPersonClick = onBanPersonClick + ) } // Only show ban from community button if its a local community @@ -208,7 +234,6 @@ fun CommentOptionsDropdown( onBanFromCommunityClick, ) } - } // Are an admin or mod, and also the comment creator if (isCreator) { @@ -229,18 +254,19 @@ fun CommentOptionsDropdown( ) } - // You can do these actions on mods above you - if (amAdmin && API.getInstanceOrNull()?.FF?.listAdminVotes() == true) { - PopupMenuItem( - text = stringResource(R.string.view_votes), - icon = ImageVector.vectorResource(R.drawable.up_filled), - onClick = { - onDismissRequest() - onViewVotesClick(commentView.comment.id) - }, - ) - } } } + // You can do these actions on mods above you + if (myUserInfo.amAdmin() && API.getInstanceOrNull()?.FF?.listAdminVotes() == true) { + PopupMenuItem( + text = stringResource(R.string.view_votes), + icon = ImageVector.vectorResource(R.drawable.up_filled), + onClick = { + onDismissRequest() + onViewVotesClick(commentView.comment.id) + }, + ) + } + } } diff --git a/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionNode.kt b/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionNode.kt deleted file mode 100644 index 600bff777..000000000 --- a/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionNode.kt +++ /dev/null @@ -1,394 +0,0 @@ -package com.jerboa.ui.components.comment.mentionnode - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.expandVertically -import androidx.compose.animation.shrinkVertically -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.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.Comment -import androidx.compose.material.icons.filled.Bookmark -import androidx.compose.material.icons.outlined.BookmarkBorder -import androidx.compose.material.icons.outlined.Link -import androidx.compose.material.icons.outlined.MarkChatRead -import androidx.compose.material.icons.outlined.MarkChatUnread -import androidx.compose.material.icons.outlined.MoreVert -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import com.jerboa.R -import com.jerboa.datatypes.sampleInstantScores -import com.jerboa.datatypes.samplePersonMentionView -import com.jerboa.db.entity.Account -import com.jerboa.db.entity.AnonAccount -import com.jerboa.feat.BlurNSFW -import com.jerboa.feat.InstantScores -import com.jerboa.feat.VoteType -import com.jerboa.feat.canMod -import com.jerboa.feat.default -import com.jerboa.ui.components.comment.CommentBody -import com.jerboa.ui.components.comment.PostAndCommunityContextHeader -import com.jerboa.ui.components.common.ActionBarButton -import com.jerboa.ui.components.common.CommentOrPostNodeHeader -import com.jerboa.ui.components.common.UpvotePercentage -import com.jerboa.ui.components.common.VoteGeneric -import com.jerboa.ui.components.common.VoteScore -import com.jerboa.ui.theme.LARGE_PADDING -import com.jerboa.ui.theme.SMALL_PADDING -import it.vercruysse.lemmyapi.datatypes.Community -import it.vercruysse.lemmyapi.datatypes.LocalUserVoteDisplayMode -import it.vercruysse.lemmyapi.datatypes.Person -import it.vercruysse.lemmyapi.datatypes.PersonId -import it.vercruysse.lemmyapi.datatypes.PersonMentionView -import it.vercruysse.lemmyapi.datatypes.PersonView -import it.vercruysse.lemmyapi.datatypes.PostId - -@Composable -fun CommentMentionNodeHeader( - personMentionView: PersonMentionView, - onPersonClick: (personId: PersonId) -> Unit, - onClick: () -> Unit, - onLongClick: () -> Unit, - showAvatar: Boolean, -) { - CommentOrPostNodeHeader( - creator = personMentionView.creator, - published = personMentionView.comment.published, - updated = personMentionView.comment.updated, - deleted = personMentionView.comment.deleted, - onPersonClick = onPersonClick, - isPostCreator = false, - isNsfw = false, - isDistinguished = personMentionView.comment.distinguished, - isCommunityBanned = personMentionView.creator_banned_from_community, - onClick = onClick, - onLongCLick = onLongClick, - showAvatar = showAvatar, - ) -} - -@Preview -@Composable -fun CommentMentionNodeHeaderPreview() { - CommentMentionNodeHeader( - personMentionView = samplePersonMentionView, - onPersonClick = {}, - onClick = {}, - onLongClick = {}, - showAvatar = true, - ) -} - -@Composable -fun CommentMentionNodeFooterLine( - personMentionView: PersonMentionView, - admins: List, - moderators: List?, - onUpvoteClick: () -> Unit, - onDownvoteClick: () -> Unit, - onReplyClick: (personMentionView: PersonMentionView) -> Unit, - onSaveClick: (personMentionView: PersonMentionView) -> Unit, - onMarkAsReadClick: (personMentionView: PersonMentionView) -> Unit, - onPersonClick: (personId: PersonId) -> Unit, - onViewSourceClick: () -> Unit, - onReportClick: (personMentionView: PersonMentionView) -> Unit, - onRemoveClick: (personMentionView: PersonMentionView) -> Unit, - onLinkClick: (personMentionView: PersonMentionView) -> Unit, - onBlockCreatorClick: (creator: Person) -> Unit, - instantScores: InstantScores, - voteDisplayMode: LocalUserVoteDisplayMode, - account: Account, - enableDownvotes: Boolean, - viewSource: Boolean, -) { - var showMoreOptions by rememberSaveable { mutableStateOf(false) } - - val canMod = - remember(admins) { - canMod( - creatorId = personMentionView.comment.creator_id, - admins = admins, - moderators = moderators, - myId = account.id, - ) - } - - if (showMoreOptions) { - CommentMentionsOptionsDropdown( - personMentionView = personMentionView, - onDismissRequest = { showMoreOptions = false }, - onPersonClick = onPersonClick, - onViewSourceClick = onViewSourceClick, - onReportClick = onReportClick, - onRemoveClick = onRemoveClick, - onBlockCreatorClick = onBlockCreatorClick, - isCreator = account.id == personMentionView.creator.id, - onCommentLinkClick = onLinkClick, - canMod = canMod, - viewSource = viewSource, - ) - } - - Row( - horizontalArrangement = Arrangement.End, - modifier = - Modifier - .fillMaxWidth() - .padding(top = LARGE_PADDING, bottom = SMALL_PADDING), - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(LARGE_PADDING), - ) { - VoteScore( - instantScores = instantScores, - onVoteClick = onUpvoteClick, - voteDisplayMode = voteDisplayMode, - account = account, - ) - UpvotePercentage( - instantScores = instantScores, - voteDisplayMode = voteDisplayMode, - account = account, - ) - VoteGeneric( - instantScores = instantScores, - voteDisplayMode = voteDisplayMode, - type = VoteType.Upvote, - onVoteClick = onUpvoteClick, - account = account, - ) - if (enableDownvotes) { - VoteGeneric( - instantScores = instantScores, - voteDisplayMode = voteDisplayMode, - type = VoteType.Downvote, - onVoteClick = onDownvoteClick, - account = account, - ) - } - ActionBarButton( - icon = Icons.Outlined.Link, - contentDescription = stringResource(R.string.commentMention_link), - onClick = { onLinkClick(personMentionView) }, - account = account, - ) - ActionBarButton( - icon = - if (personMentionView.person_mention.read) { - Icons.Outlined.MarkChatRead - } else { - Icons.Outlined.MarkChatUnread - }, - contentDescription = - if (personMentionView.person_mention.read) { - stringResource(R.string.markUnread) - } else { - stringResource(R.string.markRead) - }, - onClick = { onMarkAsReadClick(personMentionView) }, - contentColor = - if (personMentionView.person_mention.read) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.outline - }, - account = account, - ) - // Don't let you respond to your own comment. - if (personMentionView.creator.id != account.id) { - ActionBarButton( - icon = Icons.AutoMirrored.Outlined.Comment, - contentDescription = stringResource(R.string.commentFooter_reply), - onClick = { onReplyClick(personMentionView) }, - account = account, - ) - } - ActionBarButton( - icon = - if (personMentionView.saved) { - Icons.Filled.Bookmark - } else { - Icons.Outlined.BookmarkBorder - }, - contentDescription = - if (personMentionView.saved) { - stringResource(R.string.comment_unsave) - } else { - stringResource(R.string.comment_save) - }, - onClick = { onSaveClick(personMentionView) }, - contentColor = - if (personMentionView.saved) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.outline - }, - account = account, - ) - ActionBarButton( - icon = Icons.Outlined.MoreVert, - contentDescription = stringResource(R.string.moreOptions), - account = account, - onClick = { showMoreOptions = !showMoreOptions }, - requiresAccount = false, - ) - } - } -} - -@Composable -@Preview -fun CommentMentionNodeFooterLinePreview() { - CommentMentionNodeFooterLine( - personMentionView = samplePersonMentionView, - admins = listOf(), - moderators = listOf(), - onUpvoteClick = { }, - onDownvoteClick = { }, - onReplyClick = {}, - onSaveClick = {}, - onMarkAsReadClick = {}, - onPersonClick = {}, - onViewSourceClick = { }, - onReportClick = {}, - onRemoveClick = {}, - onLinkClick = {}, - onBlockCreatorClick = {}, - instantScores = sampleInstantScores, - voteDisplayMode = LocalUserVoteDisplayMode.default(), - account = AnonAccount, - enableDownvotes = true, - viewSource = false, - ) -} - -@Composable -fun CommentMentionNode( - personMentionView: PersonMentionView, - admins: List, - moderators: List?, - onUpvoteClick: (personMentionView: PersonMentionView) -> Unit, - onDownvoteClick: (personMentionView: PersonMentionView) -> Unit, - onReplyClick: (personMentionView: PersonMentionView) -> Unit, - onSaveClick: (personMentionView: PersonMentionView) -> Unit, - onMarkAsReadClick: (personMentionView: PersonMentionView) -> Unit, - onPersonClick: (personId: PersonId) -> Unit, - onCommunityClick: (community: Community) -> Unit, - onPostClick: (postId: PostId) -> Unit, - onReportClick: (personMentionView: PersonMentionView) -> Unit, - onRemoveClick: (personMentionView: PersonMentionView) -> Unit, - onLinkClick: (personMentionView: PersonMentionView) -> Unit, - onBlockCreatorClick: (creator: Person) -> Unit, - account: Account, - showAvatar: Boolean, - blurNSFW: BlurNSFW, - enableDownvotes: Boolean, - voteDisplayMode: LocalUserVoteDisplayMode, -) { - // These are necessary for instant comment voting - var instantScores by - remember { - mutableStateOf( - InstantScores( - score = personMentionView.counts.score, - myVote = personMentionView.my_vote, - upvotes = personMentionView.counts.upvotes, - downvotes = personMentionView.counts.downvotes, - ), - ) - } - - var viewSource by remember { mutableStateOf(false) } - var isExpanded by remember { mutableStateOf(true) } - var isActionBarExpanded by remember { mutableStateOf(true) } - - Column( - modifier = Modifier.padding(horizontal = LARGE_PADDING), - ) { - HorizontalDivider() - PostAndCommunityContextHeader( - post = personMentionView.post, - community = personMentionView.community, - onCommunityClick = onCommunityClick, - onPostClick = onPostClick, - blurNSFW = blurNSFW, - showAvatar = showAvatar, - ) - CommentMentionNodeHeader( - personMentionView = personMentionView, - onPersonClick = onPersonClick, - onClick = { - isExpanded = !isExpanded - }, - onLongClick = { - isActionBarExpanded = !isActionBarExpanded - }, - showAvatar = showAvatar, - ) - AnimatedVisibility( - visible = isExpanded, - enter = expandVertically(), - exit = shrinkVertically(), - ) { - Column { - CommentBody( - comment = personMentionView.comment, - viewSource = viewSource, - onClick = {}, - onLongClick = { - isActionBarExpanded = !isActionBarExpanded - true - }, - ) - AnimatedVisibility( - visible = isActionBarExpanded, - enter = expandVertically(), - exit = shrinkVertically(), - ) { - CommentMentionNodeFooterLine( - personMentionView = personMentionView, - admins = admins, - moderators = moderators, - onUpvoteClick = { - instantScores = - instantScores.update(VoteType.Upvote) - onUpvoteClick(personMentionView) - }, - onDownvoteClick = { - instantScores = - instantScores.update(VoteType.Downvote) - onDownvoteClick(personMentionView) - }, - onPersonClick = onPersonClick, - onViewSourceClick = { - viewSource = !viewSource - }, - onReplyClick = onReplyClick, - onSaveClick = onSaveClick, - onMarkAsReadClick = onMarkAsReadClick, - onReportClick = onReportClick, - onRemoveClick = onRemoveClick, - onLinkClick = onLinkClick, - onBlockCreatorClick = onBlockCreatorClick, - account = account, - enableDownvotes = enableDownvotes, - viewSource = viewSource, - instantScores = instantScores, - voteDisplayMode = voteDisplayMode, - ) - } - } - } - } -} diff --git a/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionOptionsDropdown.kt b/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionOptionsDropdown.kt deleted file mode 100644 index f167bd279..000000000 --- a/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionOptionsDropdown.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.jerboa.ui.components.comment.mentionnode - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.Comment -import androidx.compose.material.icons.outlined.Block -import androidx.compose.material.icons.outlined.ContentCopy -import androidx.compose.material.icons.outlined.CopyAll -import androidx.compose.material.icons.outlined.Description -import androidx.compose.material.icons.outlined.Flag -import androidx.compose.material.icons.outlined.Gavel -import androidx.compose.material.icons.outlined.Link -import androidx.compose.material.icons.outlined.Person -import androidx.compose.material.icons.outlined.Restore -import androidx.compose.material3.HorizontalDivider -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import com.jerboa.R -import com.jerboa.datatypes.getContent -import com.jerboa.feat.copyTextToClipboard -import com.jerboa.ui.components.common.PopupMenuItem -import com.jerboa.util.cascade.CascadeCenteredDropdownMenu -import it.vercruysse.lemmyapi.datatypes.Person -import it.vercruysse.lemmyapi.datatypes.PersonId -import it.vercruysse.lemmyapi.datatypes.PersonMentionView - -@Composable -fun CommentMentionsOptionsDropdown( - personMentionView: PersonMentionView, - onDismissRequest: () -> Unit, - onCommentLinkClick: (PersonMentionView) -> Unit, - onPersonClick: (PersonId) -> Unit, - onViewSourceClick: () -> Unit, - onBlockCreatorClick: (Person) -> Unit, - onReportClick: (PersonMentionView) -> Unit, - onRemoveClick: (PersonMentionView) -> Unit, - isCreator: Boolean, - canMod: Boolean, - viewSource: Boolean, -) { - val ctx = LocalContext.current - - CascadeCenteredDropdownMenu( - expanded = true, - onDismissRequest = onDismissRequest, - ) { - PopupMenuItem( - text = stringResource(R.string.comment_node_goto_comment), - icon = Icons.AutoMirrored.Outlined.Comment, - onClick = { - onDismissRequest() - onCommentLinkClick(personMentionView) - }, - ) - - PopupMenuItem( - text = stringResource(R.string.comment_node_go_to, personMentionView.creator.name), - icon = Icons.Outlined.Person, - onClick = { - onDismissRequest() - onPersonClick(personMentionView.creator.id) - }, - ) - - PopupMenuItem( - text = stringResource(R.string.copy), - icon = Icons.Outlined.CopyAll, - ) { - PopupMenuItem( - text = stringResource(R.string.comment_node_copy_permalink), - icon = Icons.Outlined.Link, - onClick = { - onDismissRequest() - val permalink = personMentionView.comment.ap_id - copyTextToClipboard(ctx, permalink, "Permalink", R.string.permalink_copied) - }, - ) - val content = personMentionView.comment.getContent() - PopupMenuItem( - text = stringResource(R.string.comment_node_copy_comment), - icon = Icons.Outlined.ContentCopy, - onClick = { - onDismissRequest() - copyTextToClipboard(ctx, content, "comment", R.string.comment_node_comment_copied) - }, - ) - } - - PopupMenuItem( - text = - if (viewSource) { - stringResource(R.string.comment_node_view_original) - } else { - stringResource(R.string.view_source) - }, - icon = Icons.Outlined.Description, - onClick = { - onDismissRequest() - onViewSourceClick() - }, - ) - - if (!isCreator) { - HorizontalDivider() - PopupMenuItem( - text = stringResource(R.string.block_person, personMentionView.creator.name), - icon = Icons.Outlined.Block, - onClick = { - onDismissRequest() - onBlockCreatorClick(personMentionView.creator) - }, - ) - - PopupMenuItem( - text = stringResource(R.string.comment_node_report_comment), - icon = Icons.Outlined.Flag, - onClick = { - onDismissRequest() - onReportClick(personMentionView) - }, - ) - } - - if (canMod) { - HorizontalDivider() - val (removeText, removeIcon) = - if (personMentionView.comment.removed) { - Pair(stringResource(R.string.restore_comment), Icons.Outlined.Restore) - } else { - Pair(stringResource(R.string.remove_comment), Icons.Outlined.Gavel) - } - - PopupMenuItem( - text = removeText, - icon = removeIcon, - onClick = { - onDismissRequest() - onRemoveClick(personMentionView) - }, - ) - } - } -} diff --git a/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReply.kt b/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReply.kt index 60084c92f..85c6ee9c0 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReply.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReply.kt @@ -1,4 +1,4 @@ - +// TODO this whole file can probably be deleted package com.jerboa.ui.components.comment.reply import androidx.compose.foundation.layout.Column @@ -10,38 +10,36 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import com.jerboa.R import com.jerboa.datatypes.getContent import com.jerboa.datatypes.sampleCommentView -import com.jerboa.db.entity.Account +import com.jerboa.db.entity.AppSettings import com.jerboa.nsfwCheck +import com.jerboa.showAvatar import com.jerboa.ui.components.comment.CommentNodeHeader -import com.jerboa.ui.components.comment.mentionnode.CommentMentionNodeHeader -import com.jerboa.ui.components.comment.replynode.CommentReplyNodeHeader import com.jerboa.ui.components.common.CommentOrPostNodeHeader import com.jerboa.ui.components.common.MarkdownTextField import com.jerboa.ui.theme.LARGE_PADDING import com.jerboa.ui.theme.MEDIUM_PADDING -import it.vercruysse.lemmyapi.datatypes.CommentReplyView import it.vercruysse.lemmyapi.datatypes.CommentView +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.PersonId -import it.vercruysse.lemmyapi.datatypes.PersonMentionView import it.vercruysse.lemmyapi.datatypes.PostView @Composable fun RepliedComment( commentView: CommentView, - onPersonClick: (personId: PersonId) -> Unit, showAvatar: Boolean, + onPersonClick: (personId: PersonId) -> Unit, ) { Column(modifier = Modifier.padding(MEDIUM_PADDING)) { CommentNodeHeader( commentView = commentView, onPersonClick = onPersonClick, - collapsedCommentsCount = 0, isExpanded = true, onClick = {}, onLongClick = {}, @@ -53,46 +51,6 @@ fun RepliedComment( } } -@Composable -fun RepliedCommentReply( - commentReplyView: CommentReplyView, - onPersonClick: (personId: PersonId) -> Unit, - showAvatar: Boolean, -) { - Column(modifier = Modifier.padding(MEDIUM_PADDING)) { - CommentReplyNodeHeader( - commentReplyView = commentReplyView, - onPersonClick = onPersonClick, - onClick = {}, - onLongClick = {}, - showAvatar = showAvatar, - ) - SelectionContainer { - Text(text = commentReplyView.comment.getContent()) - } - } -} - -@Composable -fun RepliedMentionReply( - personMentionView: PersonMentionView, - onPersonClick: (personId: PersonId) -> Unit, - showAvatar: Boolean, -) { - Column(modifier = Modifier.padding(MEDIUM_PADDING)) { - CommentMentionNodeHeader( - personMentionView = personMentionView, - onPersonClick = onPersonClick, - onClick = {}, - onLongClick = {}, - showAvatar = showAvatar, - ) - SelectionContainer { - Text(text = personMentionView.comment.getContent()) - } - } -} - @Preview @Composable fun RepliedCommentPreview() { @@ -125,14 +83,16 @@ fun RepliedPost( @Composable fun CommentReply( commentView: CommentView, + myUserInfo: MyUserInfo?, + appSettings: AppSettings, reply: TextFieldValue, onReplyChange: (TextFieldValue) -> Unit, onPersonClick: (personId: PersonId) -> Unit, - account: Account, modifier: Modifier = Modifier, - showAvatar: Boolean, ) { + val ctx = LocalContext.current val scrollState = rememberScrollState() + val showAvatar = myUserInfo.showAvatar(appSettings, ctx) Column( modifier = modifier.verticalScroll(scrollState), @@ -146,65 +106,7 @@ fun CommentReply( MarkdownTextField( text = reply, onTextChange = onReplyChange, - account = account, - ) - } -} - -@Composable -fun CommentReplyReply( - commentReplyView: CommentReplyView, - reply: TextFieldValue, - onReplyChange: (TextFieldValue) -> Unit, - onPersonClick: (personId: PersonId) -> Unit, - account: Account, - modifier: Modifier = Modifier, - showAvatar: Boolean, -) { - val scrollState = rememberScrollState() - - Column( - modifier = modifier.verticalScroll(scrollState), - ) { - RepliedCommentReply( - commentReplyView = commentReplyView, - onPersonClick = onPersonClick, - showAvatar = showAvatar, - ) - HorizontalDivider(modifier = Modifier.padding(vertical = LARGE_PADDING)) - MarkdownTextField( - text = reply, - onTextChange = onReplyChange, - account = account, - ) - } -} - -@Composable -fun MentionReply( - personMentionView: PersonMentionView, - reply: TextFieldValue, - onReplyChange: (TextFieldValue) -> Unit, - onPersonClick: (personId: PersonId) -> Unit, - account: Account, - modifier: Modifier = Modifier, - showAvatar: Boolean, -) { - val scrollState = rememberScrollState() - - Column( - modifier = modifier.verticalScroll(scrollState), - ) { - RepliedMentionReply( - personMentionView = personMentionView, - onPersonClick = onPersonClick, - showAvatar = showAvatar, - ) - HorizontalDivider(modifier = Modifier.padding(vertical = LARGE_PADDING)) - MarkdownTextField( - text = reply, - onTextChange = onReplyChange, - account = account, + myUserInfo = myUserInfo, ) } } @@ -215,7 +117,7 @@ fun PostReply( reply: TextFieldValue, onReplyChange: (TextFieldValue) -> Unit, onPersonClick: (personId: PersonId) -> Unit, - account: Account, + myUserInfo: MyUserInfo?, modifier: Modifier = Modifier, showAvatar: Boolean, ) { @@ -233,7 +135,7 @@ fun PostReply( MarkdownTextField( text = reply, onTextChange = onReplyChange, - account = account, + myUserInfo = myUserInfo, placeholder = stringResource(R.string.comment_reply_type_your_comment), ) } @@ -247,8 +149,8 @@ fun PostNodeHeader( ) { CommentOrPostNodeHeader( creator = postView.creator, - published = postView.post.published, - updated = postView.post.updated, + published = postView.post.published_at, + updated = postView.post.updated_at, deleted = postView.post.deleted, onPersonClick = onPersonClick, isPostCreator = true, diff --git a/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReplyScreen.kt b/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReplyScreen.kt index 4b5891cbe..506ca7836 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReplyScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReplyScreen.kt @@ -28,6 +28,7 @@ import com.jerboa.model.SiteViewModel import com.jerboa.ui.components.common.ActionTopBar import com.jerboa.ui.components.common.LoadingBar import com.jerboa.ui.components.common.getCurrentAccount +import it.vercruysse.lemmyapi.datatypes.MyUserInfo object CommentReplyReturn { const val COMMENT_VIEW = "comment-reply::return(comment-view)" @@ -37,6 +38,7 @@ object CommentReplyReturn { @Composable fun CommentReplyScreen( accountViewModel: AccountViewModel, + myUserInfo: MyUserInfo?, siteViewModel: SiteViewModel, appState: JerboaAppState, lowBandwidthMode: Boolean, @@ -68,7 +70,7 @@ fun CommentReplyScreen( loading = loading, onBackClick = appState::popBackStack, onActionClick = { - if (!account.isAnon()) { + if (myUserInfo != null) { commentReplyViewModel.createComment( replyItem, ctx = ctx, diff --git a/app/src/main/java/com/jerboa/ui/components/comment/replynode/CommentReplyNode.kt b/app/src/main/java/com/jerboa/ui/components/comment/replynode/CommentReplyNode.kt index cb79fe671..7d8b687c6 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/replynode/CommentReplyNode.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/replynode/CommentReplyNode.kt @@ -144,7 +144,7 @@ fun CommentReplyNodeInboxFooterLine( VoteGeneric( instantScores = instantScores, voteDisplayMode = voteDisplayMode, - type = VoteType.Upvote, + type = VoteAction.UpVote, onVoteClick = onUpvoteClick, account = account, ) @@ -152,7 +152,7 @@ fun CommentReplyNodeInboxFooterLine( VoteGeneric( instantScores = instantScores, voteDisplayMode = voteDisplayMode, - type = VoteType.Downvote, + type = VoteAction.DownVote, onVoteClick = onDownvoteClick, account = account, ) @@ -313,12 +313,12 @@ fun CommentReplyNodeInbox( commentReplyView = commentReplyView, onUpvoteClick = { instantScores = - instantScores.update(VoteType.Upvote) + instantScores.update(VoteAction.UpVote) onUpvoteClick(commentReplyView) }, onDownvoteClick = { instantScores = - instantScores.update(VoteType.Downvote) + instantScores.update(VoteAction.DownVote) onDownvoteClick(commentReplyView) }, onPersonClick = onPersonClick, diff --git a/app/src/main/java/com/jerboa/ui/components/common/AccountHelpers.kt b/app/src/main/java/com/jerboa/ui/components/common/AccountHelpers.kt index 97b58faa7..5afad5fb6 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/AccountHelpers.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/AccountHelpers.kt @@ -27,6 +27,10 @@ fun getCurrentAccount(accountViewModel: AccountViewModel): Account { return acc } +/** + * A special case of toEnumSafe, where the int still exists in the DB. + * TODO move this + */ fun getPostViewMode(appSettingsViewModel: AppSettingsViewModel): PostViewMode = getEnumFromIntSetting(appSettingsViewModel.appSettings) { it.postViewMode diff --git a/app/src/main/java/com/jerboa/ui/components/common/AppBars.kt b/app/src/main/java/com/jerboa/ui/components/common/AppBars.kt index d589ec622..8bf1b98aa 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/AppBars.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/AppBars.kt @@ -1,7 +1,6 @@ package com.jerboa.ui.components.common import android.annotation.SuppressLint -import android.app.Activity import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState @@ -26,8 +25,9 @@ import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.outlined.* import androidx.compose.material3.* +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -38,7 +38,6 @@ import androidx.compose.ui.draw.scale import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalResources @@ -54,11 +53,13 @@ import androidx.compose.ui.unit.TextUnitType import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.jerboa.R +import com.jerboa.userViewType import com.jerboa.api.ApiState -import com.jerboa.datatypes.UserViewType import com.jerboa.datatypes.data +import com.jerboa.datatypes.sampleMyUserInfo import com.jerboa.datatypes.samplePerson import com.jerboa.datatypes.samplePost +import com.jerboa.datatypes.sampleUnreadCountsResponse import com.jerboa.db.entity.Account import com.jerboa.db.entity.AnonAccount import com.jerboa.feat.isReadyAndIfNotShowSimplifiedInfoToast @@ -69,10 +70,13 @@ import com.jerboa.ui.components.home.NavTab import com.jerboa.ui.components.person.PersonProfileLink import com.jerboa.ui.theme.* import it.vercruysse.lemmyapi.datatypes.CommunityModeratorView +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.Person import it.vercruysse.lemmyapi.datatypes.PersonId import it.vercruysse.lemmyapi.datatypes.PersonView -import it.vercruysse.lemmyapi.dto.SortType +import it.vercruysse.lemmyapi.datatypes.UnreadCountsResponse +import it.vercruysse.lemmyapi.enums.SortType +import it.vercruysse.lemmyapi.enums.VoteAction import kotlinx.coroutines.CoroutineScope @OptIn(ExperimentalMaterial3Api::class) @@ -83,26 +87,26 @@ fun SimpleTopAppBar( scrollBehavior: TopAppBarScrollBehavior? = null, actions: @Composable RowScope.() -> Unit = {}, ) { - TopAppBar( - scrollBehavior = scrollBehavior, - title = { - Text( - text = text, - style = MaterialTheme.typography.titleMedium, - maxLines = 1, - modifier = Modifier.customMarquee(), - ) - }, - navigationIcon = { - IconButton(onClick = onClickBack, modifier = Modifier.testTag("jerboa:back")) { - Icon( - Icons.AutoMirrored.Outlined.ArrowBack, - contentDescription = stringResource(R.string.topAppBar_back), + TopAppBar( + scrollBehavior = scrollBehavior, + title = { + Text( + text = text, + style = MaterialTheme.typography.titleMedium, + maxLines = 1, + modifier = Modifier.customMarquee(), ) - } - }, - actions = actions, - ) + }, + navigationIcon = { + IconButton(onClick = onClickBack, modifier = Modifier.testTag("jerboa:back")) { + Icon( + Icons.AutoMirrored.Outlined.ArrowBack, + contentDescription = stringResource(R.string.topAppBar_back), + ) + } + }, + actions = actions, + ) } @Preview @@ -113,99 +117,81 @@ fun SimpleTopAppBarPreview() { } } -@Composable -fun BottomAppBarAll( +fun NavigationSuiteScope.adaptiveNavigationBar( + myUserInfo: MyUserInfo?, + unreadCounts: UnreadCountsResponse?, selectedTab: NavTab, - onSelect: (NavTab) -> Unit, - userViewType: UserViewType, - unreadCounts: Long, - unreadAppCount: Long?, - unreadReportCount: Long?, + onSelectTab: (NavTab) -> Unit, showTextDescriptionsInNavbar: Boolean, ) { - // Check for preview mode - if (LocalContext.current is Activity) { - val window = (LocalContext.current as Activity).window - val colorScheme = MaterialTheme.colorScheme - - DisposableEffect(Unit) { - window.navigationBarColor = colorScheme.surfaceContainer.toArgb() - - onDispose { - window.navigationBarColor = colorScheme.background.toArgb() - } + for (tab in NavTab.getEntries(myUserInfo.userViewType())) { + val selected = tab == selectedTab + val iconBadgeCount = when (tab) { + NavTab.Inbox -> unreadCounts?.notification_count + NavTab.RegistrationApplications -> unreadCounts?.registration_application_count + NavTab.Reports -> unreadCounts?.report_count + else -> null } - } - // If descriptions are hidden, make the bar shorter - val modifier = if (showTextDescriptionsInNavbar) Modifier else Modifier.navigationBarsPadding().height(56.dp) - NavigationBar( - modifier = modifier, - ) { - for (tab in NavTab.getEntries(userViewType)) { - val selected = tab == selectedTab - val iconBadgeCount = when (tab) { - NavTab.Inbox -> unreadCounts - NavTab.RegistrationApplications -> unreadAppCount - NavTab.Reports -> unreadReportCount - else -> null - } - NavigationBarItem( - icon = { - NavbarIconAndBadge( - iconBadgeCount = iconBadgeCount, - icon = - if (selected) { - tab.iconFilled - } else { - tab.iconOutlined - }, - contentDescription = stringResource(tab.contentDescriptionId), + item( + icon = { + NavbarIconAndBadge( + iconBadgeCount = iconBadgeCount, + icon = + if (selected) { + tab.iconFilled + } else { + tab.iconOutlined + }, + contentDescription = stringResource(tab.contentDescriptionId), + ) + }, + label = { + if (showTextDescriptionsInNavbar) { + Text( + textAlign = TextAlign.Center, + fontSize = TextUnit(10f, TextUnitType.Sp), + text = stringResource(tab.textId), ) - }, - label = { - if (showTextDescriptionsInNavbar) { - Text( - textAlign = TextAlign.Center, - fontSize = TextUnit(10f, TextUnitType.Sp), - text = stringResource(tab.textId), - ) - } - }, - selected = selected, - onClick = { - onSelect(tab) - }, - ) - } + } + }, + selected = selected, + onClick = { + onSelectTab(tab) + }, + ) } } @Preview @Composable -fun BottomAppBarAllPreview() { - BottomAppBarAll( - selectedTab = NavTab.Home, - onSelect = {}, - unreadCounts = 30, - unreadAppCount = 2, - unreadReportCount = 8, - userViewType = UserViewType.AdminOnly, - showTextDescriptionsInNavbar = true, +fun AdaptiveNavigationBarPreview() { + NavigationSuiteScaffold( + navigationSuiteItems = { + adaptiveNavigationBar( + selectedTab = NavTab.Home, + onSelectTab = {}, + unreadCounts = sampleUnreadCountsResponse, + myUserInfo = sampleMyUserInfo, + showTextDescriptionsInNavbar = true, + ) + }, ) } @Preview @Composable -fun BottomAppBarAllNoDescriptionsPreview() { - BottomAppBarAll( - selectedTab = NavTab.Home, - onSelect = {}, - unreadCounts = 30, - unreadAppCount = null, - unreadReportCount = null, - userViewType = UserViewType.Normal, - showTextDescriptionsInNavbar = false, +fun AdaptiveNavigationBarNoDescriptionsPreview() { + NavigationSuiteScaffold( + navigationSuiteItems = { + adaptiveNavigationBar( + selectedTab = NavTab.Home, + onSelectTab = {}, + unreadCounts = sampleUnreadCountsResponse, + myUserInfo = sampleMyUserInfo, + showTextDescriptionsInNavbar = false, + ) + }, ) } @@ -249,15 +235,15 @@ fun CommentOrPostNodeHeader( published: String, updated: String?, deleted: Boolean, - onPersonClick: (personId: PersonId) -> Unit, isPostCreator: Boolean, isDistinguished: Boolean, isCommunityBanned: Boolean, - onClick: () -> Unit, - onLongCLick: () -> Unit, isExpanded: Boolean = true, collapsedCommentsCount: Long = 0, - showAvatar: Boolean, + myUserInfo: MyUserInfo?, + onPersonClick: (personId: PersonId) -> Unit, + onClick: () -> Unit, + onLongCLick: () -> Unit, ) { FlowRow( horizontalArrangement = Arrangement.SpaceBetween, @@ -327,8 +313,8 @@ fun CommentOrPostNodeHeader( fun CommentOrPostNodeHeaderPreview() { CommentOrPostNodeHeader( creator = samplePerson, - published = samplePost.published, - updated = samplePost.updated, + published = samplePost.published_at, + updated = samplePost.updated_at, deleted = false, onPersonClick = {}, isPostCreator = true, @@ -350,18 +336,15 @@ fun ActionBarButton( text: String? = null, contentColor: Color = MaterialTheme.colorScheme.outline, noClick: Boolean = false, - account: Account, - requiresAccount: Boolean = true, + myUserInfo: MyUserInfo?, + requiresLogin: Boolean = true, ) { - val ctx = LocalContext.current - val resources = LocalResources.current - val barMod = if (noClick) { modifier } else { modifier.clickable(onClick = { - if (!requiresAccount || account.isReadyAndIfNotShowSimplifiedInfoToast(ctx, resources)) { + if (!requiresLogin || myUserInfo != null) { onClick() } }) @@ -472,11 +455,11 @@ fun DotSpacer( } @Composable -fun scoreColor(myVote: Int?): Color = +fun scoreColor(myVote: VoteAction?): Color = when (myVote) { - 1 -> MaterialTheme.colorScheme.secondary - -1 -> MaterialTheme.colorScheme.error - else -> MaterialTheme.colorScheme.outline + VoteAction.UpVote -> MaterialTheme.colorScheme.secondary + VoteAction.DownVote -> MaterialTheme.colorScheme.error + else -> MaterialTheme.colorScheme.outline } @Composable @@ -520,10 +503,12 @@ fun NavbarIconAndBadge( @Composable fun Sidebar( + name: String, title: String?, banner: String?, icon: String?, content: String?, + summary: String?, published: String, postCount: Long, commentCount: Long, @@ -538,6 +523,8 @@ fun Sidebar( ) { val listState = rememberLazyListState() + val nameOrTitle = title ?: name + LazyColumn( state = listState, modifier = @@ -568,11 +555,15 @@ fun Sidebar( modifier = Modifier.padding(MEDIUM_PADDING), verticalArrangement = Arrangement.spacedBy(MEDIUM_PADDING), ) { - title?.also { Text( - text = it, + text = nameOrTitle, style = MaterialTheme.typography.titleMedium, ) + summary?.also { + Text( + text = it, + style = MaterialTheme.typography.bodyMedium, + ) } TimeAgo( precedingString = stringResource(R.string.AppBars_created), @@ -769,7 +760,7 @@ fun JerboaLoadingBar(apiState: ApiState<*>) { } /** - * A simple top bar with a action that defaults to save + * A simple top bar with an action that defaults to save * */ @OptIn(ExperimentalMaterial3Api::class) diff --git a/app/src/main/java/com/jerboa/ui/components/common/DrawerItems.kt b/app/src/main/java/com/jerboa/ui/components/common/DrawerItems.kt index a346bf2c5..969a5c856 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/DrawerItems.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/DrawerItems.kt @@ -97,3 +97,7 @@ fun IconAndTextDrawerItemWithMorePreview() { more = true, ) } + +@Composable +fun selectedItemContainerColor(selected: Boolean) = + if (!selected) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceVariant 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 4225ae219..88bccd940 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 @@ -35,9 +35,9 @@ import com.jerboa.datatypes.data import com.jerboa.ui.theme.LARGE_PADDING import com.jerboa.ui.theme.POPUP_MENU_WIDTH_RATIO import com.jerboa.ui.theme.Shapes -import it.vercruysse.lemmyapi.dto.CommentSortType -import it.vercruysse.lemmyapi.dto.SortType import it.vercruysse.lemmyapi.dto.getSupportedEntries +import it.vercruysse.lemmyapi.enums.CommentSortType +import it.vercruysse.lemmyapi.enums.SortType import me.saket.cascade.CascadeColumnScope import me.saket.cascade.CascadeDropdownMenu diff --git a/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt b/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt index 3aa677bb2..da15eb793 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt @@ -67,18 +67,18 @@ import androidx.core.text.isDigitsOnly import com.jerboa.R import com.jerboa.api.API import com.jerboa.appendMarkdownImage -import com.jerboa.db.entity.Account -import com.jerboa.db.entity.isAnon import com.jerboa.imageInputStreamFromUri import com.jerboa.ui.theme.MARKDOWN_BAR_ICON_SIZE import com.jerboa.ui.theme.MEDIUM_PADDING +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import kotlinx.coroutines.launch @Composable fun MarkdownTextField( text: TextFieldValue, onTextChange: (TextFieldValue) -> Unit, - account: Account, + // TODO all fields and buttons should be disabled if this is null + myUserInfo: MyUserInfo?, modifier: Modifier = Modifier, placeholder: String = "", focusImmediate: Boolean = true, @@ -86,7 +86,7 @@ fun MarkdownTextField( ) { val focusRequester = remember { FocusRequester() } val imageUploading = rememberSaveable { mutableStateOf(false) } - val launcher = imageUploadLauncher(account, onTextChange, text, imageUploading) + val launcher = imageUploadLauncher(onTextChange, text, imageUploading) var showCreateLink by remember { mutableStateOf(false) } var showPreview by remember { mutableStateOf(false) } @@ -286,7 +286,6 @@ fun CreateLinkDialogPreview() { @Composable private fun imageUploadLauncher( - account: Account, onTextChange: (TextFieldValue) -> Unit, text: TextFieldValue, imageUploading: MutableState, @@ -309,15 +308,14 @@ private fun imageUploadLauncher( uri?.also { cUri -> imageUploading.value = true val imageIs = imageInputStreamFromUri(ctx, cUri) + val imageBytes = imageIs.readBytes() scope.launch { - if (!account.isAnon()) { - val url = API.uploadPictrsImage(imageIs, ctx) + val res = API.getInstance().uploadImage(imageBytes) imageUploading.value = false - if (url.isNotEmpty()) { - onTextChange(TextFieldValue(appendMarkdownImage(text.text, url))) + res.onSuccess { + onTextChange(TextFieldValue(appendMarkdownImage(text.text, it.image_url))) } } - } } } return launcher diff --git a/app/src/main/java/com/jerboa/ui/components/common/LinkDropDownMenu.kt b/app/src/main/java/com/jerboa/ui/components/common/LinkDropDownMenu.kt index f24091561..01a34a21a 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/LinkDropDownMenu.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/LinkDropDownMenu.kt @@ -23,6 +23,8 @@ import androidx.compose.ui.unit.dp import com.jerboa.JerboaAppState import com.jerboa.PostLinkType import com.jerboa.R +import com.jerboa.db.APP_SETTINGS_DEFAULT +import com.jerboa.db.entity.AppSettings import com.jerboa.feat.copyImageToClipboard import com.jerboa.feat.copyTextToClipboard import com.jerboa.feat.shareLink @@ -37,8 +39,7 @@ fun LinkDropDownMenu( link: String?, onDismissRequest: () -> Unit, appState: JerboaAppState, - useCustomTabs: Boolean, - usePrivateTabs: Boolean, + appSettings: AppSettings, ) { val ctx = LocalContext.current val resources = LocalResources.current @@ -65,7 +66,7 @@ fun LinkDropDownMenu( onClick = { onDismissRequest() if (mediaType == PostLinkType.Link) { - appState.openLink(link, useCustomTabs, usePrivateTabs) + appState.openLink(link, appSettings.useCustomTabs, appSettings.usePrivateTabs) } else { appState.openMediaViewer(link, mediaType) } @@ -76,7 +77,7 @@ fun LinkDropDownMenu( icon = Icons.Outlined.OpenInBrowser, onClick = { onDismissRequest() - appState.openLinkRaw(link, useCustomTabs, usePrivateTabs) + appState.openLinkRaw(link, appSettings.useCustomTabs, appSettings.usePrivateTabs) }, ) @@ -182,7 +183,6 @@ fun Test() { "linkDropdownExpanded.value", {}, rememberJerboaAppState(), - useCustomTabs = false, - usePrivateTabs = false, + appSettings = APP_SETTINGS_DEFAULT, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt b/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt index 694914535..940c2149c 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt @@ -27,6 +27,8 @@ import androidx.core.content.res.ResourcesCompat import coil.imageLoader import com.jerboa.JerboaAppState import com.jerboa.convertSpToPx +import com.jerboa.db.entity.AppSettings +import com.jerboa.db.entity.lowBandwidthMode import com.jerboa.util.markwon.BetterLinkMovementMethod import com.jerboa.util.markwon.ForceHttpsPlugin import com.jerboa.util.markwon.LinkOnlyImagesPlugin @@ -98,12 +100,12 @@ object MarkdownHelper { fun init( appState: JerboaAppState, - useCustomTabs: Boolean, - usePrivateTabs: Boolean, - lowBandwidthMode: Boolean, + appSettings: AppSettings, + ctx: Context, onLongClick: BetterLinkMovementMethod.OnLinkLongClickListener, ) { val context = appState.navController.context + val lowBandwidthMode = appSettings.lowBandwidthMode(ctx) val loader = context.imageLoader val imagesPlugin = if (lowBandwidthMode) { // Render `![alt](url)` as a plain clickable link — no network load. @@ -147,7 +149,11 @@ object MarkdownHelper { // Previously when openLink wasn't suspending it was somehow preventing the click from propagating // Now it doesn't anymore and we have to do it manually view.cancelPendingInputEvents() - appState.openLink(link, useCustomTabs, usePrivateTabs) + appState.openLink( + url = link, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs + ) } } }, diff --git a/app/src/main/java/com/jerboa/ui/components/common/PopupItems.kt b/app/src/main/java/com/jerboa/ui/components/common/PopupItems.kt index 8aafe5ec9..9c86a7fc1 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/PopupItems.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/PopupItems.kt @@ -7,19 +7,19 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.jerboa.R import com.jerboa.communityNameShown +import com.jerboa.datatypes.BanData import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.personNameShown -import it.vercruysse.lemmyapi.datatypes.Person @Composable fun BanPersonPopupMenuItem( - person: Person, + banData: BanData, onDismissRequest: () -> Unit, - onBanPersonClick: (person: Person) -> Unit, + onBanPersonClick: (BanData) -> Unit, ) { - val personNameShown = personNameShown(person, true) + val personNameShown = personNameShown(banData.person, true) val (banText, banIcon) = - if (person.banned) { + if (banData.banned) { Pair(stringResource(R.string.unban_person, personNameShown), Icons.Outlined.Restore) } else { Pair(stringResource(R.string.ban_person, personNameShown), Icons.Outlined.Gavel) @@ -30,7 +30,7 @@ fun BanPersonPopupMenuItem( icon = banIcon, onClick = { onDismissRequest() - onBanPersonClick(person) + onBanPersonClick(banData) }, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/common/SwipeToAction.kt b/app/src/main/java/com/jerboa/ui/components/common/SwipeToAction.kt index 833b45165..4b8b7f190 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/SwipeToAction.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/SwipeToAction.kt @@ -145,6 +145,7 @@ fun SwipeToAction( @Composable fun rememberSwipeActionState( swipeToActionPreset: SwipeToActionPreset, + enableUpVotes: Boolean, enableDownVotes: Boolean, rememberKey: Any? = Unit, onAction: (action: SwipeToActionType) -> Unit, @@ -155,18 +156,28 @@ fun rememberSwipeActionState( */ val density = LocalDensity.current - val leftActionsRanges = - remember(swipeToActionPreset, enableDownVotes) { + fun enableWithVotes(type: SwipeToActionType): Boolean { + return when (type) { + SwipeToActionType.Upvote -> enableUpVotes + SwipeToActionType.Downvote -> enableDownVotes + else -> { + true + } + } + } + + val leftActionsRanges = + remember(swipeToActionPreset) { SwipeToActionType.getActionToRangeList( swipeToActionPreset.leftActions - .filter { !(it == SwipeToActionType.Downvote && !enableDownVotes) }, + .filter {enableWithVotes(it)} ) } val rightActionsRanges = - remember(swipeToActionPreset, enableDownVotes) { + remember(swipeToActionPreset) { SwipeToActionType.getActionToRangeList( swipeToActionPreset.rightActions - .filter { !(it == SwipeToActionType.Downvote && !enableDownVotes) }, + .filter {enableWithVotes(it)} ) } @@ -208,3 +219,4 @@ fun rememberSwipeActionState( progressState.floatValue = dismissState.progress return dismissState } + diff --git a/app/src/main/java/com/jerboa/ui/components/common/TextBadge.kt b/app/src/main/java/com/jerboa/ui/components/common/TextBadge.kt index 5d384df60..bab6bdf75 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/TextBadge.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/TextBadge.kt @@ -57,7 +57,7 @@ fun TextBadge( fun ItemAndInstanceTitle( modifier: Modifier = Modifier, title: String, - actorId: String?, + apId: String?, local: Boolean, onClick: (() -> Unit)?, itemColor: Color = MaterialTheme.colorScheme.primary, @@ -66,8 +66,8 @@ fun ItemAndInstanceTitle( instanceStyle: TextStyle = MaterialTheme.typography.labelSmall, ) { val text = remember(title, local, itemColor) { - val serverStr = if (!local && actorId != null) { - "@${hostName(actorId)}" + val serverStr = if (!local && apId != null) { + "@${hostName(apId)}" } else { null } @@ -112,7 +112,7 @@ fun ItemAndInstanceTitle( fun ItemInstanceAndTitlePreview() { ItemAndInstanceTitle( title = "lemmydev", - actorId = "https://lemmy.ml/u/lemmydev", + apId = "https://lemmy.ml/u/lemmydev", local = false, onClick = {}, ) diff --git a/app/src/main/java/com/jerboa/ui/components/common/TimeAgo.kt b/app/src/main/java/com/jerboa/ui/components/common/TimeAgo.kt index 484c30f9f..ee4b36964 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/TimeAgo.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/TimeAgo.kt @@ -100,8 +100,8 @@ fun dateStringToPretty( @Composable fun TimeAgoPreview() { TimeAgo( - published = samplePerson.published, - updated = samplePerson.updated, + published = samplePerson.published_at, + updated = samplePerson.updated_at, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/common/VoteHelpers.kt b/app/src/main/java/com/jerboa/ui/components/common/VoteHelpers.kt index dfbfb0aba..8ace2f02f 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/VoteHelpers.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/VoteHelpers.kt @@ -14,53 +14,81 @@ import androidx.compose.ui.tooling.preview.Preview import com.jerboa.R import com.jerboa.SHOW_UPVOTE_PCT_THRESHOLD import com.jerboa.datatypes.sampleInstantScores +import com.jerboa.datatypes.sampleLocalSite +import com.jerboa.datatypes.sampleMyUserInfo import com.jerboa.db.entity.Account import com.jerboa.db.entity.AnonAccount import com.jerboa.feat.InstantScores -import com.jerboa.feat.VoteType -import com.jerboa.feat.default -import com.jerboa.feat.formatPercent -import com.jerboa.feat.upvotePercent -import it.vercruysse.lemmyapi.datatypes.LocalUserVoteDisplayMode +import com.jerboa.feat.PostOrCommentType +import it.vercruysse.lemmyapi.datatypes.LocalSite +import it.vercruysse.lemmyapi.datatypes.MyUserInfo +import it.vercruysse.lemmyapi.datatypes.PersonId +import it.vercruysse.lemmyapi.enums.FederationMode +import it.vercruysse.lemmyapi.enums.VoteAction +import it.vercruysse.lemmyapi.enums.VoteShow @Composable fun VoteGeneric( instantScores: InstantScores, - voteDisplayMode: LocalUserVoteDisplayMode, - type: VoteType, + myUserInfo: MyUserInfo?, + localSite: LocalSite, + voteContentType: PostOrCommentType, + voteAction: VoteAction, + /** + * The creator ID is necessary for hiding downvotes to yourself + */ + creatorId: PersonId, onVoteClick: () -> Unit, account: Account, - hideScores: Boolean = false, ) { - val iconAndColor = iconAndColor(type, instantScores) + val iconAndColor = iconAndColor(voteAction, instantScores) - val contentDescription = buildContentDescription(type, instantScores) + val contentDescription = buildContentDescription(voteAction, instantScores) - val votes = when (type) { - VoteType.Upvote -> instantScores.upvotes - VoteType.Downvote -> instantScores.downvotes - } + data class VoteData( + val votes: Long, + val enabled: Boolean, + val show: Boolean, + ) + + val voteData = when (voteAction) { + VoteAction.UpVote -> { + VoteData( + instantScores.upvotes, + enableUpvotes(localSite, voteContentType), + showUpvotes(myUserInfo, localSite, voteContentType) && instantScores.upvotes != 0L, + ) + } - val hideScore = hideScores || - when (type) { - VoteType.Upvote -> !voteDisplayMode.upvotes - VoteType.Downvote -> !voteDisplayMode.downvotes + VoteAction.DownVote -> { + VoteData( + instantScores.downvotes, + enableDownvotes(localSite, voteContentType), + showDownvotes(myUserInfo, localSite, voteContentType, creatorId) && instantScores.downvotes != 0L, + ) } + VoteAction.NoVote -> {null} + } - val voteStr = if (votes > 0 && !hideScore) { - votes.toString() + if (voteData != null) { + val voteStr = if (voteData.show) { + voteData.votes.toString() } else { null } - ActionBarButton( - onClick = onVoteClick, - contentColor = iconAndColor.second, - icon = iconAndColor.first, - text = voteStr, - contentDescription = contentDescription, - account = account, - ) + if (voteData.enabled) { + ActionBarButton( + onClick = onVoteClick, + contentColor = iconAndColor.second, + icon = iconAndColor.first, + text = voteStr, + contentDescription = contentDescription, + account = account, + ) + } + } + } @Composable @@ -68,9 +96,12 @@ fun VoteGeneric( fun VoteGenericPreview() { VoteGeneric( instantScores = sampleInstantScores, - voteDisplayMode = LocalUserVoteDisplayMode.default(), - type = VoteType.Upvote, + myUserInfo = sampleMyUserInfo, + localSite = sampleLocalSite, + voteContentType = PostOrCommentType.Post, + voteAction = VoteAction.UpVote, onVoteClick = { }, + creatorId = 0, account = AnonAccount, ) } @@ -78,21 +109,35 @@ fun VoteGenericPreview() { @Composable fun VoteScore( instantScores: InstantScores, - voteDisplayMode: LocalUserVoteDisplayMode, + myUserInfo: MyUserInfo?, + localSite: LocalSite, + voteContentType: PostOrCommentType, onVoteClick: () -> Unit, account: Account, ) { val iconAndColor = scoreIconAndColor(myVote = instantScores.myVote) - val hideScore = - voteDisplayMode.score && voteDisplayMode.upvotes && instantScores.score == instantScores.upvotes + val showUpvotes = showUpvotes(myUserInfo, localSite, voteContentType) + val showScore = showScore(myUserInfo) + val enabled = enableUpvotes(localSite, voteContentType) + + // If the score is the same as the upvotes, + // and both score and upvotes are enabled, + // only show the upvotes. + val hideScore = !showScore || (showUpvotes && instantScores.score == instantScores.upvotes) - if (voteDisplayMode.score && !hideScore) { + val scoreStr = if (showScore && !hideScore) { + instantScores.score.toString() + } else { + null + } + + if (enabled && !hideScore) { ActionBarButton( onClick = onVoteClick, contentColor = iconAndColor.second, icon = iconAndColor.first, - text = instantScores.score.toString(), + text = scoreStr, contentDescription = stringResource(R.string.score), account = account, ) @@ -104,7 +149,9 @@ fun VoteScore( fun VoteScorePreview() { VoteScore( instantScores = sampleInstantScores, - voteDisplayMode = LocalUserVoteDisplayMode.default().copy(score = true), + myUserInfo = sampleMyUserInfo, + localSite = sampleLocalSite, + voteContentType = PostOrCommentType.Post, onVoteClick = { }, account = AnonAccount, ) @@ -113,7 +160,9 @@ fun VoteScorePreview() { @Composable fun UpvotePercentage( instantScores: InstantScores, - voteDisplayMode: LocalUserVoteDisplayMode, + myUserInfo: MyUserInfo?, + localSite: LocalSite, + voteContentType: PostOrCommentType, account: Account, ) { val upvotePct = upvotePercent( @@ -121,7 +170,10 @@ fun UpvotePercentage( downvotes = instantScores.downvotes, ) - if (voteDisplayMode.upvote_percentage && (upvotePct < SHOW_UPVOTE_PCT_THRESHOLD)) { + val showPct = showPercentage(myUserInfo, localSite, voteContentType) && + (upvotePct < SHOW_UPVOTE_PCT_THRESHOLD) + + if (showPct) { ActionBarButton( onClick = {}, noClick = true, @@ -139,50 +191,54 @@ fun UpvotePercentage( fun UpvotePercentagePreview() { UpvotePercentage( instantScores = sampleInstantScores, - voteDisplayMode = LocalUserVoteDisplayMode.default().copy(upvote_percentage = true), + myUserInfo = sampleMyUserInfo, + localSite = sampleLocalSite, + voteContentType = PostOrCommentType.Post, account = AnonAccount, ) } @Composable private fun buildContentDescription( - type: VoteType, + voteAction: VoteAction, instantScores: InstantScores, -): String = - if (type == VoteType.Upvote) { - if (instantScores.myVote == 1) { - stringResource(R.string.upvoted) - } else { - stringResource(R.string.upvote) +): String? = + when(voteAction) { + VoteAction.UpVote -> { + when(instantScores.myVote) { + VoteAction.UpVote -> + stringResource(R.string.upvoted) + else -> stringResource(R.string.upvote) + } } - } else { - if (instantScores.myVote == -1) { - stringResource(R.string.downvoted) - } else { - stringResource(R.string.downvote) + VoteAction.DownVote -> { + when(instantScores.myVote) { + VoteAction.DownVote -> stringResource(R.string.downvoted) + else -> stringResource(R.string.downvote) + } } + VoteAction.NoVote -> {null} } @Composable private fun iconAndColor( - type: VoteType, + voteAction: VoteAction, instantScores: InstantScores, ): Pair = - when (type) { - VoteType.Upvote -> upvoteIconAndColor(myVote = instantScores.myVote) + when (voteAction) { + VoteAction.UpVote -> upvoteIconAndColor(myVote = instantScores.myVote) else -> downvoteIconAndColor(myVote = instantScores.myVote) } @Composable -fun upvoteIconAndColor(myVote: Int?): Pair = +fun upvoteIconAndColor(myVote: VoteAction?): Pair = when (myVote) { - 1 -> { + VoteAction.UpVote -> { Pair( ImageVector.vectorResource(id = R.drawable.up_filled), scoreColor(myVote = myVote), ) } - else -> { Pair( ImageVector.vectorResource(id = R.drawable.up_outline), @@ -192,9 +248,9 @@ fun upvoteIconAndColor(myVote: Int?): Pair = } @Composable -fun downvoteIconAndColor(myVote: Int?): Pair = +fun downvoteIconAndColor(myVote: VoteAction?): Pair = when (myVote) { - -1 -> { + VoteAction.DownVote -> { Pair( ImageVector.vectorResource(id = R.drawable.down_filled), scoreColor(myVote = myVote), @@ -210,9 +266,9 @@ fun downvoteIconAndColor(myVote: Int?): Pair = } @Composable -fun scoreIconAndColor(myVote: Int?): Pair = +fun scoreIconAndColor(myVote: VoteAction?): Pair = when (myVote) { - 1 -> { + VoteAction.UpVote -> { Pair( Icons.Outlined.Favorite, scoreColor(myVote = myVote), @@ -226,3 +282,84 @@ fun scoreIconAndColor(myVote: Int?): Pair = ) } } + +private fun showUpvotes( + myUserInfo: MyUserInfo?, + localSite: LocalSite, + type: PostOrCommentType, +): Boolean = enableUpvotes(localSite, type) && (myUserInfo?.local_user_view?.local_user?.show_upvotes ?: true) + +private fun showDownvotes( + myUserInfo: MyUserInfo?, + localSite: LocalSite, + type: PostOrCommentType, + creatorId: PersonId, +): Boolean { + val localUser = myUserInfo?.local_user_view?.local_user + val show = localUser?.show_downvotes === VoteShow.Show || + (localUser?.show_downvotes === VoteShow.ShowForOthers && localUser.person_id != creatorId) + return enableDownvotes(localSite, type) && show +} + +private fun showScore(myUserInfo: MyUserInfo?): Boolean = myUserInfo?.local_user_view?.local_user?.show_score ?: false + +private fun showPercentage( + myUserInfo: MyUserInfo?, + localSite: LocalSite, + type: PostOrCommentType, +): Boolean = myUserInfo?.local_user_view?.local_user?.show_upvote_percentage ?: true && enableUpvotes(localSite, type) && enableDownvotes(localSite, type) + +fun enableDownvotes( + localSite: LocalSite, + type: PostOrCommentType, +): Boolean = + when (type) { + PostOrCommentType.Post -> localSite.post_downvotes !== FederationMode.Disable + PostOrCommentType.Comment -> localSite.comment_downvotes !== FederationMode.Disable + } + +fun enableUpvotes( + localSite: LocalSite, + type: PostOrCommentType, +): Boolean = + when (type) { + PostOrCommentType.Post -> localSite.post_upvotes !== FederationMode.Disable + PostOrCommentType.Comment -> localSite.comment_upvotes !== FederationMode.Disable + } + +private fun upvotePercent( + upvotes: Long, + downvotes: Long, +): Float = (upvotes.toFloat() / (upvotes + downvotes)) + +private fun formatPercent(pct: Float): String = "%.0f".format(pct * 100F) + +private fun scoreOrPctStr( + myUserInfo: MyUserInfo?, + localSite: LocalSite, + type: PostOrCommentType, + score: Long, + upvotes: Long, + downvotes: Long, +): String? = + if (showScore(myUserInfo)) { + score.toString() + } else if (showPercentage(myUserInfo, localSite, type)) { + formatPercent(upvotePercent(upvotes, downvotes)) + } else { + null + } + +fun InstantScores.scoreOrPctStr( + myUserInfo: MyUserInfo?, + localSite: LocalSite, + type: PostOrCommentType, +): String? = + scoreOrPctStr( + myUserInfo = myUserInfo, + localSite = localSite, + type = type, + score = this.score, + upvotes = this.upvotes, + downvotes = this.downvotes, + ) 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 01b71b866..dab950165 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 @@ -56,8 +56,8 @@ import com.jerboa.ui.theme.ACTION_BAR_ICON_SIZE import com.jerboa.ui.theme.DRAWER_BANNER_SIZE import com.jerboa.ui.theme.MEDIUM_PADDING import it.vercruysse.lemmyapi.datatypes.CommunityView -import it.vercruysse.lemmyapi.dto.SortType -import it.vercruysse.lemmyapi.dto.SubscribedType +import it.vercruysse.lemmyapi.enums.CommunityFollowerState +import it.vercruysse.lemmyapi.enums.SortType import me.saket.cascade.CascadeDropdownMenu @Composable @@ -96,7 +96,7 @@ fun CommunityTopSection( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = communityView.community.title, + text = communityView.community.title ?: communityView.community.name, style = MaterialTheme.typography.titleMedium, ) } @@ -105,15 +105,15 @@ fun CommunityTopSection( text = stringResource( R.string.community_users_month, - communityView.counts.users_active_month, + communityView.community.users_active_month, ), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.outline, ) } Row { - when (communityView.subscribed) { - SubscribedType.Subscribed -> { + when (communityView.community_actions?.follow_state) { + CommunityFollowerState.Accepted -> { OutlinedButton( onClick = { onClickFollowCommunity(communityView) }, ) { @@ -129,19 +129,26 @@ fun CommunityTopSection( } } - SubscribedType.NotSubscribed -> { + CommunityFollowerState.Pending -> { Button( onClick = { onClickFollowCommunity(communityView) }, ) { - Text(stringResource(R.string.community_subscribe)) + Text(stringResource(R.string.community_pending)) } } - SubscribedType.Pending -> { + CommunityFollowerState.Denied -> { + Text( + stringResource(R.string.denied), + color = MaterialTheme.colorScheme.error, + ) + } + + null, CommunityFollowerState.ApprovalRequired -> { Button( onClick = { onClickFollowCommunity(communityView) }, ) { - Text(stringResource(R.string.community_pending)) + Text(stringResource(R.string.community_subscribe)) } } } @@ -272,7 +279,7 @@ fun CommunityMoreDropdown( text = { Text(text = stringResource(R.string.home_post_view_mode)) }, leadingIcon = { Icon(Icons.Outlined.ViewAgenda, contentDescription = null) }, children = { - PostViewMode.entries.map { + PostViewMode.entries.forEach { DropdownMenuItem( text = { Text(text = stringResource(it.resId)) }, onClick = { 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 a6d6f7c9c..4b69b7476 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 @@ -16,6 +16,7 @@ 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.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview @@ -24,8 +25,13 @@ import com.jerboa.R import com.jerboa.datatypes.sampleCommunity import com.jerboa.datatypes.sampleCommunityFederated import com.jerboa.datatypes.sampleCommunityView +import com.jerboa.datatypes.sampleMyUserInfo +import com.jerboa.db.APP_SETTINGS_DEFAULT +import com.jerboa.db.entity.AppSettings import com.jerboa.feat.BlurNSFW import com.jerboa.feat.needBlur +import com.jerboa.showAvatar +import com.jerboa.toEnumSafe import com.jerboa.ui.components.common.CircularIcon import com.jerboa.ui.components.common.ItemAndInstanceTitle import com.jerboa.ui.theme.DRAWER_ITEM_SPACING @@ -36,7 +42,9 @@ import com.jerboa.ui.theme.LARGE_PADDING import com.jerboa.ui.theme.LINK_ICON_SIZE import com.jerboa.ui.theme.SMALL_PADDING import it.vercruysse.lemmyapi.datatypes.Community +import it.vercruysse.lemmyapi.datatypes.CommunityId import it.vercruysse.lemmyapi.datatypes.CommunityView +import it.vercruysse.lemmyapi.datatypes.MyUserInfo @Composable fun CommunityName( @@ -47,9 +55,9 @@ fun CommunityName( style: TextStyle = MaterialTheme.typography.labelMedium, ) { ItemAndInstanceTitle( - title = community.title, + title = community.title ?: community.name, onClick = onClick, - actorId = community.actor_id, + apId = community.ap_id, local = community.local, modifier = modifier, itemColor = color, @@ -79,18 +87,21 @@ fun CommunityLink( size: Dp = ICON_SIZE, thumbnailSize: Int = ICON_THUMBNAIL_SIZE, style: TextStyle = MaterialTheme.typography.labelMedium, - onClick: (community: Community) -> Unit, clickable: Boolean = true, showDefaultIcon: Boolean, - showAvatar: Boolean, - blurNSFW: BlurNSFW, + appSettings: AppSettings, + myUserInfo: MyUserInfo?, + onClick: (CommunityId) -> Unit, ) { + val ctx = LocalContext.current + val showAvatar = myUserInfo.showAvatar(appSettings, ctx) + Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(spacing), modifier = if (clickable) { - Modifier.clickable { onClick(community) }.then(modifier) + Modifier.clickable { onClick(community.id) }.then(modifier) } else { modifier }, @@ -102,7 +113,7 @@ fun CommunityLink( contentDescription = null, size = size, thumbnailSize = thumbnailSize, - blur = blurNSFW.needBlur(community.nsfw), + blur = appSettings.blurNSFW.toEnumSafe().needBlur(community.nsfw), ) } ?: run { if (showDefaultIcon) { @@ -128,10 +139,10 @@ fun CommunityLink( @Composable fun CommunityLinkLarger( community: Community, - onClick: (community: Community) -> Unit, + myUserInfo: MyUserInfo?, + appSettings: AppSettings, showDefaultIcon: Boolean, - showAvatar: Boolean, - blurNSFW: BlurNSFW, + onClick: (CommunityId) -> Unit, ) { CommunityLink( community = community, @@ -146,22 +157,22 @@ fun CommunityLinkLarger( .fillMaxWidth(), onClick = onClick, showDefaultIcon = showDefaultIcon, - blurNSFW = blurNSFW, - showAvatar = showAvatar, + myUserInfo = myUserInfo, + appSettings = appSettings, ) } @Composable fun CommunityLinkLargerWithUserCount( communityView: CommunityView, - onClick: (community: Community) -> Unit, showDefaultIcon: Boolean, - showAvatar: Boolean, - blurNSFW: BlurNSFW, + myUserInfo: MyUserInfo?, + appSettings: AppSettings, + onClick: (CommunityId) -> Unit, ) { CommunityLink( community = communityView.community, - usersPerMonth = communityView.counts.users_active_month, + usersPerMonth = communityView.community.users_active_month, color = MaterialTheme.colorScheme.onSurface, size = LINK_ICON_SIZE, thumbnailSize = LARGER_ICON_THUMBNAIL_SIZE, @@ -173,8 +184,8 @@ fun CommunityLinkLargerWithUserCount( style = MaterialTheme.typography.titleMedium, onClick = onClick, showDefaultIcon = showDefaultIcon, - blurNSFW = blurNSFW, - showAvatar = showAvatar, + myUserInfo = myUserInfo, + appSettings = appSettings, ) } @@ -185,8 +196,8 @@ fun CommunityLinkPreview() { community = sampleCommunity, onClick = {}, showDefaultIcon = true, - showAvatar = true, - blurNSFW = BlurNSFW.NSFW, + myUserInfo = sampleMyUserInfo, + appSettings = APP_SETTINGS_DEFAULT, ) } @@ -197,7 +208,7 @@ fun CommunityLinkWithUsersPreview() { communityView = sampleCommunityView, onClick = {}, showDefaultIcon = true, - showAvatar = true, - blurNSFW = BlurNSFW.NSFW, + myUserInfo = sampleMyUserInfo, + appSettings = APP_SETTINGS_DEFAULT, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt b/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt index d7891f693..8518063e8 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt @@ -26,13 +26,14 @@ import androidx.lifecycle.viewmodel.compose.viewModel import arrow.core.Either import com.jerboa.JerboaAppState import com.jerboa.R +import com.jerboa.SelectionVisibilityState import com.jerboa.api.ApiState +import com.jerboa.api.toOpt import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.db.entity.isAnon import com.jerboa.feat.BlurNSFW import com.jerboa.feat.PostActionBarMode import com.jerboa.feat.SwipeToActionPreset -import com.jerboa.feat.VoteType import com.jerboa.feat.changeBlurTypeInsideCommunity import com.jerboa.feat.doIfReadyElseDisplayInfo import com.jerboa.feat.newVote @@ -41,6 +42,7 @@ import com.jerboa.hostName import com.jerboa.model.AccountViewModel import com.jerboa.model.AppSettingsViewModel import com.jerboa.model.CommunityViewModel +import com.jerboa.model.MyUserInfoViewModel import com.jerboa.model.ReplyItem import com.jerboa.model.SiteViewModel import com.jerboa.scrollToTop @@ -70,7 +72,8 @@ import it.vercruysse.lemmyapi.datatypes.MarkPostAsRead import it.vercruysse.lemmyapi.datatypes.PersonView import it.vercruysse.lemmyapi.datatypes.PostView import it.vercruysse.lemmyapi.datatypes.SavePost -import it.vercruysse.lemmyapi.dto.SubscribedType +import it.vercruysse.lemmyapi.enums.CommunityFollowerState +import it.vercruysse.lemmyapi.enums.VoteAction @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -78,6 +81,7 @@ fun CommunityScreen( communityArg: Either, appState: JerboaAppState, siteViewModel: SiteViewModel, + myUserInfoViewModel: MyUserInfoViewModel, accountViewModel: AccountViewModel, appSettingsViewModel: AppSettingsViewModel, showVotingArrowsInListView: Boolean, @@ -133,7 +137,7 @@ fun CommunityScreen( is ApiState.Success -> { val communityId = communityRes.data.community_view.community.id - val instance = hostName(communityRes.data.community_view.community.actor_id) + val instance = hostName(communityRes.data.community_view.community.ap_id) val communityName = communityRes.data.community_view.community.name + if (instance != null) "@$instance" else "" @@ -161,13 +165,13 @@ fun CommunityScreen( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, accountViewModel, ) { communityViewModel.blockCommunity( BlockCommunity( community_id = communityId, - block = !communityRes.data.community_view.blocked, + block = communityRes.data.community_view.community_actions?.blocked_at == null, ), ctx = ctx, ) @@ -176,14 +180,14 @@ fun CommunityScreen( onClickCommunityInfo = { appState.toCommunitySideBar(communityRes.data) }, onClickCommunityShare = { shareLink( - communityRes.data.community_view.community.actor_id, + communityRes.data.community_view.community.ap_id, ctx, resources, ) }, onClickBack = appState::navigateUp, selectedPostViewMode = getPostViewMode(appSettingsViewModel), - isBlocked = communityRes.data.community_view.blocked, + isBlocked = communityRes.data.community_view.community_actions?.blocked_at != null, ) } @@ -211,23 +215,13 @@ fun CommunityScreen( is ApiState.Holder -> { val communityRes = communityViewModel.communityRes - val moderators = - remember(communityRes) { - when (communityRes) { - is ApiState.Success -> { - communityRes.data.moderators - } - - else -> { - null - } - } - } PostListings( posts = postsRes.data, - admins = siteViewModel.admins(), - moderators = remember(moderators) { moderators?.map { it.moderator.id } }, + admins = siteViewModel.siteRes.toOpt()?.admins ?: emptyList(), + TODO + myUserInfo = myUserInfo, + localSite = localSite, contentAboveListings = { when (communityRes) { is ApiState.Success -> { @@ -240,14 +234,14 @@ fun CommunityScreen( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, accountViewModel, ) { communityViewModel.followCommunity( form = FollowCommunity( community_id = cfv.community.id, - follow = cfv.subscribed == SubscribedType.NotSubscribed, + cfv.community_actions?.follow_state != CommunityFollowerState.Accepted ), onSuccess = { siteViewModel.getSite() @@ -269,13 +263,13 @@ fun CommunityScreen( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, accountViewModel, ) { communityViewModel.likePost( form = CreatePostLike( post_id = postView.post.id, - score = newVote(postView.my_vote, VoteType.Upvote), + vote = newVote(postView.post_actions?.vote, VoteAction.UpVote), ), ) } @@ -287,13 +281,13 @@ fun CommunityScreen( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, accountViewModel, ) { communityViewModel.likePost( form = CreatePostLike( post_id = postView.post.id, - score = newVote(postView.my_vote, VoteType.Downvote), + vote = newVote(postView.post_actions?.vote, VoteAction.DownVote), ), ) } @@ -308,13 +302,13 @@ fun CommunityScreen( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, accountViewModel, ) { communityViewModel.savePost( form = SavePost( post_id = postView.post.id, - save = !postView.saved, + postView.post_actions?.saved_at == null, ), ) } @@ -336,7 +330,7 @@ fun CommunityScreen( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, accountViewModel, ) { communityViewModel.deletePost( @@ -354,13 +348,13 @@ fun CommunityScreen( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, accountViewModel, ) { communityViewModel.hidePost( HidePost( - post_ids = listOf(postView.post.id), - hide = !postView.hidden, + post_id = postView.post.id, + hide = postView.post_actions?.hidden_at == null, ), ctx, ) @@ -372,8 +366,8 @@ fun CommunityScreen( onRemoveClick = { pv -> appState.toPostRemove(post = pv.post) }, - onBanPersonClick = { p -> - appState.toBanPerson(p) + onBanPersonClick = { pv -> + appState.toBanPerson(pv) }, onBanFromCommunityClick = { d -> appState.toBanFromCommunity(banData = d) @@ -385,13 +379,14 @@ fun CommunityScreen( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, accountViewModel, ) { communityViewModel.lockPost( LockPost( post_id = pv.post.id, locked = !pv.post.locked, + reason = TODO(), ), ) } @@ -403,7 +398,7 @@ fun CommunityScreen( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, accountViewModel, ) { communityViewModel.featurePost( @@ -430,8 +425,6 @@ fun CommunityScreen( listState = postListState, postViewMode = getPostViewMode(appSettingsViewModel), showVotingArrowsInListView = showVotingArrowsInListView, - enableDownVotes = siteViewModel.enableDownvotes(), - showAvatar = siteViewModel.showAvatar() && !lowBandwidthMode, useCustomTabs = useCustomTabs, usePrivateTabs = usePrivateTabs, blurNSFW = blurNSFW.changeBlurTypeInsideCommunity(), @@ -439,24 +432,23 @@ fun CommunityScreen( appState = appState, markAsReadOnScroll = markAsReadOnScroll, onMarkAsRead = { postView -> - if (!account.isAnon() && !postView.read) { + if (!account.isAnon() && postView.post_actions?.read_at == null) { communityViewModel.markPostAsRead( MarkPostAsRead( - post_ids = listOf(postView.post.id), + post_id = postView.post.id, read = true, ), - postView, appState, ) } }, showIfRead = true, - voteDisplayMode = siteViewModel.voteDisplayMode(), postActionBarMode = postActionBarMode, showPostAppendRetry = communityViewModel.postsRes is ApiState.AppendingFailure, swipeToActionPreset = swipeToActionPreset, disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, + selectionState = SelectionVisibilityState.NoSelection,, ) } @@ -476,7 +468,7 @@ fun CommunityScreen( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, accountViewModel, loginAsToast = false, ) { diff --git a/app/src/main/java/com/jerboa/ui/components/community/list/CommunityList.kt b/app/src/main/java/com/jerboa/ui/components/community/list/CommunityList.kt index ac58b9945..cce10bc87 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/list/CommunityList.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/list/CommunityList.kt @@ -87,7 +87,7 @@ fun CommunityListings( contentType = { "communitylink" }, ) { item -> // A hack for the community follower views that were coerced into community views without counts - if (item.counts.users_active_month == 0L) { + if (item.community.users_active_month == 0L) { CommunityLinkLarger( community = item.community, onClick = onClickCommunity, diff --git a/app/src/main/java/com/jerboa/ui/components/community/list/CommunityListScreen.kt b/app/src/main/java/com/jerboa/ui/components/community/list/CommunityListScreen.kt index d7a741a7f..490fa62f7 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/list/CommunityListScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/list/CommunityListScreen.kt @@ -19,18 +19,21 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.viewModel import com.jerboa.JerboaAppState import com.jerboa.api.ApiState -import com.jerboa.feat.BlurNSFW +import com.jerboa.db.entity.AppSettings import com.jerboa.model.CommunityListViewModel +import com.jerboa.showAvatar +import com.jerboa.toEnumSafe 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.datatypes.CommunityFollowerView -import it.vercruysse.lemmyapi.datatypes.Search -import it.vercruysse.lemmyapi.dto.SearchType -import it.vercruysse.lemmyapi.dto.SortType +import it.vercruysse.lemmyapi.datatypes.ListCommunities +import it.vercruysse.lemmyapi.datatypes.MyUserInfo +import it.vercruysse.lemmyapi.enums.ListingType +import it.vercruysse.lemmyapi.enums.SortType import kotlinx.coroutines.launch object CommunityListReturn { @@ -40,15 +43,17 @@ object CommunityListReturn { @Composable fun CommunityListScreen( appState: JerboaAppState, + appSettings: AppSettings, + myUserInfo: MyUserInfo?, selectMode: Boolean = false, - followList: List, - blurNSFW: BlurNSFW, drawerState: DrawerState, - showAvatar: Boolean, padding: PaddingValues? = null, ) { Log.d("jerboa", "got to community list screen") + val ctx = LocalContext.current + + val followList = myUserInfo?.follows ?: emptyList() val communityListViewModel: CommunityListViewModel = viewModel(factory = CommunityListViewModel.Companion.Factory(followList)) @@ -57,11 +62,11 @@ fun CommunityListScreen( // Upon launch from process death LaunchedEffect(Unit) { if (search.isNotEmpty()) { - communityListViewModel.searchCommunities( + communityListViewModel.listCommunities( form = - Search( - q = search, - type_ = SearchType.Communities, + ListCommunities( + search_term = search, + type_ = ListingType.All, sort = SortType.TopAll, ), ) @@ -95,11 +100,11 @@ fun CommunityListScreen( search = search, onSearchChange = { search = it - communityListViewModel.searchCommunities( + communityListViewModel.listCommunities( form = - Search( - q = search, - type_ = SearchType.Communities, + ListCommunities( + search_term = search, + type_ = ListingType.All, sort = SortType.TopAll, ), ) @@ -107,7 +112,7 @@ fun CommunityListScreen( ) }, content = { padding -> - when (val communitiesRes = communityListViewModel.searchRes) { + when (val communitiesRes = communityListViewModel.listRes) { ApiState.Empty -> { ApiEmptyText() } @@ -122,7 +127,7 @@ fun CommunityListScreen( is ApiState.Success -> { CommunityListings( - communities = communitiesRes.data.communities, + communities = communitiesRes.data.items, onClickCommunity = { cs -> if (selectMode) { appState.apply { @@ -137,8 +142,8 @@ fun CommunityListScreen( Modifier .padding(padding) .imePadding(), - blurNSFW = blurNSFW, - showAvatar = showAvatar, + blurNSFW =appSettings.blurNSFW.toEnumSafe(), + showAvatar = myUserInfo.showAvatar(appSettings, ctx), ) } diff --git a/app/src/main/java/com/jerboa/ui/components/community/sidebar/CommunitySidebar.kt b/app/src/main/java/com/jerboa/ui/components/community/sidebar/CommunitySidebar.kt index 82f1d39c2..692213cf3 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/sidebar/CommunitySidebar.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/sidebar/CommunitySidebar.kt @@ -12,19 +12,20 @@ fun CommunitySidebar( onPersonClick: (PersonId) -> Unit, ) { val community = communityRes.community_view.community - val counts = communityRes.community_view.counts Sidebar( + name = community.name, title = community.title, - content = community.description, + content = community.sidebar, + summary = community.summary, banner = community.banner, icon = community.icon, - published = community.published, - usersActiveDay = counts.users_active_day, - usersActiveWeek = counts.users_active_week, - usersActiveMonth = counts.users_active_month, - usersActiveHalfYear = counts.users_active_half_year, - postCount = counts.posts, - commentCount = counts.comments, + published = community.published_at, + usersActiveDay = community.users_active_day, + usersActiveWeek = community.users_active_week, + usersActiveMonth = community.users_active_month, + usersActiveHalfYear = community.users_active_half_year, + postCount = community.posts, + commentCount = community.comments, moderators = communityRes.moderators, admins = emptyList(), showAvatar = showAvatar, diff --git a/app/src/main/java/com/jerboa/ui/components/community/sidebar/CommunitySidebarScreen.kt b/app/src/main/java/com/jerboa/ui/components/community/sidebar/CommunitySidebarScreen.kt index 91d86bb84..78058f22b 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/sidebar/CommunitySidebarScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/sidebar/CommunitySidebarScreen.kt @@ -10,7 +10,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.jerboa.JerboaAppState import com.jerboa.R +import com.jerboa.api.toOpt import com.jerboa.hostName +import com.jerboa.model.MyUserInfoViewModel +import com.jerboa.showAvatar import com.jerboa.ui.components.common.SimpleTopAppBar import it.vercruysse.lemmyapi.datatypes.GetCommunityResponse @@ -23,7 +26,8 @@ object CommunityViewSidebar { fun CommunitySidebarScreen( appState: JerboaAppState, onClickBack: () -> Unit, - showAvatar: Boolean, + myUserInfoViewModel: MyUserInfoViewModel, + lowBandwidthMode: Boolean, ) { Log.d("jerboa", "got to community sidebar screen") val communityRes = appState.getPrevReturn(CommunityViewSidebar.COMMUNITY_RES) @@ -36,7 +40,7 @@ fun CommunitySidebarScreen( stringResource( R.string.actionbar_info_header, community.name, - hostName(community.actor_id) ?: "invalid_actor_id", + hostName(community.ap_id) ?: "invalid_actor_id", ), onClickBack = onClickBack, ) @@ -46,7 +50,7 @@ fun CommunitySidebarScreen( CommunitySidebar( communityRes = communityRes, onPersonClick = appState::toProfile, - showAvatar = showAvatar, + showAvatar = myUserInfoViewModel.myUserRes.toOpt().showAvatar(lowBandwidthMode), ) } }, 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 1df1187ce..a9e7ba2ea 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 @@ -43,16 +43,18 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.viewmodel.compose.viewModel import com.jerboa.R -import com.jerboa.datatypes.UserViewType import com.jerboa.datatypes.getDisplayName import com.jerboa.datatypes.samplePerson +import com.jerboa.datatypes.sampleUnreadCountsResponse +import com.jerboa.db.APP_SETTINGS_DEFAULT import com.jerboa.db.entity.Account import com.jerboa.db.entity.AnonAccount +import com.jerboa.db.entity.AppSettings import com.jerboa.db.entity.isAnon import com.jerboa.db.entity.isReady -import com.jerboa.feat.BlurNSFW import com.jerboa.model.AccountViewModel import com.jerboa.model.AccountViewModelFactory +import com.jerboa.toEnumSafe import com.jerboa.ui.components.common.IconAndTextDrawerItem import com.jerboa.ui.components.common.LargerCircularIcon import com.jerboa.ui.components.common.PictrsBannerImage @@ -64,19 +66,17 @@ import com.jerboa.ui.theme.DRAWER_BANNER_SIZE import com.jerboa.ui.theme.LARGE_PADDING import com.jerboa.ui.theme.SMALL_PADDING import com.jerboa.ui.theme.XL_PADDING +import com.jerboa.userViewType import it.vercruysse.lemmyapi.datatypes.Community -import it.vercruysse.lemmyapi.datatypes.CommunityFollowerView import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.Person -import it.vercruysse.lemmyapi.dto.ListingType +import it.vercruysse.lemmyapi.datatypes.UnreadCountsResponse +import it.vercruysse.lemmyapi.enums.ListingType @Composable fun Drawer( myUserInfo: MyUserInfo?, - follows: List, - unreadCount: Long, - unreadAppCount: Long?, - unreadReportCount: Long?, + unreadCounts: UnreadCountsResponse?, accountViewModel: AccountViewModel, onAddAccount: () -> Unit, onSwitchAccountClick: (account: Account) -> Unit, @@ -86,11 +86,9 @@ fun Drawer( onCommunityClick: (community: Community) -> Unit, onClickSettings: () -> Unit, isOpen: Boolean, - blurNSFW: BlurNSFW, - showBottomNav: Boolean, closeDrawer: () -> Unit, onSelectTab: (NavTab) -> Unit, - userViewType: UserViewType, + appSettings: AppSettings, ) { var showAccountAddMode by rememberSaveable { mutableStateOf(false) } @@ -106,11 +104,9 @@ fun Drawer( // Drawer items DrawerContent( accountViewModel = accountViewModel, - follows = follows, - unreadCount = unreadCount, - unreadAppCount = unreadAppCount, - unreadReportCount = unreadReportCount, + appSettings = appSettings, myUserInfo = myUserInfo, + unreadCounts = unreadCounts, showAccountAddMode = showAccountAddMode, onAddAccount = onAddAccount, onSwitchAccountClick = onSwitchAccountClick, @@ -118,19 +114,18 @@ fun Drawer( onClickListingType = onClickListingType, onCommunityClick = onCommunityClick, onClickSettings = onClickSettings, - blurNSFW = blurNSFW, - showBottomNav = showBottomNav, closeDrawer = closeDrawer, onSelectTab = onSelectTab, onSwitchAnon = onSwitchAnon, - userViewType = userViewType, ) } @Composable fun DrawerContent( + myUserInfo: MyUserInfo?, + unreadCounts: UnreadCountsResponse?, + appSettings: AppSettings, showAccountAddMode: Boolean, - follows: List, onAddAccount: () -> Unit, accountViewModel: AccountViewModel, onSwitchAccountClick: (account: Account) -> Unit, @@ -138,17 +133,11 @@ fun DrawerContent( onClickListingType: (ListingType) -> Unit, onCommunityClick: (community: Community) -> Unit, onClickSettings: () -> Unit, - myUserInfo: MyUserInfo?, - unreadCount: Long, - unreadAppCount: Long?, - unreadReportCount: Long?, - blurNSFW: BlurNSFW, - showBottomNav: Boolean, closeDrawer: () -> Unit, onSelectTab: (NavTab) -> Unit, onSwitchAnon: () -> Unit, - userViewType: UserViewType, ) { + AnimatedVisibility( visible = showAccountAddMode, enter = expandVertically(), @@ -167,39 +156,33 @@ fun DrawerContent( HorizontalDivider() DrawerItemsMain( myUserInfo = myUserInfo, - follows = follows, + unreadCounts = unreadCounts, + appSettings = appSettings, onClickListingType = onClickListingType, onCommunityClick = onCommunityClick, - unreadCount = unreadCount, - unreadAppCount = unreadAppCount, - unreadReportCount = unreadReportCount, onClickSettings = onClickSettings, - blurNSFW = blurNSFW, - showBottomNav = showBottomNav, onSelectTab = onSelectTab, closeDrawer = closeDrawer, - userViewType = userViewType, ) } @Composable fun DrawerItemsMain( myUserInfo: MyUserInfo?, - follows: List, + appSettings: AppSettings, + unreadCounts: UnreadCountsResponse?, onClickSettings: () -> Unit, onClickListingType: (ListingType) -> Unit, onCommunityClick: (community: Community) -> Unit, - unreadCount: Long, - unreadAppCount: Long?, - unreadReportCount: Long?, - blurNSFW: BlurNSFW, - showBottomNav: Boolean, closeDrawer: () -> Unit, onSelectTab: (NavTab) -> Unit, - userViewType: UserViewType, ) { val listState = rememberLazyListState() + val follows = myUserInfo?.follows + ?.sortedBy { (it.community.title ?: it.community.name).lowercase() } + .orEmpty() + LazyColumn( state = listState, ) { @@ -239,8 +222,8 @@ fun DrawerItemsMain( HorizontalDivider() } - if (!showBottomNav) { - items(NavTab.getEntries(userViewType)) { + if (!appSettings.showBottomNav) { + items(NavTab.getEntries(myUserInfo.userViewType())) { IconAndTextDrawerItem( text = stringResource(it.textId), icon = it.iconOutlined, @@ -249,9 +232,9 @@ fun DrawerItemsMain( closeDrawer() }, iconBadgeCount = when (it) { - NavTab.Inbox -> unreadCount - NavTab.RegistrationApplications -> unreadAppCount - NavTab.Reports -> unreadReportCount + NavTab.Inbox -> unreadCounts?.notification_count + NavTab.RegistrationApplications -> unreadCounts?.registration_application_count + NavTab.Reports -> unreadCounts?.report_count else -> null }, contentDescription = stringResource(id = it.contentDescriptionId), @@ -293,7 +276,7 @@ fun DrawerItemsMain( onClick = onCommunityClick, showDefaultIcon = true, showAvatar = true, - blurNSFW = blurNSFW, + blurNSFW = appSettings.blurNSFW.toEnumSafe(), ) } } @@ -305,18 +288,13 @@ fun DrawerItemsMain( fun DrawerItemsMainPreview() { DrawerItemsMain( myUserInfo = null, - follows = emptyList(), + appSettings = APP_SETTINGS_DEFAULT, + unreadCounts = sampleUnreadCountsResponse, + onClickSettings = {}, onClickListingType = {}, onCommunityClick = {}, - onClickSettings = {}, - unreadCount = 2, - unreadReportCount = 5, - unreadAppCount = null, - blurNSFW = BlurNSFW.NSFW, - showBottomNav = false, closeDrawer = {}, onSelectTab = {}, - userViewType = UserViewType.Normal, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/drawer/MainDrawer.kt b/app/src/main/java/com/jerboa/ui/components/drawer/MainDrawer.kt index 04ae24201..229e86881 100644 --- a/app/src/main/java/com/jerboa/ui/components/drawer/MainDrawer.kt +++ b/app/src/main/java/com/jerboa/ui/components/drawer/MainDrawer.kt @@ -3,34 +3,31 @@ package com.jerboa.ui.components.drawer import androidx.activity.compose.BackHandler import androidx.compose.material3.DrawerState import androidx.compose.runtime.Composable -import com.jerboa.api.ApiState import com.jerboa.closeDrawer -import com.jerboa.datatypes.UserViewType +import com.jerboa.db.entity.AppSettings import com.jerboa.db.entity.isAnon -import com.jerboa.db.entity.isReady -import com.jerboa.feat.BlurNSFW import com.jerboa.model.AccountViewModel import com.jerboa.model.HomeViewModel -import com.jerboa.model.SiteViewModel import com.jerboa.ui.components.common.getCurrentAccount import com.jerboa.ui.components.home.NavTab import it.vercruysse.lemmyapi.datatypes.CommunityId +import it.vercruysse.lemmyapi.datatypes.MyUserInfo +import it.vercruysse.lemmyapi.datatypes.UnreadCountsResponse import kotlinx.coroutines.CoroutineScope @Composable fun MainDrawer( - siteViewModel: SiteViewModel, + myUserInfo: MyUserInfo?, + unreadCounts: UnreadCountsResponse?, accountViewModel: AccountViewModel, homeViewModel: HomeViewModel, + appSettings: AppSettings, scope: CoroutineScope, drawerState: DrawerState, onSettingsClick: () -> Unit, onCommunityClick: (CommunityId) -> Unit, onClickLogin: () -> Unit, onSelectTab: (NavTab) -> Unit, - blurNSFW: BlurNSFW, - showBottomNav: Boolean, - userViewType: UserViewType, ) { val account = getCurrentAccount(accountViewModel) @@ -39,45 +36,10 @@ fun MainDrawer( } Drawer( - myUserInfo = - when (val res = siteViewModel.siteRes) { - is ApiState.Success -> { - // JWT Failed - if (!account.isAnon() && account.isReady() && res.data.my_user == null) { - accountViewModel.invalidateAccount(account) - } - res.data.my_user - } - - is ApiState.Failure -> { - // Invalidate account - if (account.isReady()) { - accountViewModel.invalidateAccount(account) - } - - null - } - - else -> { - null - } - }, - follows = when (val res = siteViewModel.siteRes) { - is ApiState.Success -> { - res.data.my_user - ?.follows - ?.sortedBy { it.community.title.lowercase() } - .orEmpty() - } - - else -> { - emptyList() - } - }, - unreadCount = siteViewModel.unreadCount, - unreadAppCount = siteViewModel.unreadAppCount, - unreadReportCount = siteViewModel.unreadReportCount, + myUserInfo = myUserInfo, + unreadCounts = unreadCounts, accountViewModel = accountViewModel, + appSettings = appSettings, onAddAccount = onClickLogin, isOpen = drawerState.isOpen, onSwitchAccountClick = { acct -> @@ -93,7 +55,7 @@ fun MainDrawer( } }, onSwitchAnon = { - if (!account.isAnon()) { + if (myUserInfo != null) { accountViewModel.removeCurrent(true).invokeOnCompletion { onSelectTab(NavTab.Home) closeDrawer(scope, drawerState) @@ -113,10 +75,7 @@ fun MainDrawer( onSettingsClick() closeDrawer(scope, drawerState) }, - blurNSFW = blurNSFW, - showBottomNav = showBottomNav, onSelectTab = onSelectTab, closeDrawer = { closeDrawer(scope, drawerState) }, - userViewType = userViewType, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/home/AppStartupDialogs.kt b/app/src/main/java/com/jerboa/ui/components/home/AppStartupDialogs.kt index e5113b0a5..07372b822 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/AppStartupDialogs.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/AppStartupDialogs.kt @@ -15,21 +15,20 @@ import androidx.compose.ui.platform.LocalUriHandler import com.jerboa.BuildConfig import com.jerboa.DONATE_LINK import com.jerboa.api.API -import com.jerboa.api.ApiState import com.jerboa.getVersionCode import com.jerboa.model.AppSettingsViewModel -import com.jerboa.model.SiteViewModel +import com.jerboa.shouldShowDonation import com.jerboa.ui.components.common.DonationNotificationDialog import com.jerboa.ui.components.common.ShowChangelog +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import java.time.OffsetDateTime @OptIn(ExperimentalComposeUiApi::class) @Composable fun ShowAppStartupDialogs( appSettingsViewModel: AppSettingsViewModel, - siteViewModel: SiteViewModel, + myUserInfo: MyUserInfo?, ) { val ctx = LocalContext.current val scope = rememberCoroutineScope() @@ -43,7 +42,7 @@ fun ShowAppStartupDialogs( // Check which dialogs need to be shown val changelogNeedsToShow = shouldShowChangelog(lastViewedVersion, currentVersionCode) - val donationNeedsToShow = shouldShowDonation(siteViewModel) + val donationNeedsToShow = myUserInfo.shouldShowDonation() // Determine which dialog to show LaunchedEffect(changelogNeedsToShow, donationNeedsToShow) { @@ -106,26 +105,6 @@ fun shouldShowChangelog( currentVersionCode: Int, ): Boolean = lastViewedVersion != null && lastViewedVersion != currentVersionCode -fun shouldShowDonation(siteViewModel: SiteViewModel): Boolean { - val siteRes = siteViewModel.siteRes - if (siteRes !is ApiState.Success) return false - - val lastDonationNotification = siteRes.data.my_user - ?.local_user_view - ?.local_user - ?.last_donation_notification - ?: return false - - return try { - val lastDonationTime = OffsetDateTime.parse(lastDonationNotification) - val oneYearAgo = OffsetDateTime.now().minusYears(1) - lastDonationTime.isBefore(oneYearAgo) - } catch (e: Exception) { - Log.e("ShowAppStartupDialogs", "Failed to parse donation time", e) - false - } -} - fun markDonationNotificationShown( scope: CoroutineScope, onComplete: () -> Unit, diff --git a/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt b/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt deleted file mode 100644 index d588cb06a..000000000 --- a/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt +++ /dev/null @@ -1,372 +0,0 @@ -package com.jerboa.ui.components.home - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.AppRegistration -import androidx.compose.material.icons.filled.Bookmarks -import androidx.compose.material.icons.filled.Email -import androidx.compose.material.icons.filled.Flag -import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.Person -import androidx.compose.material.icons.filled.Search -import androidx.compose.material.icons.outlined.AppRegistration -import androidx.compose.material.icons.outlined.Bookmarks -import androidx.compose.material.icons.outlined.Email -import androidx.compose.material.icons.outlined.Flag -import androidx.compose.material.icons.outlined.Home -import androidx.compose.material.icons.outlined.Person -import androidx.compose.material.icons.outlined.Search -import androidx.compose.material3.DrawerState -import androidx.compose.material3.ModalDrawerSheet -import androidx.compose.material3.ModalNavigationDrawer -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState -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.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalResources -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTagsAsResourceId -import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import arrow.core.Either -import com.jerboa.JerboaAppState -import com.jerboa.R -import com.jerboa.datatypes.UserViewType -import com.jerboa.db.entity.AnonAccount -import com.jerboa.db.entity.AppSettings -import com.jerboa.db.entity.userViewType -import com.jerboa.feat.doIfReadyElseDisplayInfo -import com.jerboa.model.AccountViewModel -import com.jerboa.model.AppSettingsViewModel -import com.jerboa.model.HomeViewModel -import com.jerboa.model.SiteViewModel -import com.jerboa.toBool -import com.jerboa.toEnum -import com.jerboa.ui.components.common.BottomAppBarAll -import com.jerboa.ui.components.common.GuardAccount -import com.jerboa.ui.components.common.JerboaSnackbarHost -import com.jerboa.ui.components.community.list.CommunityListScreen -import com.jerboa.ui.components.drawer.MainDrawer -import com.jerboa.ui.components.inbox.InboxScreen -import com.jerboa.ui.components.person.PersonProfileScreen -import com.jerboa.ui.components.registrationapplications.RegistrationApplicationsScreen -import com.jerboa.ui.components.reports.ReportsScreen -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - -enum class NavTab( - val textId: Int, - val iconOutlined: ImageVector, - val iconFilled: ImageVector, - val contentDescriptionId: Int, - val userViewType: UserViewType, - val needsLogin: Boolean, -) { - Home( - textId = R.string.bottomBar_label_home, - iconOutlined = Icons.Outlined.Home, - iconFilled = Icons.Filled.Home, - contentDescriptionId = R.string.bottomBar_home, - userViewType = UserViewType.Normal, - needsLogin = false, - ), - Search( - textId = R.string.bottomBar_label_search, - iconOutlined = Icons.Outlined.Search, - iconFilled = Icons.Filled.Search, - contentDescriptionId = R.string.bottomBar_search, - userViewType = UserViewType.Normal, - needsLogin = false, - ), - Inbox( - textId = R.string.bottomBar_label_inbox, - iconOutlined = Icons.Outlined.Email, - iconFilled = Icons.Filled.Email, - contentDescriptionId = R.string.bottomBar_inbox, - userViewType = UserViewType.Normal, - needsLogin = true, - ), - RegistrationApplications( - R.string.applications_request_shorthand, - Icons.Outlined.AppRegistration, - Icons.Filled.AppRegistration, - R.string.bottomBar_registrations, - userViewType = UserViewType.AdminOnly, - needsLogin = true, - ), - Reports( - R.string.reports, - Icons.Outlined.Flag, - Icons.Filled.Flag, - R.string.bottomBar_reports, - userViewType = UserViewType.AdminOrMod, - needsLogin = true, - ), - Saved( - textId = R.string.bottomBar_label_bookmarks, - iconOutlined = Icons.Outlined.Bookmarks, - iconFilled = Icons.Filled.Bookmarks, - contentDescriptionId = R.string.bottomBar_bookmarks, - userViewType = UserViewType.Normal, - needsLogin = true, - ), - Profile( - textId = R.string.bottomBar_label_profile, - iconOutlined = Icons.Outlined.Person, - iconFilled = Icons.Filled.Person, - contentDescriptionId = R.string.bottomBar_profile, - userViewType = UserViewType.Normal, - needsLogin = true, - ), - ; - - companion object { - fun getEntries(userViewType: UserViewType) = - when (userViewType) { - UserViewType.Normal -> NavTab.entries.filter { it.userViewType == UserViewType.Normal } - UserViewType.AdminOrMod -> NavTab.entries.filter { it.userViewType != UserViewType.AdminOnly } - UserViewType.AdminOnly -> NavTab.entries - } - } -} - -@OptIn( - ExperimentalComposeUiApi::class, -) -@Composable -fun BottomNavScreen( - appState: JerboaAppState, - accountViewModel: AccountViewModel, - siteViewModel: SiteViewModel, - appSettingsViewModel: AppSettingsViewModel, - appSettings: AppSettings, - drawerState: DrawerState, - lowBandwidthMode: Boolean, -) { - val acc by accountViewModel.currentAccount.observeAsState(GuardAccount) - val account by remember { - derivedStateOf { acc ?: AnonAccount } - } - val ctx = LocalContext.current - val resources = LocalResources.current - val scope = rememberCoroutineScope() - val homeViewModel: HomeViewModel = viewModel(factory = HomeViewModel.Factory) - - val bottomNavController = rememberNavController() - val snackbarHostState = remember(account) { SnackbarHostState() } - var selectedTab by rememberSaveable { mutableStateOf(NavTab.Home) } - - val onInnerSelectTab = { tab: NavTab -> - selectedTab = tab - val currentRoute = bottomNavController.currentDestination?.route - if (currentRoute == tab.name && tab == NavTab.Home) { - scope.launch(Dispatchers.Main) { - homeViewModel.lazyListState.animateScrollToItem(0) - } - } else { - scope.launch(Dispatchers.Main) { - bottomNavController.navigate(tab.name) { - launchSingleTop = true - popUpTo(bottomNavController.graph.id) // To make back button close the app. - } - } - } - } - - val onSelectTab: (NavTab) -> Unit = { tab: NavTab -> - if (tab.needsLogin) { - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - loginAsToast = false, - ) { - onInnerSelectTab(tab) - } - } else { - onInnerSelectTab(tab) - } - } - - ModalNavigationDrawer( - gesturesEnabled = true, - drawerState = drawerState, - drawerContent = { - ModalDrawerSheet( - content = { - MainDrawer( - siteViewModel = siteViewModel, - accountViewModel = accountViewModel, - homeViewModel = homeViewModel, - scope = scope, - drawerState = drawerState, - onSelectTab = onSelectTab, - blurNSFW = appSettings.blurNSFW.toEnum(), - showBottomNav = appSettings.showBottomNav, - onCommunityClick = appState::toCommunity, - onSettingsClick = appState::toSettings, - onClickLogin = appState::toLogin, - userViewType = account.userViewType(), - ) - }, - ) - }, - modifier = Modifier.semantics { testTagsAsResourceId = true }, - content = { - Scaffold( - snackbarHost = { JerboaSnackbarHost(snackbarHostState) }, - bottomBar = { - if (appSettings.showBottomNav && acc !== GuardAccount) { - BottomAppBarAll( - selectedTab = selectedTab, - unreadCounts = siteViewModel.unreadCount, - unreadAppCount = siteViewModel.unreadAppCount, - unreadReportCount = siteViewModel.unreadReportCount, - showTextDescriptionsInNavbar = appSettings.showTextDescriptionsInNavbar, - userViewType = account.userViewType(), - onSelect = onSelectTab, - ) - } - }, - ) { padding -> - NavHost( - navController = bottomNavController, - startDestination = NavTab.Home.name, - ) { - composable(route = NavTab.Home.name) { - HomeScreen( - appState = appState, - homeViewModel = homeViewModel, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - drawerState = drawerState, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), - lowBandwidthMode = lowBandwidthMode, - padding = padding, - ) - } - - composable(route = NavTab.Search.name) { - CommunityListScreen( - appState = appState, - selectMode = false, - followList = siteViewModel.getFollowList(), - blurNSFW = appSettings.blurNSFW.toEnum(), - drawerState = drawerState, - showAvatar = siteViewModel.showAvatar() && !lowBandwidthMode, - padding = padding, - ) - } - - composable(route = NavTab.Inbox.name) { - InboxScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - blurNSFW = appSettings.blurNSFW.toEnum(), - lowBandwidthMode = lowBandwidthMode, - drawerState = drawerState, - padding = padding, - ) - } - - composable(route = NavTab.RegistrationApplications.name) { - RegistrationApplicationsScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - drawerState = drawerState, - lowBandwidthMode = lowBandwidthMode, - padding = padding, - ) - } - - composable(route = NavTab.Reports.name) { - ReportsScreen( - appState = appState, - accountViewModel = accountViewModel, - siteViewModel = siteViewModel, - drawerState = drawerState, - blurNSFW = appSettings.blurNSFW.toEnum(), - lowBandwidthMode = lowBandwidthMode, - padding = padding, - ) - } - - composable(route = NavTab.Saved.name) { - PersonProfileScreen( - personArg = Either.Left(account.id), - savedMode = true, - appState = appState, - accountViewModel = accountViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - drawerState = drawerState, - onBack = null, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), - lowBandwidthMode = lowBandwidthMode, - padding = padding, - ) - } - - composable(route = NavTab.Profile.name) { - PersonProfileScreen( - personArg = Either.Left(account.id), - savedMode = false, - appState = appState, - accountViewModel = accountViewModel, - appSettingsViewModel = appSettingsViewModel, - showVotingArrowsInListView = appSettings.showVotingArrowsInListView, - siteViewModel = siteViewModel, - useCustomTabs = appSettings.useCustomTabs, - usePrivateTabs = appSettings.usePrivateTabs, - blurNSFW = appSettings.blurNSFW.toEnum(), - showPostLinkPreviews = appSettings.showPostLinkPreviews, - drawerState = drawerState, - onBack = null, - markAsReadOnScroll = appSettings.markAsReadOnScroll, - postActionBarMode = appSettings.postActionBarMode.toEnum(), - swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), - disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), - lowBandwidthMode = lowBandwidthMode, - padding = padding, - ) - } - } - } - }, - ) -} diff --git a/app/src/main/java/com/jerboa/ui/components/home/Home.kt b/app/src/main/java/com/jerboa/ui/components/home/Home.kt index aac4f013e..2b0dc4715 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/Home.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/Home.kt @@ -33,7 +33,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource @@ -49,8 +48,8 @@ import com.jerboa.ui.components.common.MyMarkdownText import com.jerboa.ui.components.common.SortOptionsDropdown import com.jerboa.ui.theme.LARGE_PADDING import it.vercruysse.lemmyapi.datatypes.Tagline -import it.vercruysse.lemmyapi.dto.ListingType -import it.vercruysse.lemmyapi.dto.SortType +import it.vercruysse.lemmyapi.enums.ListingType +import it.vercruysse.lemmyapi.enums.SortType import me.saket.cascade.CascadeDropdownMenu @OptIn(ExperimentalMaterial3Api::class) @@ -200,7 +199,7 @@ fun HomeMoreDropdown( text = { Text(text = stringResource(R.string.home_post_view_mode)) }, leadingIcon = { Icon(Icons.Outlined.ViewAgenda, contentDescription = null) }, children = { - PostViewMode.entries.map { + PostViewMode.entries.forEach { DropdownMenuItem( text = { Text(text = stringResource(it.resId)) }, onClick = { @@ -265,9 +264,7 @@ fun ListingTypeOptionsDropDown( } @Composable -fun Taglines(taglines: List) { - if (taglines.isNotEmpty()) { - val tagline by remember { mutableStateOf(taglines.random()) } +fun TaglineDisplay(tagline: Tagline) { Column( Modifier.padding(LARGE_PADDING), ) { @@ -276,5 +273,4 @@ fun Taglines(taglines: List) { onClick = {}, ) } - } } diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeAndPostDetailScreen.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeAndPostDetailScreen.kt new file mode 100644 index 000000000..8412c3fd3 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeAndPostDetailScreen.kt @@ -0,0 +1,138 @@ +package com.jerboa.ui.components.home + +import android.annotation.SuppressLint +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.material3.DrawerState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.layout.AnimatedPane +import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold +import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole +import androidx.compose.material3.adaptive.layout.PaneAdaptedValue +import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState +import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +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.res.stringResource +import arrow.core.Either +import com.jerboa.JerboaAppState +import com.jerboa.R +import com.jerboa.SelectionVisibilityState +import com.jerboa.model.AccountViewModel +import com.jerboa.model.AppSettingsViewModel +import com.jerboa.model.HomeViewModel +import com.jerboa.ui.components.post.PostPane +import it.vercruysse.lemmyapi.datatypes.GetSiteResponse +import it.vercruysse.lemmyapi.datatypes.MyUserInfo +import it.vercruysse.lemmyapi.datatypes.PostId +import kotlinx.coroutines.launch + +@SuppressLint("UnusedContentLambdaTargetStateParameter") +@OptIn( + ExperimentalMaterial3Api::class, + ExperimentalFoundationApi::class, + ExperimentalMaterial3AdaptiveApi::class, + ExperimentalSharedTransitionApi::class, + ExperimentalLayoutApi::class, +) +@Composable +fun HomeAndPostDetailScreen( + appState: JerboaAppState, + homeViewModel: HomeViewModel, + accountViewModel: AccountViewModel, + siteRes: GetSiteResponse, + myUserInfo: MyUserInfo?, + appSettingsViewModel: AppSettingsViewModel, + drawerState: DrawerState, + padding: PaddingValues, +) { + val scope = rememberCoroutineScope() + + var selectedPostId: PostId? by rememberSaveable { mutableStateOf(null) } + + val navigator = rememberListDetailPaneScaffoldNavigator() + val isListAndDetailVisible = + navigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded && + navigator.scaffoldValue[ListDetailPaneScaffoldRole.List] == PaneAdaptedValue.Expanded + val isDetailVisible = + navigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded + + val appSettings = appSettingsViewModel.appSettings.observeAsState() + + BackHandler(enabled = navigator.canNavigateBack()) { + scope.launch { + navigator.navigateBack() + } + } + + SharedTransitionLayout { + AnimatedContent( + targetState = isListAndDetailVisible, + label = stringResource(R.string.bottomBar_label_home), + ) { + ListDetailPaneScaffold( + directive = navigator.scaffoldDirective, + value = navigator.scaffoldValue, + listPane = { + val currentSelectedId = selectedPostId + val selectionState = + if (isDetailVisible && currentSelectedId != null) { + SelectionVisibilityState.ShowSelection(currentSelectedId) + } else { + SelectionVisibilityState.NoSelection + } + + AnimatedPane { + HomePane( + appState = appState, + homeViewModel = homeViewModel, + accountViewModel = accountViewModel, + myUserInfo = myUserInfo, + appSettingsViewModel = appSettingsViewModel, + siteRes = siteRes, + drawerState = drawerState, + padding = padding, + onPostClick = { postView -> + selectedPostId = postView.post.id + scope.launch { + navigator.navigateTo(ListDetailPaneScaffoldRole.Detail) + } + }, + selectionState = selectionState, + ) + } + }, + detailPane = { + AnimatedPane { + selectedPostId?.let { + PostPane( + postOrCommentId = Either.Left(it), + accountViewModel = accountViewModel, + appState = appState, + siteRes = siteRes, + onClickBack = { + scope.launch { + selectedPostId = null + navigator.navigateBack() + } + }, + ) + } + } + }, + paneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue), + ) + } + } +} diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeNavScreen.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeNavScreen.kt new file mode 100644 index 000000000..e3a49c892 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeNavScreen.kt @@ -0,0 +1,348 @@ +package com.jerboa.ui.components.home + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AppRegistration +import androidx.compose.material.icons.filled.Bookmarks +import androidx.compose.material.icons.filled.Email +import androidx.compose.material.icons.filled.Flag +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Person +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.outlined.AppRegistration +import androidx.compose.material.icons.outlined.Bookmarks +import androidx.compose.material.icons.outlined.Email +import androidx.compose.material.icons.outlined.Flag +import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.Person +import androidx.compose.material.icons.outlined.Search +import androidx.compose.material3.DrawerState +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +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.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalResources +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTagsAsResourceId +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import arrow.core.Either +import com.jerboa.JerboaAppState +import com.jerboa.R +import com.jerboa.api.toOpt +import com.jerboa.datatypes.UserViewType +import com.jerboa.db.entity.AppSettings +import com.jerboa.model.AccountViewModel +import com.jerboa.model.AppSettingsViewModel +import com.jerboa.model.HomeViewModel +import com.jerboa.showAvatar +import com.jerboa.toBool +import com.jerboa.toEnum +import com.jerboa.ui.components.common.JerboaSnackbarHost +import com.jerboa.ui.components.common.adaptiveNavigationBar +import com.jerboa.ui.components.community.list.CommunityListScreen +import com.jerboa.ui.components.drawer.MainDrawer +import com.jerboa.ui.components.inbox.InboxScreen +import com.jerboa.ui.components.person.PersonProfileScreen +import com.jerboa.ui.components.registrationapplications.RegistrationApplicationsScreen +import com.jerboa.ui.components.reports.ReportsScreen +import it.vercruysse.lemmyapi.datatypes.GetSiteResponse +import it.vercruysse.lemmyapi.datatypes.MyUserInfo +import it.vercruysse.lemmyapi.datatypes.UnreadCountsResponse +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +enum class NavTab( + val textId: Int, + val iconOutlined: ImageVector, + val iconFilled: ImageVector, + val contentDescriptionId: Int, + val userViewType: UserViewType, + val needsLogin: Boolean, +) { + Home( + textId = R.string.bottomBar_label_home, + iconOutlined = Icons.Outlined.Home, + iconFilled = Icons.Filled.Home, + contentDescriptionId = R.string.bottomBar_home, + userViewType = UserViewType.Normal, + needsLogin = false, + ), + Search( + textId = R.string.bottomBar_label_search, + iconOutlined = Icons.Outlined.Search, + iconFilled = Icons.Filled.Search, + contentDescriptionId = R.string.bottomBar_search, + userViewType = UserViewType.Normal, + needsLogin = false, + ), + Inbox( + textId = R.string.bottomBar_label_inbox, + iconOutlined = Icons.Outlined.Email, + iconFilled = Icons.Filled.Email, + contentDescriptionId = R.string.bottomBar_inbox, + userViewType = UserViewType.Normal, + needsLogin = true, + ), + RegistrationApplications( + R.string.applications_request_shorthand, + Icons.Outlined.AppRegistration, + Icons.Filled.AppRegistration, + R.string.bottomBar_registrations, + userViewType = UserViewType.Admin, + needsLogin = true, + ), + Reports( + R.string.reports, + Icons.Outlined.Flag, + Icons.Filled.Flag, + R.string.bottomBar_reports, + userViewType = UserViewType.Mod, + needsLogin = true, + ), + Saved( + textId = R.string.bottomBar_label_bookmarks, + iconOutlined = Icons.Outlined.Bookmarks, + iconFilled = Icons.Filled.Bookmarks, + contentDescriptionId = R.string.bottomBar_bookmarks, + userViewType = UserViewType.Normal, + needsLogin = true, + ), + Profile( + textId = R.string.bottomBar_label_profile, + iconOutlined = Icons.Outlined.Person, + iconFilled = Icons.Filled.Person, + contentDescriptionId = R.string.bottomBar_profile, + userViewType = UserViewType.Normal, + needsLogin = true, + ), + ; + + companion object { + fun getEntries(userViewType: UserViewType) = + when (userViewType) { + UserViewType.NotLoggedIn -> emptyList() + UserViewType.Normal -> NavTab.entries.filter { it.userViewType == UserViewType.Normal } + UserViewType.Mod -> NavTab.entries.filter { it.userViewType != UserViewType.Admin } + UserViewType.Admin -> NavTab.entries + } + } +} + +@OptIn( + ExperimentalComposeUiApi::class, +) +@Composable +fun HomeNavScreen( + accountViewModel: AccountViewModel, + appState: JerboaAppState, + appSettingsViewModel: AppSettingsViewModel, + siteRes: GetSiteResponse, + myUserInfo: MyUserInfo?, + unreadCounts: UnreadCountsResponse?, + drawerState: DrawerState, +) { + val ctx = LocalContext.current + val resources = LocalResources.current + val scope = rememberCoroutineScope() + val homeViewModel: HomeViewModel = viewModel(factory = HomeViewModel.Factory) + + val bottomNavController = rememberNavController() + val snackbarHostState = remember { SnackbarHostState() } + var selectedTab by rememberSaveable { mutableStateOf(NavTab.Home) } + + val onInnerSelectTab = { tab: NavTab -> + selectedTab = tab + val currentRoute = bottomNavController.currentDestination?.route + if (currentRoute == tab.name && tab == NavTab.Home) { + scope.launch(Dispatchers.Main) { + homeViewModel.lazyListState.animateScrollToItem(0) + } + } else { + scope.launch(Dispatchers.Main) { + bottomNavController.navigate(tab.name) { + launchSingleTop = true + popUpTo(bottomNavController.graph.id) // To make back button close the app. + } + } + } + } + + val onSelectTab: (NavTab) -> Unit = { tab: NavTab -> + // TODO the tab should be greyed instead + if (tab.needsLogin && myUserInfo != null) { + onInnerSelectTab(tab) + } else { + onInnerSelectTab(tab) + } + } + + ModalNavigationDrawer( + gesturesEnabled = true, + drawerState = drawerState, + drawerContent = { + ModalDrawerSheet( + content = { + MainDrawer( + myUserInfo = myUserInfo, + unreadCounts = unreadCounts, + appSettings = appSettings, + accountViewModel = accountViewModel, + homeViewModel = homeViewModel, + scope = scope, + drawerState = drawerState, + onSelectTab = onSelectTab, + onCommunityClick = appState::toCommunity, + onSettingsClick = appState::toSettings, + onClickLogin = appState::toLogin, + ) + }, + ) + }, + modifier = Modifier.semantics { testTagsAsResourceId = true }, + content = { + NavigationSuiteScaffold( + navigationSuiteItems = { + if (appSettings.showBottomNav) { + adaptiveNavigationBar( + selectedTab = selectedTab, + myUserInfo = myUserInfo, + unreadCounts = unreadCounts, + showTextDescriptionsInNavbar = appSettings.showTextDescriptionsInNavbar, + onSelectTab = onSelectTab, + ) + } + }, + ) { + Scaffold( + snackbarHost = { JerboaSnackbarHost(snackbarHostState) }, + content = { padding -> + NavHost( + navController = bottomNavController, + startDestination = NavTab.Home.name, + ) { + composable(route = NavTab.Home.name) { + HomeAndPostDetailScreen( + appState = appState, + appSettingsViewModel = appSettingsViewModel, + homeViewModel = homeViewModel, + accountViewModel = accountViewModel, + siteRes = siteRes, + drawerState = drawerState, + padding = padding, + ) + } + + composable(route = NavTab.Search.name) { + CommunityListScreen( + appState = appState, + followList = myUserInfoViewModel.myUserRes.toOpt()?.follows ?: emptyList(), + blurNSFW = appSettings.blurNSFW.toEnum(), + drawerState = drawerState, + showAvatar = myUserInfoViewModel.myUserRes.toOpt().showAvatar() && !lowBandwidthMode, + padding = padding,, + ) + } + + composable(route = NavTab.Inbox.name) { + InboxScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + blurNSFW = appSettings.blurNSFW.toEnum(), + lowBandwidthMode = lowBandwidthMode, + drawerState = drawerState, + padding = padding, + ) + } + + composable(route = NavTab.RegistrationApplications.name) { + RegistrationApplicationsScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + drawerState = drawerState, + lowBandwidthMode = lowBandwidthMode, + padding = padding, + ) + } + + composable(route = NavTab.Reports.name) { + ReportsScreen( + appState = appState, + accountViewModel = accountViewModel, + siteViewModel = siteViewModel, + drawerState = drawerState, + blurNSFW = appSettings.blurNSFW.toEnum(), + lowBandwidthMode = lowBandwidthMode, + padding = padding, + ) + } + + composable(route = NavTab.Saved.name) { + PersonProfileScreen( + personArg = Either.Left(account.id), + savedMode = true, + appState = appState, + accountViewModel = accountViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + siteViewModel = siteViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreviews = appSettings.showPostLinkPreviews, + drawerState = drawerState, + onBack = null, + markAsReadOnScroll = appSettings.markAsReadOnScroll, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), + lowBandwidthMode = lowBandwidthMode, + padding = padding, + ) + } + + composable(route = NavTab.Profile.name) { + PersonProfileScreen( + personArg = Either.Left(account.id), + savedMode = false, + appState = appState, + accountViewModel = accountViewModel, + appSettingsViewModel = appSettingsViewModel, + showVotingArrowsInListView = appSettings.showVotingArrowsInListView, + siteViewModel = siteViewModel, + useCustomTabs = appSettings.useCustomTabs, + usePrivateTabs = appSettings.usePrivateTabs, + blurNSFW = appSettings.blurNSFW.toEnum(), + showPostLinkPreviews = appSettings.showPostLinkPreviews, + drawerState = drawerState, + onBack = null, + markAsReadOnScroll = appSettings.markAsReadOnScroll, + postActionBarMode = appSettings.postActionBarMode.toEnum(), + swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), + disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), + lowBandwidthMode = lowBandwidthMode, + padding = padding, + ) + } + } + }, + ) + } + }, + ) +} diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/HomePane.kt similarity index 71% rename from app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt rename to app/src/main/java/com/jerboa/ui/components/home/HomePane.kt index f4fd36127..2cec86661 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomePane.kt @@ -23,7 +23,6 @@ import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.ExperimentalComposeUiApi @@ -36,29 +35,24 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId import com.jerboa.JerboaAppState import com.jerboa.R +import com.jerboa.SelectionVisibilityState import com.jerboa.api.ApiState import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.db.entity.Account -import com.jerboa.db.entity.isAnon -import com.jerboa.db.entity.isReady -import com.jerboa.feat.BlurNSFW import com.jerboa.feat.PostActionBarMode import com.jerboa.feat.SwipeToActionPreset -import com.jerboa.feat.VoteType import com.jerboa.feat.doIfReadyElseDisplayInfo import com.jerboa.feat.newVote import com.jerboa.model.AccountViewModel import com.jerboa.model.AppSettingsViewModel import com.jerboa.model.HomeViewModel +import com.jerboa.model.MyUserInfoViewModel import com.jerboa.model.ReplyItem -import com.jerboa.model.SiteViewModel import com.jerboa.scrollToTop import com.jerboa.ui.components.ban.BanFromCommunityReturn import com.jerboa.ui.components.ban.BanPersonReturn -import com.jerboa.ui.components.common.ApiErrorText import com.jerboa.ui.components.common.JerboaLoadingBar import com.jerboa.ui.components.common.JerboaSnackbarHost -import com.jerboa.ui.components.common.LoadingBar import com.jerboa.ui.components.common.apiErrorToast import com.jerboa.ui.components.common.getCurrentAccount import com.jerboa.ui.components.common.getPostViewMode @@ -70,37 +64,33 @@ import com.jerboa.ui.components.remove.post.PostRemoveReturn import it.vercruysse.lemmyapi.datatypes.CreatePostLike import it.vercruysse.lemmyapi.datatypes.DeletePost import it.vercruysse.lemmyapi.datatypes.FeaturePost +import it.vercruysse.lemmyapi.datatypes.GetSiteResponse import it.vercruysse.lemmyapi.datatypes.HidePost import it.vercruysse.lemmyapi.datatypes.LockPost import it.vercruysse.lemmyapi.datatypes.MarkPostAsRead +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.PersonView +import it.vercruysse.lemmyapi.datatypes.PostId import it.vercruysse.lemmyapi.datatypes.PostView import it.vercruysse.lemmyapi.datatypes.SavePost -import it.vercruysse.lemmyapi.datatypes.Tagline +import it.vercruysse.lemmyapi.enums.VoteAction import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) @Composable -fun HomeScreen( +fun HomePane( appState: JerboaAppState, homeViewModel: HomeViewModel, accountViewModel: AccountViewModel, - siteViewModel: SiteViewModel, + siteRes: GetSiteResponse, + myUserInfo: MyUserInfo?, appSettingsViewModel: AppSettingsViewModel, - showVotingArrowsInListView: Boolean, - useCustomTabs: Boolean, - usePrivateTabs: Boolean, drawerState: DrawerState, - blurNSFW: BlurNSFW, - showPostLinkPreviews: Boolean, - markAsReadOnScroll: Boolean, - postActionBarMode: PostActionBarMode, - swipeToActionPreset: SwipeToActionPreset, - disableVideoAutoplay: Boolean, - lowBandwidthMode: Boolean, padding: PaddingValues, + selectionState: SelectionVisibilityState, + onPostClick: (PostView) -> Unit, ) { - Log.d("jerboa", "got to home screen") + Log.d("jerboa", "got to home pane") val scope = rememberCoroutineScope() val postListState = homeViewModel.lazyListState @@ -121,20 +111,6 @@ fun HomeScreen( appState.ConsumeReturn(BanPersonReturn.PERSON_VIEW, homeViewModel::updateBanned) appState.ConsumeReturn(BanFromCommunityReturn.BAN_DATA_VIEW, homeViewModel::updateBannedFromCommunity) - LaunchedEffect(account) { - if (!account.isAnon() && !account.isReady()) { - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) {} - } - } - val baseModifier = Modifier .padding(padding) @@ -167,22 +143,18 @@ fun HomeScreen( Box(modifier = Modifier.padding(innerPadding)) { MainPostListingsContent( homeViewModel = homeViewModel, - siteViewModel = siteViewModel, + siteRes = siteRes, + myUserInfo = myUserInfo, appSettingsViewModel = appSettingsViewModel, account = account, appState = appState, postListState = postListState, - showVotingArrowsInListView = showVotingArrowsInListView, - useCustomTabs = useCustomTabs, - usePrivateTabs = usePrivateTabs, - blurNSFW = blurNSFW, - showPostLinkPreviews = showPostLinkPreviews, - markAsReadOnScroll = markAsReadOnScroll, snackbarHostState = snackbarHostState, postActionBarMode = postActionBarMode, swipeToActionPreset = swipeToActionPreset, disableVideoAutoplay = disableVideoAutoplay, - lowBandwidthMode = lowBandwidthMode, + onPostClick = onPostClick, + selectionState = selectionState, ) } }, @@ -196,7 +168,7 @@ fun HomeScreen( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, accountViewModel, loginAsToast = false, ) { @@ -219,43 +191,24 @@ fun HomeScreen( @Composable fun MainPostListingsContent( homeViewModel: HomeViewModel, - siteViewModel: SiteViewModel, + siteRes: GetSiteResponse, + myUserInfo: MyUserInfo?, + appSettingsViewModel: AppSettingsViewModel, account: Account, appState: JerboaAppState, postListState: LazyListState, - appSettingsViewModel: AppSettingsViewModel, - showVotingArrowsInListView: Boolean, - useCustomTabs: Boolean, - usePrivateTabs: Boolean, - blurNSFW: BlurNSFW, - showPostLinkPreviews: Boolean, snackbarHostState: SnackbarHostState, - markAsReadOnScroll: Boolean, postActionBarMode: PostActionBarMode, swipeToActionPreset: SwipeToActionPreset, disableVideoAutoplay: Boolean, - lowBandwidthMode: Boolean, + onPostClick: (PostView) -> Unit, + selectionState: SelectionVisibilityState, ) { val ctx = LocalContext.current val resources = LocalResources.current val scope = rememberCoroutineScope() - var taglines: List? = null - when (val siteRes = siteViewModel.siteRes) { - ApiState.Loading -> { - LoadingBar() - } - - is ApiState.Failure -> { - ApiErrorText(siteRes.msg) - } - - is ApiState.Success -> { - taglines = siteRes.data.taglines - } - - else -> {} - } + val tagline = siteRes.tagline ReportDrawn() @@ -282,63 +235,33 @@ fun MainPostListingsContent( PostListings( posts = posts, - admins = siteViewModel.admins(), + siteRes = siteRes, // No community moderators available here - moderators = null, - contentAboveListings = { if (taglines !== null) Taglines(taglines = taglines) }, + contentAboveListings = { tagline?.let {TaglineDisplay(it)} }, onUpvoteClick = { postView -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - ) { homeViewModel.likePost( CreatePostLike( post_id = postView.post.id, - score = newVote(postView.my_vote, VoteType.Upvote), + vote = newVote(postView.post_actions?.vote, VoteAction.UpVote), ), ) - } }, onDownvoteClick = { postView -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - ) { homeViewModel.likePost( CreatePostLike( post_id = postView.post.id, - score = newVote(postView.my_vote, VoteType.Downvote), + vote = newVote(postView.post_actions?.vote, VoteAction.DownVote), ), ) - } - }, - onPostClick = { postView -> - appState.toPost(id = postView.post.id) }, + onPostClick = onPostClick, onSaveClick = { postView -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - ) { homeViewModel.savePost( SavePost( post_id = postView.post.id, - save = !postView.saved, + save = postView.post_actions?.saved_at == null, ), ) - } }, onReplyClick = { pv -> appState.toCommentReply( @@ -351,39 +274,21 @@ fun MainPostListingsContent( ) }, onDeletePostClick = { postView -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - ) { homeViewModel.deletePost( DeletePost( post_id = postView.post.id, deleted = !postView.post.deleted, ), ) - } }, onHidePostClick = { postView -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - ) { homeViewModel.hidePost( HidePost( - post_ids = listOf(postView.post.id), - hide = !postView.hidden, + post_id = postView.post.id, + hide = postView.post_actions?.hidden_at == null, ), ctx, ) - } }, onReportClick = { postView -> appState.toPostReport(id = postView.post.id) @@ -398,31 +303,15 @@ fun MainPostListingsContent( appState.toBanFromCommunity(banData = d) }, onLockPostClick = { pv -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - ) { homeViewModel.lockPost( LockPost( post_id = pv.post.id, locked = !pv.post.locked, + reason = TODO ), ) - } }, onFeaturePostClick = { data -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - ) { homeViewModel.featurePost( FeaturePost( post_id = data.post.id, @@ -430,7 +319,6 @@ fun MainPostListingsContent( feature_type = data.type, ), ) - } }, onViewPostVotesClick = appState::toPostLikes, onCommunityClick = { community -> @@ -443,34 +331,24 @@ fun MainPostListingsContent( account = account, listState = postListState, postViewMode = getPostViewMode(appSettingsViewModel), - showVotingArrowsInListView = showVotingArrowsInListView, - enableDownVotes = siteViewModel.enableDownvotes(), - showAvatar = siteViewModel.showAvatar() && !lowBandwidthMode, - useCustomTabs = useCustomTabs, - usePrivateTabs = usePrivateTabs, - blurNSFW = blurNSFW, - showPostLinkPreviews = showPostLinkPreviews, appState = appState, - markAsReadOnScroll = markAsReadOnScroll, onMarkAsRead = { postView -> - if (!account.isAnon() && !postView.read) { + if (postView.post_actions?.read_at != null) { homeViewModel.markPostAsRead( MarkPostAsRead( - post_ids = listOf(postView.post.id), + post_id = postView.post.id, read = true, ), - postView, appState, ) } }, showIfRead = true, - voteDisplayMode = siteViewModel.voteDisplayMode(), postActionBarMode = postActionBarMode, showPostAppendRetry = homeViewModel.postsRes is ApiState.AppendingFailure, swipeToActionPreset = swipeToActionPreset, disableVideoAutoplay = disableVideoAutoplay, - lowBandwidthMode = lowBandwidthMode, + selectionState = selectionState, ) } } diff --git a/app/src/main/java/com/jerboa/ui/components/inbox/InboxScreen.kt b/app/src/main/java/com/jerboa/ui/components/inbox/InboxScreen.kt index 1279ca4b7..9f39141d7 100644 --- a/app/src/main/java/com/jerboa/ui/components/inbox/InboxScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/inbox/InboxScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState @@ -47,7 +46,7 @@ import com.jerboa.feat.doIfReadyElseDisplayInfo import com.jerboa.feat.newVote import com.jerboa.getCommentParentId import com.jerboa.model.AccountViewModel -import com.jerboa.model.InboxViewModel +import com.jerboa.model.NotificationViewModel import com.jerboa.model.ReplyItem import com.jerboa.model.SiteViewModel import com.jerboa.ui.components.comment.mentionnode.CommentMentionNode @@ -93,7 +92,7 @@ fun InboxScreen( val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) - val inboxViewModel: InboxViewModel = viewModel(factory = InboxViewModel.Companion.Factory(account, siteViewModel)) + val notificationViewModel: NotificationViewModel = viewModel(factory = NotificationViewModel.Companion.Factory(account, siteViewModel)) val baseModifier = if (padding == null) { Modifier @@ -118,7 +117,7 @@ fun InboxScreen( drawerState.open() } }, - selectedUnreadOrAll = unreadOrAllFromBool(inboxViewModel.unreadOnly), + selectedUnreadOrAll = unreadOrAllFromBool(notificationViewModel.unreadOnly), onClickUnreadOrAll = { unreadOrAll -> account.doIfReadyElseDisplayInfo( appState, @@ -130,16 +129,16 @@ fun InboxScreen( accountViewModel, loginAsToast = true, ) { - inboxViewModel.resetPages() - inboxViewModel.updateUnreadOnly(unreadOrAll == UnreadOrAll.Unread) - inboxViewModel.getReplies( - inboxViewModel.getFormReplies(), + notificationViewModel.resetPages() + notificationViewModel.updateUnreadOnly(unreadOrAll == UnreadOrAll.Unread) + notificationViewModel.getReplies( + notificationViewModel.getFormReplies(), ) - inboxViewModel.getMentions( - inboxViewModel.getFormMentions(), + notificationViewModel.getMentions( + notificationViewModel.getFormMentions(), ) - inboxViewModel.getMessages( - inboxViewModel.getFormMessages(), + notificationViewModel.getMessages( + notificationViewModel.getFormMessages(), ) } }, @@ -153,18 +152,18 @@ fun InboxScreen( siteViewModel, accountViewModel, ) { - inboxViewModel.markAllAsRead( + notificationViewModel.markAllAsRead( onComplete = { siteViewModel.fetchUnreadCounts() - inboxViewModel.resetPages() - inboxViewModel.getReplies( - inboxViewModel.getFormReplies(), + notificationViewModel.resetPages() + notificationViewModel.getReplies( + notificationViewModel.getFormReplies(), ) - inboxViewModel.getMentions( - inboxViewModel.getFormMentions(), + notificationViewModel.getMentions( + notificationViewModel.getFormMentions(), ) - inboxViewModel.getMessages( - inboxViewModel.getFormMessages(), + notificationViewModel.getMessages( + notificationViewModel.getFormMessages(), ) }, ) @@ -176,7 +175,7 @@ fun InboxScreen( Box(modifier = Modifier.padding(it)) { InboxTabs( appState = appState, - inboxViewModel = inboxViewModel, + notificationViewModel = notificationViewModel, siteViewModel = siteViewModel, ctx = ctx, resources = resources, @@ -203,7 +202,7 @@ enum class InboxTab( @Composable fun InboxTabs( appState: JerboaAppState, - inboxViewModel: InboxViewModel, + notificationViewModel: NotificationViewModel, siteViewModel: SiteViewModel, ctx: Context, resources: Resources, @@ -242,7 +241,7 @@ fun InboxTabs( val listState = rememberLazyListState() TriggerWhenReachingEnd(listState, false) { - inboxViewModel.appendReplies() + notificationViewModel.appendReplies() } val goToComment = { crv: CommentReplyView -> @@ -264,7 +263,7 @@ fun InboxTabs( scope, siteViewModel, ) { - inboxViewModel.markReplyAsRead( + notificationViewModel.markReplyAsRead( MarkCommentReplyAsRead( comment_reply_id = crv.comment_reply.id, read = !crv.comment_reply.read, @@ -277,7 +276,7 @@ fun InboxTabs( } PullToRefreshBox( - isRefreshing = inboxViewModel.repliesRes.isRefreshing(), + isRefreshing = notificationViewModel.notifsRes.isRefreshing(), onRefresh = { account.doIfReadyElseDisplayInfo( appState, @@ -287,18 +286,18 @@ fun InboxTabs( scope, siteViewModel, ) { - inboxViewModel.resetPageReplies() - inboxViewModel.getReplies( - inboxViewModel.getFormReplies(), + notificationViewModel.resetPageReplies() + notificationViewModel.getReplies( + notificationViewModel.getFormReplies(), ApiState.Refreshing, ) siteViewModel.fetchUnreadCounts() } }, ) { - JerboaLoadingBar(inboxViewModel.repliesRes) + JerboaLoadingBar(notificationViewModel.notifsRes) - when (val repliesRes = inboxViewModel.repliesRes) { + when (val repliesRes = notificationViewModel.notifsRes) { ApiState.Empty -> { ApiEmptyText() } @@ -332,10 +331,10 @@ fun InboxTabs( scope, siteViewModel, ) { - inboxViewModel.likeReply( + notificationViewModel.likeComment( CreateCommentLike( comment_id = cr.comment.id, - score = newVote(cr.my_vote, VoteType.Upvote), + score = newVote(cr.my_vote, VoteAction.UpVote), ), ) } @@ -349,10 +348,10 @@ fun InboxTabs( scope, siteViewModel, ) { - inboxViewModel.likeReply( + notificationViewModel.likeComment( CreateCommentLike( comment_id = cr.comment.id, - score = newVote(cr.my_vote, VoteType.Downvote), + score = newVote(cr.my_vote, VoteAction.DownVote), ), ) } @@ -371,7 +370,7 @@ fun InboxTabs( scope, siteViewModel, ) { - inboxViewModel.saveReply( + notificationViewModel.saveComment( SaveComment( comment_id = cr.comment.id, save = !cr.saved, @@ -406,7 +405,7 @@ fun InboxTabs( scope, siteViewModel, ) { - inboxViewModel.blockPerson( + notificationViewModel.blockPerson( BlockPerson( person_id = person.id, block = true, @@ -440,11 +439,11 @@ fun InboxTabs( val listState = rememberLazyListState() TriggerWhenReachingEnd(listState, false) { - inboxViewModel.appendMentions() + notificationViewModel.appendMentions() } PullToRefreshBox( - isRefreshing = inboxViewModel.mentionsRes.isRefreshing(), + isRefreshing = notificationViewModel.mentionsRes.isRefreshing(), onRefresh = { account.doIfReadyElseDisplayInfo( appState, @@ -454,18 +453,18 @@ fun InboxTabs( scope, siteViewModel, ) { - inboxViewModel.resetPageMentions() - inboxViewModel.getMentions( - inboxViewModel.getFormMentions(), + notificationViewModel.resetPageMentions() + notificationViewModel.getMentions( + notificationViewModel.getFormMentions(), ApiState.Refreshing, ) siteViewModel.fetchUnreadCounts() } }, ) { - JerboaLoadingBar(inboxViewModel.mentionsRes) + JerboaLoadingBar(notificationViewModel.mentionsRes) - when (val mentionsRes = inboxViewModel.mentionsRes) { + when (val mentionsRes = notificationViewModel.mentionsRes) { ApiState.Empty -> { ApiEmptyText() } @@ -502,10 +501,10 @@ fun InboxTabs( scope, siteViewModel, ) { - inboxViewModel.likeMention( + notificationViewModel.likeMention( CreateCommentLike( comment_id = pm.comment.id, - score = newVote(pm.my_vote, VoteType.Upvote), + score = newVote(pm.my_vote, VoteAction.UpVote), ), ) } @@ -519,10 +518,10 @@ fun InboxTabs( scope, siteViewModel, ) { - inboxViewModel.likeMention( + notificationViewModel.likeMention( CreateCommentLike( comment_id = pm.comment.id, - score = newVote(pm.my_vote, VoteType.Downvote), + score = newVote(pm.my_vote, VoteAction.DownVote), ), ) } @@ -541,7 +540,7 @@ fun InboxTabs( scope, siteViewModel, ) { - inboxViewModel.saveMention( + notificationViewModel.saveMention( SaveComment( comment_id = pm.comment.id, save = !pm.saved, @@ -558,7 +557,7 @@ fun InboxTabs( scope, siteViewModel, ) { - inboxViewModel.markPersonMentionAsRead( + notificationViewModel.markPersonMentionAsRead( MarkPersonMentionAsRead( person_mention_id = pm.person_mention.id, read = !pm.person_mention.read, @@ -601,7 +600,7 @@ fun InboxTabs( scope, siteViewModel, ) { - inboxViewModel.blockPerson( + notificationViewModel.blockPerson( BlockPerson( person_id = person.id, block = true, @@ -630,11 +629,11 @@ fun InboxTabs( val listState = rememberLazyListState() TriggerWhenReachingEnd(listState, false) { - inboxViewModel.appendMessages() + notificationViewModel.appendMessages() } PullToRefreshBox( - isRefreshing = inboxViewModel.messagesRes.isRefreshing(), + isRefreshing = notificationViewModel.messagesRes.isRefreshing(), onRefresh = { account.doIfReadyElseDisplayInfo( appState, @@ -644,18 +643,18 @@ fun InboxTabs( scope, siteViewModel, ) { - inboxViewModel.resetPageMessages() - inboxViewModel.getMessages( - inboxViewModel.getFormMessages(), + notificationViewModel.resetPageMessages() + notificationViewModel.getMessages( + notificationViewModel.getFormMessages(), ApiState.Refreshing, ) siteViewModel.fetchUnreadCounts() } }, ) { - JerboaLoadingBar(inboxViewModel.messagesRes) + JerboaLoadingBar(notificationViewModel.messagesRes) - when (val messagesRes = inboxViewModel.messagesRes) { + when (val messagesRes = notificationViewModel.messagesRes) { ApiState.Empty -> { ApiEmptyText() } @@ -687,7 +686,7 @@ fun InboxTabs( ) }, onMarkAsReadClick = { pm -> - inboxViewModel.markPrivateMessageAsRead( + notificationViewModel.markPrivateMessageAsRead( MarkPrivateMessageAsRead( private_message_id = pm.private_message.id, read = !pm.private_message.read, diff --git a/app/src/main/java/com/jerboa/ui/components/login/LoginScreen.kt b/app/src/main/java/com/jerboa/ui/components/login/LoginScreen.kt index d6ca500c1..479d37d6b 100644 --- a/app/src/main/java/com/jerboa/ui/components/login/LoginScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/login/LoginScreen.kt @@ -19,6 +19,7 @@ import com.jerboa.JerboaAppState import com.jerboa.R import com.jerboa.model.AccountViewModel import com.jerboa.model.LoginViewModel +import com.jerboa.model.MyUserInfoViewModel import com.jerboa.model.SiteViewModel import com.jerboa.ui.components.common.SimpleTopAppBar @@ -28,6 +29,7 @@ fun LoginScreen( appState: JerboaAppState, accountViewModel: AccountViewModel, siteViewModel: SiteViewModel, + myUserInfoViewModel: MyUserInfoViewModel, ) { Log.d("jerboa", "Got to login screen") @@ -61,6 +63,7 @@ fun LoginScreen( resources = resources, accountViewModel = accountViewModel, siteViewModel = siteViewModel, + myUserInfoViewModel = myUserInfoViewModel, onGoHome = appState::toHome, ) }, diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileLink.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileLink.kt index a8087a1a2..387c3b6bc 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileLink.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileLink.kt @@ -41,7 +41,7 @@ fun PersonName( } else { ItemAndInstanceTitle( title = name, - actorId = person.actor_id, + apId = person.actor_id, local = person.local, itemColor = color, itemStyle = style, @@ -65,15 +65,15 @@ fun PersonNameFederatedPreview() { @Composable fun PersonProfileLink( person: Person, - onClick: (personId: PersonId) -> Unit, - modifier: Modifier = Modifier, + isBanned: Boolean, clickable: Boolean = true, showTags: Boolean = false, isPostCreator: Boolean = false, isDistinguished: Boolean = false, - isCommunityBanned: Boolean = false, color: Color = MaterialTheme.colorScheme.tertiary, showAvatar: Boolean, + onClick: (personId: PersonId) -> Unit, + modifier: Modifier = Modifier, ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -101,7 +101,7 @@ fun PersonProfileLink( tint = MaterialTheme.colorScheme.tertiary, ) } - if (isCommunityBanned || person.banned) { + if (isBanned) { Icon( imageVector = Icons.Outlined.NoAccounts, contentDescription = stringResource(R.string.person_iconBanned), diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt index 49202f7cf..32661c9cb 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt @@ -41,6 +41,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import arrow.core.Either import com.jerboa.JerboaAppState import com.jerboa.R +import com.jerboa.SelectionVisibilityState import com.jerboa.api.ApiState import com.jerboa.commentsToFlatNodes import com.jerboa.datatypes.BanFromCommunityData @@ -502,7 +503,7 @@ fun UserTabs( score = newVote( pv.my_vote, - VoteType.Upvote, + VoteAction.UpVote, ), ), ) @@ -522,7 +523,7 @@ fun UserTabs( post_id = pv.post.id, score = newVote( pv.my_vote, - VoteType.Downvote, + VoteAction.DownVote, ), ), ) @@ -666,7 +667,7 @@ fun UserTabs( if (!account.isAnon() && !it.read) { personProfileViewModel.markPostAsRead( MarkPostAsRead( - post_ids = listOf(it.post.id), + post_id = it.post.id, read = true, ), it, @@ -681,6 +682,7 @@ fun UserTabs( swipeToActionPreset = swipeToActionPreset, disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, + selectionState = SelectionVisibilityState.NoSelection, ) } @@ -769,7 +771,7 @@ fun UserTabs( personProfileViewModel.likeComment( CreateCommentLike( comment_id = cv.comment.id, - score = newVote(cv.my_vote, VoteType.Upvote), + score = newVote(cv.my_vote, VoteAction.UpVote), ), ) } @@ -786,7 +788,7 @@ fun UserTabs( personProfileViewModel.likeComment( CreateCommentLike( comment_id = cv.comment.id, - score = newVote(cv.my_vote, VoteType.Downvote), + score = newVote(cv.my_vote, VoteAction.DownVote), ), ) } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt index c1057197c..2c9c3281f 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt @@ -44,13 +44,14 @@ import com.jerboa.R import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.datatypes.PostFeatureData import com.jerboa.db.entity.Account +import com.jerboa.db.entity.AppSettings import com.jerboa.feat.BlurNSFW import com.jerboa.feat.InstantScores import com.jerboa.feat.PostActionBarMode import com.jerboa.feat.SwipeToActionPreset import com.jerboa.feat.SwipeToActionType -import com.jerboa.feat.VoteType import com.jerboa.feat.isReadyAndIfNotShowSimplifiedInfoToast +import com.jerboa.toEnumSafe import com.jerboa.ui.components.common.EmbeddedDataLoader import com.jerboa.ui.components.common.MyMarkdownText import com.jerboa.ui.components.common.PictrsThumbnailImage @@ -65,24 +66,32 @@ import com.jerboa.ui.theme.Shapes import com.jerboa.ui.theme.THUMBNAIL_CARET_SIZE import com.jerboa.ui.theme.jerboaColorScheme import it.vercruysse.lemmyapi.datatypes.Community +import it.vercruysse.lemmyapi.datatypes.GetSiteResponse import it.vercruysse.lemmyapi.datatypes.ImageDetails -import it.vercruysse.lemmyapi.datatypes.LocalUserVoteDisplayMode -import it.vercruysse.lemmyapi.datatypes.Person +import it.vercruysse.lemmyapi.datatypes.LocalSite +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.PersonId import it.vercruysse.lemmyapi.datatypes.PersonView import it.vercruysse.lemmyapi.datatypes.Post import it.vercruysse.lemmyapi.datatypes.PostId import it.vercruysse.lemmyapi.datatypes.PostView +import it.vercruysse.lemmyapi.enums.VoteAction @ExperimentalLayoutApi @OptIn(ExperimentalMaterial3Api::class) @Composable fun PostListing( postView: PostView, - admins: List, - moderators: List?, - useCustomTabs: Boolean, - usePrivateTabs: Boolean, + siteRes: GetSiteResponse, + myUserInfo: MyUserInfo?, + appSettings: AppSettings, + showReply: Boolean = false, + showCommunityName: Boolean = true, + fullBody: Boolean, + account: Account, + postViewMode: PostViewMode, + appState: JerboaAppState, + showIfRead: Boolean, onUpvoteClick: (postView: PostView) -> Unit, onDownvoteClick: (postView: PostView) -> Unit, onReplyClick: (postView: PostView) -> Unit = {}, @@ -94,29 +103,12 @@ fun PostListing( onHidePostClick: (postView: PostView) -> Unit, onReportClick: (postView: PostView) -> Unit, onRemoveClick: (postView: PostView) -> Unit, - onBanPersonClick: (person: Person) -> Unit, + onBanPersonClick: (personView: PersonView) -> Unit, onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onLockPostClick: (postView: PostView) -> Unit, onFeaturePostClick: (data: PostFeatureData) -> Unit, onPersonClick: (personId: PersonId) -> Unit, onViewVotesClick: (PostId) -> Unit, - showReply: Boolean = false, - showCommunityName: Boolean = true, - fullBody: Boolean, - account: Account, - postViewMode: PostViewMode, - showVotingArrowsInListView: Boolean, - enableDownVotes: Boolean, - showAvatar: Boolean, - blurNSFW: BlurNSFW, - appState: JerboaAppState, - showPostLinkPreview: Boolean, - showIfRead: Boolean, - voteDisplayMode: LocalUserVoteDisplayMode, - postActionBarMode: PostActionBarMode, - swipeToActionPreset: SwipeToActionPreset, - disableVideoAutoplay: Boolean, - lowBandwidthMode: Boolean, ) { val ctx = LocalContext.current val resources = LocalResources.current @@ -124,24 +116,24 @@ fun PostListing( var instantScores by remember { mutableStateOf( InstantScores( - myVote = postView.my_vote, - score = postView.counts.score, - upvotes = postView.counts.upvotes, - downvotes = postView.counts.downvotes, + myVote = postView.post_actions?.vote, + score = postView.post.score, + upvotes = postView.post.upvotes, + downvotes = postView.post.downvotes, ), ) } val upvoteClick = remember(postView, instantScores) { { - instantScores = instantScores.update(VoteType.Upvote) + instantScores = instantScores.update(VoteAction.UpVote) onUpvoteClick(postView) } } val downvoteClick = remember(postView, instantScores) { { - instantScores = instantScores.update(VoteType.Downvote) + instantScores = instantScores.update(VoteAction.DownVote) onDownvoteClick(postView) } } @@ -162,7 +154,7 @@ fun PostListing( } val swipeState = rememberSwipeActionState( - swipeToActionPreset = swipeToActionPreset, + swipeToActionPreset = appSettings.swipeToActionPreset.toEnumSafe(), enableDownVotes = enableDownVotes, onAction = swipeAction, rememberKey = postView, @@ -175,7 +167,8 @@ fun PostListing( PostListingCard( postView = postView, admins = admins, - moderators = moderators, + myUserInfo = myUserInfo, + localSite = localSite, instantScores = instantScores, onUpvoteClick = upvoteClick, onDownvoteClick = downvoteClick, @@ -203,7 +196,6 @@ fun PostListing( fullBody = fullBody, account = account, expandedImage = true, - enableDownVotes = enableDownVotes, showAvatar = showAvatar, useCustomTabs = useCustomTabs, usePrivateTabs = usePrivateTabs, @@ -211,7 +203,6 @@ fun PostListing( showPostLinkPreview = showPostLinkPreview, appState = appState, showIfRead = showIfRead, - voteDisplayMode = voteDisplayMode, postActionBarMode = postActionBarMode, disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, @@ -222,7 +213,8 @@ fun PostListing( PostListingCard( postView = postView, admins = admins, - moderators = moderators, + myUserInfo = myUserInfo, + localSite = localSite, instantScores = instantScores, onUpvoteClick = upvoteClick, onDownvoteClick = downvoteClick, @@ -250,7 +242,6 @@ fun PostListing( fullBody = fullBody, account = account, expandedImage = false, - enableDownVotes = enableDownVotes, showAvatar = showAvatar, useCustomTabs = useCustomTabs, usePrivateTabs = usePrivateTabs, @@ -258,7 +249,6 @@ fun PostListing( showPostLinkPreview = showPostLinkPreview, appState = appState, showIfRead = showIfRead, - voteDisplayMode = voteDisplayMode, postActionBarMode = postActionBarMode, disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, @@ -269,6 +259,8 @@ fun PostListing( PostListingList( postView = postView, instantScores = instantScores, + myUserInfo = myUserInfo, + localSite = localSite, onUpvoteClick = upvoteClick, onDownvoteClick = downvoteClick, onPostClick = onPostClick, @@ -280,8 +272,6 @@ fun PostListing( blurNSFW = blurNSFW, appState = appState, showIfRead = showIfRead, - enableDownVotes = enableDownVotes, - voteDisplayMode = voteDisplayMode, lowBandwidthMode = lowBandwidthMode, ) } @@ -466,7 +456,7 @@ fun MetadataCard(post: Post) { } } post.embed_description?.let { - // This is actually html, but markdown can render it + // This is actually HTML, but markdown can render it MyMarkdownText( markdown = it, onClick = {}, diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListingCard.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListingCard.kt index 54dc9322d..95969899f 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListingCard.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListingCard.kt @@ -36,7 +36,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue 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 @@ -44,12 +43,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import com.jerboa.JerboaAppState import com.jerboa.PostLinkType import com.jerboa.R @@ -60,19 +57,17 @@ import com.jerboa.datatypes.sampleImagePostView import com.jerboa.datatypes.sampleInstantScores import com.jerboa.datatypes.sampleLinkNoThumbnailPostView import com.jerboa.datatypes.sampleLinkPostView +import com.jerboa.datatypes.sampleLocalSite import com.jerboa.datatypes.sampleMarkdownPostView +import com.jerboa.datatypes.sampleMyUserInfo import com.jerboa.datatypes.samplePostView import com.jerboa.db.entity.Account import com.jerboa.db.entity.AnonAccount import com.jerboa.feat.BlurNSFW import com.jerboa.feat.InstantScores import com.jerboa.feat.PostActionBarMode -import com.jerboa.feat.VoteType -import com.jerboa.feat.amMod -import com.jerboa.feat.canMod -import com.jerboa.feat.default +import com.jerboa.feat.PostOrCommentType import com.jerboa.feat.needBlur -import com.jerboa.feat.simulateModerators import com.jerboa.hostNameCleaned import com.jerboa.isSameInstance import com.jerboa.rememberJerboaAppState @@ -108,12 +103,14 @@ import com.jerboa.ui.theme.SMALL_PADDING import com.jerboa.ui.theme.VERTICAL_SPACING import com.jerboa.ui.theme.XXL_PADDING import it.vercruysse.lemmyapi.datatypes.Community -import it.vercruysse.lemmyapi.datatypes.LocalUserVoteDisplayMode +import it.vercruysse.lemmyapi.datatypes.LocalSite +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.Person import it.vercruysse.lemmyapi.datatypes.PersonId import it.vercruysse.lemmyapi.datatypes.PersonView import it.vercruysse.lemmyapi.datatypes.PostId import it.vercruysse.lemmyapi.datatypes.PostView +import it.vercruysse.lemmyapi.enums.VoteAction import kotlinx.coroutines.CoroutineScope @ExperimentalLayoutApi @@ -121,7 +118,8 @@ import kotlinx.coroutines.CoroutineScope fun PostListingCard( postView: PostView, admins: List, - moderators: List?, + myUserInfo: MyUserInfo?, + localSite: LocalSite, instantScores: InstantScores, onUpvoteClick: () -> Unit, onDownvoteClick: () -> Unit, @@ -134,7 +132,7 @@ fun PostListingCard( onHidePostClick: (postView: PostView) -> Unit, onReportClick: (postView: PostView) -> Unit, onRemoveClick: (postView: PostView) -> Unit, - onBanPersonClick: (person: Person) -> Unit, + onBanPersonClick: (personView: PersonView) -> Unit, onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onLockPostClick: (postView: PostView) -> Unit, onFeaturePostClick: (data: PostFeatureData) -> Unit, @@ -146,7 +144,6 @@ fun PostListingCard( fullBody: Boolean, account: Account, expandedImage: Boolean, - enableDownVotes: Boolean, showCommunityName: Boolean = true, showAvatar: Boolean, useCustomTabs: Boolean, @@ -155,7 +152,6 @@ fun PostListingCard( showPostLinkPreview: Boolean, appState: JerboaAppState, showIfRead: Boolean, - voteDisplayMode: LocalUserVoteDisplayMode, postActionBarMode: PostActionBarMode, disableVideoAutoplay: Boolean, lowBandwidthMode: Boolean, @@ -197,7 +193,8 @@ fun PostListingCard( PostFooterLine( postView = postView, admins = admins, - moderators = moderators, + myUserInfo = myUserInfo, + localSite = localSite, instantScores = instantScores, onUpvoteClick = onUpvoteClick, onDownvoteClick = onDownvoteClick, @@ -219,11 +216,9 @@ fun PostListingCard( modifier = Modifier.padding(horizontal = MEDIUM_PADDING), showReply = showReply, account = account, - enableDownVotes = enableDownVotes, viewSource = viewSource, postActionBarMode = postActionBarMode, fromPostActivity = fullBody, - voteDisplayMode = voteDisplayMode, scope = appState.coroutineScope, ) } @@ -236,7 +231,8 @@ fun PreviewPostListingCard() { PostListingCard( postView = samplePostView, admins = emptyList(), - moderators = emptyList(), + myUserInfo = sampleMyUserInfo, + localSite = sampleLocalSite, useCustomTabs = false, usePrivateTabs = false, onUpvoteClick = {}, @@ -258,13 +254,11 @@ fun PreviewPostListingCard() { onPersonClick = {}, fullBody = false, account = AnonAccount, - enableDownVotes = true, showAvatar = true, blurNSFW = BlurNSFW.NSFW, appState = rememberJerboaAppState(), showPostLinkPreview = true, showIfRead = true, - voteDisplayMode = LocalUserVoteDisplayMode.default(), postActionBarMode = PostActionBarMode.Long, instantScores = sampleInstantScores, onViewSourceClick = {}, @@ -282,7 +276,8 @@ fun PreviewLinkPostListing() { PostListingCard( postView = sampleLinkPostView, admins = emptyList(), - moderators = emptyList(), + myUserInfo = sampleMyUserInfo, + localSite = sampleLocalSite, useCustomTabs = false, usePrivateTabs = false, onUpvoteClick = {}, @@ -304,13 +299,11 @@ fun PreviewLinkPostListing() { onPersonClick = {}, fullBody = false, account = AnonAccount, - enableDownVotes = true, showAvatar = true, blurNSFW = BlurNSFW.NSFW, appState = rememberJerboaAppState(), showPostLinkPreview = true, showIfRead = true, - voteDisplayMode = LocalUserVoteDisplayMode.default(), postActionBarMode = PostActionBarMode.Long, instantScores = sampleInstantScores, onViewSourceClick = {}, @@ -328,7 +321,8 @@ fun PreviewImagePostListingCard() { PostListingCard( postView = sampleImagePostView, admins = emptyList(), - moderators = emptyList(), + myUserInfo = sampleMyUserInfo, + localSite = sampleLocalSite, useCustomTabs = false, usePrivateTabs = false, onUpvoteClick = {}, @@ -350,13 +344,11 @@ fun PreviewImagePostListingCard() { onPersonClick = {}, fullBody = false, account = AnonAccount, - enableDownVotes = true, showAvatar = true, blurNSFW = BlurNSFW.NSFW, appState = rememberJerboaAppState(), showPostLinkPreview = true, showIfRead = true, - voteDisplayMode = LocalUserVoteDisplayMode.default(), postActionBarMode = PostActionBarMode.Long, instantScores = sampleInstantScores, onViewSourceClick = {}, @@ -374,7 +366,8 @@ fun PreviewImagePostListingSmallCard() { PostListingCard( postView = sampleImagePostView, admins = emptyList(), - moderators = emptyList(), + myUserInfo = sampleMyUserInfo, + localSite = sampleLocalSite, useCustomTabs = false, usePrivateTabs = false, onUpvoteClick = {}, @@ -396,13 +389,11 @@ fun PreviewImagePostListingSmallCard() { onPersonClick = {}, fullBody = false, account = AnonAccount, - enableDownVotes = true, showAvatar = true, blurNSFW = BlurNSFW.NSFW, appState = rememberJerboaAppState(), showPostLinkPreview = true, showIfRead = true, - voteDisplayMode = LocalUserVoteDisplayMode.default(), postActionBarMode = PostActionBarMode.Long, instantScores = sampleInstantScores, onViewSourceClick = {}, @@ -420,7 +411,8 @@ fun PreviewLinkNoThumbnailPostListing() { PostListingCard( postView = sampleLinkNoThumbnailPostView, admins = emptyList(), - moderators = emptyList(), + myUserInfo = sampleMyUserInfo, + localSite = sampleLocalSite, useCustomTabs = false, usePrivateTabs = false, onUpvoteClick = {}, @@ -442,13 +434,11 @@ fun PreviewLinkNoThumbnailPostListing() { onPersonClick = {}, fullBody = false, account = AnonAccount, - enableDownVotes = true, showAvatar = true, blurNSFW = BlurNSFW.NSFW, appState = rememberJerboaAppState(), showPostLinkPreview = true, showIfRead = true, - voteDisplayMode = LocalUserVoteDisplayMode.default(), postActionBarMode = PostActionBarMode.Long, instantScores = sampleInstantScores, onViewSourceClick = {}, @@ -463,9 +453,9 @@ fun PreviewLinkNoThumbnailPostListing() { fun PostFooterLine( postView: PostView, admins: List, - moderators: List?, + myUserInfo: MyUserInfo?, + localSite: LocalSite, instantScores: InstantScores, - voteDisplayMode: LocalUserVoteDisplayMode, onUpvoteClick: () -> Unit, onDownvoteClick: () -> Unit, onReplyClick: (postView: PostView) -> Unit, @@ -486,42 +476,19 @@ fun PostFooterLine( modifier: Modifier = Modifier, showReply: Boolean = false, account: Account, - enableDownVotes: Boolean, viewSource: Boolean, postActionBarMode: PostActionBarMode, fromPostActivity: Boolean, scope: CoroutineScope, ) { - val ctx = LocalContext.current var showMoreOptions by rememberSaveable { mutableStateOf(false) } if (showMoreOptions) { - val fallbackModerators = remember(moderators) { - moderators ?: simulateModerators( - ctx = ctx, - account = account, - forCommunity = postView.community.id, - ) - } - - val amMod = remember(moderators) { - amMod( - moderators = fallbackModerators, - myId = account.id, - ) - } - - val canMod = remember(admins, moderators) { - canMod( - creatorId = postView.creator.id, - admins = admins, - moderators = fallbackModerators, - myId = account.id, - ) - } PostOptionsDropdown( postView = postView, + admins = admins, + myUserInfo = myUserInfo, onDismissRequest = { showMoreOptions = false }, onCommunityClick = onCommunityClick, onPersonClick = onPersonClick, @@ -536,10 +503,6 @@ fun PostFooterLine( onFeaturePostClick = onFeaturePostClick, onViewVotesClick = onViewVotesClick, onViewSourceClick = onViewSourceClick, - isCreator = account.id == postView.creator.id, - canMod = canMod, - amAdmin = account.isAdmin, - amMod = amMod, viewSource = viewSource, showViewSource = fromPostActivity, scope = scope, @@ -561,47 +524,55 @@ fun PostFooterLine( .fillMaxWidth() .padding(bottom = SMALL_PADDING), ) { - // Right handside shows the comments on the left side + // Right hand side shows the comments on the left side if (postActionBarMode == PostActionBarMode.RightHandShort) { CommentNewCountRework( - comments = postView.counts.comments, - unreadCount = postView.unread_comments, + comments = postView.post.comments, + readComments = postView.post_actions?.read_comments_amount, account = account, modifier = Modifier.weight(1F, true), ) } VoteScore( instantScores = instantScores, + myUserInfo = myUserInfo, + localSite = localSite, + voteContentType = PostOrCommentType.Post, onVoteClick = onUpvoteClick, - voteDisplayMode = voteDisplayMode, account = account, ) UpvotePercentage( instantScores = instantScores, - voteDisplayMode = voteDisplayMode, + myUserInfo = myUserInfo, + localSite = localSite, + voteContentType = PostOrCommentType.Post, account = account, ) VoteGeneric( instantScores = instantScores, - type = VoteType.Upvote, + myUserInfo = myUserInfo, + localSite = localSite, + voteContentType = PostOrCommentType.Post, + voteAction = VoteAction.UpVote, + creatorId = postView.post.creator_id, onVoteClick = onUpvoteClick, - voteDisplayMode = voteDisplayMode, account = account, ) - if (enableDownVotes) { VoteGeneric( instantScores = instantScores, - type = VoteType.Downvote, + myUserInfo = myUserInfo, + localSite = localSite, + voteContentType = PostOrCommentType.Post, + voteAction = VoteAction.DownVote, + creatorId = postView.post.creator_id, onVoteClick = onDownvoteClick, - voteDisplayMode = voteDisplayMode, account = account, ) - } if (postActionBarMode == PostActionBarMode.Long) { CommentNewCountRework( - comments = postView.counts.comments, - unreadCount = postView.unread_comments, + comments = postView.post.comments, + readComments = postView.post_actions?.read_comments_amount, account = account, modifier = Modifier.weight(1F, true), ) @@ -616,7 +587,7 @@ fun PostFooterLine( ) } SavedButton( - saved = postView.saved, + saved = postView.post_actions?.saved_at != null, account = account, onSaveClick = { onSaveClick(postView) }, ) @@ -638,8 +609,8 @@ fun PostFooterLine( if (postActionBarMode == PostActionBarMode.LeftHandShort) { CommentNewCountRework( - comments = postView.counts.comments, - unreadCount = postView.unread_comments, + comments = postView.post.comments, + readComments = postView.post_actions?.read_comments_amount, account = account, ) } @@ -652,9 +623,9 @@ fun PostFooterLinePreview() { PostFooterLine( postView = samplePostView, admins = emptyList(), - moderators = emptyList(), + myUserInfo = sampleMyUserInfo, + localSite = sampleLocalSite, instantScores = sampleInstantScores, - voteDisplayMode = LocalUserVoteDisplayMode.default(), onUpvoteClick = {}, onDownvoteClick = {}, onReplyClick = {}, @@ -673,7 +644,6 @@ fun PostFooterLinePreview() { onPersonClick = {}, onViewSourceClick = {}, account = AnonAccount, - enableDownVotes = true, viewSource = false, postActionBarMode = PostActionBarMode.Long, fromPostActivity = true, @@ -781,8 +751,8 @@ fun PostCommunityAndCreatorBlock( } } TimeAgo( - published = postView.post.published, - updated = postView.post.updated, + published = postView.post.published_at, + updated = postView.post.updated_at, modifier = centerMod, ) } @@ -1098,7 +1068,7 @@ fun PostTitleAndImageLink( // Title of the post PostName( post = postView.post, - read = postView.read, + read = postView.post_actions?.read_at != null, showIfRead = showIfRead, modifier = Modifier.padding(horizontal = MEDIUM_PADDING), ) @@ -1130,7 +1100,7 @@ fun PostTitleAndVideoLink( // Title of the post PostName( post = postView.post, - read = postView.read, + read = postView.post_actions?.read_at != null, showIfRead = showIfRead, modifier = Modifier.padding(horizontal = MEDIUM_PADDING), ) @@ -1171,7 +1141,7 @@ fun PostTitleAndMediaPlaceholder( PostName( post = postView.post, - read = postView.read, + read = postView.post_actions?.read_at != null, showIfRead = showIfRead, modifier = Modifier.padding(horizontal = MEDIUM_PADDING), ) @@ -1225,7 +1195,7 @@ fun PostTitleAndThumbnail( ) { PostName( post = postView.post, - read = postView.read, + read = postView.post_actions?.read_at != null, showIfRead = showIfRead, ) HostnameSimplified(postView.post.url, account) @@ -1265,20 +1235,21 @@ fun HostnameSimplified( @Composable fun CommentNewCountRework( comments: Long, - unreadCount: Long, + readComments: Long?, account: Account, modifier: Modifier = Modifier, ) { - val unread = - if (unreadCount == 0L || comments == unreadCount) { - null - } else { - (if (unreadCount > 0) "+" else "") + siFormat(unreadCount) - } + val read = readComments ?: 0 + val unreadStr = if (read >= comments || read == 0L) { + null + } else { + val unreadCount = comments - read + "+" + siFormat(unreadCount) + } ActionBarButtonAndBadge( icon = Icons.Outlined.ChatBubbleOutline, - iconBadgeCount = unread, + iconBadgeCount = unreadStr, contentDescription = null, text = siFormat(comments), noClick = true, diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListingList.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListingList.kt index 8b39e9f98..fea35a4ca 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListingList.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListingList.kt @@ -28,13 +28,14 @@ import androidx.compose.ui.unit.dp import com.jerboa.JerboaAppState import com.jerboa.R import com.jerboa.datatypes.sampleInstantScores +import com.jerboa.datatypes.sampleLocalSite +import com.jerboa.datatypes.sampleMyUserInfo import com.jerboa.datatypes.samplePostView import com.jerboa.db.entity.Account import com.jerboa.db.entity.AnonAccount import com.jerboa.feat.BlurNSFW import com.jerboa.feat.InstantScores -import com.jerboa.feat.VoteType -import com.jerboa.feat.default +import com.jerboa.feat.PostOrCommentType import com.jerboa.feat.needBlur import com.jerboa.hostNameCleaned import com.jerboa.isSameInstance @@ -45,19 +46,25 @@ import com.jerboa.ui.components.common.NsfwBadge import com.jerboa.ui.components.common.TimeAgo import com.jerboa.ui.components.common.VoteGeneric import com.jerboa.ui.components.common.scoreColor +import com.jerboa.ui.components.common.scoreOrPctStr import com.jerboa.ui.components.community.CommunityLink import com.jerboa.ui.components.person.PersonProfileLink import com.jerboa.ui.theme.MEDIUM_PADDING import com.jerboa.ui.theme.SMALLER_PADDING import com.jerboa.ui.theme.SMALL_PADDING -import it.vercruysse.lemmyapi.datatypes.LocalUserVoteDisplayMode +import it.vercruysse.lemmyapi.datatypes.LocalSite +import it.vercruysse.lemmyapi.datatypes.MyUserInfo +import it.vercruysse.lemmyapi.datatypes.PersonId import it.vercruysse.lemmyapi.datatypes.PostView +import it.vercruysse.lemmyapi.enums.VoteAction @OptIn(ExperimentalLayoutApi::class) @Composable fun PostListingList( postView: PostView, instantScores: InstantScores, + myUserInfo: MyUserInfo?, + localSite: LocalSite, onUpvoteClick: () -> Unit, onDownvoteClick: () -> Unit, onPostClick: (postView: PostView) -> Unit, @@ -69,8 +76,6 @@ fun PostListingList( blurNSFW: BlurNSFW, appState: JerboaAppState, showIfRead: Boolean, - enableDownVotes: Boolean, - voteDisplayMode: LocalUserVoteDisplayMode, lowBandwidthMode: Boolean, ) { Column( @@ -91,11 +96,12 @@ fun PostListingList( if (showVotingArrowsInListView) { PostVotingTile( instantScores = instantScores, + myUserInfo = myUserInfo, + localSite = localSite, + creatorId = postView.post.creator_id, onUpvoteClick = onUpvoteClick, onDownvoteClick = onDownvoteClick, account = account, - enableDownVotes = enableDownVotes, - voteDisplayMode = voteDisplayMode, ) } Column( @@ -105,7 +111,8 @@ fun PostListingList( .clickable { onPostClick(postView) }, verticalArrangement = Arrangement.spacedBy(SMALL_PADDING), ) { - PostName(post = postView.post, read = postView.read, showIfRead = showIfRead) + PostName( + post = postView.post, read = postView.post_actions?.read_at != null, showIfRead = showIfRead) FlowRow( horizontalArrangement = Arrangement.spacedBy(SMALLER_PADDING, Alignment.Start), ) { @@ -148,8 +155,8 @@ fun PostListingList( } } TimeAgo( - published = postView.post.published, - updated = postView.post.updated, + published = postView.post.published_at, + updated = postView.post.updated_at, modifier = centerMod, ) } @@ -169,14 +176,14 @@ fun PostListingList( text = stringResource( R.string.post_listing_comments_count, - postView.counts.comments, + postView.post.comments, ), style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.outline, ) CommentNewCount( - comments = postView.counts.comments, - unreadCount = postView.unread_comments, + comments = postView.post.comments, + readComments = postView.post_actions?.read_comments_amount, style = MaterialTheme.typography.labelMedium, ) NsfwBadge(visible = nsfwCheck(postView)) @@ -200,6 +207,8 @@ fun PostListingList( fun PostListingListPreview() { PostListingList( postView = samplePostView, + myUserInfo = sampleMyUserInfo, + localSite = sampleLocalSite, instantScores = sampleInstantScores, onUpvoteClick = {}, onDownvoteClick = {}, @@ -211,8 +220,6 @@ fun PostListingListPreview() { blurNSFW = BlurNSFW.NSFW, appState = rememberJerboaAppState(), showIfRead = true, - enableDownVotes = false, - voteDisplayMode = LocalUserVoteDisplayMode.default(), lowBandwidthMode = false, ) } @@ -223,6 +230,8 @@ fun PostListingListWithThumbPreview() { PostListingList( postView = samplePostView, instantScores = sampleInstantScores, + myUserInfo = sampleMyUserInfo, + localSite = sampleLocalSite, onUpvoteClick = {}, onDownvoteClick = {}, onPostClick = {}, @@ -233,8 +242,6 @@ fun PostListingListWithThumbPreview() { blurNSFW = BlurNSFW.NSFW, appState = rememberJerboaAppState(), showIfRead = true, - enableDownVotes = false, - voteDisplayMode = LocalUserVoteDisplayMode.default(), lowBandwidthMode = false, ) } @@ -242,16 +249,17 @@ fun PostListingListWithThumbPreview() { @Composable fun CommentNewCount( comments: Long, - unreadCount: Long, + readComments: Long?, style: TextStyle = MaterialTheme.typography.labelSmall.copy(fontStyle = FontStyle.Italic), spacing: Dp = 0.dp, ) { - val unread = - if (unreadCount == 0L || comments == unreadCount) { - null - } else { - unreadCount - } + val read = readComments ?: 0 + val unread = if (read >= comments || read == 0L) { + null + } else { + comments - read + } + if (unread != null) { Spacer(Modifier.padding(horizontal = spacing)) @@ -266,11 +274,12 @@ fun CommentNewCount( @Composable fun PostVotingTile( instantScores: InstantScores, + myUserInfo: MyUserInfo?, + localSite: LocalSite, + creatorId: PersonId, onUpvoteClick: () -> Unit, onDownvoteClick: () -> Unit, account: Account, - enableDownVotes: Boolean, - voteDisplayMode: LocalUserVoteDisplayMode, ) { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -282,14 +291,22 @@ fun PostVotingTile( ) { VoteGeneric( instantScores = instantScores, - voteDisplayMode = voteDisplayMode, - type = VoteType.Upvote, + myUserInfo = myUserInfo, + localSite = localSite, + voteAction = VoteAction.UpVote, + voteContentType = PostOrCommentType.Post, + creatorId = creatorId, onVoteClick = onUpvoteClick, account = account, - hideScores = true, + // TODO what is hide scores for? +// hideScores = true, ) - val scoreOrPctStr = instantScores.scoreOrPctStr(voteDisplayMode) + val scoreOrPctStr = instantScores.scoreOrPctStr( + myUserInfo = myUserInfo, + localSite = localSite, + type = PostOrCommentType.Post, + ) Text( text = scoreOrPctStr ?: "", @@ -299,7 +316,6 @@ fun PostVotingTile( modifier = Modifier.alpha(if (scoreOrPctStr != null) 1f else 0f), ) - if (enableDownVotes) { // invisible Text below aligns width of PostVotingTiles Text( text = "00000", @@ -308,12 +324,15 @@ fun PostVotingTile( ) VoteGeneric( instantScores = instantScores, - voteDisplayMode = voteDisplayMode, - type = VoteType.Downvote, + myUserInfo = myUserInfo, + localSite = localSite, + voteAction = VoteAction.DownVote, + voteContentType = PostOrCommentType.Post, + creatorId = creatorId, onVoteClick = onDownvoteClick, account = account, - hideScores = true, + // TODO +// hideScores = true, ) - } } } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt index 81900c983..434283fa7 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt @@ -1,5 +1,7 @@ package com.jerboa.ui.components.post +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -16,6 +18,7 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.tooling.preview.Preview import com.jerboa.JerboaAppState import com.jerboa.PostViewMode +import com.jerboa.SelectionVisibilityState import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.datatypes.PostFeatureData import com.jerboa.datatypes.sampleLinkPostView @@ -29,10 +32,13 @@ import com.jerboa.feat.default import com.jerboa.rememberJerboaAppState import com.jerboa.ui.components.common.RetryLoadingPosts import com.jerboa.ui.components.common.TriggerWhenReachingEnd -import com.jerboa.ui.theme.SMALL_PADDING +import com.jerboa.ui.components.common.selectedItemContainerColor +import com.jerboa.ui.theme.MEDIUM_PADDING import it.vercruysse.lemmyapi.datatypes.Community +import it.vercruysse.lemmyapi.datatypes.GetSiteResponse +import it.vercruysse.lemmyapi.datatypes.LocalSite import it.vercruysse.lemmyapi.datatypes.LocalUserVoteDisplayMode -import it.vercruysse.lemmyapi.datatypes.Person +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.PersonId import it.vercruysse.lemmyapi.datatypes.PersonView import it.vercruysse.lemmyapi.datatypes.PostId @@ -43,7 +49,8 @@ import it.vercruysse.lemmyapi.datatypes.PostView fun PostListings( posts: List, admins: List, - moderators: List?, + myUserInfo: MyUserInfo?, + localSite: LocalSite, contentAboveListings: @Composable () -> Unit = {}, onUpvoteClick: (postView: PostView) -> Unit, onDownvoteClick: (postView: PostView) -> Unit, @@ -55,7 +62,7 @@ fun PostListings( onHidePostClick: (postView: PostView) -> Unit, onReportClick: (postView: PostView) -> Unit, onRemoveClick: (postView: PostView) -> Unit, - onBanPersonClick: (person: Person) -> Unit, + onBanPersonClick: (personView: PersonView) -> Unit, onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onLockPostClick: (postView: PostView) -> Unit, onFeaturePostClick: (data: PostFeatureData) -> Unit, @@ -68,8 +75,6 @@ fun PostListings( listState: LazyListState, postViewMode: PostViewMode, showVotingArrowsInListView: Boolean, - enableDownVotes: Boolean, - showAvatar: Boolean, useCustomTabs: Boolean, usePrivateTabs: Boolean, blurNSFW: BlurNSFW, @@ -78,12 +83,13 @@ fun PostListings( markAsReadOnScroll: Boolean, onMarkAsRead: (postView: PostView) -> Unit, showIfRead: Boolean, - voteDisplayMode: LocalUserVoteDisplayMode, postActionBarMode: PostActionBarMode, showPostAppendRetry: Boolean, swipeToActionPreset: SwipeToActionPreset, disableVideoAutoplay: Boolean, lowBandwidthMode: Boolean, + selectionState: SelectionVisibilityState, + siteRes: GetSiteResponse, ) { LazyColumn( state = listState, @@ -99,60 +105,72 @@ fun PostListings( items = posts, contentType = { _, _ -> "Post" }, ) { index, postView -> - PostListing( - postView = postView, - admins = admins, - moderators = moderators, - useCustomTabs = useCustomTabs, - usePrivateTabs = usePrivateTabs, - onUpvoteClick = onUpvoteClick, - onDownvoteClick = onDownvoteClick, - onReplyClick = onReplyClick, - onPostClick = onPostClick, - onSaveClick = onSaveClick, - onCommunityClick = onCommunityClick, - onEditPostClick = onEditPostClick, - onDeletePostClick = onDeletePostClick, - onHidePostClick = onHidePostClick, - onReportClick = onReportClick, - onRemoveClick = onRemoveClick, - onBanPersonClick = onBanPersonClick, - onBanFromCommunityClick = onBanFromCommunityClick, - onLockPostClick = onLockPostClick, - onFeaturePostClick = onFeaturePostClick, - onViewVotesClick = onViewPostVotesClick, - onPersonClick = onPersonClick, - showCommunityName = showCommunityName, - fullBody = false, - account = account, - postViewMode = postViewMode, - showVotingArrowsInListView = showVotingArrowsInListView, - enableDownVotes = enableDownVotes, - showAvatar = showAvatar, - blurNSFW = blurNSFW, - appState = appState, - showPostLinkPreview = showPostLinkPreviews, - showIfRead = showIfRead, - voteDisplayMode = voteDisplayMode, - postActionBarMode = postActionBarMode, - swipeToActionPreset = swipeToActionPreset, - disableVideoAutoplay = disableVideoAutoplay, - lowBandwidthMode = lowBandwidthMode, - ).let { - if (!postView.read && markAsReadOnScroll) { - DisposableEffect(key1 = postView.post.id) { - onDispose { - if (listState.isScrollInProgress && index < listState.firstVisibleItemIndex) { - onMarkAsRead(postView) + + val selected = when (selectionState) { + is SelectionVisibilityState.ShowSelection -> selectionState.selectedItem == postView.post.id + SelectionVisibilityState.NoSelection -> false + } + + Column( + modifier = Modifier + .background(selectedItemContainerColor(selected)), + ) { + PostListing( + postView = postView, + admins = admins, + myUserInfo = myUserInfo, + localSite = localSite, + useCustomTabs = useCustomTabs, + usePrivateTabs = usePrivateTabs, + onUpvoteClick = onUpvoteClick, + onDownvoteClick = onDownvoteClick, + onReplyClick = onReplyClick, + onPostClick = onPostClick, + onSaveClick = onSaveClick, + onCommunityClick = onCommunityClick, + onEditPostClick = onEditPostClick, + onDeletePostClick = onDeletePostClick, + onHidePostClick = onHidePostClick, + onReportClick = onReportClick, + onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, + onLockPostClick = onLockPostClick, + onFeaturePostClick = onFeaturePostClick, + onViewVotesClick = onViewPostVotesClick, + onPersonClick = onPersonClick, + showCommunityName = showCommunityName, + fullBody = false, + account = account, + postViewMode = postViewMode, + showVotingArrowsInListView = showVotingArrowsInListView, + enableDownVotes = enableDownVotes, + showAvatar = showAvatar, + blurNSFW = blurNSFW, + appState = appState, + showPostLinkPreview = showPostLinkPreviews, + showIfRead = showIfRead, + voteDisplayMode = voteDisplayMode, + postActionBarMode = postActionBarMode, + swipeToActionPreset = swipeToActionPreset, + disableVideoAutoplay = disableVideoAutoplay, + lowBandwidthMode = lowBandwidthMode, + ).let { + if (!postView.read && markAsReadOnScroll) { + DisposableEffect(key1 = postView.post.id) { + onDispose { + if (listState.isScrollInProgress && index < listState.firstVisibleItemIndex) { + onMarkAsRead(postView) + } } } } } + HorizontalDivider( + modifier = Modifier.padding(top = MEDIUM_PADDING), + color = MaterialTheme.colorScheme.surfaceVariant, + ) } - HorizontalDivider( - modifier = Modifier.padding(vertical = SMALL_PADDING), - color = MaterialTheme.colorScheme.surfaceVariant, - ) } if (showPostAppendRetry) { @@ -210,5 +228,6 @@ fun PreviewPostListings() { onReplyClick = {}, disableVideoAutoplay = false, lowBandwidthMode = false, + selectionState = SelectionVisibilityState.NoSelection, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostScreen.kt b/app/src/main/java/com/jerboa/ui/components/post/PostPane.kt similarity index 68% rename from app/src/main/java/com/jerboa/ui/components/post/PostScreen.kt rename to app/src/main/java/com/jerboa/ui/components/post/PostPane.kt index b2f756e74..e122fed84 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostPane.kt @@ -56,11 +56,7 @@ import com.jerboa.api.ApiState import com.jerboa.buildCommentsTree import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.datatypes.getLocalizedCommentSortTypeName -import com.jerboa.db.entity.isAnon -import com.jerboa.feat.BlurNSFW -import com.jerboa.feat.PostActionBarMode -import com.jerboa.feat.SwipeToActionPreset -import com.jerboa.feat.VoteType +import com.jerboa.db.entity.AppSettings import com.jerboa.feat.doIfReadyElseDisplayInfo import com.jerboa.feat.newVote import com.jerboa.getCommentParentId @@ -68,7 +64,7 @@ import com.jerboa.getDepthFromComment import com.jerboa.model.AccountViewModel import com.jerboa.model.PostViewModel import com.jerboa.model.ReplyItem -import com.jerboa.model.SiteViewModel +import com.jerboa.nowBoolean import com.jerboa.scrollToNextParentComment import com.jerboa.scrollToPreviousParentComment import com.jerboa.ui.components.ban.BanFromCommunityReturn @@ -99,14 +95,17 @@ import it.vercruysse.lemmyapi.datatypes.DeleteComment import it.vercruysse.lemmyapi.datatypes.DeletePost import it.vercruysse.lemmyapi.datatypes.DistinguishComment import it.vercruysse.lemmyapi.datatypes.FeaturePost +import it.vercruysse.lemmyapi.datatypes.GetSiteResponse import it.vercruysse.lemmyapi.datatypes.HidePost import it.vercruysse.lemmyapi.datatypes.LockPost +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.PersonView import it.vercruysse.lemmyapi.datatypes.PostId import it.vercruysse.lemmyapi.datatypes.PostView import it.vercruysse.lemmyapi.datatypes.SaveComment import it.vercruysse.lemmyapi.datatypes.SavePost -import it.vercruysse.lemmyapi.dto.CommentSortType +import it.vercruysse.lemmyapi.enums.CommentSortType +import it.vercruysse.lemmyapi.enums.VoteAction object PostViewReturn { const val POST_VIEW = "post-view::return(post-view)" @@ -118,30 +117,26 @@ object PostViewReturn { ExperimentalComposeUiApi::class, ) @Composable -fun PostScreen( - id: Either, - siteViewModel: SiteViewModel, +fun PostPane( + postOrCommentId: Either, + siteRes: GetSiteResponse, + myUserInfo: MyUserInfo?, + appSettings: AppSettings, accountViewModel: AccountViewModel, appState: JerboaAppState, - useCustomTabs: Boolean, - usePrivateTabs: Boolean, - showCollapsedCommentContent: Boolean, - showActionBarByDefault: Boolean, - showVotingArrowsInListView: Boolean, - showParentCommentNavigationButtons: Boolean, - navigateParentCommentsWithVolumeButtons: Boolean, - blurNSFW: BlurNSFW, - showPostLinkPreview: Boolean, - postActionBarMode: PostActionBarMode, - swipeToActionPreset: SwipeToActionPreset, - disableVideoAutoplay: Boolean, - lowBandwidthMode: Boolean, + onClickBack: () -> Unit, ) { - Log.d("jerboa", "got to post screen") + Log.d("jerboa", "got to post pane") val resources = LocalResources.current - val postViewModel: PostViewModel = viewModel(factory = PostViewModel.Companion.Factory(id)) + val postViewModel: PostViewModel = viewModel(factory = PostViewModel.Companion.Factory(postOrCommentId)) + + // Some hacky code required to update the screen for tablet view + // Necessary because the viewmodel initializes only once. + if (postViewModel.id !== postOrCommentId) { + postViewModel.reInitializeWithNewId(postOrCommentId) + } appState.ConsumeReturn(PostEditReturn.POST_VIEW, postViewModel::updatePost) appState.ConsumeReturn(PostRemoveReturn.POST_VIEW, postViewModel::updatePost) @@ -183,7 +178,7 @@ fun PostScreen( .focusRequester(focusRequester) .focusable() .onKeyEvent { keyEvent -> - if (navigateParentCommentsWithVolumeButtons) { + if (appSettings.navigateParentCommentsWithVolumeButtons) { when (keyEvent.key) { Key.VolumeUp -> { scrollToPreviousParentComment(scope, postViewModel.parentListStateIndexes, listState) @@ -204,7 +199,7 @@ fun PostScreen( } }, bottomBar = { - if (showParentCommentNavigationButtons) { + if (appSettings.showParentCommentNavigationButtons) { CommentNavigationBottomAppBar( scope, postViewModel.parentListStateIndexes, @@ -224,7 +219,7 @@ fun PostScreen( navigationIcon = { IconButton( modifier = Modifier.testTag("jerboa:back"), - onClick = appState::navigateUp, + onClick = onClickBack, ) { Icon( Icons.AutoMirrored.Outlined.ArrowBack, @@ -266,24 +261,15 @@ fun PostScreen( }, ) { MainPostScreenBody( - id = id, + id = postOrCommentId, postViewModel = postViewModel, - siteViewModel = siteViewModel, + siteRes = siteRes, + appSettings = appSettings, + myUserInfo = myUserInfo, accountViewModel = accountViewModel, appState = appState, listState = listState, snackbarHostState = snackbarHostState, - useCustomTabs = useCustomTabs, - usePrivateTabs = usePrivateTabs, - showCollapsedCommentContent = showCollapsedCommentContent, - showActionBarByDefault = showActionBarByDefault, - showVotingArrowsInListView = showVotingArrowsInListView, - blurNSFW = blurNSFW, - showPostLinkPreview = showPostLinkPreview, - postActionBarMode = postActionBarMode, - swipeToActionPreset = swipeToActionPreset, - disableVideoAutoplay = disableVideoAutoplay, - lowBandwidthMode = lowBandwidthMode, ) } }, @@ -295,22 +281,13 @@ fun PostScreen( fun MainPostScreenBody( id: Either, postViewModel: PostViewModel, - siteViewModel: SiteViewModel, accountViewModel: AccountViewModel, appState: JerboaAppState, listState: LazyListState, snackbarHostState: SnackbarHostState, - useCustomTabs: Boolean, - usePrivateTabs: Boolean, - showCollapsedCommentContent: Boolean, - showActionBarByDefault: Boolean, - showVotingArrowsInListView: Boolean, - blurNSFW: BlurNSFW, - showPostLinkPreview: Boolean, - postActionBarMode: PostActionBarMode, - swipeToActionPreset: SwipeToActionPreset, - disableVideoAutoplay: Boolean, - lowBandwidthMode: Boolean, + siteRes: GetSiteResponse, + appSettings: AppSettings, + myUserInfo: MyUserInfo?, ) { val ctx = LocalContext.current val resources = LocalResources.current @@ -334,10 +311,10 @@ fun MainPostScreenBody( val postView = postRes.data.post_view val moderators = remember(postRes) { postRes.data.moderators.map { it.moderator.id } } - if (!account.isAnon()) { + if (myUserInfo != null) { appState.addReturn( PostViewReturn.POST_VIEW, - postView.copy(read = true), + postView.copy(post_actions = postView.post_actions?.copy(read_at = nowBoolean(true))), ) } LazyColumn( @@ -347,43 +324,24 @@ fun MainPostScreenBody( item(key = "${postView.post.id}_listing", "post_listing") { PostListing( postView = postView, - admins = siteViewModel.admins(), - moderators = moderators, + siteRes = siteRes, + myUserInfo = myUserInfo, + appSettings = appSettings, onUpvoteClick = { pv -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) { postViewModel.likePost( CreatePostLike( post_id = pv.post.id, - score = newVote(pv.my_vote, VoteType.Upvote), + vote = newVote(pv.post_actions?.vote, VoteAction.UpVote), ), ) - } }, onDownvoteClick = { pv -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) { postViewModel.likePost( CreatePostLike( post_id = pv.post.id, - score = newVote(pv.my_vote, VoteType.Downvote), + vote = newVote(pv.post_actions?.vote, VoteAction.DownVote), ), ) - } }, onReplyClick = { pv -> appState.toCommentReply( @@ -392,22 +350,12 @@ fun MainPostScreenBody( }, onPostClick = {}, onSaveClick = { pv -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) { postViewModel.savePost( SavePost( post_id = pv.post.id, - save = !pv.saved, + save = pv.post_actions?.saved_at == null, ), ) - } }, onCommunityClick = { community -> appState.toCommunity(id = community.id) @@ -418,41 +366,21 @@ fun MainPostScreenBody( ) }, onDeletePostClick = { pv -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) { postViewModel.deletePost( DeletePost( post_id = pv.post.id, deleted = !pv.post.deleted, ), ) - } }, onHidePostClick = { pv -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) { postViewModel.hidePost( HidePost( - post_ids = listOf(pv.post.id), - hide = !pv.hidden, + post_id = pv.post.id, + hide = pv.post_actions?.hidden_at == null, ), ctx, ) - } }, onReportClick = { pv -> appState.toPostReport(id = pv.post.id) @@ -460,40 +388,22 @@ fun MainPostScreenBody( onRemoveClick = { pv -> appState.toPostRemove(post = pv.post) }, - onBanPersonClick = { p -> - appState.toBanPerson(person = p) + onBanPersonClick = { pv -> + appState.toBanPerson(personView = pv) }, onBanFromCommunityClick = { d -> appState.toBanFromCommunity(banData = d) }, onLockPostClick = { pv -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) { postViewModel.lockPost( LockPost( post_id = pv.post.id, locked = !pv.post.locked, + reason = TODO ), ) - } }, onFeaturePostClick = { data -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) { postViewModel.featurePost( FeaturePost( post_id = data.post.id, @@ -501,7 +411,6 @@ fun MainPostScreenBody( feature_type = data.type, ), ) - } }, onViewVotesClick = appState::toPostLikes, onPersonClick = appState::toProfile, @@ -510,20 +419,8 @@ fun MainPostScreenBody( fullBody = true, account = account, postViewMode = PostViewMode.SmallCard, - enableDownVotes = siteViewModel.enableDownvotes(), - showAvatar = siteViewModel.showAvatar() && !lowBandwidthMode, - showVotingArrowsInListView = showVotingArrowsInListView, - useCustomTabs = useCustomTabs, - usePrivateTabs = usePrivateTabs, - blurNSFW = blurNSFW, appState = appState, - showPostLinkPreview = showPostLinkPreview, showIfRead = false, - voteDisplayMode = siteViewModel.voteDisplayMode(), - postActionBarMode = postActionBarMode, - swipeToActionPreset = swipeToActionPreset, - disableVideoAutoplay = disableVideoAutoplay, - lowBandwidthMode = lowBandwidthMode, ) } @@ -550,7 +447,7 @@ fun MainPostScreenBody( is ApiState.Holder -> { val commentTree = buildCommentsTree( - commentsRes.data.comments, + commentsRes.data.items, id.fold( { null }, { it }, @@ -587,8 +484,9 @@ fun MainPostScreenBody( commentNodeItems( nodes = commentTree, - admins = siteViewModel.admins(), - moderators = moderators, + siteRes = siteRes, + myUserInfo = myUserInfo, + appSettings = appSettings, increaseLazyListIndexTracker = { lazyListIndexTracker++ }, @@ -602,46 +500,26 @@ fun MainPostScreenBody( onMarkAsReadClick = {}, onCommentClick = postViewModel::toggleExpanded, onUpvoteClick = { cv -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) { postViewModel.likeComment( CreateCommentLike( comment_id = cv.comment.id, - score = newVote( - cv.my_vote, - VoteType.Upvote, + vote = newVote( + cv.comment_actions?.vote, + VoteAction.UpVote, ), ), ) - } }, onDownvoteClick = { cv -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) { postViewModel.likeComment( CreateCommentLike( comment_id = cv.comment.id, - score = newVote( - cv.my_vote, - VoteType.Downvote, + vote = newVote( + cv.comment_actions?.vote, + VoteAction.DownVote, ), ), ) - } }, onReplyClick = { cv -> appState.toCommentReply( @@ -649,22 +527,12 @@ fun MainPostScreenBody( ) }, onSaveClick = { cv -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) { postViewModel.saveComment( SaveComment( comment_id = cv.comment.id, - save = !cv.saved, + save = cv.comment_actions?.saved_at == null, ), ) - } }, onPersonClick = appState::toProfile, onViewVotesClick = appState::toCommentLikes, @@ -672,40 +540,20 @@ fun MainPostScreenBody( onHeaderLongClick = postViewModel::toggleActionBar, onEditCommentClick = appState::toCommentEdit, onDeleteCommentClick = { cv -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) { postViewModel.deleteComment( DeleteComment( comment_id = cv.comment.id, deleted = !cv.comment.deleted, ), ) - } }, onDistinguishClick = { cv -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) { postViewModel.distinguishComment( DistinguishComment( comment_id = cv.comment.id, distinguished = !cv.comment.distinguished, ), ) - } }, onReportClick = { cv -> appState.toCommentReport(id = cv.comment.id) @@ -720,15 +568,6 @@ fun MainPostScreenBody( }, onFetchChildrenClick = postViewModel::fetchMoreChildren, onBlockCreatorClick = { person -> - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - ) { postViewModel.blockPerson( BlockPerson( person_id = person.id, @@ -736,7 +575,6 @@ fun MainPostScreenBody( ), ctx, ) - } }, onCommunityClick = { community -> appState.toCommunity(id = community.id) @@ -744,19 +582,13 @@ fun MainPostScreenBody( // Do nothing onPostClick = {}, account = account, - enableDownVotes = siteViewModel.enableDownvotes(), - showAvatar = siteViewModel.showAvatar() && !lowBandwidthMode, isCollapsedByParent = false, - showCollapsedCommentContent = showCollapsedCommentContent, showActionBar = { commentId -> - showActionBarByDefault xor - commentsWithToggledActionBar.contains( - commentId, - ) + appSettings.showCommentActionBarByDefault xor + commentsWithToggledActionBar.contains( + commentId, + ) }, - blurNSFW = blurNSFW, - voteDisplayMode = siteViewModel.voteDisplayMode(), - swipeToActionPreset = swipeToActionPreset, ) item { Spacer(modifier = Modifier.height(100.dp)) diff --git a/app/src/main/java/com/jerboa/ui/components/post/composables/PostOptionsDropdown.kt b/app/src/main/java/com/jerboa/ui/components/post/composables/PostOptionsDropdown.kt index 23e3baafe..e8368c1a4 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/composables/PostOptionsDropdown.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/composables/PostOptionsDropdown.kt @@ -30,12 +30,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import com.jerboa.PostLinkType import com.jerboa.R +import com.jerboa.amAdmin import com.jerboa.api.API.getInstanceOrNull import com.jerboa.communityNameShown import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.datatypes.PostFeatureData import com.jerboa.feat.blockCommunity import com.jerboa.feat.blockPerson +import com.jerboa.feat.canModOrAdmin import com.jerboa.feat.copyTextToClipboard import com.jerboa.feat.getInstanceFromCommunityUrl import com.jerboa.feat.shareLink @@ -47,14 +49,17 @@ import com.jerboa.ui.components.common.BanPersonPopupMenuItem import com.jerboa.ui.components.common.PopupMenuItem import com.jerboa.util.cascade.CascadeCenteredDropdownMenu import it.vercruysse.lemmyapi.datatypes.BlockCommunity -import it.vercruysse.lemmyapi.datatypes.BlockInstance import it.vercruysse.lemmyapi.datatypes.BlockPerson import it.vercruysse.lemmyapi.datatypes.Community +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.Person import it.vercruysse.lemmyapi.datatypes.PersonId +import it.vercruysse.lemmyapi.datatypes.PersonView import it.vercruysse.lemmyapi.datatypes.PostId import it.vercruysse.lemmyapi.datatypes.PostView -import it.vercruysse.lemmyapi.dto.PostFeatureType +import it.vercruysse.lemmyapi.datatypes.UserBlockInstanceCommunitiesParams +import it.vercruysse.lemmyapi.datatypes.UserBlockInstancePersonsParams +import it.vercruysse.lemmyapi.enums.PostFeatureType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -63,6 +68,8 @@ import kotlinx.coroutines.withContext @Composable fun PostOptionsDropdown( postView: PostView, + admins: List, + myUserInfo: MyUserInfo?, onDismissRequest: () -> Unit, onCommunityClick: (Community) -> Unit, onPersonClick: (PersonId) -> Unit, @@ -77,10 +84,6 @@ fun PostOptionsDropdown( onFeaturePostClick: (PostFeatureData) -> Unit, onViewVotesClick: (PostId) -> Unit, onViewSourceClick: () -> Unit, - isCreator: Boolean, - canMod: Boolean, - amMod: Boolean, - amAdmin: Boolean, viewSource: Boolean, showViewSource: Boolean, scope: CoroutineScope, @@ -90,6 +93,14 @@ fun PostOptionsDropdown( val api = getInstanceOrNull() val (featureIcon, unFeatureIcon) = Pair(Icons.Outlined.PushPin, Icons.Outlined.CancelPresentation) + val isCreator = postView.post.creator_id == myUserInfo?.local_user_view?.local_user?.person_id + val canModOrAdmin = canModOrAdmin( + canMod = postView.can_mod, + creatorId = postView.post.creator_id, + myUserInfo = myUserInfo, + admins = admins, + ) + CascadeCenteredDropdownMenu( expanded = true, onDismissRequest = onDismissRequest, @@ -260,7 +271,7 @@ fun PostOptionsDropdown( // Hide / unhide post if (api != null && api.FF.hidePost()) { - if (postView.hidden) { + if (postView.post_actions?.hidden_at != null) { PopupMenuItem( text = stringResource(R.string.unhide_post), icon = Icons.Outlined.Visibility, @@ -370,22 +381,41 @@ fun PostOptionsDropdown( ) if (api != null && api.FF.instanceBlock()) { - val instance = getInstanceFromCommunityUrl(postView.community.actor_id) + val instance = getInstanceFromCommunityUrl(postView.community.ap_id) + PopupMenuItem( + text = stringResource(R.string.block_x_users, instance), + icon = Icons.Outlined.Block, + onClick = { + onDismissRequest() + scope.launch(Dispatchers.IO) { + val resp = + api.userBlockInstancePersons( + UserBlockInstancePersonsParams( + postView.community.instance_id, + true, + ), + ) + withContext(Dispatchers.Main) { + showBlockInstanceToast(resp, instance, blocked = true, ctx) + } + } + }, + ) PopupMenuItem( - text = stringResource(R.string.block_person, instance), + text = stringResource(R.string.block_x_communities, instance), icon = Icons.Outlined.Block, onClick = { onDismissRequest() scope.launch(Dispatchers.IO) { val resp = - api.blockInstance( - BlockInstance( + api.userBlockInstanceCommunities( + UserBlockInstanceCommunitiesParams( postView.community.instance_id, true, ), ) withContext(Dispatchers.Main) { - showBlockInstanceToast(resp, instance, ctx) + showBlockInstanceToast(resp, instance, blocked = true, ctx) } } }, @@ -404,13 +434,12 @@ fun PostOptionsDropdown( } // The moderation subfield - if (amMod || amAdmin) { + if (canModOrAdmin) { PopupMenuItem( text = stringResource(R.string.moderation), icon = Icons.Outlined.Shield, ) { // Moddable items limited to mods below you - if (canMod) { val (removeText, removeIcon) = if (postView.post.removed) { Pair(stringResource(R.string.restore_post), Icons.Outlined.Restore) @@ -426,11 +455,16 @@ fun PostOptionsDropdown( onRemoveClick(postView) }, ) - if (amAdmin) { - BanPersonPopupMenuItem(postView.creator, onDismissRequest, onBanPersonClick) + if (myUserInfo.amAdmin()) { + BanPersonPopupMenuItem( + person = postView.creator, + banned = postView.creator_banned, + onDismissRequest = onDismissRequest, + onBanPersonClick = onBanPersonClick, + ) } - // Only show ban from community button if its a local community + // Only show ban from community button if it's a local community if (postView.community.local) { BanFromCommunityPopupMenuItem( BanFromCommunityData( @@ -442,21 +476,7 @@ fun PostOptionsDropdown( onBanFromCommunityClick, ) } - } - // You can do these actions on mods above you - - // These are all amMod || amAdmin - if (amAdmin && getInstanceOrNull()?.FF?.listAdminVotes() == true) { - PopupMenuItem( - text = stringResource(R.string.view_votes), - icon = ImageVector.vectorResource(R.drawable.up_filled), - onClick = { - onDismissRequest() - onViewVotesClick(postView.post.id) - }, - ) - } val (lockText, lockIcon) = if (postView.post.locked) { @@ -496,7 +516,7 @@ fun PostOptionsDropdown( }, ) - if (amAdmin) { + if (myUserInfo.amAdmin()) { val (featureInLocalText, featureLocalIconUsed) = if (postView.post.featured_local) { Pair(stringResource(R.string.unfeature_in_local), unFeatureIcon) @@ -520,6 +540,19 @@ fun PostOptionsDropdown( ) } } + // You can do these actions on mods above you + // These are all amMod || amAdmin + if (myUserInfo.amAdmin() && getInstanceOrNull()?.FF?.listAdminVotes() == true) { + PopupMenuItem( + text = stringResource(R.string.view_votes), + icon = ImageVector.vectorResource(R.drawable.up_filled), + onClick = { + onDismissRequest() + onViewVotesClick(postView.post.id) + }, + ) + } + } } } diff --git a/app/src/main/java/com/jerboa/ui/components/post/create/CreatePostScreen.kt b/app/src/main/java/com/jerboa/ui/components/post/create/CreatePostScreen.kt index 47a451c85..0814ce70f 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/create/CreatePostScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/create/CreatePostScreen.kt @@ -48,6 +48,7 @@ import it.vercruysse.lemmyapi.datatypes.PostId import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.milliseconds private var fetchSiteMetadataJob: Job? = null @@ -112,7 +113,7 @@ fun CreatePostScreen( fetchSiteMetadataJob?.cancel() fetchSiteMetadataJob = scope.launch { - delay(DEBOUNCE_DELAY) + delay(DEBOUNCE_DELAY.milliseconds) if (Patterns.WEB_URL.matcher(initialUrl).matches()) { createPostViewModel.getSiteMetadata(GetSiteMetadata(initialUrl)) } @@ -121,7 +122,7 @@ fun CreatePostScreen( } val (suggestedTitle, suggestedTitleLoading) = - // If its a torrent magnet link, use the dn param to extract the torrent title + // If it's a torrent magnet link, use the dn param to extract the torrent title if (url.startsWith("magnet")) { val query = Uri.parse(url).query // Unfortunately you have to use a fake http uri, otherwise getQueryParameter throws a non-hierarchical error. @@ -207,7 +208,7 @@ fun CreatePostScreen( fetchSiteMetadataJob?.cancel() fetchSiteMetadataJob = scope.launch { - delay(DEBOUNCE_DELAY) + delay(DEBOUNCE_DELAY.milliseconds) if (Patterns.WEB_URL.matcher(url).matches()) { createPostViewModel.getSiteMetadata(GetSiteMetadata(url.padUrlWithHttps())) } @@ -218,10 +219,14 @@ fun CreatePostScreen( onImagePicked = { uri -> if (!account.isAnon() && uri != Uri.EMPTY) { val imageIs = imageInputStreamFromUri(ctx, uri) + val imageBytes = imageIs.readBytes() scope.launch { isUploadingImage = true - url = API.uploadPictrsImage(imageIs, ctx) + val res = API.getInstance().uploadImage(imageBytes) isUploadingImage = false + res.onSuccess { + url = it.image_url + } } } }, @@ -234,10 +239,12 @@ fun CreatePostScreen( onCustomThumbnailImagePicked = { uri -> if (!account.isAnon() && uri != Uri.EMPTY) { val imageIs = imageInputStreamFromUri(ctx, uri) + val imageBytes = imageIs.readBytes() scope.launch { isUploadingCustomThumbnailImage = true - customThumbnail = API.uploadPictrsImage(imageIs, ctx) + val res = API.getInstance().uploadImage(imageBytes) isUploadingCustomThumbnailImage = false + res.onSuccess { customThumbnail = it.image_url } } } }, diff --git a/app/src/main/java/com/jerboa/ui/components/post/edit/PostEditScreen.kt b/app/src/main/java/com/jerboa/ui/components/post/edit/PostEditScreen.kt index 9b4075b7d..cb0fa1a6b 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/edit/PostEditScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/edit/PostEditScreen.kt @@ -122,10 +122,15 @@ fun PostEditScreen( onImagePicked = { uri -> if (!account.isAnon()) { val imageIs = imageInputStreamFromUri(ctx, uri) + val imageBytes = imageIs.readBytes() scope.launch { isUploadingImage = true - url = API.uploadPictrsImage(imageIs, ctx) + val res = API.getInstance().uploadImage(imageBytes) isUploadingImage = false + res.onSuccess { + url = it.image_url + } + } } }, @@ -139,10 +144,15 @@ fun PostEditScreen( onCustomThumbnailImagePicked = { uri -> if (!account.isAnon() && uri != Uri.EMPTY) { val imageIs = imageInputStreamFromUri(ctx, uri) + val imageBytes = imageIs.readBytes() + scope.launch { isUploadingCustomThumbnailImage = true - customThumbnail = API.uploadPictrsImage(imageIs, ctx) + val res = API.getInstance().uploadImage(imageBytes) isUploadingCustomThumbnailImage = false + res.onSuccess { + customThumbnail =it.image_url + } } } }, diff --git a/app/src/main/java/com/jerboa/ui/components/privatemessage/CreatePrivateMessageScreen.kt b/app/src/main/java/com/jerboa/ui/components/privatemessage/CreatePrivateMessageScreen.kt index ba7c505ee..9fc280e67 100644 --- a/app/src/main/java/com/jerboa/ui/components/privatemessage/CreatePrivateMessageScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/privatemessage/CreatePrivateMessageScreen.kt @@ -27,12 +27,10 @@ import com.jerboa.R import com.jerboa.api.API import com.jerboa.api.ApiState import com.jerboa.api.toApiState -import com.jerboa.db.entity.isAnon -import com.jerboa.model.AccountViewModel import com.jerboa.ui.components.common.ActionTopBar import com.jerboa.ui.components.common.MarkdownTextField -import com.jerboa.ui.components.common.getCurrentAccount import it.vercruysse.lemmyapi.datatypes.CreatePrivateMessage +import it.vercruysse.lemmyapi.datatypes.MyUserInfo import it.vercruysse.lemmyapi.datatypes.PersonId import it.vercruysse.lemmyapi.datatypes.PrivateMessageResponse import kotlinx.coroutines.Dispatchers @@ -43,13 +41,12 @@ import kotlinx.coroutines.withContext fun CreatePrivateMessageScreen( personId: PersonId, personName: String, - accountViewModel: AccountViewModel, + myUserInfo: MyUserInfo, onBack: () -> Unit, ) { Log.d("jerboa", "got to create private message screen") val ctx = LocalContext.current - val account = getCurrentAccount(accountViewModel = accountViewModel) val scope = rememberCoroutineScope() var textBody by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } @@ -66,7 +63,6 @@ fun CreatePrivateMessageScreen( loading = loading, onBackClick = onBack, onActionClick = { - if (!account.isAnon()) { scope.launch { loading = true @@ -94,7 +90,6 @@ fun CreatePrivateMessageScreen( Toast.makeText(ctx, R.string.private_message_success, Toast.LENGTH_SHORT).show() onBack() } - } }, actionText = R.string.form_submit, actionIcon = Icons.AutoMirrored.Outlined.Send, @@ -114,7 +109,7 @@ fun CreatePrivateMessageScreen( MarkdownTextField( text = textBody, onTextChange = { textBody = it }, - account = account, + myUserInfo = myUserInfo, placeholder = stringResource(R.string.private_message_placeholder), ) } diff --git a/app/src/main/java/com/jerboa/ui/components/privatemessage/PrivateMessage.kt b/app/src/main/java/com/jerboa/ui/components/privatemessage/PrivateMessage.kt index df08f094e..8c59af5ac 100644 --- a/app/src/main/java/com/jerboa/ui/components/privatemessage/PrivateMessage.kt +++ b/app/src/main/java/com/jerboa/ui/components/privatemessage/PrivateMessage.kt @@ -72,8 +72,8 @@ fun PrivateMessageHeader( } TimeAgo( - published = privateMessageView.private_message.published, - updated = privateMessageView.private_message.updated, + published = privateMessageView.private_message.published_at, + updated = privateMessageView.private_message.updated_at, ) } } diff --git a/app/src/main/java/com/jerboa/ui/components/registrationapplications/RegistrationApplications.kt b/app/src/main/java/com/jerboa/ui/components/registrationapplications/RegistrationApplications.kt index ff98e65d4..e274e39ba 100644 --- a/app/src/main/java/com/jerboa/ui/components/registrationapplications/RegistrationApplications.kt +++ b/app/src/main/java/com/jerboa/ui/components/registrationapplications/RegistrationApplications.kt @@ -39,7 +39,6 @@ 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.compose.ui.platform.LocalResources import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue @@ -56,8 +55,8 @@ import com.jerboa.datatypes.samplePendingRegistrationApplicationView import com.jerboa.db.entity.Account import com.jerboa.db.entity.AnonAccount import com.jerboa.feat.doIfReadyElseDisplayInfo +import com.jerboa.model.MyUserInfoViewModel import com.jerboa.model.RegistrationApplicationsViewModel -import com.jerboa.model.SiteViewModel import com.jerboa.ui.components.common.ApiEmptyText import com.jerboa.ui.components.common.ApiErrorText import com.jerboa.ui.components.common.DualHeaderTitle @@ -148,7 +147,7 @@ fun RegistrationApplicationsHeaderTitle( fun RegistrationApplications( appState: JerboaAppState, registrationApplicationsViewModel: RegistrationApplicationsViewModel, - siteViewModel: SiteViewModel, + myUserInfoViewModel: MyUserInfoViewModel, ctx: Context, resources: Resources, account: Account, @@ -166,7 +165,7 @@ fun RegistrationApplications( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, ) { registrationApplicationsViewModel.appendApplications() } @@ -183,14 +182,14 @@ fun RegistrationApplications( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, ) { registrationApplicationsViewModel.resetPage() registrationApplicationsViewModel.listApplications( registrationApplicationsViewModel.getFormApplications(), ApiState.Refreshing, ) - siteViewModel.fetchUnreadAppCount() + myUserInfoViewModel.fetchUnreadCounts() } }, ) { @@ -205,8 +204,8 @@ fun RegistrationApplications( ApiErrorText(appsRes.msg) } - is ApiState.Holder -> { - val apps = appsRes.data.registration_applications + is ApiState.Success -> { + val apps = appsRes.data.items LazyColumn( state = listState, modifier = @@ -229,7 +228,7 @@ fun RegistrationApplications( resources, snackbarHostState, scope, - siteViewModel, + myUserInfoViewModel, ) { registrationApplicationsViewModel.approveOrDenyApplication( form, @@ -239,7 +238,7 @@ fun RegistrationApplications( onPersonClick = { personId -> appState.toProfile(id = personId) }, - showAvatar = siteViewModel.showAvatar() && !lowBandwidthMode, + showAvatar = myUserInfoViewModel.showAvatar() && !lowBandwidthMode, account = account, ) } @@ -298,7 +297,7 @@ fun RegistrationApplicationItem( } TimeAgo( - published = app.published, + published = app.published_at, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/reports/CommentReportItem.kt b/app/src/main/java/com/jerboa/ui/components/reports/CommentReportItem.kt index adb2263a2..6ee7dd9de 100644 --- a/app/src/main/java/com/jerboa/ui/components/reports/CommentReportItem.kt +++ b/app/src/main/java/com/jerboa/ui/components/reports/CommentReportItem.kt @@ -17,7 +17,6 @@ import it.vercruysse.lemmyapi.datatypes.CommentReportView import it.vercruysse.lemmyapi.datatypes.CommentView import it.vercruysse.lemmyapi.datatypes.PersonId import it.vercruysse.lemmyapi.datatypes.ResolveCommentReport -import it.vercruysse.lemmyapi.dto.SubscribedType @Composable fun CommentReportItem( @@ -31,23 +30,25 @@ fun CommentReportItem( // not the current state. val origComment = commentReportView.comment.copy( content = commentReportView.comment_report.original_comment_text, - published = commentReportView.comment_report.published, + published_at = commentReportView.comment_report.published_at, ) val commentView = CommentView( comment = origComment, post = commentReportView.post, creator = commentReportView.comment_creator, - creator_banned_from_community = commentReportView.creator_banned_from_community, - my_vote = commentReportView.my_vote, - subscribed = SubscribedType.NotSubscribed, community = commentReportView.community, - counts = commentReportView.counts, - creator_blocked = false, - creator_is_admin = false, - creator_is_moderator = false, - saved = false, - banned_from_community = false, + community_actions = commentReportView.community_actions, + comment_actions = commentReportView.comment_actions, + person_actions = commentReportView.person_actions, + creator_banned = commentReportView.creator_banned, + creator_banned_from_community = commentReportView.creator_banned_from_community, + creator_ban_expires_at = commentReportView.creator_ban_expires_at, + creator_community_ban_expires_at = commentReportView.creator_community_ban_expires_at, + creator_is_admin = commentReportView.creator_is_admin, + creator_is_moderator = commentReportView.creator_is_moderator, + tags = emptyList(), + can_mod = true, ) Column( diff --git a/app/src/main/java/com/jerboa/ui/components/settings/block/BlocksScreen.kt b/app/src/main/java/com/jerboa/ui/components/settings/block/BlocksScreen.kt index b66af4041..7a5c07278 100644 --- a/app/src/main/java/com/jerboa/ui/components/settings/block/BlocksScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/settings/block/BlocksScreen.kt @@ -1,8 +1,6 @@ package com.jerboa.ui.components.settings.block -import android.util.Log import androidx.annotation.StringRes -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -27,7 +25,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Surface import androidx.compose.material3.Tab import androidx.compose.material3.TabRow import androidx.compose.material3.Text @@ -47,14 +44,12 @@ import com.jerboa.R import com.jerboa.api.ApiAction import com.jerboa.api.ApiState import com.jerboa.model.BlockViewModel -import com.jerboa.model.SiteViewModel -import com.jerboa.ui.components.common.ApiEmptyText +import com.jerboa.model.MyUserInfoViewModel import com.jerboa.ui.components.common.ApiErrorText import com.jerboa.ui.components.common.ItemAndInstanceTitle import com.jerboa.ui.components.common.JerboaLoadingBar import com.jerboa.ui.components.common.JerboaSnackbarHost import com.jerboa.ui.components.common.SimpleTopAppBar -import com.jerboa.ui.components.common.Title import com.jerboa.ui.theme.MEDIUM_PADDING import it.vercruysse.lemmyapi.datatypes.MyUserInfo import kotlinx.coroutines.launch @@ -62,25 +57,12 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun BlocksScreen( - siteViewModel: SiteViewModel, + myUserInfoViewModel: MyUserInfoViewModel, onBack: () -> Unit, ) { - val snackbarHostState = remember { SnackbarHostState() } - - LaunchedEffect(Unit) { - Log.d("BlocksScreen", "Refreshing site") - when (val res = siteViewModel.siteRes) { - is ApiState.Success -> { - siteViewModel.getSite(ApiState.Appending(res.data)) - } + val blockViewModel: BlockViewModel = viewModel() - is ApiState.Loading, is ApiState.Appending -> {} - - else -> { - siteViewModel.getSite() - } - } - } + val snackbarHostState = remember { SnackbarHostState() } Scaffold( snackbarHost = { JerboaSnackbarHost(snackbarHostState) }, @@ -90,24 +72,23 @@ fun BlocksScreen( content = { padding -> PullToRefreshBox( modifier = Modifier.padding(padding), - isRefreshing = siteViewModel.siteRes is ApiState.Loading, - onRefresh = { siteViewModel.getSite() }, + isRefreshing = myUserInfoViewModel.myUserRes is ApiState.Loading, + onRefresh = { myUserInfoViewModel.getMyUser() }, ) { - JerboaLoadingBar(siteViewModel.siteRes) + JerboaLoadingBar(myUserInfoViewModel.myUserRes) - when (val res = siteViewModel.siteRes) { + when (val res = myUserInfoViewModel.myUserRes) { is ApiState.Failure -> { ApiErrorText(res.msg) } - is ApiState.Holder -> { - res.data.my_user?.let { - BlockList(it) - } ?: ApiEmptyText() + is ApiState.Success -> { + BlockList( + blockViewModel = blockViewModel, + myUserInfo = res.data + ) } - else -> { - Unit } } } @@ -118,34 +99,31 @@ fun BlocksScreen( enum class BlocksTab( @param:StringRes val label: Int, ) { - Instances(R.string.instances), + InstanceCommunities(R.string.communities_from_these_instances), + InstancePersons(R.string.users_from_these_instances), Communities(R.string.communities), Users(R.string.users), } @Composable -fun BlockList(userInfo: MyUserInfo) { +fun BlockList( + blockViewModel: BlockViewModel, + myUserInfo: MyUserInfo +) { val ctx = LocalContext.current val resources = LocalResources.current - val viewModel: BlockViewModel = viewModel() val scope = rememberCoroutineScope() val tabTitles = BlocksTab.entries.map { resources.getString(it.label) } val pagerState = rememberPagerState { tabTitles.size } - LaunchedEffect(userInfo) { - viewModel.initData(userInfo) - } + LaunchedEffect(myUserInfo) { blockViewModel.initData(myUserInfo) } Column { TabRow(selectedTabIndex = pagerState.currentPage) { tabTitles.forEachIndexed { index, title -> Tab( selected = pagerState.currentPage == index, - onClick = { - scope.launch { - pagerState.animateScrollToPage(index) - } - }, + onClick = { scope.launch { pagerState.animateScrollToPage(index) } }, text = { Text(text = title) }, ) } @@ -157,44 +135,55 @@ fun BlockList(userInfo: MyUserInfo) { ) { tabIndex -> LazyColumn(contentPadding = PaddingValues(MEDIUM_PADDING, 0.dp)) { when (tabIndex) { - BlocksTab.Instances.ordinal -> { + BlocksTab.InstanceCommunities.ordinal -> { + itemsWithEmpty( + items = blockViewModel.instanceCommunitiesBlocks.value, + key = { "IC${it.data.id}" }, + emptyText = R.string.you_have_no_blocked_instances, + ) { + ItemBlockView(it.data.domain, null, it) { + blockViewModel.unBlockInstanceCommunities(it.data, ctx) + } + } + } + + BlocksTab.InstancePersons.ordinal -> { itemsWithEmpty( - items = viewModel.instanceBlocks.value, - key = { "I${it.data.id}" }, + items = blockViewModel.instancePersonsBlocks.value, + key = { "IP${it.data.id}" }, emptyText = R.string.you_have_no_blocked_instances, ) { ItemBlockView(it.data.domain, null, it) { - viewModel.unBlockInstance(it.data, ctx) + blockViewModel.unBlockInstancePersons(it.data, ctx) } } } BlocksTab.Communities.ordinal -> { itemsWithEmpty( - items = viewModel.communityBlocks.value, + items = blockViewModel.communityBlocks.value, key = { "C${it.data.id}" }, emptyText = R.string.you_have_no_blocked_communities, ) { - ItemBlockView(it.data.name, it.data.actor_id, it) { - viewModel.unBlockCommunity(it.data, ctx) + ItemBlockView(it.data.name, it.data.ap_id, it) { + blockViewModel.unBlockCommunity(it.data, ctx) } } } BlocksTab.Users.ordinal -> { itemsWithEmpty( - items = viewModel.personBlocks.value, + items = blockViewModel.personBlocks.value, key = { "U${it.data.id}" }, emptyText = R.string.you_have_no_blocked_users, ) { - ItemBlockView(it.data.name, it.data.actor_id, it) { - viewModel.unBlockPerson(it.data, ctx) + ItemBlockView(it.data.name, it.data.ap_id, it) { + blockViewModel.unBlockPerson(it.data, ctx) } } } else -> { - Unit } } } @@ -215,9 +204,7 @@ inline fun LazyListScope.itemsWithEmpty( Box( modifier = Modifier.fillParentMaxSize(), contentAlignment = Alignment.Center, - ) { - Text(stringResource(emptyText)) - } + ) { Text(stringResource(emptyText)) } } } else { items( @@ -243,7 +230,7 @@ fun ItemBlockView( ) { ItemAndInstanceTitle( title = name, - actorId = actor, + apId = actor, local = false, onClick = null, ) @@ -253,36 +240,26 @@ fun ItemBlockView( enabled = action !is ApiAction.Loading, ) { when (action) { - is ApiAction.Loading -> CircularProgressIndicator(color = MaterialTheme.colorScheme.secondary) + is ApiAction.Loading -> { + CircularProgressIndicator(color = MaterialTheme.colorScheme.secondary) + } - is ApiAction.Failed -> Icon( - imageVector = Icons.Filled.Refresh, - contentDescription = stringResource(id = R.string.retry), - tint = MaterialTheme.colorScheme.error, - ) + is ApiAction.Failed -> { + Icon( + imageVector = Icons.Filled.Refresh, + contentDescription = stringResource(id = R.string.retry), + tint = MaterialTheme.colorScheme.error, + ) + } - is ApiAction.Ok -> Icon( - imageVector = Icons.Filled.Close, - contentDescription = stringResource(id = R.string.unblock), - tint = MaterialTheme.colorScheme.secondary, - ) + is ApiAction.Ok -> { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = stringResource(id = R.string.unblock), + tint = MaterialTheme.colorScheme.secondary, + ) + } } } } } - -@OptIn(ExperimentalFoundationApi::class) -fun LazyListScope.blockListHeader( - @StringRes resId: Int, -) { - stickyHeader( - contentType = "blockHeader", - ) { - Surface( - modifier = Modifier.fillMaxWidth(), - color = MaterialTheme.colorScheme.background, - ) { - Title(stringResource(id = resId)) - } - } -} diff --git a/app/src/main/java/com/jerboa/ui/components/videoviewer/hosts/RedgifsVideoHost.kt b/app/src/main/java/com/jerboa/ui/components/videoviewer/hosts/RedgifsVideoHost.kt index 07ddc0db4..b81cae58c 100644 --- a/app/src/main/java/com/jerboa/ui/components/videoviewer/hosts/RedgifsVideoHost.kt +++ b/app/src/main/java/com/jerboa/ui/components/videoviewer/hosts/RedgifsVideoHost.kt @@ -43,7 +43,7 @@ class RedgifsVideoHost : SupportedVideoHost { .url(apiUrl) .build() - val response = API.httpClient.newCall(request).execute() + val response = API.okHttpClient.newCall(request).execute() if (!response.isSuccessful) { throw ResourceMissing() diff --git a/app/src/main/java/com/jerboa/ui/components/videoviewer/hosts/SendvidVideoHost.kt b/app/src/main/java/com/jerboa/ui/components/videoviewer/hosts/SendvidVideoHost.kt index 63b8cafd2..41444d9d2 100644 --- a/app/src/main/java/com/jerboa/ui/components/videoviewer/hosts/SendvidVideoHost.kt +++ b/app/src/main/java/com/jerboa/ui/components/videoviewer/hosts/SendvidVideoHost.kt @@ -28,7 +28,7 @@ class SendvidVideoHost : SupportedVideoHost { .url(url) .build() - val response = API.httpClient.newCall(request).execute() + val response = API.okHttpClient.newCall(request).execute() if (!response.isSuccessful) { throw ResourceMissing() diff --git a/app/src/main/java/com/jerboa/ui/components/viewvotes/ViewVotes.kt b/app/src/main/java/com/jerboa/ui/components/viewvotes/ViewVotes.kt index 6650a2bed..9663b70cd 100644 --- a/app/src/main/java/com/jerboa/ui/components/viewvotes/ViewVotes.kt +++ b/app/src/main/java/com/jerboa/ui/components/viewvotes/ViewVotes.kt @@ -23,6 +23,7 @@ import com.jerboa.ui.components.person.PersonProfileLink import com.jerboa.ui.theme.MEDIUM_PADDING import it.vercruysse.lemmyapi.datatypes.PersonId import it.vercruysse.lemmyapi.datatypes.VoteView +import it.vercruysse.lemmyapi.enums.VoteAction @Composable fun ViewVotesBody( @@ -44,7 +45,7 @@ fun ViewVotesBody( key = { like -> like.creator.id }, contentType = { "like" }, ) { like -> - val voteInfo = buildVoteInfo(vote = like.score) + val voteInfo = buildVoteInfo(vote = like.vote) Row( modifier = Modifier.fillMaxWidth(), @@ -75,9 +76,9 @@ private data class VoteInfo( ) @Composable -private fun buildVoteInfo(vote: Long): VoteInfo = +private fun buildVoteInfo(vote: VoteAction): VoteInfo = when (vote) { - 1L -> { + VoteAction.UpVote -> { VoteInfo( ImageVector.vectorResource(id = R.drawable.up_filled), MaterialTheme.colorScheme.secondary, diff --git a/app/src/main/java/com/jerboa/ui/components/viewvotes/comment/CommentLikesScreen.kt b/app/src/main/java/com/jerboa/ui/components/viewvotes/comment/CommentLikesScreen.kt index 671b2559c..9f4d5c754 100644 --- a/app/src/main/java/com/jerboa/ui/components/viewvotes/comment/CommentLikesScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/viewvotes/comment/CommentLikesScreen.kt @@ -69,7 +69,7 @@ fun CommentLikesScreen( } is ApiState.Holder -> { - val likes = likesRes.data.comment_likes + val likes = likesRes.data.items ViewVotesBody( likes = likes, listState = listState, diff --git a/app/src/main/java/com/jerboa/ui/components/viewvotes/post/PostLikesScreen.kt b/app/src/main/java/com/jerboa/ui/components/viewvotes/post/PostLikesScreen.kt index ac10b8d46..3df544c4c 100644 --- a/app/src/main/java/com/jerboa/ui/components/viewvotes/post/PostLikesScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/viewvotes/post/PostLikesScreen.kt @@ -69,7 +69,7 @@ fun PostLikesScreen( } is ApiState.Holder -> { - val likes = likesRes.data.post_likes + val likes = likesRes.data.items ViewVotesBody( likes = likes, listState = listState, diff --git a/app/src/main/java/com/jerboa/util/downloadprogress/DownloadProgress.kt b/app/src/main/java/com/jerboa/util/downloadprogress/DownloadProgress.kt index 6aac99af7..e4ff5e4db 100644 --- a/app/src/main/java/com/jerboa/util/downloadprogress/DownloadProgress.kt +++ b/app/src/main/java/com/jerboa/util/downloadprogress/DownloadProgress.kt @@ -9,7 +9,7 @@ object DownloadProgress { val downloadProgressFlow = MutableStateFlow(initProgress) val downloadProgressHttpClient = - API.httpClient + API.okHttpClient .newBuilder() .addInterceptor(DownloadProgressInterceptor(downloadProgressFlow)) .build() diff --git a/app/src/main/java/com/jerboa/util/downloadprogress/DownloadProgressInterceptor.kt b/app/src/main/java/com/jerboa/util/downloadprogress/DownloadProgressInterceptor.kt index fa70a90f5..04d0e2824 100644 --- a/app/src/main/java/com/jerboa/util/downloadprogress/DownloadProgressInterceptor.kt +++ b/app/src/main/java/com/jerboa/util/downloadprogress/DownloadProgressInterceptor.kt @@ -15,13 +15,11 @@ class DownloadProgressInterceptor( val downloadIdentifier = originalResponse.request.url.toString() - val downloadResponseBody = originalResponse.body?.let { - DownloadProgressResponseBody( - downloadIdentifier, - it, - downloadFlow, - ) - } + val downloadResponseBody = DownloadProgressResponseBody( + downloadIdentifier, + originalResponse.body, + downloadFlow, + ) responseBuilder.body(downloadResponseBody) return responseBuilder.build() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 21b080010..af34061b4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -125,6 +125,7 @@ Site info Subscribed Moderator view + Suggested Subscriptions Switch to %1$s@%2$s Filter @@ -460,7 +461,9 @@ Creator Message Report %1$s - Block %1$s + Block %1$s + Block %1$s users + Block %1$s communities Undistinguish comment Distinguish comment %1$s info @@ -509,4 +512,7 @@ Unmute Play Pause + Denied + Communities from these instances + Users from these instances diff --git a/app/src/test/java/com/jerboa/UtilsKtTest.kt b/app/src/test/java/com/jerboa/UtilsKtTest.kt index b81fe6487..ace6dcc2f 100644 --- a/app/src/test/java/com/jerboa/UtilsKtTest.kt +++ b/app/src/test/java/com/jerboa/UtilsKtTest.kt @@ -50,49 +50,49 @@ class UtilsKtTest { @Test fun testInstantScores() { val initial = InstantScores(0, 0, 0, 0) - val upvoted = initial.update(VoteType.Upvote) + val upvoted = initial.update(VoteAction.UpVote) assertEquals(1, upvoted.myVote) assertEquals(1, upvoted.upvotes) assertEquals(0, upvoted.downvotes) assertEquals(1, upvoted.score) - val downvoted = initial.update(VoteType.Downvote) + val downvoted = initial.update(VoteAction.DownVote) assertEquals(-1, downvoted.myVote) assertEquals(0, downvoted.upvotes) assertEquals(1, downvoted.downvotes) assertEquals(-1, downvoted.score) - val upvotedAgain = downvoted.update(VoteType.Upvote) + val upvotedAgain = downvoted.update(VoteAction.UpVote) assertEquals(1, upvotedAgain.myVote) assertEquals(1, upvotedAgain.upvotes) assertEquals(0, upvotedAgain.downvotes) assertEquals(1, upvotedAgain.score) - val downvotedAgain = upvoted.update(VoteType.Downvote) + val downvotedAgain = upvoted.update(VoteAction.DownVote) assertEquals(-1, downvotedAgain.myVote) assertEquals(0, downvotedAgain.upvotes) assertEquals(1, downvotedAgain.downvotes) assertEquals(-1, downvotedAgain.score) - val unvoted = upvoted.update(VoteType.Upvote) + val unvoted = upvoted.update(VoteAction.UpVote) assertEquals(0, unvoted.myVote) assertEquals(0, unvoted.upvotes) assertEquals(0, unvoted.downvotes) assertEquals(0, unvoted.score) - val unvoted2 = downvoted.update(VoteType.Downvote) + val unvoted2 = downvoted.update(VoteAction.DownVote) assertEquals(0, unvoted2.myVote) assertEquals(0, unvoted2.upvotes) assertEquals(0, unvoted2.downvotes) assertEquals(0, unvoted2.score) - val upvotedAlr = upvoted.copy(myVote = 0).update(VoteType.Upvote) + val upvotedAlr = upvoted.copy(myVote = 0).update(VoteAction.UpVote) assertEquals(1, upvotedAlr.myVote) assertEquals(2, upvotedAlr.upvotes) assertEquals(0, upvotedAlr.downvotes) assertEquals(2, upvotedAlr.score) - val downvotedAlr = downvoted.copy(myVote = 0).update(VoteType.Downvote) + val downvotedAlr = downvoted.copy(myVote = 0).update(VoteAction.DownVote) assertEquals(-1, downvotedAlr.myVote) assertEquals(0, downvotedAlr.upvotes) assertEquals(2, downvotedAlr.downvotes) diff --git a/scripts/deploy_to_device.sh b/scripts/deploy_to_device.sh index a1d9add1f..9f47997bd 100755 --- a/scripts/deploy_to_device.sh +++ b/scripts/deploy_to_device.sh @@ -4,7 +4,7 @@ set -e pushd ../ # Build and push -./gradlew installDebug +./gradlew installFdroidDebug # Run the app adb shell monkey -p com.jerboa 1