Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding tablet UI for home #1723

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
@file:Suppress("UnstableApiUsage")

import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag


plugins {
id("com.android.application")
Expand All @@ -16,13 +14,13 @@ plugins {
apply(from = "update_instances.gradle.kts")

android {
compileSdk = 34
compileSdk = 35

defaultConfig {
applicationId = "com.jerboa"
namespace = "com.jerboa"
minSdk = 26
targetSdk = 34
targetSdk = 35
versionCode = 77
versionName = "0.0.77"

Expand Down Expand Up @@ -106,19 +104,22 @@ dependencies {
// Exporting / importing DB helper
implementation("com.github.dessalines:room-db-export-import:0.1.0")

// Unfortunately, ui tooling, and the markdown thing, still brings in the other material2 dependencies
// This is the "official" composeBom, but it breaks the imageviewer until 1.7 is released. See:
// https://github.com/LemmyNet/jerboa/pull/1502#issuecomment-2137935525
// val composeBom = platform("androidx.compose:compose-bom:2024.05.00")

val composeBom = platform("dev.chrisbanes.compose:compose-bom:2024.08.00-alpha02")
// Compose BOM
val composeBom = platform("androidx.compose:compose-bom:2024.11.00")
api(composeBom)
implementation("androidx.activity:activity-ktx")
implementation("androidx.activity:activity-compose")
implementation("androidx.appcompat:appcompat:1.7.0")
androidTestApi(composeBom)
testImplementation("androidx.arch.core:core-testing:2.2.0")

// Adaptive layouts
// TODO still need the alphas for pane features
implementation("androidx.compose.material3.adaptive:adaptive:1.1.0-beta02")
implementation("androidx.compose.material3.adaptive:adaptive-layout:1.1.0-beta02")
implementation("androidx.compose.material3.adaptive:adaptive-navigation:1.1.0-beta02")
implementation("androidx.compose.material3:material3-adaptive-navigation-suite")

implementation("me.zhanghai.compose.preference:library:1.1.1")

// Markdown support
Expand Down
4 changes: 0 additions & 4 deletions app/src/debug/res/values-ru/strings.xml

This file was deleted.

1 change: 0 additions & 1 deletion app/src/main/java/com/jerboa/JerboaAppState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import it.vercruysse.lemmyapi.datatypes.PrivateMessageView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
Expand Down
1,273 changes: 640 additions & 633 deletions app/src/main/java/com/jerboa/MainActivity.kt

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions app/src/main/java/com/jerboa/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ fun getCommentParentId(comment: Comment): CommentId? = getCommentParentId(commen
fun getCommentParentId(commentPath: String): CommentId? {
val split = commentPath.split(".").toMutableList()
// remove the 0
split.removeFirst()
split.removeAt(0)
return if (split.size > 1) {
split[split.size - 2].toLong()
} else {
Expand Down Expand Up @@ -1370,8 +1370,7 @@ fun ConnectivityManager?.isCurrentlyConnected(): Boolean =
this
?.activeNetwork
?.let(::getNetworkCapabilities)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
?: true
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) != false

/**
* When calling this, you must call ActivityResultLauncher.unregister()
Expand Down Expand Up @@ -1500,3 +1499,11 @@ fun Context.getVersionCode(): Int =
}

fun Int.toBool() = this > 0

sealed interface SelectionVisibilityState<out Item> {
object NoSelection : SelectionVisibilityState<Nothing>

data class ShowSelection<Item>(
val selectedItem: Item,
) : SelectionVisibilityState<Item>
}
2 changes: 1 addition & 1 deletion app/src/main/java/com/jerboa/feat/ModActions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fun futureDaysToUnixTime(days: Long?): Long? =
fun amMod(
moderators: List<PersonId>?,
myId: PersonId,
): Boolean = moderators?.contains(myId) ?: false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imo the previous reads easier but you can leave as is.

): Boolean = moderators?.contains(myId) == true

/**
* In screens with posts from different communities we don't have access to moderators of those communities
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/java/com/jerboa/feat/UserActions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,12 @@ fun shareMedia(

val uri = FileProvider.getUriForFile(ctx, ctx.packageName + ".provider", file)
val shareIntent = Intent()
shareIntent.setAction(Intent.ACTION_SEND)
shareIntent.action = Intent.ACTION_SEND
shareIntent.putExtra(Intent.EXTRA_STREAM, uri)
when (mediaType) {
PostType.Image -> shareIntent.setType("image/*")
PostType.Video -> shareIntent.setType("video/*")
PostType.Link -> shareIntent.setType("text/*")
PostType.Image -> shareIntent.type = "image/*"
PostType.Video -> shareIntent.type = "video/*"
PostType.Link -> shareIntent.type = "text/*"
}
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
ctx.startActivitySafe(Intent.createChooser(shareIntent, ctx.getString(R.string.share)))
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/jerboa/feat/Voting.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ fun LocalUserVoteDisplayMode.Companion.default(score: Boolean? = false) =
local_user_id = -1,
upvotes = true,
downvotes = true,
score = score ?: false,
score = score == true,
upvote_percentage = false,
)

Expand Down
7 changes: 6 additions & 1 deletion app/src/main/java/com/jerboa/model/PostViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import it.vercruysse.lemmyapi.dto.ListingType
import kotlinx.coroutines.launch

class PostViewModel(
val id: Either<PostId, CommentId>,
var id: Either<PostId, CommentId>,
) : ViewModel() {
var postRes: ApiState<GetPostResponse> by mutableStateOf(ApiState.Empty)
private set
Expand Down Expand Up @@ -79,6 +79,11 @@ class PostViewModel(
this.getData()
}

fun reInitializeWithNewId(id: Either<PostId, CommentId>) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't fully checked it out

But I think the idea was rather that you would recreate a new postViewModel each time.

I haven't this out yet but this might not play well with the state that won't be remembered. Like sorts.

this.id = id
this.getData()
}

fun updateSortType(sortType: CommentSortType) {
this.sortType = sortType
}
Expand Down
1 change: 0 additions & 1 deletion app/src/main/java/com/jerboa/model/PostsViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/java/com/jerboa/model/SiteViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,7 @@ class SiteViewModel(
res.data.my_user
?.local_user_view
?.local_user
?.show_avatars
?: true
?.show_avatars != false

else -> true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ fun getCurrentAccount(accountViewModel: AccountViewModel): Account {
return acc
}

/**
* A special case of toEnumSafe, where the int still exists in the DB.
*/
fun getPostViewMode(appSettingsViewModel: AppSettingsViewModel): PostViewMode =
getEnumFromIntSetting<PostViewMode>(appSettingsViewModel.appSettings) {
it.postViewMode
Expand Down
139 changes: 63 additions & 76 deletions app/src/main/java/com/jerboa/ui/components/common/AppBars.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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.testTag
Expand Down Expand Up @@ -112,99 +111,87 @@ fun SimpleTopAppBarPreview() {
}
}

@Composable
fun BottomAppBarAll(
fun NavigationSuiteScope.adaptiveNavigationBar(
selectedTab: NavTab,
onSelect: (NavTab) -> Unit,
onSelectTab: (NavTab) -> Unit,
userViewType: UserViewType,
unreadCounts: Long,
unreadAppCount: Long?,
unreadReportCount: Long?,
showTextDescriptionsInNavbar: Boolean,
) {
// Check for preview mode
if (LocalContext.current is Activity) {
val window = (LocalContext.current as Activity).window
val colorScheme = MaterialTheme.colorScheme

DisposableEffect(Unit) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this is also gone but this was really necessary for the navigation bar to look the same as system navigation bar.

It looked quite odd. I'll check it out later to see how this looks now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any need for these now in the jetpack compose code, maybe they were necessary once, but now with edgeToedge , it seems to handle the navbar coloring fine.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not on older API levels

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before

image

After

image

I prefer we keep it, this was tested on Android 12, so not even that old.

window.navigationBarColor = colorScheme.surfaceContainer.toArgb()

onDispose {
window.navigationBarColor = colorScheme.background.toArgb()
}
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
}
}
// 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 = 30,
unreadAppCount = 2,
unreadReportCount = 8,
userViewType = UserViewType.AdminOnly,
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 = 30,
unreadAppCount = 2,
unreadReportCount = 8,
userViewType = UserViewType.AdminOnly,
showTextDescriptionsInNavbar = false,
)
},
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,7 @@ fun IconAndTextDrawerItemWithMorePreview() {
more = true,
)
}

@Composable
fun selectedItemContainerColor(selected: Boolean) =
if (!selected) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceVariant
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,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.datatypes.BanFromCommunityData
import com.jerboa.db.entity.isAnon
Expand Down Expand Up @@ -426,6 +427,7 @@ fun CommunityScreen(
postActionBarMode = postActionBarMode,
showPostAppendRetry = communityViewModel.postsRes is ApiState.AppendingFailure,
swipeToActionPreset = swipeToActionPreset,
selectionState = SelectionVisibilityState.NoSelection,
)
}

Expand Down
Loading