diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2b20fed0..b326dbab 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,7 @@ import com.google.devtools.ksp.gradle.KspAATask -import org.jetbrains.compose.desktop.application.dsl.TargetFormat.* +import org.jetbrains.compose.desktop.application.dsl.TargetFormat.Deb +import org.jetbrains.compose.desktop.application.dsl.TargetFormat.Dmg +import org.jetbrains.compose.desktop.application.dsl.TargetFormat.Msi plugins { alias(libs.plugins.androidApplication) @@ -150,8 +152,8 @@ android { applicationId = "com.daniebeler.pfpixelix" minSdk = 26 targetSdk = 35 - versionCode = 32 - versionName = "4.1.1" + versionCode = 33 + versionName = "4.2.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/commonMain/composeResources/values-de-rDE/strings.xml b/app/src/commonMain/composeResources/values-de-rDE/strings.xml index 3a22ca8a..f4dcd8c8 100644 --- a/app/src/commonMain/composeResources/values-de-rDE/strings.xml +++ b/app/src/commonMain/composeResources/values-de-rDE/strings.xml @@ -20,12 +20,12 @@ Follower Follower - Followers + Follower Gefolgt - Following - Following + Gefolgt + Gefolgt Beitrag @@ -264,19 +264,19 @@ Jahr Jahre - Blur sensitive content - Autoplay Videos - Report this post - Report + sensiblen Inhalt unschärfen + Videos automatisch abspielen + Diesen Beitrag melden + Melden Spam - Adult or Sensitive Content - Abusive or Harmful - Underage Account - Violence - Copyright infringement - Impersonation - Scam - Terrorism - Reported - Delete Account + Erwachsenen oder sensibler Inhalt + Missbräuchlich oder Schädigend + Minderjährigen-Konto + Gewalt + Urheberrechtsverletzung + Identitätswechsel + Betrug + Terrorismus + Gemeldet + Konto löschen diff --git a/app/src/commonMain/composeResources/values-el-rGR/strings.xml b/app/src/commonMain/composeResources/values-el-rGR/strings.xml index 6e5c925e..38a3aa84 100644 --- a/app/src/commonMain/composeResources/values-el-rGR/strings.xml +++ b/app/src/commonMain/composeResources/values-el-rGR/strings.xml @@ -264,19 +264,19 @@ έτος έτη - Blur sensitive content - Autoplay Videos - Report this post - Report - Spam - Adult or Sensitive Content - Abusive or Harmful - Underage Account - Violence - Copyright infringement - Impersonation - Scam - Terrorism - Reported - Delete Account + Θόλωμα ευαίσθητου περιεχομένου + Αυτόματη Αναπαραγωγή Βίντεο + Αναφορά αυτής της ανάρτησης + Αναφορά + Σπαμ + Ενήλικο ή Ευαίσθητο Περιεχόμενο + Καταχρηστικό ή Επιβλαβές + Λογαριασμός Ανηλίκου + Βία + Παραβίαση πνευματικών δικαιωμάτων + Πλαστοπροσωπία + Απάτη + Τρομοκρατία + Αναφέρθηκε + Διαγραφή Λογαριασμού diff --git a/app/src/commonMain/composeResources/values-en-rUS/strings.xml b/app/src/commonMain/composeResources/values-en-rUS/strings.xml index 706fd559..260b83ab 100644 --- a/app/src/commonMain/composeResources/values-en-rUS/strings.xml +++ b/app/src/commonMain/composeResources/values-en-rUS/strings.xml @@ -66,7 +66,7 @@ yearly monthly daily - I don’t have a profile + I don\'t have a profile Server URL Are you sure you want to log out? Logout? diff --git a/app/src/commonMain/composeResources/values-fr-rFR/strings.xml b/app/src/commonMain/composeResources/values-fr-rFR/strings.xml index 02926154..62e41420 100644 --- a/app/src/commonMain/composeResources/values-fr-rFR/strings.xml +++ b/app/src/commonMain/composeResources/values-fr-rFR/strings.xml @@ -3,7 +3,7 @@ Notifications Paramètres Publications aimées - Publications favorites + Publications sauvegardées Hashtags suivis Comptes bloqués Comptes masqués @@ -85,13 +85,13 @@ Description du média Aucune publication pour l’instant vous a mentionné·e - Aucune publication favorite + Aucune publication sauvegardée Rien à afficher Nouvelle publication Publier Texte Alternatif - légende - Média suggestif/sensible + Description + Média suggestif ou sensible avertissement de contenu ou texte caché Visibilité non répertorié @@ -160,10 +160,10 @@ Ne plus masquer le compte ? Masquer Masquer le compte ? - a envoyé un mp - système - sombre - clair + a envoyé un message privé + Système + Sombre + Clair Thème de l\'application Envoyer le message Sélectionner un destinataire @@ -200,7 +200,7 @@ Si une icône autre que celle par défaut est sélectionnée, deux icônes s’afficheront dans votre tiroir d’applications. Compte Actif : Autres comptes : - Ajouter un compte Pixeled + Ajouter un compte Pixelfed Télécharger l’image Profil Boîte de réception @@ -223,7 +223,7 @@ Localisation Mentions Explorer - reblogged by %1$s + republié par %1$s Mode sans distraction Masque la description, les J’aime et les boutons des messages. Paramètres de republication @@ -233,7 +233,7 @@ Lire la suite En voir moins Inscrit·e le %1$s - ago + Supprimer le message Lecteur vidéo non pris en charge @@ -264,19 +264,19 @@ année années - Blur sensitive content - Autoplay Videos - Report this post - Report - Spam - Adult or Sensitive Content - Abusive or Harmful - Underage Account + Flouter le contenu sensible + Lecture automatique des vidéos + Signaler cet publication + Signaler + Indésirable + Contenu pour adulte ou sensible + Abusif ou Dangereux + Compte Mineur Violence - Copyright infringement - Impersonation - Scam - Terrorism - Reported - Delete Account + Violation de droit d\'auteur + Usurpation d\'identité + Fraude + Terrorisme + Signalé + Supprimer le compte diff --git a/app/src/commonMain/composeResources/values-it-rIT/strings.xml b/app/src/commonMain/composeResources/values-it-rIT/strings.xml index 015bb83c..1ff21ee4 100644 --- a/app/src/commonMain/composeResources/values-it-rIT/strings.xml +++ b/app/src/commonMain/composeResources/values-it-rIT/strings.xml @@ -19,17 +19,17 @@ Smetti di seguire Seguaci - Follower - Followers + Seguace + Seguaci Seguiti - Following - Following + Seguito + Seguiti Post - Posts + Post Post Hashtag @@ -229,54 +229,54 @@ Impostazioni di ripubblicazione AMOLED Ancora nessun seguace - Not following anyone - Read More - Read Less - Joined %1$s + Non stai seguendo nessuno + Leggi di più + Leggi di meno + Unito il %1$s fa Elimina messaggio - Video player is not supported + Il riproduttore video non è supportato - second - seconds + secondo + secondi - minute - minutes + minuto + minuti - hour - hours + ora + ore - day - days + giorno + giorni - week - weeks + settimana + settimane - month - months + mese + mesi - year - years + anno + anni - Blur sensitive content - Autoplay Videos - Report this post - Report + Nascondi contenuto sensibile + Riproduci automaticamente i video + Segnala questo post + Segnala Spam - Adult or Sensitive Content - Abusive or Harmful - Underage Account - Violence - Copyright infringement - Impersonation - Scam - Terrorism - Reported - Delete Account + Contenuto sensibile o per adulti + Dannoso o Abusivo + Account Minorenne + Violenza + Violazione del copyright + Impersonificazione + Truffa + Terrorismo + Segnalato + Elimina Account diff --git a/app/src/commonMain/composeResources/values-zh-rTW/strings.xml b/app/src/commonMain/composeResources/values-zh-rTW/strings.xml index 8ab1324a..c0eebbae 100644 --- a/app/src/commonMain/composeResources/values-zh-rTW/strings.xml +++ b/app/src/commonMain/composeResources/values-zh-rTW/strings.xml @@ -1,205 +1,205 @@ - Notifications - Settings - Liked Posts - Bookmarked Posts - Followed Hashtags - Blocked Profiles - Muted Profiles - Logout - Open in browser - Share this profile - Block this profile - Unblock this profile - Mute this profile - Unmute this profile - Load More - Follow - Unfollow - Followers + 通知 + 設定 + 喜歡的貼文 + 已加書籤的貼文 + 已追蹤的標籤 + 已封鎖個人檔案 + 已靜音個人檔案 + 登出 + 以瀏覽器開啟 + 分享此個人檔案 + 封鎖此個人檔案 + 解封此個人檔案 + 靜音此個人檔案 + 取消靜音此個人檔案 + 載入更多 + 追蹤 + 取消追蹤 + 追蹤者 - Followers + 追蹤者 - Following + 追蹤中 - Following + 追蹤中 - Posts + 貼文 - Posts - Hashtags - reblogged your post - liked your post - followed you - Share this post - Trending - Profiles - Rules - Instance version - Terms of use - Privacy Policy - Admin - Users - Stats - unblock - Unblock - Unblock Profile? - Confirm to unblock this profile - no blocked profiles - Cancel - Post by - Liked by - Delete this post - Delete post? - This action cannot be undone - Delete - This is the End! - and - Followed by - other - others - yearly - monthly - daily - I don\'t have a profile - Server URL - Are you sure you want to log out? - Logout? - View %1$s comments - Leave a comment - No comments yet - No likes yet - no liked posts - Local - Global - Home - %1$s likes - Search - Unmute - unmute - Media Description - No posts yet - mentioned you - No bookmarked posts - Nothing to show - New post - Publish - Alt Text - caption - Sensitive/NSFW Media - content warning or spoiler text - Audience - unlisted - followers only - public - No followed hashtags - No muted profiles - Visibility: %1$s - Clear cache - Preferences - Hide sensitive content - About %1$s - More settings - Use in-app-browser + 貼文 + 標籤 + 轉發了你的貼文 + 對你的貼文表示喜歡 + 已追蹤你 + 分享這篇貼文 + 熱門 + 個人檔案 + 規則 + 實例版本 + 使用條款 + 隱私政策 + 管理員 + 用戶 + 狀態 + 解封 + 解封 + 解封個人檔案? + 確認解封此個人檔案 + 沒有封鎖的個人檔案 + 取消 + 作者 + 表示喜歡的用戶 + 刪除這則貼文 + + 此動作無法復原 + 刪除 + 到底了! + + 被這些人追蹤 + 其他 + 其他 + 每年 + 每月 + 每日 + 我沒有個人檔案 + 伺服器URL + 你確定要登出嗎? + 登出? + 查看%1$s則留言 + 留言 + 沒有留言 + 沒有喜歡 + 沒有喜歡的貼文 + 在地 + 全球 + 首頁 + %1$s個喜歡 + 搜尋 + 取消靜音 + 取消靜音 + 媒體描述 + 沒有貼文 + 提及你 + 沒有已加書籤的貼文 + 沒有可顯示的內容 + 新貼文 + 發布 + 替代文字 + 標題 + 敏感/NSFW 媒體 + 內容警告或劇透文本 + 受眾 + 未列出 + 僅限追蹤者 + 公開 + 沒有追蹤的標籤 + 沒有靜音的個人檔案 + 可見度:%1$s + 清理快取 + 偏好 + 隱藏敏感內容 + 關於:%1$s + 更多設定 + 使用內建瀏覽器 No trending profiles No trending Hashtags No trending posts - Upload your first post - The profiles you follow will appear here + 上傳你的第一則貼文 + 你追蹤的個人資料將在此出現 Explore trending profiles - Nobody follows you yet - Empty + 沒有人追蹤你 + Follow profiles or hashtags to fill your home timeline - No posts + 沒有貼文 The Home timeline shows you posts from people and hashtags that you follow The Local timeline shows posts from all users in your server The Global timeline shows posts from all users in all servers your server federates with - This user has not postet anything yet - You don\'t have any notifications - view more - Edit Profile - Save - Delete reply - Reply - Post + 這位用戶尚未發布任何內容 + 未收到任何通知 + 查看更多 + 編輯個人檔案 + 保存 + 刪除回覆 + 回覆 + 貼文 Pixelfed is a photo and image sharing network on the Fediverse with a photo-oriented interface that includes albums, filters, moments etc. Mastodon is a free and open source microblogging platform PeerTube is a tool for sharing online videos developed by Framasoft, a french non-profit. Lemmy is a selfhosted social link aggregation and discussion platform. It is completely free and open, and not controlled by any company. This means that there is no advertising, tracking, or secret algorithms. Content is organized into communities, so it is easy to subscribe to topics that you are interested in, and ignore others. Voting is used to bring the most interesting items to the top. Threads is an app from Meta where you can view and share public conversations. You can also post threads, reply to others and follow profiles you are interested in. - More information + 更多資訊 What makes a post trend? Posts trend if they were posted during the selected timeframe and have many likes. The trending profiles section lists the profiles on your instance with the most followers. The profiles you already follow are hidden. - What makes an profile trend? - What makes a hashtag trend? - Hashtags trend if many people have used them recently. - Private profile - • You won’t see the user in your home feed + 什麼造就了個人資料趨勢? + 什麼導致了標籤趨勢? + 如果最近有很多人使用標籤,那麼該標籤就會成為趨勢。 + 不公開個人檔案 + • 您不會在首頁動態中看到該用戶 • You won’t see other people boosting the user - • You won’t see other people mentioning the user - • You won’t see the user in public timelines - The user has no way of knowing they have been muted. - • You won’t see the user in your home feed + • 您不會看到其他人提及該用戶 + • 您不會在公開時間線上看到該用戶 + 使用者無法知道他們已被靜音。 + • 您不會在首頁動態中看到該用戶 • You won’t see other people boosting the user - • You won’t see other people mentioning the user - • You won’t see the user in public timelines - • You won’t see notifications from that user - • The user is forced to unfollow you - • The user cannot follow you - • The user won’t see other people’s boosts of you - • The user won’t see you in public timelines - If you and the blocked user are on the same server, the blocked user will not be able to view your posts on your profile while logged in. - Block - Block profile? - Unmute Profile? - Mute - Mute Profile? - sent a dm - system - dark - light - App theme - Send message - Select recipient - New Direct Message - Warning - "Direct messages on Pixelfed are not end-to-end encrypted. Use caution when sharing sensitive data. " - Confirm - This is the beginning of your chat with this user. Don\'t forget to be respectful. - Rate us - About Pixelix - Developed by - Conversations - Share this collection - By %1$s - New Collection - Creating collections is not yet supported in the pixelfed api, but a new collection can be created in the pixelfed web app: - New - Collections - Follows you - muted - blocked - Message - All - Likes - Reposts - Edit this post - Stay updated with your latest notifications from Pixelfed right on your home screen - Customize app icon - Change app icon? - It may take a while until the new icon is shown. - Change - mentioned you + • 您不會看到其他人提及該用戶 + • 您不會在公開時間線上看到該用戶 + • 您將看不到該使用者的通知 + • 用戶被迫取消追蹤你 + • 使用者無法追蹤您 + • 使用者不會看到其他人對您的評價 + • 用戶不會在公開時間線上看到你 + 如果您和被封鎖的使用者位於同一台伺服器上,則被封鎖的使用者將無法在登入時查看您個人資料上的貼文。 + 封鎖 + 封鎖個人檔案? + 解封個人檔案? + 靜音 + 靜音個人檔案? + 發送私訊 + 系統 + 深色 + 淺色 + 應用主題 + 傳送訊息 + 選擇接收者 + 新的Direct訊息 + 警告 + "Pixelfed 上的Direct訊息並非端對端加密。分享敏感資料時請務必謹慎。 " + 確認 + 您與該用戶的聊天即將開始。請保持禮貌。 + 評價我們 + 關於 Pixelix + 開發者 + 對話 + 分享這個收藏 + 由 %1$s + 新收藏 + pixelfed api中尚不支援建立收藏,但可以在pixelfed 網頁應用程式中建立新的收藏: + + 收藏 + 追蹤了你 + 已靜音 + 已封鎖 + 訊息 + 全部 + 喜歡 + 轉發 + 編輯貼文 + 在主畫面隨時取得Pixelfed的最新通知 + 自定義應用圖示 + 更改應用圖示? + 新圖示可能需要一段時間才會顯示 + 更改 + 提及了你 have a look at the newest post from your home timeline, right on your homescreen - If an icon other than the default icon is selected, two icons will be displayed in your apps drawer. - Current Profile: - Other Profiles: - Add Pixelfed profile - Download image - Profile + 如果選擇了預設以外的圖示,則應用程式抽屜將有兩個圖示 + 目前的個人檔案: + 其他個人檔案: + 新增Pixelfed個人檔案 + 下載圖片 + 個人檔案 Inbox Are you sure you want to remove this Profile? Remove Profile? diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/App.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/App.kt index bb1612a5..8e72e1a1 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/App.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/App.kt @@ -1,7 +1,9 @@ package com.daniebeler.pfpixelix +import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues @@ -13,6 +15,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHostState import androidx.compose.material.icons.Icons @@ -96,8 +99,7 @@ val LocalSnackbarPresenter = compositionLocalOf<(String) -> Unit> { @OptIn(ExperimentalMaterial3Api::class) @Composable fun App( - appComponent: AppComponent, - exitApp: () -> Unit + appComponent: AppComponent, exitApp: () -> Unit ) { val uriHandler = LocalUriHandler.current DisposableEffect(uriHandler) { @@ -165,25 +167,17 @@ fun App( } }) } - } - ) { + }) { Scaffold( contentWindowInsets = WindowInsets(0), snackbarHost = { SnackbarHost(snackbarHostState) }, - bottomBar = { - BottomBar( - navController = navController, - openAccountSwitchBottomSheet = { - showAccountSwitchBottomSheet = true - }, - ) - }, - content = { paddingValues -> + ) { paddingValues -> + Box(Modifier.fillMaxSize().padding(paddingValues)) { val startDestination = if (activeUser == null) Destination.FirstLogin else Destination.HomeTabFeeds NavHost( - modifier = Modifier.fillMaxSize().padding(paddingValues) + modifier = Modifier.fillMaxSize().padding(bottom = 76.dp) .consumeWindowInsets(WindowInsets.navigationBars), navController = navController, startDestination = startDestination, @@ -193,10 +187,23 @@ fun App( { scope.launch { drawerState.open() } }, exitApp ) - } - ) + }) + + Box( + modifier = Modifier.align(Alignment.BottomCenter) + .clip(RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)) + .background(MaterialTheme.colorScheme.surface) + ) { + BottomBar( + navController = navController, + openAccountSwitchBottomSheet = { + showAccountSwitchBottomSheet = true + }, + ) + } } - ) + + } } } @@ -235,10 +242,7 @@ private enum class HomeTab( val label: StringResource ) { Feeds( - Destination.HomeTabFeeds, - Res.drawable.house, - Res.drawable.house_fill, - Res.string.home + Destination.HomeTabFeeds, Res.drawable.house, Res.drawable.house_fill, Res.string.home ), Search( Destination.HomeTabSearch, @@ -268,8 +272,7 @@ private enum class HomeTab( @Composable private fun BottomBar( - navController: NavHostController, - openAccountSwitchBottomSheet: () -> Unit + navController: NavHostController, openAccountSwitchBottomSheet: () -> Unit ) { val systemNavigationBarHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() @@ -278,9 +281,7 @@ private fun BottomBar( val appComponent = LocalAppComponent.current LaunchedEffect(Unit) { val authService = appComponent.authService - authService.activeUser - .map { authService.getCurrentSession() } - .collect { + authService.activeUser.map { authService.getCurrentSession() }.collect { avatar = it?.avatar } } @@ -329,10 +330,7 @@ private fun BottomBar( model = avatar, error = painterResource(Res.drawable.default_avatar), contentDescription = "", - modifier = Modifier - .height(30.dp) - .width(30.dp) - .clip(CircleShape) + modifier = Modifier.height(30.dp).width(30.dp).clip(CircleShape) ) Icon( Icons.Outlined.UnfoldMore, @@ -348,14 +346,10 @@ private fun BottomBar( contentDescription = stringResource(tab.label) ) } - }, - selected = isSelected, - colors = NavigationBarItemDefaults.colors( + }, selected = isSelected, colors = NavigationBarItemDefaults.colors( selectedIconColor = MaterialTheme.colorScheme.inverseSurface, indicatorColor = Color.Transparent - ), - interactionSource = interactionSource, - onClick = { + ), interactionSource = interactionSource, onClick = { if (!isLongPress) { if (!isSelected) { //switch tab @@ -373,16 +367,14 @@ private fun BottomBar( if (!isOnRoot) { //back to root navController.popBackStack( - route = tabRoot.route!!, - inclusive = false + route = tabRoot.route!!, inclusive = false ) } else if (currentDestination.hasRoute()) { appComponent.searchFieldFocus.focus() } } } - } - ) + }) } } } diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/CustomPost.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/CustomPost.kt index e8d79928..c4ad617d 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/CustomPost.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/CustomPost.kt @@ -23,14 +23,12 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewModelScope import androidx.navigation.NavController import coil3.compose.AsyncImage import com.daniebeler.pfpixelix.di.LocalAppComponent import com.daniebeler.pfpixelix.domain.model.Post import com.daniebeler.pfpixelix.ui.navigation.Destination import com.daniebeler.pfpixelix.utils.BlurHashDecoder -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res @@ -60,7 +58,7 @@ fun CustomPost( ?: "LEHLk~WB2yk8pyo0adR*.7kCMdnj" val blurHashBitmap = remember(firstBlurHash) { BlurHashDecoder.decode(firstBlurHash) } - Box(modifier = customModifier.aspectRatio(1f)) { + Box(modifier = customModifier.clip(RoundedCornerShape(6.dp)).aspectRatio(1f)) { if (blurHashBitmap != null) { Image( blurHashBitmap, diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/FollowButton.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/FollowButton.kt index 5fa5b15b..85ba8efb 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/FollowButton.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/FollowButton.kt @@ -70,14 +70,15 @@ private fun IconFollowButton( ) ) { CircularProgressIndicator( - modifier = Modifier.size(20.dp) + modifier = Modifier.size(20.dp), + color = MaterialTheme.colorScheme.onSecondaryContainer ) } } else { IconButton( onClick = {}, colors = IconButtonDefaults.iconButtonColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary ) ) { CircularProgressIndicator( @@ -105,8 +106,8 @@ private fun IconFollowButton( onClick = { onFollowClick() }, colors = IconButtonDefaults.iconButtonColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary ) ) { Icon( diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/HomeComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/HomeComposable.kt index cea13c2c..85d965e9 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/HomeComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/HomeComposable.kt @@ -6,14 +6,15 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -23,6 +24,8 @@ import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.Scaffold import androidx.compose.material3.Tab import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -31,9 +34,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex import androidx.navigation.NavController import com.daniebeler.pfpixelix.ui.composables.timelines.global_timeline.GlobalTimelineComposable import com.daniebeler.pfpixelix.ui.composables.timelines.home_timeline.HomeTimelineComposable @@ -64,9 +69,14 @@ fun HomeComposable(navController: NavController, openPreferencesDrawer: () -> Un var showBottomSheet by remember { mutableStateOf(false) } Scaffold( - topBar = { - CenterAlignedTopAppBar(title = { - Text(stringResource(Res.string.app_name), fontWeight = FontWeight.Bold) + contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { + TopAppBar( + title = { + Text( + stringResource(Res.string.app_name), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) }, navigationIcon = { IconButton(onClick = { showBottomSheet = true }) { Icon( @@ -94,80 +104,89 @@ fun HomeComposable(navController: NavController, openPreferencesDrawer: () -> Un ) } } - }) + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) }) { paddingValues -> + Box( + Modifier.fillMaxSize().padding(paddingValues) + .background(MaterialTheme.colorScheme.background) + ) { + PrimaryTabRow( + selectedTabIndex = pagerState.currentPage, + divider = {}, + containerColor = MaterialTheme.colorScheme.surfaceContainer, + modifier = Modifier.clip( + RoundedCornerShape( + bottomStart = 24.dp, bottomEnd = 24.dp + ) + ).zIndex(1f) + ) { + Tab( + text = { Text(stringResource(Res.string.home)) }, + selected = pagerState.currentPage == 0, + selectedContentColor = MaterialTheme.colorScheme.primary, + unselectedContentColor = MaterialTheme.colorScheme.onBackground, + onClick = { + scope.launch { + pagerState.animateScrollToPage(0) + } + }) - Column( - Modifier - .fillMaxSize() - .padding(paddingValues) - ) { + Tab( + text = { Text(stringResource(Res.string.local)) }, + selected = pagerState.currentPage == 1, + selectedContentColor = MaterialTheme.colorScheme.primary, + unselectedContentColor = MaterialTheme.colorScheme.onBackground, + onClick = { + scope.launch { + pagerState.animateScrollToPage(1) + } + }) - PrimaryTabRow(selectedTabIndex = pagerState.currentPage) { - Tab(text = { Text(stringResource(Res.string.home)) }, - selected = pagerState.currentPage == 0, - selectedContentColor = MaterialTheme.colorScheme.primary, - unselectedContentColor = MaterialTheme.colorScheme.onBackground, - onClick = { - scope.launch { - pagerState.animateScrollToPage(0) - } - }) + Tab( + text = { Text(stringResource(Res.string.global)) }, + selected = pagerState.currentPage == 2, + selectedContentColor = MaterialTheme.colorScheme.primary, + unselectedContentColor = MaterialTheme.colorScheme.onBackground, + onClick = { + scope.launch { + pagerState.animateScrollToPage(2) + } + }) + } - Tab(text = { Text(stringResource(Res.string.local)) }, - selected = pagerState.currentPage == 1, - selectedContentColor = MaterialTheme.colorScheme.primary, - unselectedContentColor = MaterialTheme.colorScheme.onBackground, - onClick = { - scope.launch { - pagerState.animateScrollToPage(1) + HorizontalPager( + state = pagerState, + beyondViewportPageCount = 3, + modifier = Modifier.padding(top = 24.dp) + .background(MaterialTheme.colorScheme.background).zIndex(0f) + ) { tabIndex -> + when (tabIndex) { + 0 -> Box(modifier = Modifier.fillMaxSize()) { + HomeTimelineComposable(navController) } - }) - Tab(text = { Text(stringResource(Res.string.global)) }, - selected = pagerState.currentPage == 2, - selectedContentColor = MaterialTheme.colorScheme.primary, - unselectedContentColor = MaterialTheme.colorScheme.onBackground, - onClick = { - scope.launch { - pagerState.animateScrollToPage(2) + 1 -> Box(modifier = Modifier.fillMaxSize()) { + LocalTimelineComposable(navController) } - }) - } - HorizontalPager( - state = pagerState, - beyondViewportPageCount = 3, - modifier = Modifier - .weight(1f) - .background(MaterialTheme.colorScheme.background) - ) { tabIndex -> - when (tabIndex) { - 0 -> Box(modifier = Modifier.fillMaxSize()) { - HomeTimelineComposable(navController) - } - 1 -> Box(modifier = Modifier.fillMaxSize()) { - LocalTimelineComposable(navController) - } - - 2 -> Box(modifier = Modifier.fillMaxSize()) { - GlobalTimelineComposable(navController) + 2 -> Box(modifier = Modifier.fillMaxSize()) { + GlobalTimelineComposable(navController) + } } } } - } } if (showBottomSheet) { ModalBottomSheet( onDismissRequest = { showBottomSheet = false - }, - sheetState = sheetState + }, sheetState = sheetState ) { Box( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 12.dp) + modifier = Modifier.fillMaxSize().padding(horizontal = 12.dp) ) { Column { Spacer(modifier = Modifier.height(18.dp)) @@ -195,7 +214,7 @@ fun HomeComposable(navController: NavController, openPreferencesDrawer: () -> Un @Composable fun SheetItem(header: String, description: String) { Column { - Text(text = header, fontSize = 24.sp, fontWeight = FontWeight.Bold) + Text(text = header, fontSize = 18.sp, fontWeight = FontWeight.Bold) Spacer(modifier = Modifier.height(8.dp)) Text(text = description) Spacer(modifier = Modifier.height(16.dp)) diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/InfinitePostsGrid.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/InfinitePostsGrid.kt index 2a78c1e4..56a809f5 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/InfinitePostsGrid.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/InfinitePostsGrid.kt @@ -2,6 +2,7 @@ package com.daniebeler.pfpixelix.ui.composables import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height @@ -17,6 +18,7 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.daniebeler.pfpixelix.domain.model.Post @@ -39,6 +41,7 @@ fun InfinitePostsGrid( emptyMessage: EmptyState, navController: NavController, getItemsPaginated: () -> Unit = { }, + contentPaddingTop: Dp = 0.dp, before: @Composable (() -> Unit)? = null, after: @Composable (() -> Unit)? = null, onRefresh: () -> Unit = { }, @@ -52,13 +55,13 @@ fun InfinitePostsGrid( PullToRefreshBox( isRefreshing = isRefreshing, onRefresh = { onRefresh() }, modifier = Modifier.fillMaxSize() ) { - privateInfinitePostsGrid(items, isLoading, isRefreshing, error, endReached, emptyMessage, navController, getItemsPaginated, before, after, edit, editRemove, onClick) + privateInfinitePostsGrid(items, isLoading, isRefreshing, error, endReached, emptyMessage, navController, getItemsPaginated, contentPaddingTop, before, after, edit, editRemove, onClick) } } else { Box( modifier = Modifier.fillMaxSize() ) { - privateInfinitePostsGrid(items, isLoading, isRefreshing, error, endReached, emptyMessage, navController, getItemsPaginated, before, after, edit, editRemove, onClick) + privateInfinitePostsGrid(items, isLoading, isRefreshing, error, endReached, emptyMessage, navController, getItemsPaginated, contentPaddingTop, before, after, edit, editRemove, onClick) } } } @@ -73,6 +76,7 @@ fun privateInfinitePostsGrid( emptyMessage: EmptyState, navController: NavController, getItemsPaginated: () -> Unit = { }, + contentPaddingTop: Dp, before: @Composable (() -> Unit)? = null, after: @Composable (() -> Unit)? = null, edit: Boolean = false, @@ -86,9 +90,10 @@ fun privateInfinitePostsGrid( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .fillMaxSize() - .padding(horizontal = 12.dp), + .padding(horizontal = 4.dp), state = lazyGridState, - columns = GridCells.Fixed(3) + columns = GridCells.Fixed(3), + contentPadding = PaddingValues(top = contentPaddingTop) ) { if (before != null) { @@ -98,7 +103,7 @@ fun privateInfinitePostsGrid( } item(span = { GridItemSpan(3) }) { - Spacer(Modifier.height(12.dp)) + Spacer(Modifier.height(4.dp)) } itemsIndexed(items) { index, photo -> diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/InfinitePostsList.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/InfinitePostsList.kt index 2c2ebe7f..6631ecbd 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/InfinitePostsList.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/InfinitePostsList.kt @@ -38,6 +38,7 @@ fun InfinitePostsList( changeView: (view: ViewEnum) -> Unit = {}, isFirstItemLarge: Boolean = false, postsCount: Int? = null, + contentPaddingTop: Dp = 0.dp, contentPaddingBottom: Dp = 4.dp ) { val lazyListState = rememberLazyListState() @@ -58,11 +59,7 @@ fun InfinitePostsList( verticalArrangement = Arrangement.spacedBy(4.dp), state = lazyListState, contentPadding = PaddingValues( - top = if (postsCount != null) { - 0.dp - } else { - 12.dp - }, bottom = contentPaddingBottom + top = contentPaddingTop, bottom = contentPaddingBottom ) ) { postsCount?.let { diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/collection/CollectionComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/collection/CollectionComposable.kt index feeb3ccb..6740486b 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/collection/CollectionComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/collection/CollectionComposable.kt @@ -7,19 +7,17 @@ import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.outlined.AddCircle import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.FavoriteBorder import androidx.compose.material.icons.outlined.MoreVert -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -30,6 +28,8 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -37,8 +37,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -49,11 +49,9 @@ import com.daniebeler.pfpixelix.ui.composables.ButtonRowElement import com.daniebeler.pfpixelix.ui.composables.InfinitePostsGrid import com.daniebeler.pfpixelix.ui.composables.states.EmptyState import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res import pixelix.app.generated.resources.by import pixelix.app.generated.resources.cancel -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.confirm import pixelix.app.generated.resources.open_in_browser import pixelix.app.generated.resources.open_outline @@ -77,91 +75,19 @@ fun CollectionComposable( viewModel.loadData(collectionId) } - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - if (viewModel.collectionState.collection != null) { - if (viewModel.editState.editMode) { - TextField( - value = viewModel.editState.name, - singleLine = true, - onValueChange = { viewModel.editState = viewModel.editState.copy(name = it) }, - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(16.dp), - colors = TextFieldDefaults.colors( - unfocusedIndicatorColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, - unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer - ) - ) - } else { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text( - viewModel.collectionState.collection!!.title, fontWeight = FontWeight.Bold - ) - Text( - stringResource( - Res.string.by, viewModel.collectionState.collection!!.username - ), fontSize = 12.sp, lineHeight = 6.sp - ) - } - } - } - }, navigationIcon = { - IconButton(onClick = { - navController.popBackStack() - }) { - Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), contentDescription = "" - ) - } - }, actions = { - if (viewModel.editState.editMode) { - TextButton(onClick = { - viewModel.toggleEditMode() - }) { - Text(stringResource(Res.string.cancel)) - } - TextButton(onClick = { - viewModel.confirmEdit() - }) { - Text(stringResource(Res.string.confirm)) - } - } else { - - viewModel.collectionState.collection?.let { - if (it.username == viewModel.myUsername) { - IconButton(onClick = { - viewModel.toggleEditMode() - }) { - Icon( - imageVector = Icons.Outlined.Edit, contentDescription = "" - ) - } - } - } - - IconButton(onClick = { - //Navigate.navigate("settings_screen", navController) - showBottomSheet = true - }) { - Icon( - imageVector = Icons.Outlined.MoreVert, contentDescription = "" - ) - } - } - }) - }) { paddingValues -> + Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top)) { paddingValues -> Column( - modifier = Modifier - .fillMaxSize() + modifier = Modifier.fillMaxSize() + .padding(top = TopAppBarDefaults.TopAppBarExpandedHeight - 24.dp) .padding(paddingValues) ) { - InfinitePostsGrid(items = if (viewModel.editState.editMode) { - viewModel.editState.editPosts - } else { - viewModel.collectionPostsState.posts - }, + InfinitePostsGrid( + contentPaddingTop = 24.dp, + items = if (viewModel.editState.editMode) { + viewModel.editState.editPosts + } else { + viewModel.collectionPostsState.posts + }, isLoading = viewModel.collectionPostsState.isLoading, isRefreshing = viewModel.collectionPostsState.isRefreshing, error = viewModel.collectionPostsState.error, @@ -199,25 +125,25 @@ fun CollectionComposable( ModalBottomSheet( onDismissRequest = { showBottomSheet = false - }, - sheetState = sheetState + }, sheetState = sheetState ) { Column( modifier = Modifier.padding(bottom = 32.dp) ) { - ButtonRowElement(icon = Res.drawable.open_outline, text = stringResource( - Res.string.open_in_browser - ), onClick = { - if (viewModel.collectionState.collection != null) { - viewModel.openUrl(viewModel.collectionState.collection!!.url) - } - }) + ButtonRowElement( + icon = Res.drawable.open_outline, text = stringResource( + Res.string.open_in_browser + ), onClick = { + if (viewModel.collectionState.collection != null) { + viewModel.openUrl(viewModel.collectionState.collection!!.url) + } + }) - ButtonRowElement(icon = Res.drawable.share_social_outline, + ButtonRowElement( + icon = Res.drawable.share_social_outline, text = stringResource(Res.string.share_this_collection), - onClick = { viewModel.shareCollectionUrl() } - ) + onClick = { viewModel.shareCollectionUrl() }) } } @@ -227,13 +153,13 @@ fun CollectionComposable( ModalBottomSheet( onDismissRequest = { showAddPostBottomSheet = false - }, - sheetState = showAddPostBottomSheetState + }, sheetState = showAddPostBottomSheetState ) { Column( modifier = Modifier.padding(bottom = 32.dp) ) { - InfinitePostsGrid(items = viewModel.editState.allPostsExceptCollection, + InfinitePostsGrid( + items = viewModel.editState.allPostsExceptCollection, isLoading = viewModel.editState.isLoading, isRefreshing = false, error = viewModel.editState.error, @@ -246,10 +172,96 @@ fun CollectionComposable( }, onRefresh = { viewModel.refresh() - }, onClick = { viewModel.addPostToCollection(it) }, pullToRefresh = false + }, + onClick = { viewModel.addPostToCollection(it) }, + pullToRefresh = false ) } } } + + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + if (viewModel.collectionState.collection != null) { + if (viewModel.editState.editMode) { + TextField( + value = viewModel.editState.name, + singleLine = true, + onValueChange = { + viewModel.editState = viewModel.editState.copy(name = it) + }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + colors = TextFieldDefaults.colors( + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) + } else { + Column { + Text( + viewModel.collectionState.collection!!.title, + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + Text( + stringResource( + Res.string.by, viewModel.collectionState.collection!!.username + ), fontSize = 12.sp, lineHeight = 6.sp + ) + } + } + } + }, navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" + ) + } + }, actions = { + if (viewModel.editState.editMode) { + TextButton(onClick = { + viewModel.toggleEditMode() + }) { + Text(stringResource(Res.string.cancel)) + } + TextButton(onClick = { + viewModel.confirmEdit() + }) { + Text(stringResource(Res.string.confirm)) + } + } else { + + viewModel.collectionState.collection?.let { + if (it.username == viewModel.myUsername) { + IconButton(onClick = { + viewModel.toggleEditMode() + }) { + Icon( + imageVector = Icons.Outlined.Edit, contentDescription = "" + ) + } + } + } + + IconButton(onClick = { + //Navigate.navigate("settings_screen", navController) + showBottomSheet = true + }) { + Icon( + imageVector = Icons.Outlined.MoreVert, contentDescription = "" + ) + } + } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) } } \ No newline at end of file diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatComposable.kt index 5ea37bd9..caa3d11b 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatComposable.kt @@ -8,14 +8,13 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn @@ -25,6 +24,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.Send import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator @@ -34,9 +34,9 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -57,10 +57,8 @@ import com.daniebeler.pfpixelix.ui.navigation.Destination import com.daniebeler.pfpixelix.utils.imeAwareInsets import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res import pixelix.app.generated.resources.beginning_of_chat_note -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.default_avatar import pixelix.app.generated.resources.message @@ -77,175 +75,195 @@ fun ChatComposable( viewModel.getChat(accountId) } - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - TopAppBar(title = { - if (viewModel.chatState.chat != null) { - Row( - modifier = Modifier.clickable { - navController.navigate(Destination.Profile(accountId)) - }, verticalAlignment = Alignment.CenterVertically - ) { - AsyncImage( - model = viewModel.chatState.chat!!.avatar, - error = painterResource(Res.drawable.default_avatar), - contentDescription = "", - modifier = Modifier - .height(46.dp) - .width(46.dp) - .clip(CircleShape) - ) - Spacer(modifier = Modifier.width(10.dp)) - - Column { - - Text(text = viewModel.chatState.chat!!.name ?: "") - Text( - text = viewModel.chatState.chat!!.url.substringAfter("https://") - .substringBefore("/"), - fontSize = 12.sp, - lineHeight = 6.sp, - color = MaterialTheme.colorScheme.primary - ) - } - } - } + Box(modifier = Modifier.fillMaxSize()) { + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() - }, navigationIcon = { - IconButton(onClick = { - navController.popBackStack() - }) { - Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), contentDescription = "" - ) - } - }) - }) { paddingValues -> - - PullToRefreshBox ( - isRefreshing = viewModel.chatState.isRefreshing, - onRefresh = { viewModel.getChat(accountId, true) }, - modifier = Modifier - .imeAwareInsets(90.dp) - .padding(paddingValues) + Box( + modifier = Modifier.padding(top = TopAppBarDefaults.TopAppBarExpandedHeight + statusBarPadding - 24.dp) + .fillMaxSize() ) { - Column( + + PullToRefreshBox( + isRefreshing = viewModel.chatState.isRefreshing, + onRefresh = { viewModel.getChat(accountId, true) }, modifier = Modifier - .fillMaxSize() - .padding(12.dp) + .imeAwareInsets(90.dp) ) { - LazyColumn(state = lazyListState, - modifier = Modifier.weight(1f), - reverseLayout = true, - content = { - if (viewModel.chatState.chat != null && viewModel.chatState.chat?.messages!!.isNotEmpty()) { - - items(viewModel.chatState.chat!!.messages, key = { - it.id - }) { - ConversationElementComposable( - message = it, { viewModel.deleteMessage(it.reportId) }, navController = navController - ) - } + Column( + modifier = Modifier + .fillMaxSize() + .padding(bottom = 16.dp, start = 8.dp, end = 8.dp) + ) { + LazyColumn( + state = lazyListState, + modifier = Modifier.weight(1f), + reverseLayout = true, + contentPadding = PaddingValues(top = 24.dp), + content = { + if (viewModel.chatState.chat != null && viewModel.chatState.chat?.messages!!.isNotEmpty()) { - if (viewModel.chatState.isLoading) { - item { - CircularProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) - .wrapContentSize(Alignment.Center) + items(viewModel.chatState.chat!!.messages, key = { + it.id + }) { + ConversationElementComposable( + message = it, + { viewModel.deleteMessage(it.reportId) }, + navController = navController ) } + + if (viewModel.chatState.isLoading) { + item { + CircularProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .height(80.dp) + .wrapContentSize(Alignment.Center) + ) + } + } + + if (viewModel.chatState.endReached) { + item { + EndOfListComposable() + } + } } - if (viewModel.chatState.endReached) { + if (viewModel.chatState.chat != null && viewModel.chatState.chat?.messages?.isEmpty() == true) { item { - EndOfListComposable() + Spacer(modifier = Modifier.height(56.dp)) + Box( + modifier = Modifier + .fillMaxWidth() + .clip( + RoundedCornerShape(8.dp) + ) + .background(MaterialTheme.colorScheme.primaryContainer) + .padding(8.dp) + ) { + Text( + text = stringResource(Res.string.beginning_of_chat_note), + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier.fillMaxWidth() + ) + } } } - } + }) - if (viewModel.chatState.chat != null && viewModel.chatState.chat?.messages?.isEmpty() == true) { - item { - Spacer(modifier = Modifier.height(56.dp)) - Box( - modifier = Modifier + + Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.Bottom) { + OutlinedTextField( + value = viewModel.newMessage, + onValueChange = { viewModel.newMessage = it }, + label = { Text(stringResource(Res.string.message)) }, + singleLine = false, + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, + disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer, + unfocusedBorderColor = MaterialTheme.colorScheme.background + ), + shape = RoundedCornerShape(12.dp), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Default), + modifier = Modifier.weight(1f) + ) + Spacer(Modifier.width(12.dp)) + if (viewModel.newMessageState.isLoading) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .height(56.dp) + .width(56.dp) + .padding(0.dp, 0.dp) + .clip(RoundedCornerShape(12.dp)) + .background(MaterialTheme.colorScheme.primary) + ) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + color = MaterialTheme.colorScheme.onPrimary + ) + } + } else { + Button( + onClick = { + viewModel.sendMessage(accountId) + }, + Modifier + .height(56.dp) + .width(56.dp) + .padding(0.dp, 0.dp), + shape = RoundedCornerShape(12.dp), + contentPadding = PaddingValues(12.dp) + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.Send, + contentDescription = "send", + Modifier + .fillMaxSize() .fillMaxWidth() - .clip( - RoundedCornerShape(8.dp) - ) - .background(MaterialTheme.colorScheme.primaryContainer) - .padding(8.dp) - ) { - Text( - text = stringResource(Res.string.beginning_of_chat_note), - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onPrimaryContainer, - modifier = Modifier.fillMaxWidth() - ) - } + ) } } - }) - - - Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.Bottom) { - OutlinedTextField(value = viewModel.newMessage, - onValueChange = { viewModel.newMessage = it }, - label = { Text(stringResource(Res.string.message)) }, - singleLine = false, - colors = OutlinedTextFieldDefaults.colors( - focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, - unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, - disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer, - unfocusedBorderColor = MaterialTheme.colorScheme.background - ), - shape = RoundedCornerShape(12.dp), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Default), - modifier = Modifier.weight(1f) - ) - Spacer(Modifier.width(12.dp)) - if (viewModel.newMessageState.isLoading) { - Box( - contentAlignment = Alignment.Center, + } + + ErrorComposable(message = viewModel.chatState.error) + } + } + } + + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), + title = { + if (viewModel.chatState.chat != null) { + Row( + modifier = Modifier.clickable { + navController.navigate(Destination.Profile(accountId)) + }, verticalAlignment = Alignment.CenterVertically + ) { + AsyncImage( + model = viewModel.chatState.chat!!.avatar, + error = painterResource(Res.drawable.default_avatar), + contentDescription = "", modifier = Modifier - .height(56.dp) - .width(56.dp) - .padding(0.dp, 0.dp) - .clip(RoundedCornerShape(12.dp)) - .background(MaterialTheme.colorScheme.primary) - ) { - CircularProgressIndicator( - modifier = Modifier.size(24.dp), - color = MaterialTheme.colorScheme.onPrimary - ) - } - } else { - Button( - onClick = { - viewModel.sendMessage(accountId) - }, - Modifier - .height(56.dp) - .width(56.dp) - .padding(0.dp, 0.dp), - shape = RoundedCornerShape(12.dp), - contentPadding = PaddingValues(12.dp) - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.Send, - contentDescription = "send", - Modifier - .fillMaxSize() - .fillMaxWidth() + .height(46.dp) + .width(46.dp) + .clip(CircleShape) + ) + Spacer(modifier = Modifier.width(10.dp)) + + Column { + + Text(text = viewModel.chatState.chat!!.name ?: "") + Text( + text = viewModel.chatState.chat!!.url.substringAfter("https://") + .substringBefore("/"), + fontSize = 12.sp, + lineHeight = 6.sp, + color = MaterialTheme.colorScheme.primary ) } } } - ErrorComposable(message = viewModel.chatState.error) - } - } + }, navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" + ) + } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) + + InfiniteListHandler(lazyListState = lazyListState) { viewModel.getChatPaginated(accountId) } diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/conversations/ConversationsComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/conversations/ConversationsComposable.kt index 7a88c217..8803b19d 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/conversations/ConversationsComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/conversations/ConversationsComposable.kt @@ -4,35 +4,35 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.AlertDialog -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -47,6 +47,7 @@ import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.ui.composables.InfiniteListHandler @@ -62,7 +63,6 @@ import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res import pixelix.app.generated.resources.add_outline import pixelix.app.generated.resources.cancel -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.confirm import pixelix.app.generated.resources.conversations import pixelix.app.generated.resources.direct_messages_encryption_description @@ -86,86 +86,108 @@ fun ConversationsComposable( val lazyListState = rememberLazyListState() - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), floatingActionButton = { - FloatingActionButton(onClick = { - showNewChatDialog.value = true - }) { - Icon(vectorResource(Res.drawable.add_outline), contentDescription = "Add") + Box(modifier = Modifier.fillMaxSize()) { + + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + + Box( + modifier = Modifier.padding(top = TopAppBarDefaults.TopAppBarExpandedHeight + statusBarPadding - 24.dp) + .fillMaxSize() + ) { + PullToRefreshBox( + isRefreshing = viewModel.conversationsState.isRefreshing, + onRefresh = { viewModel.refresh() }, + modifier = Modifier.fillMaxSize() + ) { + LazyColumn( + state = lazyListState, + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(top = 28.dp), + content = { + if (viewModel.conversationsState.conversations.isNotEmpty()) { + items(viewModel.conversationsState.conversations, key = { + it.id + }) { + ConversationElementComposable( + conversation = it, navController = navController + ) + } + + if (viewModel.conversationsState.isLoading && !viewModel.conversationsState.isRefreshing) { + item { + CircularProgressIndicator( + modifier = Modifier.fillMaxWidth().height(80.dp) + .wrapContentSize(Alignment.Center) + ) + } + } + + if (viewModel.conversationsState.endReached && viewModel.conversationsState.conversations.size > 10) { + item { + EndOfListComposable() + } + } + } + }) + + if (!viewModel.conversationsState.isLoading && viewModel.conversationsState.error.isEmpty() && viewModel.conversationsState.conversations.isEmpty()) { + FullscreenEmptyStateComposable( + EmptyState( + icon = vectorResource(Res.drawable.mail_outline), + heading = stringResource( + Res.string.you_don_t_have_any_notifications + ) + ) + ) + } + + if (!viewModel.conversationsState.isRefreshing && viewModel.conversationsState.conversations.isEmpty()) { + LoadingComposable(isLoading = viewModel.conversationsState.isLoading) + } + ErrorComposable(message = viewModel.conversationsState.error) + } } - }, topBar = { - CenterAlignedTopAppBar(title = { - Text(stringResource(Res.string.conversations), fontWeight = FontWeight.Bold) + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + Text( + stringResource(Res.string.conversations), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) }, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), contentDescription = "" + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" ) } }, actions = { - IconButton(onClick = { showBottomSheet = true }) { - Icon( - imageVector = vectorResource(Res.drawable.help_outline), - tint = MaterialTheme.colorScheme.error, - contentDescription = null - ) - } - }) - }) { paddingValues -> - PullToRefreshBox( - isRefreshing = viewModel.conversationsState.isRefreshing, - onRefresh = { viewModel.refresh() }, - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - ) { - LazyColumn(state = lazyListState, modifier = Modifier.fillMaxSize(), content = { - if (viewModel.conversationsState.conversations.isNotEmpty()) { - items(viewModel.conversationsState.conversations, key = { - it.id - }) { - ConversationElementComposable( - conversation = it, navController = navController - ) - } - - if (viewModel.conversationsState.isLoading && !viewModel.conversationsState.isRefreshing) { - item { - CircularProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) - .wrapContentSize(Alignment.Center) - ) - } - } - - if (viewModel.conversationsState.endReached && viewModel.conversationsState.conversations.size > 10) { - item { - EndOfListComposable() - } - } + Row { + IconButton(onClick = { showNewChatDialog.value = true }) { + Icon( + imageVector = vectorResource(Res.drawable.add_outline), + contentDescription = null + ) } - }) - if (!viewModel.conversationsState.isLoading && viewModel.conversationsState.error.isEmpty() && viewModel.conversationsState.conversations.isEmpty()) { - FullscreenEmptyStateComposable( - EmptyState( - icon = vectorResource(Res.drawable.mail_outline), heading = stringResource( - Res.string.you_don_t_have_any_notifications - ) + IconButton(onClick = { showBottomSheet = true }) { + Icon( + imageVector = vectorResource(Res.drawable.help_outline), + tint = MaterialTheme.colorScheme.error, + contentDescription = null ) - ) + } } - if (!viewModel.conversationsState.isRefreshing && viewModel.conversationsState.conversations.isEmpty()) { - LoadingComposable(isLoading = viewModel.conversationsState.isLoading) - } - ErrorComposable(message = viewModel.conversationsState.error) - } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) InfiniteListHandler(lazyListState = lazyListState) { //viewModel.getNotificationsPaginated() @@ -175,13 +197,10 @@ fun ConversationsComposable( ModalBottomSheet( onDismissRequest = { showBottomSheet = false - }, - sheetState = sheetState + }, sheetState = sheetState ) { Box( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 12.dp) + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp) ) { Column { Spacer(modifier = Modifier.height(18.dp)) @@ -228,19 +247,15 @@ private fun CreateNewConversation( ) if (viewModel.newConversationState.suggestions.isNotEmpty()) { Box( - modifier = Modifier - .padding(top = 4.dp) - .clip(shape = RoundedCornerShape(12.dp)) + modifier = Modifier.padding(top = 4.dp).clip(shape = RoundedCornerShape(12.dp)) .background(MaterialTheme.colorScheme.surface) ) { Column( modifier = Modifier.padding(12.dp) ) { viewModel.newConversationState.suggestions.map { - Box(modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - .clickable { + Box( + modifier = Modifier.fillMaxWidth().padding(8.dp).clickable { viewModel.newConversationUsername = TextFieldValue( it.acct, selection = TextRange(it.acct.length) ) diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/edit_profile/EditProfileComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/edit_profile/EditProfileComposable.kt index 846fabb5..edcc0255 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/edit_profile/EditProfileComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/edit_profile/EditProfileComposable.kt @@ -27,7 +27,6 @@ import androidx.compose.material.icons.filled.Done import androidx.compose.material.icons.filled.Refresh import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -38,6 +37,7 @@ import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -48,11 +48,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.decodeToImageBitmap -import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties import androidx.navigation.NavController import coil3.compose.AsyncImage import com.attafitamim.krop.core.crop.CropResult @@ -72,10 +71,8 @@ import io.github.vinceglb.filekit.dialogs.compose.rememberFilePickerLauncher import io.github.vinceglb.filekit.readBytes import kotlinx.coroutines.launch import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res import pixelix.app.generated.resources.bio -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.displayname import pixelix.app.generated.resources.edit_profile import pixelix.app.generated.resources.private_profile @@ -89,234 +86,218 @@ fun EditProfileComposable( viewModel: EditProfileViewModel = injectViewModel(key = "edit-profile-viewmodel-key") { editProfileViewModel } ) { - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - Scaffold( - contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - CenterAlignedTopAppBar( - scrollBehavior = scrollBehavior, - title = { - Text( - text = stringResource(Res.string.edit_profile), - fontWeight = FontWeight.Bold - ) - }, - navigationIcon = { - IconButton(onClick = { - navController.popBackStack() - }) { - Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), - contentDescription = "" - ) - } - }, - actions = { - if (viewModel.firstLoaded) { - if (viewModel.displayname == (viewModel.accountState.account?.displayname - ?: "") && viewModel.note == (viewModel.accountState.account?.note - ?: "") && "https://" + viewModel.website == (viewModel.accountState.account?.website - ?: "") && viewModel.newAvatar == null && viewModel.privateProfile == viewModel.accountState.account?.locked - ) { - if (!viewModel.accountState.isLoading) { - Button( - onClick = {}, - modifier = Modifier.width(120.dp), - shape = RoundedCornerShape(12.dp), - enabled = false, - colors = ButtonDefaults.buttonColors( - disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer, - disabledContentColor = MaterialTheme.colorScheme.onSurface - ) - ) { - Text(text = stringResource(Res.string.save)) - } - } - } else { - if (viewModel.accountState.isLoading) { - Button( - onClick = {}, - modifier = Modifier.width(120.dp), - shape = RoundedCornerShape(12.dp) - ) { - CircularProgressIndicator( - modifier = Modifier.size(20.dp), - color = MaterialTheme.colorScheme.onPrimary - ) - } - } else { - Button( - onClick = { viewModel.save() }, - modifier = Modifier.width(120.dp), - shape = RoundedCornerShape(12.dp) - ) { - Text(text = stringResource(Res.string.save)) - } - } - } - } - }) - }) { paddingValues -> - Box( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize() + contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top) + ) { paddingValues -> + Column( + Modifier.padding(paddingValues) + .padding(top = TopAppBarDefaults.TopAppBarExpandedHeight - 24.dp).fillMaxSize() + .padding(horizontal = 12.dp).verticalScroll(state = rememberScrollState()).imeAwareInsets(90.dp) ) { - Column( - Modifier - .fillMaxSize() - .padding(12.dp) - .verticalScroll(state = rememberScrollState()) - .imeAwareInsets(90.dp) - ) { - if (viewModel.accountState.account != null) { + if (viewModel.accountState.account != null) { - Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - val coroutineScope = rememberCoroutineScope() - val imageCropper = rememberImageCropper() - val cropState = imageCropper.cropState - if (cropState != null) { - ImageCropperFullscreenDialog(cropState) - } + Spacer(Modifier.height(32.dp)) - val filePicker = rememberFilePickerLauncher( - type = FileKitType.Image, mode = FileKitMode.Single - ) { file -> - file ?: return@rememberFilePickerLauncher - coroutineScope.launch { - val cropResult = imageCropper.crop { - ImageBitmapSrc(file.readBytes().decodeToImageBitmap()) - } - if (cropResult is CropResult.Success) { - viewModel.newAvatar = cropResult.bitmap - } + Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + val coroutineScope = rememberCoroutineScope() + val imageCropper = rememberImageCropper() + val cropState = imageCropper.cropState + if (cropState != null) { + ImageCropperFullscreenDialog(cropState) + } + + val filePicker = rememberFilePickerLauncher( + type = FileKitType.Image, mode = FileKitMode.Single + ) { file -> + file ?: return@rememberFilePickerLauncher + coroutineScope.launch { + val cropResult = imageCropper.crop { + ImageBitmapSrc(file.readBytes().decodeToImageBitmap()) + } + if (cropResult is CropResult.Success) { + viewModel.newAvatar = cropResult.bitmap } } + } - val newAvatar = viewModel.newAvatar - if (newAvatar != null) { - Image( - bitmap = newAvatar, - contentDescription = "", - modifier = Modifier - .height(112.dp) - .width(112.dp) - .clip(CircleShape) - .clickable { filePicker.launch() } - ) - } else { - AsyncImage( - model = viewModel.avatarUri.toString(), - contentDescription = "", - modifier = Modifier - .height(112.dp) - .width(112.dp) - .clip(CircleShape) - .clickable { filePicker.launch() } - ) - } + val newAvatar = viewModel.newAvatar + if (newAvatar != null) { + Image( + bitmap = newAvatar, + contentDescription = "", + modifier = Modifier.height(112.dp).width(112.dp).clip(CircleShape) + .clickable { filePicker.launch() }) + } else { + AsyncImage( + model = viewModel.avatarUri.toString(), + contentDescription = "", + modifier = Modifier.height(112.dp).width(112.dp).clip(CircleShape) + .clickable { filePicker.launch() }) } + } - Spacer(modifier = Modifier.height(18.dp)) + Spacer(modifier = Modifier.height(18.dp)) - Row { - Spacer(Modifier.width(6.dp)) - Text( - text = stringResource(Res.string.displayname), - fontWeight = FontWeight.Bold - ) - } + Row { + Spacer(Modifier.width(6.dp)) + Text( + text = stringResource(Res.string.displayname), fontWeight = FontWeight.Bold + ) + } - Spacer(Modifier.height(6.dp)) + Spacer(Modifier.height(6.dp)) - TextField( - value = viewModel.displayname, - singleLine = true, - onValueChange = { viewModel.displayname = it }, - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(16.dp), - colors = TextFieldDefaults.colors( - unfocusedIndicatorColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, - unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer - ) + TextField( + value = viewModel.displayname, + singleLine = true, + onValueChange = { viewModel.displayname = it }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + colors = TextFieldDefaults.colors( + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer ) + ) - Spacer(modifier = Modifier.height(18.dp)) + Spacer(modifier = Modifier.height(18.dp)) - Row { - Spacer(Modifier.width(6.dp)) - Text(text = stringResource(Res.string.bio), fontWeight = FontWeight.Bold) - } + Row { + Spacer(Modifier.width(6.dp)) + Text(text = stringResource(Res.string.bio), fontWeight = FontWeight.Bold) + } - Spacer(Modifier.height(6.dp)) + Spacer(Modifier.height(6.dp)) - TextField( - value = viewModel.note, - onValueChange = { viewModel.note = it }, - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(16.dp), - colors = TextFieldDefaults.colors( - unfocusedIndicatorColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, - unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer - ) + TextField( + value = viewModel.note, + onValueChange = { viewModel.note = it }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + colors = TextFieldDefaults.colors( + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer ) + ) - Spacer(modifier = Modifier.height(18.dp)) + Spacer(modifier = Modifier.height(18.dp)) - Row { - Spacer(Modifier.width(6.dp)) - Text( - text = stringResource(Res.string.website), - fontWeight = FontWeight.Bold - ) - } + Row { + Spacer(Modifier.width(6.dp)) + Text( + text = stringResource(Res.string.website), fontWeight = FontWeight.Bold + ) + } - Spacer(Modifier.height(6.dp)) + Spacer(Modifier.height(6.dp)) - TextField( - value = viewModel.website, - singleLine = true, - prefix = { - Text(text = "https://") - }, - onValueChange = { viewModel.website = it }, - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(16.dp), - colors = TextFieldDefaults.colors( - unfocusedIndicatorColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, - unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer - ) + TextField( + value = viewModel.website, + singleLine = true, + prefix = { + Text(text = "https://") + }, + onValueChange = { viewModel.website = it }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + colors = TextFieldDefaults.colors( + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer ) + ) - Spacer(modifier = Modifier.height(18.dp)) - - Row(verticalAlignment = Alignment.CenterVertically) { - Spacer(Modifier.width(6.dp)) - Text( - text = stringResource(Res.string.private_profile), - fontWeight = FontWeight.Bold - ) - Spacer(modifier = Modifier.weight(1f)) - Switch( - checked = viewModel.privateProfile, - onCheckedChange = { viewModel.privateProfile = it }) - } + Spacer(modifier = Modifier.height(18.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Spacer(Modifier.width(6.dp)) + Text( + text = stringResource(Res.string.private_profile), + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.weight(1f)) + Switch( + checked = viewModel.privateProfile, + onCheckedChange = { viewModel.privateProfile = it }) } + Spacer(Modifier.height(24.dp)) } + } + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + Text( + text = stringResource(Res.string.edit_profile), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + }, navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" + ) + } + }, actions = { + if (viewModel.firstLoaded) { + if (viewModel.displayname == (viewModel.accountState.account?.displayname + ?: "") && viewModel.note == (viewModel.accountState.account?.note + ?: "") && "https://" + viewModel.website == (viewModel.accountState.account?.website + ?: "") && viewModel.newAvatar == null && viewModel.privateProfile == viewModel.accountState.account?.locked + ) { + if (!viewModel.accountState.isLoading) { + Button( + onClick = {}, + modifier = Modifier.width(120.dp), + shape = RoundedCornerShape(12.dp), + enabled = false, + colors = ButtonDefaults.buttonColors( + disabledContainerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + disabledContentColor = MaterialTheme.colorScheme.onSurface + ) + ) { + Text(text = stringResource(Res.string.save)) + } + } + } else { + if (viewModel.accountState.isLoading) { + Button( + onClick = {}, + modifier = Modifier.width(120.dp), + shape = RoundedCornerShape(12.dp) + ) { + CircularProgressIndicator( + modifier = Modifier.size(20.dp), + color = MaterialTheme.colorScheme.onPrimary + ) + } + } else { + Button( + onClick = { viewModel.save() }, + modifier = Modifier.width(120.dp), + shape = RoundedCornerShape(12.dp) + ) { + Text(text = stringResource(Res.string.save)) + } + } + } + } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) + } } @@ -339,34 +320,32 @@ private fun ImageCropperFullscreenDialog( Scaffold( contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar( - title = {}, - navigationIcon = { + TopAppBar( + title = {}, navigationIcon = { androidx.compose.material.IconButton(onClick = { state.done(accept = false) }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null) } - }, - actions = { + }, actions = { IconButton(onClick = { state.reset() }) { Icon(Icons.Default.Refresh, null) } IconButton( - onClick = { state.done(accept = true) }, - enabled = !state.accepted + onClick = { state.done(accept = true) }, enabled = !state.accepted ) { Icon(Icons.Default.Done, null) } - } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) ) - } - ) { paddingValues -> + }) { paddingValues -> Box( modifier = Modifier.fillMaxSize().padding(paddingValues) ) { CropperPreview(state = state, modifier = Modifier.fillMaxSize()) - Box(Modifier - .fillMaxSize() - .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)) + Box( + Modifier.fillMaxSize() + .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)) ) { DefaultControls(state) } diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/ExploreComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/ExploreComposable.kt index 745224c2..5ec49301 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/ExploreComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/ExploreComposable.kt @@ -105,12 +105,15 @@ fun ExploreComposable( Box(Modifier .fillMaxSize() - .background(MaterialTheme.colorScheme.surface) + .background(MaterialTheme.colorScheme.surfaceContainer) .semantics { isTraversalGroup = true }) { SearchBar( modifier = Modifier .align(Alignment.TopCenter) .semantics { traversalIndex = 0f }, + colors = SearchBarDefaults.colors( + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh + ), inputField = { SearchBarDefaults.InputField( query = textFieldState.text.toString(), diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/TrendingComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/TrendingComposable.kt index 2e76b331..3955419a 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/TrendingComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/TrendingComposable.kt @@ -4,14 +4,12 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet @@ -26,6 +24,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.daniebeler.pfpixelix.ui.composables.SheetItem @@ -59,15 +58,42 @@ fun TrendingComposable(navController: NavController, initialPage: Int) { val range by remember { mutableStateOf("daily") } - Column( - Modifier.fillMaxSize() + Box( + Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background) ) { + HorizontalPager( + state = pagerState, + beyondViewportPageCount = 3, + modifier = Modifier.padding(top = 24.dp).background(MaterialTheme.colorScheme.background) + ) { tabIndex -> + when (tabIndex) { + 0 -> Box(modifier = Modifier.fillMaxSize()) { + TrendingPostsComposable(range, navController = navController) + } - PrimaryTabRow(selectedTabIndex = pagerState.currentPage) { - Tab(text = { Text(stringResource(Res.string.posts)) }, + 1 -> Box(modifier = Modifier.fillMaxSize()) { + TrendingAccountsComposable(navController = navController) + } + + 2 -> Box(modifier = Modifier.fillMaxSize()) { + TrendingHashtagsComposable(navController = navController) + } + + } + } + + PrimaryTabRow( + selectedTabIndex = pagerState.currentPage, + divider = {}, + containerColor = MaterialTheme.colorScheme.surfaceContainer, + modifier = Modifier + .clip(RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp)) + ) { + Tab( + text = { Text(stringResource(Res.string.posts)) }, selected = pagerState.currentPage == 0, selectedContentColor = MaterialTheme.colorScheme.primary, - unselectedContentColor = MaterialTheme.colorScheme.onBackground, + unselectedContentColor = MaterialTheme.colorScheme.onSurface, onClick = { scope.launch { pagerState.animateScrollToPage(0) @@ -75,62 +101,38 @@ fun TrendingComposable(navController: NavController, initialPage: Int) { }) - Tab(text = { Text(stringResource(Res.string.accounts)) }, + Tab( + text = { Text(stringResource(Res.string.accounts)) }, selected = pagerState.currentPage == 1, selectedContentColor = MaterialTheme.colorScheme.primary, - unselectedContentColor = MaterialTheme.colorScheme.onBackground, + unselectedContentColor = MaterialTheme.colorScheme.onSurface, onClick = { scope.launch { pagerState.animateScrollToPage(1) } }) - Tab(text = { Text(stringResource(Res.string.hashtags)) }, + Tab( + text = { Text(stringResource(Res.string.hashtags)) }, selected = pagerState.currentPage == 2, selectedContentColor = MaterialTheme.colorScheme.primary, - unselectedContentColor = MaterialTheme.colorScheme.onBackground, + unselectedContentColor = MaterialTheme.colorScheme.onSurface, onClick = { scope.launch { pagerState.animateScrollToPage(2) } }) } - - HorizontalPager( - state = pagerState, - beyondViewportPageCount = 3, - modifier = Modifier - .weight(1f) - .background(MaterialTheme.colorScheme.background) - ) { tabIndex -> - when (tabIndex) { - 0 -> Box(modifier = Modifier.fillMaxSize()) { - TrendingPostsComposable(range, navController = navController) - } - - 1 -> Box(modifier = Modifier.fillMaxSize()) { - TrendingAccountsComposable(navController = navController) - } - - 2 -> Box(modifier = Modifier.fillMaxSize()) { - TrendingHashtagsComposable(navController = navController) - } - - } - } } if (showBottomSheet) { ModalBottomSheet( onDismissRequest = { showBottomSheet = false - }, - sheetState = sheetState + }, sheetState = sheetState ) { Box( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 12.dp) + modifier = Modifier.fillMaxSize().padding(horizontal = 12.dp) ) { Column { Spacer(modifier = Modifier.height(18.dp)) diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_accounts/TrendingAccountElement.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_accounts/TrendingAccountElement.kt index 55035291..db35b04a 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_accounts/TrendingAccountElement.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_accounts/TrendingAccountElement.kt @@ -1,5 +1,6 @@ package com.daniebeler.pfpixelix.ui.composables.explore.trending.trending_accounts +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -9,6 +10,7 @@ import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier @@ -33,9 +35,8 @@ fun TrendingAccountElement( } Column( - Modifier - .padding(12.dp) - .fillMaxWidth() + Modifier.clip(RoundedCornerShape(16.dp)) + .background(MaterialTheme.colorScheme.surfaceContainerLow).padding(8.dp).fillMaxWidth() .clickable { navController.navigate(Destination.Profile(account.id)) }) { @@ -47,8 +48,8 @@ fun TrendingAccountElement( text = account.note, mentions = null, navController = navController, - modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), openUrl = { url -> viewModel.openUrl(url) } - ) + modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), + openUrl = { url -> viewModel.openUrl(url) }) } NonlazyGrid(itemCount = minOf(9, viewModel.postsState.posts.size)) { @@ -63,13 +64,15 @@ fun TrendingAccountElement( @Composable private fun NonlazyGrid( - itemCount: Int, - content: @Composable (Int) -> Unit + itemCount: Int, content: @Composable (Int) -> Unit ) { val columns = 3 - Column(modifier = Modifier.clip(RoundedCornerShape(12.dp)), verticalArrangement = Arrangement.spacedBy(4.dp)) { + Column( + modifier = Modifier.clip(RoundedCornerShape(12.dp)), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { var rows = (itemCount / columns) if (itemCount.mod(columns) > 0) { rows += 1 @@ -78,14 +81,11 @@ private fun NonlazyGrid( for (rowId in 0 until rows) { val firstIndex = rowId * columns - Row (horizontalArrangement = Arrangement.spacedBy(4.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { for (columnId in 0 until columns) { val index = firstIndex + columnId Box( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(1f) - .weight(1f) + modifier = Modifier.fillMaxWidth().aspectRatio(1f).weight(1f) ) { if (index < itemCount) { content(index) diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_accounts/TrendingAccountsComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_accounts/TrendingAccountsComposable.kt index 3a865a98..82cc2669 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_accounts/TrendingAccountsComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_accounts/TrendingAccountsComposable.kt @@ -1,7 +1,9 @@ package com.daniebeler.pfpixelix.ui.composables.explore.trending.trending_accounts import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.ExperimentalMaterial3Api @@ -29,7 +31,8 @@ fun TrendingAccountsComposable( isRefreshing = viewModel.trendingAccountsState.isRefreshing, onRefresh = { viewModel.getTrendingAccountsState(true) }, ) { - LazyColumn(modifier = Modifier.fillMaxSize(), + LazyColumn(modifier = Modifier.fillMaxSize().padding(horizontal = 4.dp), + contentPadding = PaddingValues(top = 32.dp, bottom = 12.dp), verticalArrangement = Arrangement.spacedBy(20.dp), content = { items(viewModel.trendingAccountsState.trendingAccounts, key = { diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_hashtags/TrendingHashtagElement.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_hashtags/TrendingHashtagElement.kt index 8a1e85be..6b417cbd 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_hashtags/TrendingHashtagElement.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_hashtags/TrendingHashtagElement.kt @@ -1,5 +1,6 @@ package com.daniebeler.pfpixelix.ui.composables.explore.trending.trending_hashtags +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -49,6 +50,8 @@ fun TrendingHashtagElement( Column( Modifier + .clip(RoundedCornerShape(16.dp)) + .background(MaterialTheme.colorScheme.surfaceContainerLow) .padding(vertical = 8.dp) .fillMaxWidth() .clickable { @@ -73,6 +76,9 @@ fun TrendingHashtagElement( } } + Box(modifier = Modifier.padding(horizontal = 8.dp).clip( + RoundedCornerShape(12.dp) + )) { LazyHorizontalGrid( rows = GridCells.Fixed(3), horizontalArrangement = Arrangement.spacedBy(4.dp), @@ -80,10 +86,6 @@ fun TrendingHashtagElement( modifier = Modifier.height(428.dp) ) { - item(span = { GridItemSpan(3) }) { - Spacer(Modifier.width(12.dp)) - } - itemsIndexed(viewModel.postsState.posts) { index, post -> val postsCount = viewModel.postsState.posts.size; @@ -119,11 +121,7 @@ fun TrendingHashtagElement( CustomPost(post = post, navController = navController, customModifier = customModifier) } } - - item(span = { GridItemSpan(3) }) { - Spacer(Modifier.width(12.dp)) - } } - + } } } \ No newline at end of file diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_hashtags/TrendingHashtagsComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_hashtags/TrendingHashtagsComposable.kt index a65dcc79..3ffdfede 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_hashtags/TrendingHashtagsComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_hashtags/TrendingHashtagsComposable.kt @@ -1,13 +1,16 @@ package com.daniebeler.pfpixelix.ui.composables.explore.trending.trending_hashtags +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.ui.composables.states.EmptyState @@ -18,7 +21,7 @@ import org.jetbrains.compose.resources.stringResource import pixelix.app.generated.resources.Res import pixelix.app.generated.resources.no_trending_hashtags -@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun TrendingHashtagsComposable( navController: NavController, @@ -28,26 +31,30 @@ fun TrendingHashtagsComposable( isRefreshing = viewModel.trendingHashtagsState.isRefreshing, onRefresh = { viewModel.getTrendingHashtags(true) }, ) { - LazyColumn(modifier = Modifier - .fillMaxSize(), content = { - items(viewModel.trendingHashtagsState.trendingHashtags, key = { - it.hashtag ?: "" - }) { - TrendingHashtagElement(hashtag = it, navController = navController) - } - }) + LazyColumn( + modifier = Modifier.fillMaxSize().padding(horizontal = 4.dp), + contentPadding = PaddingValues(top = 32.dp, bottom = 12.dp), + verticalArrangement = Arrangement.spacedBy(20.dp), + content = { + items(viewModel.trendingHashtagsState.trendingHashtags, key = { + it.hashtag ?: "" + }) { + TrendingHashtagElement(hashtag = it, navController = navController) + } + }) - if (viewModel.trendingHashtagsState.trendingHashtags.isEmpty()) { - if (viewModel.trendingHashtagsState.isLoading && !viewModel.trendingHashtagsState.isRefreshing) { - FullscreenLoadingComposable() - } + if (viewModel.trendingHashtagsState.trendingHashtags.isEmpty()) { + if (viewModel.trendingHashtagsState.isLoading && !viewModel.trendingHashtagsState.isRefreshing) { + FullscreenLoadingComposable() + } - if (viewModel.trendingHashtagsState.error.isNotEmpty()) { - FullscreenErrorComposable(message = viewModel.trendingHashtagsState.error) - } + if (viewModel.trendingHashtagsState.error.isNotEmpty()) { + FullscreenErrorComposable(message = viewModel.trendingHashtagsState.error) + } - if (!viewModel.trendingHashtagsState.isLoading && viewModel.trendingHashtagsState.error.isEmpty()) { - FullscreenEmptyStateComposable(EmptyState(heading = stringResource(Res.string.no_trending_hashtags))) + if (!viewModel.trendingHashtagsState.isLoading && viewModel.trendingHashtagsState.error.isEmpty()) { + FullscreenEmptyStateComposable(EmptyState(heading = stringResource(Res.string.no_trending_hashtags))) + } } } -}} \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_posts/TrendingPostsComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_posts/TrendingPostsComposable.kt index e9cfe0b3..869a939f 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_posts/TrendingPostsComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_posts/TrendingPostsComposable.kt @@ -1,28 +1,15 @@ package com.daniebeler.pfpixelix.ui.composables.explore.trending.trending_posts import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.layout.waterfall -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel -import com.daniebeler.pfpixelix.ui.composables.InfinitePostsGrid import com.daniebeler.pfpixelix.ui.composables.InfinitePostsList import com.daniebeler.pfpixelix.ui.composables.profile.ViewEnum -import com.daniebeler.pfpixelix.ui.composables.states.EmptyState -import org.jetbrains.compose.resources.stringResource -import pixelix.app.generated.resources.Res -import pixelix.app.generated.resources.no_trending_posts @Composable fun TrendingPostsComposable( @@ -51,7 +38,8 @@ fun TrendingPostsComposable( onRefresh = { viewModel.getTrendingPosts(range, true) }, navController = navController, postGetsUpdated = { }, - contentPaddingBottom = 10.dp + contentPaddingTop = 32.dp, + contentPaddingBottom = 20.dp ) } } \ No newline at end of file diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowersComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowersComposable.kt index d2dd4f1b..21e37b59 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowersComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowersComposable.kt @@ -1,19 +1,18 @@ package com.daniebeler.pfpixelix.ui.composables.followers +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Groups import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.input.key.Key.Companion.R import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel @@ -36,7 +35,7 @@ fun FollowersComposable( ) { val lazyListState = rememberLazyListState() - LazyColumn(state = lazyListState, content = { + LazyColumn(state = lazyListState, contentPadding = PaddingValues(top = 24.dp), content = { items(viewModel.followersState.followers, key = { it.id }) { diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowersMainComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowersMainComposable.kt index 2b08cf37..5005fb8a 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowersMainComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowersMainComposable.kt @@ -13,7 +13,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -22,22 +24,23 @@ import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.Scaffold import androidx.compose.material3.Tab import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import kotlinx.coroutines.launch import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.followers import pixelix.app.generated.resources.following @@ -65,14 +68,17 @@ fun FollowersMainComposable( val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), + Scaffold( + contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - CenterAlignedTopAppBar(title = { - Column (horizontalAlignment = Alignment.CenterHorizontally) { + TopAppBar( + title = { + Column { Text( text = viewModel.accountState.account?.username ?: "", - fontWeight = FontWeight.Bold + fontWeight = FontWeight.Bold, + fontSize = 18.sp ) Text( text = viewModel.accountState.account?.url?.substringAfter("https://") @@ -84,30 +90,42 @@ fun FollowersMainComposable( navController.popBackStack() }) { Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), contentDescription = "" + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "" ) } - }) + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) }) { paddingValues -> - Column( - Modifier - .fillMaxSize() - .padding(paddingValues) + Box( + Modifier.fillMaxSize().padding(paddingValues) ) { - PrimaryTabRow(selectedTabIndex = pagerState.currentPage) { - Tab(text = { - if (viewModel.accountState.account != null) { - Text( - viewModel.accountState.account?.followersCount.toString() + " " + stringResource( - Res.string.followers + PrimaryTabRow( + selectedTabIndex = pagerState.currentPage, + divider = {}, + containerColor = MaterialTheme.colorScheme.surfaceContainer, + modifier = Modifier.clip( + RoundedCornerShape( + bottomStart = 24.dp, bottomEnd = 24.dp + ) + ).zIndex(1f) + ) { + Tab( + text = { + if (viewModel.accountState.account != null) { + Text( + viewModel.accountState.account?.followersCount.toString() + " " + stringResource( + Res.string.followers + ) ) - ) - } else { - Text(text = stringResource(Res.string.followers)) - } - }, + } else { + Text(text = stringResource(Res.string.followers)) + } + }, selected = pagerState.currentPage == 0, selectedContentColor = MaterialTheme.colorScheme.primary, unselectedContentColor = MaterialTheme.colorScheme.onBackground, @@ -117,17 +135,18 @@ fun FollowersMainComposable( } }) - Tab(text = { - if (viewModel.accountState.account != null) { - Text( - viewModel.accountState.account?.followingCount.toString() + " " + stringResource( - Res.string.following + Tab( + text = { + if (viewModel.accountState.account != null) { + Text( + viewModel.accountState.account?.followingCount.toString() + " " + stringResource( + Res.string.following + ) ) - ) - } else { - Text(text = stringResource(Res.string.following)) - } - }, + } else { + Text(text = stringResource(Res.string.following)) + } + }, selected = pagerState.currentPage == 1, selectedContentColor = MaterialTheme.colorScheme.primary, unselectedContentColor = MaterialTheme.colorScheme.onBackground, @@ -140,9 +159,8 @@ fun FollowersMainComposable( HorizontalPager( state = pagerState, - modifier = Modifier - .weight(1f) - .background(MaterialTheme.colorScheme.background) + modifier = Modifier.padding(top = 24.dp) + .background(MaterialTheme.colorScheme.background).zIndex(0f) ) { tabIndex -> when (tabIndex) { 0 -> Box(modifier = Modifier.fillMaxSize()) { diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowingComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowingComposable.kt index af02847d..dae0db7b 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowingComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowingComposable.kt @@ -1,12 +1,12 @@ package com.daniebeler.pfpixelix.ui.composables.followers +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Groups import androidx.compose.material3.CircularProgressIndicator @@ -30,7 +30,6 @@ import pixelix.app.generated.resources.explore_trending_profiles import pixelix.app.generated.resources.not_following_anyone import pixelix.app.generated.resources.the_profiles_you_follow_will_appear_here -@OptIn(ExperimentalMaterialApi::class) @Composable fun FollowingComposable( navController: NavController, @@ -38,7 +37,7 @@ fun FollowingComposable( ) { val lazyListState = rememberLazyListState() - LazyColumn(state = lazyListState, content = { + LazyColumn(state = lazyListState, contentPadding = PaddingValues(top = 24.dp), content = { items(viewModel.followingState.following, key = { it.id }) { @@ -48,9 +47,7 @@ fun FollowingComposable( if (viewModel.followingState.following.isNotEmpty() && viewModel.followingState.isLoading && !viewModel.followingState.isRefreshing) { item { CircularProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) + modifier = Modifier.fillMaxWidth().height(80.dp) .wrapContentSize(Alignment.Center) ) } @@ -65,13 +62,13 @@ fun FollowingComposable( if (!viewModel.followingState.isLoading && viewModel.followingState.error.isEmpty() && viewModel.followingState.following.isEmpty()) { - val message = if (viewModel.loggedInAccountId == viewModel.accountId) - stringResource(Res.string.the_profiles_you_follow_will_appear_here) - else - stringResource(Res.string.not_following_anyone) + val message = + if (viewModel.loggedInAccountId == viewModel.accountId) stringResource(Res.string.the_profiles_you_follow_will_appear_here) + else stringResource(Res.string.not_following_anyone) FullscreenEmptyStateComposable( - emptyState = EmptyState(icon = Icons.Outlined.Groups, + emptyState = EmptyState( + icon = Icons.Outlined.Groups, heading = stringResource(Res.string.empty), message = message, buttonText = stringResource(Res.string.explore_trending_profiles), diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/mention/MentionComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/mention/MentionComposable.kt index fa4ea8f7..f8518bf6 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/mention/MentionComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/mention/MentionComposable.kt @@ -17,12 +17,12 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -73,7 +73,7 @@ fun MentionComposable( } Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { + TopAppBar(title = { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( text = stringResource(Res.string.post), fontWeight = FontWeight.Bold diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/newpost/NewPostComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/newpost/NewPostComposable.kt index 34043e74..9874b18c 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/newpost/NewPostComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/newpost/NewPostComposable.kt @@ -15,14 +15,13 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.pager.HorizontalPager @@ -37,7 +36,6 @@ import androidx.compose.material.icons.outlined.Check import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.Card -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -46,12 +44,13 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -61,17 +60,17 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController -import co.touchlab.kermit.Logger import coil3.compose.AsyncImage import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.domain.model.Visibility -import com.daniebeler.pfpixelix.ui.composables.states.ErrorComposable import com.daniebeler.pfpixelix.ui.composables.states.ErrorComposableDialog import com.daniebeler.pfpixelix.ui.composables.states.LoadingComposable import com.daniebeler.pfpixelix.ui.composables.textfield_location.TextFieldLocationsComposable @@ -120,174 +119,202 @@ fun NewPostComposable( } } - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - Text(text = stringResource(Res.string.new_post), fontWeight = FontWeight.Bold) - }, actions = { - Button( - onClick = { showReleaseAlert = true }, - enabled = (viewModel.images.isNotEmpty() && viewModel.images.none { it.isLoading }) - ) { - Text(text = stringResource(Res.string.release)) - } - }) - }) { paddingValues -> - Box { - Column( - Modifier.padding(paddingValues).imeAwareInsets(90.dp).fillMaxSize() - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - ImagesPager(viewModel.images, - { index, altText -> - viewModel.updateAltTextVariable( - index, altText - ) - }, - { index -> viewModel.moveMediaAttachmentUp(index) }, - { index -> viewModel.moveMediaAttachmentDown(index) }, - { index -> viewModel.deleteMedia(index) }, - { kmpUri: KmpUri -> viewModel.addImage(kmpUri) }) + Box(modifier = Modifier.fillMaxSize()) { + + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() - Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { - NewPostTextField( - value = viewModel.caption, - onChange = { viewModel.caption = it }, - label = stringResource(Res.string.caption) - ) - NewPostPref(leadingIcon = Res.drawable.browsers_outline, - title = stringResource(Res.string.sensitive_nsfw_media), - trailingContent = { - Switch(checked = viewModel.sensitive, - onCheckedChange = { viewModel.sensitive = it }) - }) - AnimatedVisibility( - visible = viewModel.sensitive, - enter = slideInVertically() + fadeIn(), - exit = shrinkVertically(animationSpec = spring(stiffness = Spring.StiffnessMedium)) + fadeOut(), + Box( + modifier = Modifier.padding(top = TopAppBarDefaults.TopAppBarExpandedHeight + statusBarPadding - 24.dp) + .fillMaxSize() + ) { + Box { + Column( + Modifier.imeAwareInsets(90.dp).fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Spacer(Modifier.height(24.dp)) + + ImagesPager( + viewModel.images, + { index, altText -> + viewModel.updateAltTextVariable( + index, altText + ) + }, + { index -> viewModel.moveMediaAttachmentUp(index) }, + { index -> viewModel.moveMediaAttachmentDown(index) }, + { index -> viewModel.deleteMedia(index) }, + { kmpUri: KmpUri -> viewModel.addImage(kmpUri) }) + + Column( + Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp) ) { NewPostTextField( - value = viewModel.sensitiveText, - onChange = { viewModel.sensitiveText = it }, - label = stringResource(Res.string.content_warning_or_spoiler_text) + value = viewModel.caption, + onChange = { viewModel.caption = it }, + label = stringResource(Res.string.caption) ) - } - NewPostPref(leadingIcon = Res.drawable.browsers_outline, - title = stringResource(Res.string.audience), - trailingContent = { - Box { - OutlinedButton(onClick = { expanded = !expanded }) { - val buttonText: String = when (viewModel.audience) { - Visibility.PUBLIC -> stringResource(Res.string.audience_public) - Visibility.UNLISTED -> stringResource(Res.string.unlisted) - Visibility.PRIVATE -> stringResource(Res.string.followers_only) - else -> "" + NewPostPref( + leadingIcon = Res.drawable.browsers_outline, + title = stringResource(Res.string.sensitive_nsfw_media), + trailingContent = { + Switch( + checked = viewModel.sensitive, + onCheckedChange = { viewModel.sensitive = it }) + }) + AnimatedVisibility( + visible = viewModel.sensitive, + enter = slideInVertically() + fadeIn(), + exit = shrinkVertically(animationSpec = spring(stiffness = Spring.StiffnessMedium)) + fadeOut(), + ) { + NewPostTextField( + value = viewModel.sensitiveText, + onChange = { viewModel.sensitiveText = it }, + label = stringResource(Res.string.content_warning_or_spoiler_text) + ) + } + NewPostPref( + leadingIcon = Res.drawable.browsers_outline, + title = stringResource(Res.string.audience), + trailingContent = { + Box { + OutlinedButton(onClick = { expanded = !expanded }) { + val buttonText: String = when (viewModel.audience) { + Visibility.PUBLIC -> stringResource(Res.string.audience_public) + Visibility.UNLISTED -> stringResource(Res.string.unlisted) + Visibility.PRIVATE -> stringResource(Res.string.followers_only) + else -> "" + } + Text(text = buttonText) + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }) { + DropdownMenuItem( + text = { Text(stringResource(Res.string.audience_public)) }, + onClick = { viewModel.audience = Visibility.PUBLIC }, + trailingIcon = { + if (viewModel.audience == Visibility.PUBLIC) { + Icon( + imageVector = Icons.Outlined.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } + }) + DropdownMenuItem( + text = { Text(stringResource(Res.string.unlisted)) }, + onClick = { viewModel.audience = Visibility.UNLISTED }, + trailingIcon = { + if (viewModel.audience == Visibility.UNLISTED) { + Icon( + imageVector = Icons.Outlined.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } + }) + DropdownMenuItem( + text = { Text(stringResource(Res.string.followers_only)) }, + onClick = { viewModel.audience = Visibility.PRIVATE }, + trailingIcon = { + if (viewModel.audience == Visibility.PRIVATE) { + Icon( + imageVector = Icons.Outlined.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } + }) } - Text(text = buttonText) - } - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }) { - DropdownMenuItem(text = { Text(stringResource(Res.string.audience_public)) }, - onClick = { viewModel.audience = Visibility.PUBLIC }, - trailingIcon = { - if (viewModel.audience == Visibility.PUBLIC) { - Icon( - imageVector = Icons.Outlined.Check, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary - ) - } - }) - DropdownMenuItem(text = { Text(stringResource(Res.string.unlisted)) }, - onClick = { viewModel.audience = Visibility.UNLISTED }, - trailingIcon = { - if (viewModel.audience == Visibility.UNLISTED) { - Icon( - imageVector = Icons.Outlined.Check, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary - ) - } - }) - DropdownMenuItem(text = { Text(stringResource(Res.string.followers_only)) }, - onClick = { viewModel.audience = Visibility.PRIVATE }, - trailingIcon = { - if (viewModel.audience == Visibility.PRIVATE) { - Icon( - imageVector = Icons.Outlined.Check, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary - ) - } - }) - } - } - }) - TextFieldLocationsComposable( - submit = { viewModel.setLocation(it) }, - submitPlace = {}, - initialValue = null, - labelStringId = Res.string.location, - modifier = Modifier.fillMaxWidth(), - imeAction = ImeAction.Default, - suggestionsBoxColor = MaterialTheme.colorScheme.surfaceContainer, - submitButton = null - ) + } + }) + TextFieldLocationsComposable( + submit = { viewModel.setLocation(it) }, + submitPlace = {}, + initialValue = null, + labelStringId = Res.string.location, + modifier = Modifier.fillMaxWidth(), + imeAction = ImeAction.Default, + suggestionsBoxColor = MaterialTheme.colorScheme.surfaceContainer, + submitButton = null + ) + } } - } - if (viewModel.addImageError.first.isNotBlank()) { - AlertDialog(title = { - Text(text = viewModel.addImageError.first) - }, text = { - Text(text = viewModel.addImageError.second) - }, onDismissRequest = { - viewModel.addImageError = Pair("", "") - }, confirmButton = { - TextButton(onClick = { + if (viewModel.addImageError.first.isNotBlank()) { + AlertDialog(title = { + Text(text = viewModel.addImageError.first) + }, text = { + Text(text = viewModel.addImageError.second) + }, onDismissRequest = { viewModel.addImageError = Pair("", "") - }) { - Text("Ok") - } - }) - } + }, confirmButton = { + TextButton(onClick = { + viewModel.addImageError = Pair("", "") + }) { + Text("Ok") + } + }) + } - if (showReleaseAlert) { - AlertDialog(title = { - Text(text = "Are you sure?") - }, onDismissRequest = { - showReleaseAlert = false - }, dismissButton = { - TextButton(onClick = { + if (showReleaseAlert) { + AlertDialog(title = { + Text(text = "Are you sure?") + }, onDismissRequest = { showReleaseAlert = false - }) { - Text(stringResource(Res.string.cancel)) - } - }, confirmButton = { - TextButton(onClick = { - showReleaseAlert = false - viewModel.post(navController) - }) { - Text(stringResource(Res.string.release)) - } - }) - } + }, dismissButton = { + TextButton(onClick = { + showReleaseAlert = false + }) { + Text(stringResource(Res.string.cancel)) + } + }, confirmButton = { + TextButton(onClick = { + showReleaseAlert = false + viewModel.post(navController) + }) { + Text(stringResource(Res.string.release)) + } + }) + } - LoadingComposable(isLoading = viewModel.createPostState.isLoading) - //LoadingComposable(isLoading = viewModel.mediaUploadState.isLoading) - ErrorComposableDialog( - errorMessage = viewModel.mediaUploadState.error, - onDismiss = { viewModel.mediaUploadState = viewModel.mediaUploadState.copy(error = "") } - ) + LoadingComposable(isLoading = viewModel.createPostState.isLoading) + //LoadingComposable(isLoading = viewModel.mediaUploadState.isLoading) + ErrorComposableDialog( + errorMessage = viewModel.mediaUploadState.error, onDismiss = { + viewModel.mediaUploadState = viewModel.mediaUploadState.copy(error = "") + }) - ErrorComposableDialog( - errorMessage = viewModel.createPostState.error, - onDismiss = { viewModel.createPostState = viewModel.createPostState.copy(error = "") } - ) + ErrorComposableDialog( + errorMessage = viewModel.createPostState.error, onDismiss = { + viewModel.createPostState = viewModel.createPostState.copy(error = "") + }) + } } + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + Text( + text = stringResource(Res.string.new_post), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + }, actions = { + Button( + onClick = { showReleaseAlert = true }, + enabled = (viewModel.images.isNotEmpty() && viewModel.images.none { it.isLoading }) + ) { + Text(text = stringResource(Res.string.release)) + } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) } } @@ -324,8 +351,7 @@ fun ImagesPager( Card(Modifier.fillMaxWidth().aspectRatio(1f).clickable { launcher.launch() }) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Icon( - modifier = Modifier.height(50.dp) - .width(50.dp), + modifier = Modifier.height(50.dp).width(50.dp), imageVector = vectorResource(Res.drawable.add_outline), contentDescription = null, ) diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/notifications/NotificationsComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/notifications/NotificationsComposable.kt index 91557215..0579cd8f 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/notifications/NotificationsComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/notifications/NotificationsComposable.kt @@ -1,5 +1,6 @@ package com.daniebeler.pfpixelix.ui.composables.notifications +import androidx.compose.foundation.background import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -24,7 +25,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Email import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -32,12 +32,15 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.domain.service.platform.PlatformFeatures @@ -70,28 +73,37 @@ fun NotificationsComposable( val scrollState = rememberScrollState() Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - Text(stringResource(Res.string.notifications), fontWeight = FontWeight.Bold) - }, actions = { - if (PlatformFeatures.notificationWidgets) { - IconButton(onClick = { - viewModel.pinWidget() - }) { - Icon( - imageVector = vectorResource(Res.drawable.extension_puzzle_outline), - contentDescription = "add widget" - ) + TopAppBar( + title = { + Text( + stringResource(Res.string.notifications), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + }, actions = { + if (PlatformFeatures.notificationWidgets) { + IconButton(onClick = { + viewModel.pinWidget() + }) { + Icon( + imageVector = vectorResource(Res.drawable.extension_puzzle_outline), + contentDescription = "add widget" + ) + } } - } - }) + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) }) { paddingValues -> Box( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) + modifier = Modifier.fillMaxSize().padding(paddingValues) ) { Column { - Row(modifier = Modifier.horizontalScroll(scrollState)) { + Row( + modifier = Modifier.background(MaterialTheme.colorScheme.surfaceContainer) + .horizontalScroll(scrollState) + ) { Spacer(modifier = Modifier.width(12.dp)) if (viewModel.filter == NotificationsFilterEnum.All) { ActiveFilterButton(text = stringResource(Res.string.all)) @@ -107,8 +119,7 @@ fun NotificationsComposable( ActiveFilterButton(text = stringResource(Res.string.followers)) } else { InactiveFilterButton( - text = stringResource(Res.string.followers), - onClick = { + text = stringResource(Res.string.followers), onClick = { viewModel.changeFilter(NotificationsFilterEnum.Followers) }) } @@ -142,9 +153,14 @@ fun NotificationsComposable( viewModel.changeFilter(NotificationsFilterEnum.Mentions) }) } + + Spacer(modifier = Modifier.width(12.dp)) } - Spacer(modifier = Modifier.height(12.dp)) + Spacer( + modifier = Modifier.fillMaxWidth().height(12.dp) + .background(MaterialTheme.colorScheme.surfaceContainer) + ) PullToRefreshBox( isRefreshing = viewModel.notificationsState.isRefreshing, @@ -181,9 +197,7 @@ fun NotificationsComposable( if (viewModel.notificationsState.isLoading && !viewModel.notificationsState.isRefreshing) { item { CircularProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) + modifier = Modifier.fillMaxWidth().height(80.dp) .wrapContentSize(Alignment.Center) ) } @@ -234,7 +248,7 @@ private fun InactiveFilterButton(text: String, onClick: () -> Unit) { onClick = { onClick() }, shape = RoundedCornerShape(12.dp), colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.surfaceContainer, + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, contentColor = MaterialTheme.colorScheme.onSurface ) ) { diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/PostComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/PostComposable.kt index 0812eb7a..be6ad7e5 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/PostComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/PostComposable.kt @@ -13,16 +13,13 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState @@ -77,7 +74,6 @@ import androidx.compose.ui.zIndex import androidx.navigation.NavController import coil3.compose.AsyncImage import coil3.compose.AsyncImagePainter -import com.daniebeler.pfpixelix.di.LocalAppComponent import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.domain.model.MediaAttachment import com.daniebeler.pfpixelix.domain.model.Post @@ -110,14 +106,12 @@ import pixelix.app.generated.resources.heart import pixelix.app.generated.resources.heart_outline import pixelix.app.generated.resources.liked_by import pixelix.app.generated.resources.media_description -import pixelix.app.generated.resources.no_likes_yet import pixelix.app.generated.resources.ok import pixelix.app.generated.resources.others import pixelix.app.generated.resources.reblogged_by import pixelix.app.generated.resources.sync_outline import pixelix.app.generated.resources.sync_outline_bold import pixelix.app.generated.resources.this_action_cannot_be_undone -import pixelix.app.generated.resources.view_comments @OptIn(ExperimentalMaterial3Api::class) @@ -202,22 +196,30 @@ fun PostComposable( }) if (viewModel.post != null) { - Column(modifier = modifier) { - + Column( + modifier = modifier.clip( + RoundedCornerShape(16.dp) + ).background(MaterialTheme.colorScheme.surfaceContainerLow) + .padding(top = 12.dp, bottom = 12.dp) + ) { post.rebloggedBy?.let { reblogAccount -> Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.padding(start = 16.dp, end = 12.dp).clickable(onClick = { navController.navigate(Destination.Profile(reblogAccount.id)) }) ) { - Icon(Icons.Outlined.Cached, contentDescription = "reblogged by") + Icon( + Icons.Outlined.Cached, + contentDescription = "reblogged by", + modifier = Modifier.size(20.dp) + ) Text( stringResource( Res.string.reblogged_by, reblogAccount.displayname ?: reblogAccount.username - ), fontSize = 12.sp + ), fontSize = 11.sp ) } } @@ -232,18 +234,21 @@ fun PostComposable( model = viewModel.post!!.account.avatar, error = painterResource(Res.drawable.default_avatar), contentDescription = "", - modifier = Modifier.height(36.dp).width(36.dp).clip(CircleShape) + modifier = Modifier.height(40.dp).width(40.dp).clip(CircleShape) ) Column(modifier = Modifier.padding(start = 8.dp)) { -// Text( -// text = viewModel.post!!.account.displayname ?: "", -// fontWeight = FontWeight.Bold, -// lineHeight = 10.sp -// ) Text( text = viewModel.post!!.account.acct, fontSize = 14.sp, - fontWeight = FontWeight.Bold + fontWeight = FontWeight.Bold, + lineHeight = 8.sp + ) + + Text( + text = timeAgoText.value, + fontSize = 12.sp, + lineHeight = 8.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant ) if (viewModel.post!!.place != null) { @@ -273,6 +278,7 @@ fun PostComposable( }) { Icon( imageVector = vectorResource(Res.drawable.ellipsis_vertical), + modifier = Modifier.size(20.dp), contentDescription = "" ) } @@ -283,7 +289,9 @@ fun PostComposable( if (viewModel.post!!.mediaAttachments.isNotEmpty()) { if (viewModel.post!!.sensitive && !viewModel.showPost && viewModel.blurSensitiveContent) { - Box(modifier.padding(start = 12.dp, end = 12.dp).clip(RoundedCornerShape(16.dp))) { + Box( + modifier.padding(start = 8.dp, end = 8.dp).clip(RoundedCornerShape(16.dp)) + ) { val blurHashBitmap = BlurHashDecoder.decode( viewModel.post!!.mediaAttachments[0].blurHash ) @@ -327,8 +335,9 @@ fun PostComposable( } else { if (viewModel.post!!.mediaAttachments.count() > 1) { - val smallestAspectRatio = viewModel.post!!.mediaAttachments - .minByOrNull { it.meta?.original?.aspect ?: 1.0 } + val smallestAspectRatio = viewModel.post!!.mediaAttachments.minByOrNull { + it.meta?.original?.aspect ?: 1.0 + } Box { HorizontalPager( state = pagerState, modifier = Modifier.zIndex(50f).aspectRatio( @@ -337,7 +346,7 @@ fun PostComposable( ) { page -> Box( modifier = Modifier.zIndex(10f) - .padding(start = 12.dp, end = 12.dp) + .padding(start = 8.dp, end = 8.dp) ) { PostImage( mediaAttachment = viewModel.post!!.mediaAttachments[page], @@ -352,13 +361,13 @@ fun PostComposable( Box( modifier = Modifier.align(Alignment.TopEnd).zIndex(51f) - .padding(top = 16.dp, end = 28.dp).clip(CircleShape) + .padding(top = 20.dp, end = 20.dp).clip(CircleShape) .background(MaterialTheme.colorScheme.background.copy(alpha = 0.5f)) - .padding(vertical = 3.dp, horizontal = 12.dp) + .padding(vertical = 2.dp, horizontal = 8.dp) ) { Text( text = (pagerState.currentPage + 1).toString() + "/" + viewModel.post!!.mediaAttachments.count(), - fontSize = 14.sp + fontSize = 13.sp ) } } @@ -366,7 +375,7 @@ fun PostComposable( Spacer(modifier = Modifier.height(5.dp)) Row( Modifier.wrapContentHeight().fillMaxWidth() - .align(Alignment.CenterHorizontally).padding(bottom = 8.dp), + .align(Alignment.CenterHorizontally), horizontalArrangement = Arrangement.Center ) { repeat(pagerState.pageCount) { iteration -> @@ -417,63 +426,78 @@ fun PostComposable( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.clip( + RoundedCornerShape(percent = 50) + ).background(MaterialTheme.colorScheme.surfaceContainerHigh) + .padding(horizontal = 10.dp, vertical = 4.dp) + ) { + if (viewModel.post!!.favourited) { + Icon( + imageVector = vectorResource(Res.drawable.heart), + modifier = Modifier.size(22.dp).clickable { + viewModel.unlikePost(postId, updatePost) + }.scale(heartScale), + contentDescription = "unlike post", + tint = Color(0xFFDD2E44) + ) + } else { + Icon( + imageVector = vectorResource(Res.drawable.heart_outline), + modifier = Modifier.size(22.dp).clickable { + animateHeart = true + viewModel.likePost(postId, updatePost) + }, + contentDescription = "like post" + ) - Row(verticalAlignment = Alignment.CenterVertically) { - if (viewModel.post!!.favourited) { - Icon( - imageVector = vectorResource(Res.drawable.heart), - modifier = Modifier.size(24.dp).clickable { - viewModel.unlikePost(postId, updatePost) - }.scale(heartScale), - contentDescription = "unlike post", - tint = Color(0xFFDD2E44) - ) - } else { - Icon( - imageVector = vectorResource(Res.drawable.heart_outline), - modifier = Modifier.size(24.dp).clickable { - animateHeart = true - viewModel.likePost(postId, updatePost) - }, - contentDescription = "like post" - ) - - } - - Spacer(Modifier.width(4.dp)) + } - Text( - text = viewModel.post!!.favouritesCount.toString(), - fontSize = 16.sp, - fontWeight = FontWeight.Bold - ) + Spacer(Modifier.width(4.dp)) - Spacer(Modifier.width(32.dp)) + Text( + text = viewModel.post!!.favouritesCount.toString(), + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } - Icon( - imageVector = vectorResource(Res.drawable.chatbubble_outline), - modifier = Modifier.size(24.dp).clickable { - viewModel.loadReplies( - postId - ) - showBottomSheet = 1 - }, - contentDescription = "comments of post" - ) - Spacer(Modifier.width(4.dp)) + Spacer(Modifier.width(16.dp)) - Text( - text = viewModel.post!!.replyCount.toString(), - fontSize = 16.sp, - fontWeight = FontWeight.Bold - ) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.clip( + RoundedCornerShape(percent = 50) + ).background(MaterialTheme.colorScheme.surfaceContainerHigh) + .padding(horizontal = 10.dp, vertical = 4.dp) + ) { + Icon( + imageVector = vectorResource(Res.drawable.chatbubble_outline), + modifier = Modifier.size(22.dp).clickable { + viewModel.loadReplies( + postId + ) + showBottomSheet = 1 + }, + contentDescription = "comments of post" + ) + Spacer(Modifier.width(4.dp)) + Text( + text = viewModel.post!!.replyCount.toString(), + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } } Row { - if (viewModel.post!!.reblogged) { IconButton(onClick = { viewModel.unreblogPost(postId, updatePost) @@ -549,10 +573,6 @@ fun PostComposable( showBottomSheet = 3 }) } - } else { - Text( - text = stringResource(Res.string.no_likes_yet), fontSize = 14.sp - ) } } @@ -569,29 +589,6 @@ fun PostComposable( ) } } - - if (viewModel.post!!.replyCount > 0 && showReplies) { - - Spacer(modifier = Modifier.height(6.dp)) - - Text( - text = stringResource( - Res.string.view_comments, viewModel.post!!.replyCount - ), - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.clickable { - viewModel.loadReplies( - postId - ) - showBottomSheet = 1 - }) - } - - Text( - text = timeAgoText.value, - fontSize = 12.sp, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) } } @@ -604,8 +601,7 @@ fun PostComposable( ModalBottomSheet( onDismissRequest = { showBottomSheet = 0 - }, - sheetState = sheetState + }, sheetState = sheetState ) { if (showBottomSheet == 1) { CommentsBottomSheet(post, navController, viewModel) @@ -618,8 +614,7 @@ fun PostComposable( post, pagerState.currentPage, navController, - { showBottomSheet = 0 } - ) + { showBottomSheet = 0 }) } else { ShareBottomSheet( post.url, @@ -628,8 +623,7 @@ fun PostComposable( post, pagerState.currentPage, navController, - { showBottomSheet = 0 } - ) + { showBottomSheet = 0 }) } } else if (showBottomSheet == 3) { LikesBottomSheet(viewModel, navController) @@ -844,8 +838,7 @@ fun MediaDialog( Box( modifier = Modifier.fillMaxSize().background(Color.Black.copy(alpha = 0.8f)).clickable { closeDialog() - }, - contentAlignment = Alignment.Center + }, contentAlignment = Alignment.Center ) { Box(modifier = Modifier.zIndex(2f).zoomable(zoomState).clickable { }) { if (mediaAttachment.type != "video") { diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/CollectionsComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/CollectionsComposable.kt index 615c5109..0276d1c1 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/CollectionsComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/CollectionsComposable.kt @@ -116,7 +116,7 @@ fun CollectionsComposable( .height(84.dp) .width(84.dp) .clip(CircleShape) - .background(MaterialTheme.colorScheme.surfaceContainer), + .background(MaterialTheme.colorScheme.surfaceContainerHigh), contentAlignment = Alignment.Center ) { Icon( diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/PostsWrapperComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/PostsWrapperComposable.kt index 9cb3b296..123f93d4 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/PostsWrapperComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/PostsWrapperComposable.kt @@ -96,7 +96,7 @@ private fun LazyListScope.PostsGridInScope( item { Row( horizontalArrangement = Arrangement.spacedBy(4.dp), - modifier = Modifier.padding(horizontal = 12.dp) + modifier = Modifier.padding(horizontal = 4.dp) ) { Box(modifier = Modifier.fillMaxWidth(1.99f / 3f)) { CustomPost( @@ -133,7 +133,7 @@ private fun LazyListScope.PostsGridInScope( val rows = posts.takeLast(posts.size - 3).chunked(3) itemsIndexed(rows) { rowIndex, rowItems -> Row( - modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp), + modifier = Modifier.fillMaxWidth().padding(horizontal = 2.dp), horizontalArrangement = Arrangement.SpaceBetween ) { // Fill the row with 3 items (or fewer for the last row) @@ -268,7 +268,7 @@ private fun LazyListScope.PostsListInScope( updatePost: (post: Post) -> Unit, navController: NavController ) { - val spacedBy: Dp = 28.dp + val spacedBy: Dp = 24.dp if (posts.isNotEmpty()) { @@ -278,7 +278,7 @@ private fun LazyListScope.PostsListInScope( val zIndex = remember { mutableFloatStateOf(1f) } - Box(modifier = Modifier.zIndex(zIndex.floatValue)) { + Box(modifier = Modifier.zIndex(zIndex.floatValue).padding(horizontal = 8.dp)) { PostComposable(post = item, postGetsDeleted = postGetsDeleted, navController = navController, diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/ProfileTopSection.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/ProfileTopSection.kt index 38f9ce20..4144a01a 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/ProfileTopSection.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/ProfileTopSection.kt @@ -60,16 +60,13 @@ fun ProfileTopSection( openUrl: (url: String) -> Unit ) { if (account != null) { - Column(Modifier.padding(12.dp)) { + Column(Modifier.padding(12.dp).fillMaxWidth()) { Row(verticalAlignment = Alignment.CenterVertically) { AsyncImage( model = account.avatar, error = painterResource(Res.drawable.default_avatar), contentDescription = "", - modifier = Modifier - .height(76.dp) - .width(76.dp) - .clip(CircleShape) + modifier = Modifier.height(76.dp).width(76.dp).clip(CircleShape) ) Row( @@ -82,10 +79,14 @@ fun ProfileTopSection( fontWeight = FontWeight.Bold, fontSize = 18.sp ) - Text(text = pluralStringResource(Res.plurals.posts, account.postsCount), fontSize = 12.sp) + Text( + text = pluralStringResource(Res.plurals.posts, account.postsCount), + fontSize = 12.sp + ) } - Column(horizontalAlignment = Alignment.CenterHorizontally, + Column( + horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.clickable { navController.navigate(Destination.Followers(account.id, true)) }) { @@ -94,10 +95,15 @@ fun ProfileTopSection( fontWeight = FontWeight.Bold, fontSize = 18.sp ) - Text(text = pluralStringResource(Res.plurals.follower, account.followersCount), fontSize = 12.sp) + Text( + text = pluralStringResource( + Res.plurals.follower, account.followersCount + ), fontSize = 12.sp + ) } - Column(horizontalAlignment = Alignment.CenterHorizontally, + Column( + horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.clickable { navController.navigate(Destination.Followers(account.id, false)) }) { @@ -106,7 +112,11 @@ fun ProfileTopSection( fontWeight = FontWeight.Bold, fontSize = 18.sp ) - Text(text = pluralStringResource(Res.plurals.following, account.followingCount), fontSize = 12.sp) + Text( + text = pluralStringResource( + Res.plurals.following, account.followingCount + ), fontSize = 12.sp + ) } } } @@ -164,20 +174,22 @@ fun ProfileTopSection( Spacer(modifier = Modifier.height(12.dp)) if (account.note.isNotBlank()) { - HashtagsMentionsTextView(text = account.note, + HashtagsMentionsTextView( + text = account.note, + textSize = 14.sp, mentions = null, navController = navController, openUrl = { url -> openUrl(url) }) } - account.website?.let { + if (account.website.isNotBlank()) { Row(Modifier.padding(top = 12.dp), verticalAlignment = Alignment.CenterVertically) { Text( - text = account.website.toString().substringAfter("https://"), + text = account.website.substringAfter("https://"), color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.Bold, - modifier = Modifier.clickable(onClick = { openUrl(account.website.toString()) }) + modifier = Modifier.clickable(onClick = { openUrl(account.website) }) ) } } @@ -193,11 +205,8 @@ fun ProfileTopSection( } Text( text = stringResource( - Res.string.joined_date, - formatter.format(date) - ), - color = MaterialTheme.colorScheme.onSurfaceVariant, - fontSize = 10.sp + Res.string.joined_date, formatter.format(date) + ), color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 10.sp ) } } @@ -207,11 +216,9 @@ fun ProfileTopSection( @Composable private fun ProfileBadge(text: String, color: Color = MaterialTheme.colorScheme.onSurfaceVariant) { Box( - Modifier - .border( + Modifier.border( BorderStroke(1.dp, color), shape = RoundedCornerShape(8.dp) - ) - .padding(horizontal = 6.dp) + ).padding(horizontal = 6.dp) ) { Text(text = text, fontSize = 9.sp, color = color) } diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/SwitchViewComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/SwitchViewComposable.kt index 7c8cfc44..4c2452cb 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/SwitchViewComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/SwitchViewComposable.kt @@ -1,5 +1,6 @@ package com.daniebeler.pfpixelix.ui.composables.profile +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -10,6 +11,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/other_profile/OtherProfileComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/other_profile/OtherProfileComposable.kt index 5f495f86..8d219980 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/other_profile/OtherProfileComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/other_profile/OtherProfileComposable.kt @@ -1,32 +1,32 @@ package com.daniebeler.pfpixelix.ui.composables.profile.other_profile +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.Photo import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider @@ -34,9 +34,10 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -46,7 +47,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color @@ -70,7 +70,6 @@ import com.daniebeler.pfpixelix.ui.composables.states.EmptyState import com.daniebeler.pfpixelix.ui.navigation.Destination import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res import pixelix.app.generated.resources.block import pixelix.app.generated.resources.block_account @@ -87,7 +86,6 @@ import pixelix.app.generated.resources.block_consequence_9 import pixelix.app.generated.resources.block_this_profile import pixelix.app.generated.resources.browsers_outline import pixelix.app.generated.resources.cancel -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.default_avatar import pixelix.app.generated.resources.follow import pixelix.app.generated.resources.message @@ -111,8 +109,8 @@ import pixelix.app.generated.resources.unmute_account import pixelix.app.generated.resources.unmute_caps import pixelix.app.generated.resources.unmute_this_profile -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class, - ExperimentalComposeUiApi::class +@OptIn( + ExperimentalMaterial3Api::class ) @Composable fun OtherProfileComposable( @@ -139,197 +137,206 @@ fun OtherProfileComposable( } } + Box(modifier = Modifier.fillMaxSize()) { - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - Row { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text( - text = viewModel.accountState.account?.username ?: "", - fontWeight = FontWeight.Bold - ) - Text( - text = viewModel.domain, fontSize = 12.sp, lineHeight = 6.sp - ) - } - - } - }, navigationIcon = { - IconButton(onClick = { - navController.popBackStack() - }) { - Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), contentDescription = "" - ) - } - }, actions = { - - if (viewModel.domain.isNotEmpty()) { - DomainSoftwareComposable( - domain = viewModel.domain - ) - } - - IconButton(onClick = { - showBottomSheet = true - }) { - Icon( - imageVector = Icons.Outlined.MoreVert, contentDescription = "" - ) - } - }) + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() - }) { paddingValues -> - PullToRefreshBox ( - isRefreshing = viewModel.accountState.refreshing || viewModel.postsState.refreshing, - onRefresh = { viewModel.loadData(userId, true, navController) }, - modifier = Modifier + Box( + modifier = Modifier.padding(top = TopAppBarDefaults.TopAppBarExpandedHeight + statusBarPadding - 24.dp) .fillMaxSize() - .padding(paddingValues) ) { - - LazyColumn( - verticalArrangement = Arrangement.spacedBy(4.dp), - state = lazyGridState + PullToRefreshBox( + isRefreshing = viewModel.accountState.refreshing || viewModel.postsState.refreshing, + onRefresh = { viewModel.loadData(userId, true, navController) }, + modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background) ) { - item { - Column { - if (viewModel.accountState.account != null) { - ProfileTopSection(account = viewModel.accountState.account, - relationship = viewModel.relationshipState.accountRelationship, - navController, - openUrl = { url -> - viewModel.openUrl(url) - }) - } - - MutualFollowersComposable( - mutualFollowersState = viewModel.mutualFollowersState, - navController = navController - ) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 12.dp) + LazyColumn( + verticalArrangement = Arrangement.spacedBy(4.dp), state = lazyGridState + ) { + item { + Column( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ).background(MaterialTheme.colorScheme.surfaceContainer) + .padding(top = 24.dp, bottom = 12.dp) ) { - var containerColor by remember { - mutableStateOf(Color(0xFFFFFFFF)) + if (viewModel.accountState.account != null) { + ProfileTopSection( + account = viewModel.accountState.account, + relationship = viewModel.relationshipState.accountRelationship, + navController, + openUrl = { url -> + viewModel.openUrl(url) + }) } - var contentColor by remember { - mutableStateOf(Color(0xFFFFFFFF)) - } + MutualFollowersComposable( + mutualFollowersState = viewModel.mutualFollowersState, + navController = navController + ) - if (viewModel.relationshipState.accountRelationship?.following == true) { - containerColor = MaterialTheme.colorScheme.secondaryContainer - contentColor = MaterialTheme.colorScheme.onSecondaryContainer - } else { - containerColor = MaterialTheme.colorScheme.primary - contentColor = MaterialTheme.colorScheme.onPrimary - } + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp) + ) { + var containerColor by remember { + mutableStateOf(Color(0xFFFFFFFF)) + } - Button( - onClick = { - if (!viewModel.relationshipState.isLoading && viewModel.relationshipState.accountRelationship != null) { - if (viewModel.relationshipState.accountRelationship?.following == true) { - viewModel.unfollowAccount(viewModel.userId) - } else { - viewModel.followAccount(viewModel.userId) + var contentColor by remember { + mutableStateOf(Color(0xFFFFFFFF)) + } + + if (viewModel.relationshipState.accountRelationship?.following == true) { + containerColor = MaterialTheme.colorScheme.secondaryContainer + contentColor = MaterialTheme.colorScheme.onSecondaryContainer + } else { + containerColor = MaterialTheme.colorScheme.primary + contentColor = MaterialTheme.colorScheme.onPrimary + } + + Button( + onClick = { + if (!viewModel.relationshipState.isLoading && viewModel.relationshipState.accountRelationship != null) { + if (viewModel.relationshipState.accountRelationship?.following == true) { + viewModel.unfollowAccount(viewModel.userId) + } else { + viewModel.followAccount(viewModel.userId) + } } - } - }, - modifier = Modifier.weight(1f), - shape = RoundedCornerShape(12.dp), - contentPadding = PaddingValues(12.dp), - colors = ButtonDefaults.buttonColors( - containerColor = containerColor, contentColor = contentColor - ) - ) { - if (viewModel.relationshipState.isLoading) { - CircularProgressIndicator( - modifier = Modifier.size(20.dp), color = contentColor + }, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(12.dp), + contentPadding = PaddingValues(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = containerColor, contentColor = contentColor ) - } else { - if (viewModel.relationshipState.accountRelationship?.following == true) { - Text(text = stringResource(Res.string.unfollow)) + ) { + if (viewModel.relationshipState.isLoading) { + CircularProgressIndicator( + modifier = Modifier.size(20.dp), color = contentColor + ) } else { - Text(text = stringResource(Res.string.follow)) + if (viewModel.relationshipState.accountRelationship?.following == true) { + Text(text = stringResource(Res.string.unfollow)) + } else { + Text(text = stringResource(Res.string.follow)) + } } } - } - Spacer(modifier = Modifier.width(12.dp)) + Spacer(modifier = Modifier.width(12.dp)) - Button( - onClick = { - viewModel.accountState.account?.let { account -> - navController.navigate(Destination.Chat(account.id)) - } - }, - modifier = Modifier.weight(1f), - shape = RoundedCornerShape(12.dp), - contentPadding = PaddingValues(12.dp), - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.surfaceContainer, - contentColor = MaterialTheme.colorScheme.onSecondaryContainer - ) - ) { - Text(text = stringResource(Res.string.message)) + Button( + onClick = { + viewModel.accountState.account?.let { account -> + navController.navigate(Destination.Chat(account.id)) + } + }, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(12.dp), + contentPadding = PaddingValues(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + contentColor = MaterialTheme.colorScheme.onSurface + ) + ) { + Text(text = stringResource(Res.string.message)) + } } - } - viewModel.accountState.account?.let { account -> - CollectionsComposable(collectionsState = viewModel.collectionsState, - getMoreCollections = {viewModel.getCollections(account.id, true)}, - navController = navController, - instanceDomain = viewModel.domain, - openUrl = { url -> viewModel.openUrl(url) }) + viewModel.accountState.account?.let { account -> + CollectionsComposable( + collectionsState = viewModel.collectionsState, + getMoreCollections = { + viewModel.getCollections( + account.id, true + ) + }, + navController = navController, + instanceDomain = viewModel.domain, + openUrl = { url -> viewModel.openUrl(url) }) + } } + } - HorizontalDivider(Modifier.padding(bottom = 12.dp, top = 12.dp)) - - SwitchViewComposable(postsCount = viewModel.accountState.account?.postsCount - ?: 0, + item { + SwitchViewComposable( + postsCount = viewModel.accountState.account?.postsCount ?: 0, viewType = viewModel.view, onViewChange = { viewModel.changeView(it) }) } - } - PostsWrapperComposable( - posts = viewModel.postsState.posts, - isLoading = viewModel.postsState.isLoading, - isRefreshing = viewModel.accountState.refreshing || viewModel.postsState.refreshing, - error = viewModel.postsState.error, - endReached = viewModel.postsState.endReached, - emptyMessage = EmptyState( - icon = Icons.Outlined.Photo, heading = "No Posts" - ), - view = viewModel.view, - isFirstImageLarge = true, - postGetsDeleted = { viewModel.postGetsDeleted(it) }, - updatePost = { viewModel.updatePost(it) }, - navController = navController - ) - - /*PostsWrapperComposable( - accountState = viewModel.accountState, - postsState = viewModel.postsState, - navController = navController, - emptyState = EmptyState( - icon = Icons.Outlined.Photo, heading = "No Posts" - ), - view = viewModel.view, - postGetsDeleted = { viewModel.postGetsDeleted(it) }, - isFirstImageLarge = true - )*/ + PostsWrapperComposable( + posts = viewModel.postsState.posts, + isLoading = viewModel.postsState.isLoading, + isRefreshing = viewModel.accountState.refreshing || viewModel.postsState.refreshing, + error = viewModel.postsState.error, + endReached = viewModel.postsState.endReached, + emptyMessage = EmptyState( + icon = Icons.Outlined.Photo, heading = "No Posts" + ), + view = viewModel.view, + isFirstImageLarge = true, + postGetsDeleted = { viewModel.postGetsDeleted(it) }, + updatePost = { viewModel.updatePost(it) }, + navController = navController + ) + } } + } + + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + Row { + Column { + Text( + text = viewModel.accountState.account?.username ?: "", + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + Text( + text = viewModel.domain, fontSize = 12.sp, lineHeight = 6.sp + ) + } + + } + }, navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" + ) + } + }, actions = { + + if (viewModel.domain.isNotEmpty()) { + DomainSoftwareComposable( + domain = viewModel.domain + ) + } + + IconButton(onClick = { + showBottomSheet = true + }) { + Icon( + imageVector = Icons.Outlined.MoreVert, contentDescription = "" + ) + } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) } - ToTopButton(listState = lazyGridState, refresh = {viewModel.loadData(userId, true, navController)}) + ToTopButton( + listState = lazyGridState, refresh = { viewModel.loadData(userId, true, navController) }) InfiniteListHandler(lazyListState = lazyGridState) { viewModel.getPostsPaginated(viewModel.userId) @@ -346,47 +353,49 @@ fun OtherProfileComposable( ) { if (viewModel.relationshipState.accountRelationship != null) { if (viewModel.relationshipState.accountRelationship!!.muting) { - ButtonRowElement(icon = Res.drawable.remove_circle_outline, - text = stringResource( + ButtonRowElement( + icon = Res.drawable.remove_circle_outline, text = stringResource( Res.string.unmute_this_profile - ), - onClick = { + ), onClick = { showUnMuteAlert = true }) } else { - ButtonRowElement(icon = Res.drawable.remove_circle_outline, - text = stringResource( + ButtonRowElement( + icon = Res.drawable.remove_circle_outline, text = stringResource( Res.string.mute_this_profile - ), - onClick = { + ), onClick = { showMuteAlert = true }) } if (viewModel.relationshipState.accountRelationship!!.blocking) { - ButtonRowElement(icon = Res.drawable.remove_circle_outline, text = stringResource( - Res.string.unblock_this_profile - ), onClick = { - showUnBlockAlert = true - }) + ButtonRowElement( + icon = Res.drawable.remove_circle_outline, text = stringResource( + Res.string.unblock_this_profile + ), onClick = { + showUnBlockAlert = true + }) } else { - ButtonRowElement(icon = Res.drawable.remove_circle_outline, text = stringResource( - Res.string.block_this_profile - ), onClick = { - showBlockAlert = true - }) + ButtonRowElement( + icon = Res.drawable.remove_circle_outline, text = stringResource( + Res.string.block_this_profile + ), onClick = { + showBlockAlert = true + }) } } HorizontalDivider(Modifier.padding(12.dp)) - ButtonRowElement(icon = Res.drawable.browsers_outline, text = stringResource( - Res.string.open_in_browser - ), onClick = { - viewModel.openUrl(viewModel.accountState.account!!.url) - }) + ButtonRowElement( + icon = Res.drawable.browsers_outline, text = stringResource( + Res.string.open_in_browser + ), onClick = { + viewModel.openUrl(viewModel.accountState.account!!.url) + }) - ButtonRowElement(icon = Res.drawable.share_social_outline, + ButtonRowElement( + icon = Res.drawable.share_social_outline, text = stringResource(Res.string.share_this_profile), onClick = { viewModel.shareAccountUrl() @@ -396,31 +405,35 @@ fun OtherProfileComposable( } if (showUnMuteAlert) { - UnMuteAccountAlert(onDismissRequest = { showUnMuteAlert = false }, onConfirmation = { - showUnMuteAlert = false - viewModel.unMuteAccount(viewModel.userId) - }, account = viewModel.accountState.account!! + UnMuteAccountAlert( + onDismissRequest = { showUnMuteAlert = false }, onConfirmation = { + showUnMuteAlert = false + viewModel.unMuteAccount(viewModel.userId) + }, account = viewModel.accountState.account!! ) } if (showMuteAlert) { - MuteAccountAlert(onDismissRequest = { showMuteAlert = false }, onConfirmation = { - showMuteAlert = false - viewModel.muteAccount(viewModel.userId) - }, account = viewModel.accountState.account!! + MuteAccountAlert( + onDismissRequest = { showMuteAlert = false }, onConfirmation = { + showMuteAlert = false + viewModel.muteAccount(viewModel.userId) + }, account = viewModel.accountState.account!! ) } if (showBlockAlert) { - BlockAccountAlert(onDismissRequest = { showBlockAlert = false }, onConfirmation = { - showBlockAlert = false - viewModel.blockAccount(viewModel.userId) - }, account = viewModel.accountState.account!! + BlockAccountAlert( + onDismissRequest = { showBlockAlert = false }, onConfirmation = { + showBlockAlert = false + viewModel.blockAccount(viewModel.userId) + }, account = viewModel.accountState.account!! ) } if (showUnBlockAlert) { - UnBlockAccountAlert(onDismissRequest = { showUnBlockAlert = false }, onConfirmation = { - showUnBlockAlert = false - viewModel.unblockAccount(viewModel.userId) - }, account = viewModel.accountState.account!! + UnBlockAccountAlert( + onDismissRequest = { showUnBlockAlert = false }, onConfirmation = { + showUnBlockAlert = false + viewModel.unblockAccount(viewModel.userId) + }, account = viewModel.accountState.account!! ) } } @@ -574,10 +587,7 @@ fun AlertTopSection(account: Account) { model = account.avatar, error = painterResource(Res.drawable.default_avatar), contentDescription = "", - modifier = Modifier - .height(46.dp) - .width(46.dp) - .clip(CircleShape) + modifier = Modifier.height(46.dp).width(46.dp).clip(CircleShape) ) Spacer(modifier = Modifier.width(10.dp)) Column { diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/OwnProfileComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/OwnProfileComposable.kt index 3fa0b30b..e5f848f9 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/OwnProfileComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/OwnProfileComposable.kt @@ -1,17 +1,18 @@ package com.daniebeler.pfpixelix.ui.composables.profile.own_profile +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape @@ -20,15 +21,14 @@ import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.Photo import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -37,13 +37,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController -import com.daniebeler.pfpixelix.di.LocalAppComponent import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.domain.service.platform.PlatformFeatures import com.daniebeler.pfpixelix.ui.composables.InfiniteListHandler @@ -71,138 +70,137 @@ fun OwnProfileComposable( val lazyGridState = rememberLazyListState() - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - Row(Modifier.clickable { showBottomSheet = 2 }) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text( - text = viewModel.accountState.account?.username ?: "", - fontWeight = FontWeight.Bold - ) - Text( - text = viewModel.ownDomain, fontSize = 12.sp, lineHeight = 6.sp - ) - } - } - }, actions = { - if (viewModel.ownDomain.isNotEmpty()) { - DomainSoftwareComposable( - domain = viewModel.ownDomain - ) - } - - IconButton(onClick = { - showBottomSheet = 1 - }) { - Icon( - imageVector = Icons.Outlined.MoreVert, contentDescription = "preferences" - ) - } - }) + Box(modifier = Modifier.fillMaxSize()) { - } + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() - ) { paddingValues -> - PullToRefreshBox( - isRefreshing = viewModel.accountState.refreshing || viewModel.postsState.refreshing, - onRefresh = { viewModel.loadData(true) }, - modifier = Modifier + Box( + modifier = Modifier.padding(top = TopAppBarDefaults.TopAppBarExpandedHeight + statusBarPadding - 24.dp) .fillMaxSize() - .padding(paddingValues) ) { - LazyColumn( - verticalArrangement = Arrangement.spacedBy(4.dp), - state = lazyGridState + PullToRefreshBox( + isRefreshing = viewModel.accountState.refreshing || viewModel.postsState.refreshing, + onRefresh = { viewModel.loadData(true) }, + modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background) ) { - item { - Column { - if (viewModel.accountState.account != null) { - ProfileTopSection(account = viewModel.accountState.account, - relationship = null, - navController, - openUrl = { url -> viewModel.openUrl(url) } - ) - - Row( - Modifier - .fillMaxWidth() - .padding(horizontal = 12.dp) - ) { - Button( - onClick = { - navController.navigate(Destination.EditProfile) - }, - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(12.dp), - contentPadding = PaddingValues(12.dp), - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.surfaceContainer, - contentColor = MaterialTheme.colorScheme.onSurface - ) + LazyColumn( + verticalArrangement = Arrangement.spacedBy(4.dp), + state = lazyGridState, + ) { + item { + Column( + modifier = Modifier.fillMaxWidth().clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ).background(MaterialTheme.colorScheme.surfaceContainer) + .padding(top = 24.dp, bottom = 12.dp) + ) { + if (viewModel.accountState.account != null) { + ProfileTopSection( + account = viewModel.accountState.account, + relationship = null, + navController, + openUrl = { url -> viewModel.openUrl(url) }) + + Row( + Modifier.fillMaxWidth().padding(horizontal = 12.dp) ) { - Text(text = stringResource(Res.string.edit_profile)) + Button( + onClick = { + navController.navigate(Destination.EditProfile) + }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + contentPadding = PaddingValues(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + contentColor = MaterialTheme.colorScheme.onSurface + ) + ) { + Text(text = stringResource(Res.string.edit_profile)) + } } } - } - CollectionsComposable( - collectionsState = viewModel.collectionsState, - getMoreCollections = { - viewModel.accountState.account?.let { - viewModel.getCollections( - it.id, - true - ) - } - }, - navController = navController, - addNewButton = PlatformFeatures.addCollection, - instanceDomain = viewModel.ownDomain, - ) { url -> viewModel.openUrl(url) } - - HorizontalDivider(Modifier.padding(bottom = 12.dp, top = 12.dp)) + CollectionsComposable( + collectionsState = viewModel.collectionsState, + getMoreCollections = { + viewModel.accountState.account?.let { + viewModel.getCollections( + it.id, true + ) + } + }, + navController = navController, + addNewButton = PlatformFeatures.addCollection, + instanceDomain = viewModel.ownDomain, + ) { url -> viewModel.openUrl(url) } + } + } - SwitchViewComposable(postsCount = viewModel.accountState.account?.postsCount - ?: 0, + item { + SwitchViewComposable( + postsCount = viewModel.accountState.account?.postsCount ?: 0, viewType = viewModel.view, onViewChange = { viewModel.changeView(it) }) } - } - PostsWrapperComposable( - posts = viewModel.postsState.posts, - isLoading = viewModel.postsState.isLoading, - isRefreshing = viewModel.accountState.refreshing || viewModel.postsState.refreshing, - error = viewModel.postsState.error, - endReached = viewModel.postsState.endReached, - emptyMessage = EmptyState( - icon = Icons.Outlined.Photo, heading = "No Posts" - ), - view = viewModel.view, - isFirstImageLarge = true, - postGetsDeleted = { viewModel.postGetsDeleted(it) }, - updatePost = { viewModel.updatePost(it) }, - navController = navController - ) - /*PostsWrapperComposable( - accountState = viewModel.accountState, - postsState = viewModel.postsState, - navController = navController, - emptyState = EmptyState( - icon = Icons.Outlined.Photo, heading = "No Posts" - ), - view = viewModel.view, - postGetsDeleted = { viewModel.postGetsDeleted(it) }, - isFirstImageLarge = true - )*/ - - } + PostsWrapperComposable( + posts = viewModel.postsState.posts, + isLoading = viewModel.postsState.isLoading, + isRefreshing = viewModel.accountState.refreshing || viewModel.postsState.refreshing, + error = viewModel.postsState.error, + endReached = viewModel.postsState.endReached, + emptyMessage = EmptyState( + icon = Icons.Outlined.Photo, heading = "No Posts" + ), + view = viewModel.view, + isFirstImageLarge = true, + postGetsDeleted = { viewModel.postGetsDeleted(it) }, + updatePost = { viewModel.updatePost(it) }, + navController = navController + ) + } - if (viewModel.postsState.posts.isEmpty() && viewModel.postsState.error.isNotBlank()) { - FullscreenErrorComposable(message = viewModel.postsState.error) + if (viewModel.postsState.posts.isEmpty() && viewModel.postsState.error.isNotBlank()) { + FullscreenErrorComposable(message = viewModel.postsState.error) + } } } + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + Row(Modifier.clickable { showBottomSheet = 2 }) { + Column { + Text( + text = viewModel.accountState.account?.username ?: "", + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + Text( + text = viewModel.ownDomain, fontSize = 12.sp, lineHeight = 6.sp + ) + } + } + }, actions = { + if (viewModel.ownDomain.isNotEmpty()) { + DomainSoftwareComposable( + domain = viewModel.ownDomain + ) + } + + IconButton(onClick = { + showBottomSheet = 1 + }) { + Icon( + imageVector = Icons.Outlined.MoreVert, contentDescription = "preferences" + ) + } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) } InfiniteListHandler(lazyListState = lazyGridState) { @@ -217,12 +215,15 @@ fun OwnProfileComposable( ) { if (showBottomSheet == 1) { val icon = viewModel.appIcon.collectAsState() - ModalBottomSheetContent(navController = navController, + ModalBottomSheetContent( + navController = navController, instanceDomain = viewModel.ownDomain, appIcon = icon.value, closeBottomSheet = { showBottomSheet = 0 - }, openPreferencesDrawer) + }, + openPreferencesDrawer + ) } else if (showBottomSheet == 2) { AccountSwitchBottomSheet( navController = navController, diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/about_instance/AboutInstanceComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/about_instance/AboutInstanceComposable.kt index 04720463..a1bc87a8 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/about_instance/AboutInstanceComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/about_instance/AboutInstanceComposable.kt @@ -1,29 +1,34 @@ package com.daniebeler.pfpixelix.ui.composables.settings.about_instance +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -40,10 +45,8 @@ import com.daniebeler.pfpixelix.ui.navigation.Destination import com.daniebeler.pfpixelix.utils.StringFormat import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res import pixelix.app.generated.resources.admin -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.default_avatar import pixelix.app.generated.resources.instance_version import pixelix.app.generated.resources.posts @@ -61,197 +64,215 @@ fun AboutInstanceComposable( ) { val lazyListState = rememberLazyListState() - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - Text(text = viewModel.ownInstanceDomain, fontWeight = FontWeight.Bold) - }, navigationIcon = { - IconButton(onClick = { - navController.popBackStack() - }) { - Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), contentDescription = "" - ) - } - }) - }) { paddingValues -> - LazyColumn( - modifier = Modifier.padding(paddingValues), state = lazyListState + + Box(modifier = Modifier.fillMaxSize()) { + + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + + Box( + modifier = Modifier.padding(top = TopAppBarDefaults.TopAppBarExpandedHeight + statusBarPadding - 24.dp) + .fillMaxSize() ) { - if (!viewModel.instanceState.isLoading && viewModel.instanceState.error.isEmpty()) { - item { - AsyncImage( - model = viewModel.instanceState.instance?.thumbnailUrl, - contentDescription = null, - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(18.dp)) - Text( - text = viewModel.instanceState.instance?.description ?: "", - Modifier.padding(12.dp, 0.dp) - ) - Spacer(modifier = Modifier.height(18.dp)) + LazyColumn( + state = lazyListState + ) { + if (!viewModel.instanceState.isLoading && viewModel.instanceState.error.isEmpty()) { + item { + Box( + Modifier.fillParentMaxWidth().height(24.dp) + .background(MaterialTheme.colorScheme.surfaceContainer) + ) + AsyncImage( + model = viewModel.instanceState.instance?.thumbnailUrl, + contentDescription = null, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(18.dp)) + Text( + text = viewModel.instanceState.instance?.description ?: "", + Modifier.padding(12.dp, 0.dp) + ) + Spacer(modifier = Modifier.height(18.dp)) - Text( - text = stringResource(Res.string.stats), - fontWeight = FontWeight.Bold, - fontSize = 18.sp, - modifier = Modifier.padding(12.dp, 0.dp) - ) + Text( + text = stringResource(Res.string.stats), + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + modifier = Modifier.padding(12.dp, 0.dp) + ) - Row( - horizontalArrangement = Arrangement.SpaceEvenly, - modifier = Modifier.fillMaxWidth() - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text( - text = StringFormat.groupDigits( - viewModel.instanceState.instance?.stats?.userCount - ), fontWeight = FontWeight.Bold, fontSize = 18.sp - ) - Text(text = stringResource(Res.string.users), fontSize = 12.sp) + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + modifier = Modifier.fillMaxWidth() + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = StringFormat.groupDigits( + viewModel.instanceState.instance?.stats?.userCount + ), fontWeight = FontWeight.Bold, fontSize = 18.sp + ) + Text(text = stringResource(Res.string.users), fontSize = 12.sp) + } + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = StringFormat.groupDigits( + viewModel.instanceState.instance?.stats?.statusCount + ), fontWeight = FontWeight.Bold, fontSize = 18.sp + ) + Text(text = stringResource(Res.string.posts), fontSize = 12.sp) + } } - Column(horizontalAlignment = Alignment.CenterHorizontally) { + Spacer(modifier = Modifier.height(18.dp)) + + viewModel.instanceState.instance?.admin?.let { account -> Text( - text = StringFormat.groupDigits( - viewModel.instanceState.instance?.stats?.statusCount - ), fontWeight = FontWeight.Bold, fontSize = 18.sp + text = stringResource(Res.string.admin), + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + modifier = Modifier.padding(12.dp, 0.dp) ) - Text(text = stringResource(Res.string.posts), fontSize = 12.sp) + + Row( + modifier = Modifier.clickable { + navController.navigate(Destination.Profile(account.id)) + }.padding(horizontal = 12.dp, vertical = 8.dp).fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + AsyncImage( + model = account.avatar, + error = painterResource(Res.drawable.default_avatar), + contentDescription = "", + modifier = Modifier.height(46.dp).width(46.dp).clip(CircleShape) + ) + Spacer(modifier = Modifier.width(10.dp)) + Column { + if (account.displayname != null) { + Text(text = account.displayname) + } + Text(text = "@${account.username}") + } + } } - } - Spacer(modifier = Modifier.height(18.dp)) + Spacer(modifier = Modifier.height(18.dp)) - viewModel.instanceState.instance?.admin?.let { account -> Text( - text = stringResource(Res.string.admin), + text = stringResource(Res.string.privacy_policy), fontWeight = FontWeight.Bold, fontSize = 18.sp, modifier = Modifier.padding(12.dp, 0.dp) ) - Row(modifier = Modifier - .clickable { - navController.navigate(Destination.Profile(account.id)) - } - .padding(horizontal = 12.dp, vertical = 8.dp) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically) { - AsyncImage( - model = account.avatar, - error = painterResource(Res.drawable.default_avatar), - contentDescription = "", - modifier = Modifier - .height(46.dp) - .width(46.dp) - .clip(CircleShape) - ) - Spacer(modifier = Modifier.width(10.dp)) - Column { - if (account.displayname != null) { - Text(text = account.displayname) + Text( + text = "https://" + viewModel.instanceState.instance?.domain + "/site/privacy", + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(12.dp, 0.dp).clickable { + if (viewModel.instanceState.instance != null) { + viewModel.openUrl( + url = "https://" + viewModel.instanceState.instance!!.domain + "/site/privacy" + ) } - Text(text = "@${account.username}") - } - } - } + }) - Spacer(modifier = Modifier.height(18.dp)) - Text( - text = stringResource(Res.string.privacy_policy), - fontWeight = FontWeight.Bold, - fontSize = 18.sp, - modifier = Modifier.padding(12.dp, 0.dp) - ) + Spacer(modifier = Modifier.height(18.dp)) - Text(text = "https://" + viewModel.instanceState.instance?.domain + "/site/privacy", - color = MaterialTheme.colorScheme.primary, - modifier = Modifier - .padding(12.dp, 0.dp) - .clickable { - if (viewModel.instanceState.instance != null) { - viewModel.openUrl( - url = "https://" + viewModel.instanceState.instance!!.domain + "/site/privacy" - ) - } - }) + Text( + text = stringResource(Res.string.terms_of_use), + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + modifier = Modifier.padding(12.dp, 0.dp) + ) - Spacer(modifier = Modifier.height(18.dp)) + Text( + text = "https://" + viewModel.instanceState.instance?.domain + "/site/terms", + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(12.dp, 0.dp).clickable { + if (viewModel.instanceState.instance != null) { + viewModel.openUrl( + url = "https://" + viewModel.instanceState.instance!!.domain + "/site/terms" + ) + } + }) - Text( - text = stringResource(Res.string.terms_of_use), - fontWeight = FontWeight.Bold, - fontSize = 18.sp, - modifier = Modifier.padding(12.dp, 0.dp) - ) + Spacer(modifier = Modifier.height(18.dp)) - Text(text = "https://" + viewModel.instanceState.instance?.domain + "/site/terms", - color = MaterialTheme.colorScheme.primary, - modifier = Modifier - .padding(12.dp, 0.dp) - .clickable { - if (viewModel.instanceState.instance != null) { - viewModel.openUrl( - url = "https://" + viewModel.instanceState.instance!!.domain + "/site/terms" - ) - } - }) + Text( + text = stringResource(Res.string.rules), + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + modifier = Modifier.padding(12.dp, 0.dp) + ) + } + items(viewModel.instanceState.instance?.rules ?: emptyList()) { + Row(modifier = Modifier.padding(vertical = 12.dp, horizontal = 12.dp)) { + Text( + text = it.id, + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.width(18.dp)) + Text(text = it.text) + } + } - Spacer(modifier = Modifier.height(18.dp)) + item { + Spacer(modifier = Modifier.height(18.dp)) - Text( - text = stringResource(Res.string.rules), - fontWeight = FontWeight.Bold, - fontSize = 18.sp, - modifier = Modifier.padding(12.dp, 0.dp) - ) - } + Text( + text = stringResource(Res.string.instance_version), + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + modifier = Modifier.padding(12.dp, 0.dp) + ) - items(viewModel.instanceState.instance?.rules ?: emptyList()) { - Row(modifier = Modifier.padding(vertical = 12.dp, horizontal = 12.dp)) { - Text( - text = it.id, - fontSize = 24.sp, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.width(18.dp)) - Text(text = it.text) + Text( + text = viewModel.instanceState.instance?.version ?: "", + modifier = Modifier.padding(12.dp, 0.dp) + ) + + Spacer(modifier = Modifier.height(32.dp)) + } } + } - item { - Spacer(modifier = Modifier.height(18.dp)) + } + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { Text( - text = stringResource(Res.string.instance_version), + text = viewModel.ownInstanceDomain, fontWeight = FontWeight.Bold, - fontSize = 18.sp, - modifier = Modifier.padding(12.dp, 0.dp) - ) - - Text( - text = viewModel.instanceState.instance?.version ?: "", - modifier = Modifier.padding(12.dp, 0.dp) + fontSize = 18.sp ) + }, navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" + ) + } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) - Spacer(modifier = Modifier.height(32.dp)) - } + if (viewModel.instanceState.isLoading) { + FullscreenLoadingComposable() } + if (viewModel.instanceState.error.isNotBlank()) { + FullscreenErrorComposable(message = viewModel.instanceState.error) + } } - - if (viewModel.instanceState.isLoading) { - FullscreenLoadingComposable() - } - - if (viewModel.instanceState.error.isNotBlank()) { - FullscreenErrorComposable(message = viewModel.instanceState.error) - } -} } \ No newline at end of file diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/about_pixelix/AboutPixelixComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/about_pixelix/AboutPixelixComposable.kt index 996852eb..a51eeeec 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/about_pixelix/AboutPixelixComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/about_pixelix/AboutPixelixComposable.kt @@ -3,32 +3,34 @@ package com.daniebeler.pfpixelix.ui.composables.settings.about_pixelix import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.outlined.Language -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment @@ -45,11 +47,9 @@ import com.daniebeler.pfpixelix.ui.composables.ButtonRowElement import com.daniebeler.pfpixelix.ui.navigation.Destination import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res import pixelix.app.generated.resources.about_pixelix import pixelix.app.generated.resources.browsers_outline -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.code_slash_outline import pixelix.app.generated.resources.developed_by import pixelix.app.generated.resources.mastodon_logo @@ -65,236 +65,213 @@ fun AboutPixelixComposable( ) { val scrollState = rememberScrollState() - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - Text(text = stringResource(Res.string.about_pixelix), fontWeight = FontWeight.Bold) - }, navigationIcon = { - IconButton(onClick = { - navController.popBackStack() - }) { - Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), - contentDescription = "" - ) - } - }) - }) { paddingValues -> - Column( - modifier = Modifier - .padding(paddingValues) + Box(modifier = Modifier.fillMaxSize()) { + + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + + Box( + modifier = Modifier.padding(top = TopAppBarDefaults.TopAppBarExpandedHeight + statusBarPadding - 24.dp) .fillMaxSize() - .verticalScroll(scrollState) ) { Column( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 56.dp), - horizontalAlignment = Alignment.CenterHorizontally + modifier = Modifier.fillMaxSize().verticalScroll(scrollState) ) { - val icon = viewModel.appIcon.collectAsState() - Image( - painterResource(icon.value), - contentDescription = null, - Modifier - .width(84.dp) - .height(84.dp) - .clip(CircleShape) - ) + Column( + modifier = Modifier.fillMaxWidth().padding(top = 80.dp, bottom = 56.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + val icon = viewModel.appIcon.collectAsState() + Image( + painterResource(icon.value), + contentDescription = null, + Modifier.width(84.dp).height(84.dp).clip(CircleShape) + ) - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(12.dp)) - Text( - text = "Pixelix", fontSize = 36.sp, fontWeight = FontWeight.Bold - ) - - Text( - text = "Version " + viewModel.versionName, - fontSize = 16.sp, - color = MaterialTheme.colorScheme.secondary - ) + Text( + text = "Pixelix", fontSize = 36.sp, fontWeight = FontWeight.Bold + ) - } + Text( + text = "Version " + viewModel.versionName, + fontSize = 16.sp, + color = MaterialTheme.colorScheme.secondary + ) - HorizontalDivider(Modifier.padding(12.dp)) + } - ButtonRowElement( - icon = Res.drawable.star_outline, - text = "Rate Pixelix on Google Play Store", - onClick = { viewModel.rateApp() }) + HorizontalDivider(Modifier.padding(12.dp)) - HorizontalDivider(Modifier.padding(12.dp)) + ButtonRowElement( + icon = Res.drawable.star_outline, + text = "Rate Pixelix on Google Play Store", + onClick = { viewModel.rateApp() }) - ButtonRowElement( - icon = Res.drawable.browsers_outline, - text = "Homepage", - smallText = "https://app.pixelix.social", - onClick = { viewModel.openUrl("https://app.pixelix.social") }) + HorizontalDivider(Modifier.padding(12.dp)) - ButtonRowElement( - icon = Res.drawable.shield_outline, - text = "Privacy Policy", - smallText = "https://app.pixelix.social/privacy", - onClick = { viewModel.openUrl("https://app.pixelix.social/privacy") }) + ButtonRowElement( + icon = Res.drawable.browsers_outline, + text = "Homepage", + smallText = "https://app.pixelix.social", + onClick = { viewModel.openUrl("https://app.pixelix.social") }) - ButtonRowElement( - icon = Res.drawable.code_slash_outline, - text = "Source Code", - smallText = "https://github.com/daniebeler/pixelix", - onClick = { viewModel.openUrl("https://github.com/daniebeler/pixelix") }) + ButtonRowElement( + icon = Res.drawable.shield_outline, + text = "Privacy Policy", + smallText = "https://app.pixelix.social/privacy", + onClick = { viewModel.openUrl("https://app.pixelix.social/privacy") }) + ButtonRowElement( + icon = Res.drawable.code_slash_outline, + text = "Source Code", + smallText = "https://github.com/daniebeler/pixelix", + onClick = { viewModel.openUrl("https://github.com/daniebeler/pixelix") }) - HorizontalDivider(Modifier.padding(12.dp)) + HorizontalDivider(Modifier.padding(12.dp)) - Text( - text = stringResource(Res.string.developed_by), - fontSize = 18.sp, - modifier = Modifier - .padding(12.dp, 0.dp) - .fillMaxWidth(), - textAlign = TextAlign.Center - ) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 16.dp) - ) { - Text(text = "Emanuel Hiebeler", fontWeight = FontWeight.Bold) + Text( + text = stringResource(Res.string.developed_by), + fontSize = 18.sp, + modifier = Modifier.padding(12.dp, 0.dp).fillMaxWidth(), + textAlign = TextAlign.Center + ) - Row { - Image( - painter = painterResource(Res.drawable.pixelfed_logo), - contentDescription = null, - Modifier - .width(32.dp) - .height(32.dp) - .clickable { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 16.dp) + ) { + Text(text = "Emanuel Hiebeler", fontWeight = FontWeight.Bold) + + Row { + Image( + painter = painterResource(Res.drawable.pixelfed_logo), + contentDescription = null, + Modifier.width(32.dp).height(32.dp).clickable { navController.navigate(Destination.ProfileByUsername("hiebeler05@pixelix.social")) }) - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.width(16.dp)) - Image( - painter = painterResource(Res.drawable.mastodon_logo), - contentDescription = null, - Modifier - .width(32.dp) - .height(32.dp) - .clickable { + Image( + painter = painterResource(Res.drawable.mastodon_logo), + contentDescription = null, + Modifier.width(32.dp).height(32.dp).clickable { viewModel.openUrl("https://techhub.social/@Hiebeler05") }) - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.width(16.dp)) - Icon( - imageVector = Icons.Outlined.Language, - contentDescription = "", - Modifier - .size(32.dp) - .clickable { + Icon( + imageVector = Icons.Outlined.Language, + contentDescription = "", + Modifier.size(32.dp).clickable { viewModel.openUrl("https://emanuelhiebeler.me") }, - tint = Color(0xFF4793FF) - ) + tint = Color(0xFF4793FF) + ) + } } - } - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 16.dp) - ) { - Text(text = "Daniel Hiebeler", fontWeight = FontWeight.Bold) - - Row { - Image( - painter = painterResource(Res.drawable.pixelfed_logo), - contentDescription = null, - Modifier - .width(32.dp) - .height(32.dp) - .clickable { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 16.dp) + ) { + Text(text = "Daniel Hiebeler", fontWeight = FontWeight.Bold) + + Row { + Image( + painter = painterResource(Res.drawable.pixelfed_logo), + contentDescription = null, + Modifier.width(32.dp).height(32.dp).clickable { navController.navigate(Destination.ProfileByUsername("daniebeler@pixelix.social")) }) - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.width(16.dp)) - Image( - painter = painterResource(Res.drawable.mastodon_logo), - contentDescription = null, - Modifier - .width(32.dp) - .height(32.dp) - .clickable { + Image( + painter = painterResource(Res.drawable.mastodon_logo), + contentDescription = null, + Modifier.width(32.dp).height(32.dp).clickable { viewModel.openUrl("https://techhub.social/@daniebeler") }) - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.width(16.dp)) - Icon( - imageVector = Icons.Outlined.Language, - contentDescription = "", - Modifier - .size(32.dp) - .clickable { + Icon( + imageVector = Icons.Outlined.Language, + contentDescription = "", + Modifier.size(32.dp).clickable { viewModel.openUrl("https://daniebeler.com") }, - tint = Color(0xFF4793FF) - ) + tint = Color(0xFF4793FF) + ) + } } - } - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 16.dp) - ) { - Text(text = "Konstantin Tskhovrebov", fontWeight = FontWeight.Bold) - - Row { - Image( - painter = painterResource(Res.drawable.pixelfed_logo), - contentDescription = null, - Modifier - .width(32.dp) - .height(32.dp) - .clickable { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 16.dp) + ) { + Text(text = "Konstantin Tskhovrebov", fontWeight = FontWeight.Bold) + + Row { + Image( + painter = painterResource(Res.drawable.pixelfed_logo), + contentDescription = null, + Modifier.width(32.dp).height(32.dp).clickable { navController.navigate(Destination.ProfileByUsername("dagboek@pixey.org")) }) - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.width(16.dp)) - Image( - painter = painterResource(Res.drawable.mastodon_logo), - contentDescription = null, - Modifier - .width(32.dp) - .height(32.dp) - .clickable { + Image( + painter = painterResource(Res.drawable.mastodon_logo), + contentDescription = null, + Modifier.width(32.dp).height(32.dp).clickable { viewModel.openUrl("https://androiddev.social/@terrakok") }) - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.width(16.dp)) - Icon( - imageVector = Icons.Outlined.Language, - contentDescription = "", - Modifier - .size(32.dp) - .clickable { + Icon( + imageVector = Icons.Outlined.Language, + contentDescription = "", + Modifier.size(32.dp).clickable { viewModel.openUrl("https://github.com/terrakok") }, - tint = Color(0xFF4793FF) - ) + tint = Color(0xFF4793FF) + ) + } } } } + + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + Text( + text = stringResource(Res.string.about_pixelix), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + }, navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" + ) + } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) } } diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/blocked_accounts/BlockedAccountsComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/blocked_accounts/BlockedAccountsComposable.kt index 4608e1ee..fe2df812 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/blocked_accounts/BlockedAccountsComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/blocked_accounts/BlockedAccountsComposable.kt @@ -1,26 +1,32 @@ package com.daniebeler.pfpixelix.ui.composables.settings.blocked_accounts import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Scaffold +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.ui.composables.states.EmptyState @@ -28,43 +34,30 @@ import com.daniebeler.pfpixelix.ui.composables.states.FullscreenEmptyStateCompos import com.daniebeler.pfpixelix.ui.composables.states.FullscreenErrorComposable import com.daniebeler.pfpixelix.ui.composables.states.FullscreenLoadingComposable import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res import pixelix.app.generated.resources.blocked_accounts -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.no_blocked_accounts -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun BlockedAccountsComposable( navController: NavController, viewModel: BlockedAccountsViewModel = injectViewModel(key = "blocked-accounts-key") { blockedAccountsViewModel } ) { - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - Text( - text = stringResource(Res.string.blocked_accounts), fontWeight = FontWeight.Bold - ) - }, navigationIcon = { - IconButton(onClick = { - navController.popBackStack() - }) { - Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), contentDescription = "" - ) - } - }) + Box(modifier = Modifier.fillMaxSize()) { + + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() - }) { paddingValues -> Box( - modifier = Modifier.padding(paddingValues) + modifier = Modifier.padding(top = TopAppBarDefaults.TopAppBarExpandedHeight + statusBarPadding - 24.dp) + .fillMaxSize() ) { PullToRefreshBox( isRefreshing = viewModel.blockedAccountsState.isRefreshing, onRefresh = { viewModel.getBlockedAccounts(true) }, ) { LazyColumn( - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(top = 24.dp) ) { items(viewModel.blockedAccountsState.blockedAccounts, key = { it.id @@ -78,18 +71,41 @@ fun BlockedAccountsComposable( } } - if (viewModel.blockedAccountsState.blockedAccounts.isEmpty()) { - if (viewModel.blockedAccountsState.isLoading && !viewModel.blockedAccountsState.isRefreshing) { - FullscreenLoadingComposable() - } + } - if (viewModel.blockedAccountsState.error.isNotEmpty()) { - FullscreenErrorComposable(message = viewModel.blockedAccountsState.error) - } + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + Text( + text = stringResource(Res.string.blocked_accounts), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + }, navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" + ) + } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) - if (!viewModel.blockedAccountsState.isLoading && viewModel.blockedAccountsState.error.isEmpty()) { - FullscreenEmptyStateComposable(EmptyState(heading = stringResource(Res.string.no_blocked_accounts))) - } + if (viewModel.blockedAccountsState.blockedAccounts.isEmpty()) { + if (viewModel.blockedAccountsState.isLoading && !viewModel.blockedAccountsState.isRefreshing) { + FullscreenLoadingComposable() + } + + if (viewModel.blockedAccountsState.error.isNotEmpty()) { + FullscreenErrorComposable(message = viewModel.blockedAccountsState.error) + } + + if (!viewModel.blockedAccountsState.isLoading && viewModel.blockedAccountsState.error.isEmpty()) { + FullscreenEmptyStateComposable(EmptyState(heading = stringResource(Res.string.no_blocked_accounts))) } } } diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/bookmarked_posts/BookmarkedPostsComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/bookmarked_posts/BookmarkedPostsComposable.kt index 6d8f3d87..03c8a1df 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/bookmarked_posts/BookmarkedPostsComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/bookmarked_posts/BookmarkedPostsComposable.kt @@ -2,29 +2,33 @@ package com.daniebeler.pfpixelix.ui.composables.settings.bookmarked_posts import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars -import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Scaffold +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.ui.composables.InfinitePostsGrid import com.daniebeler.pfpixelix.ui.composables.states.EmptyState import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res import pixelix.app.generated.resources.bookmarked_posts -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.no_bookmarked_posts @OptIn(ExperimentalMaterial3Api::class) @@ -34,26 +38,16 @@ fun BookmarkedPostsComposable( viewModel: BookmarkedPostsViewModel = injectViewModel(key = "bookmarksviewmodel") { bookmarkedPostsViewModel } ) { - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - Text(stringResource(Res.string.bookmarked_posts), fontWeight = FontWeight.Bold) - }, navigationIcon = { - IconButton(onClick = { - navController.popBackStack() - }) { - Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), contentDescription = "" - ) - } - }) + Box(modifier = Modifier.fillMaxSize()) { + + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() - }) { paddingValues -> Box( - modifier = Modifier + modifier = Modifier.padding(top = TopAppBarDefaults.TopAppBarExpandedHeight + statusBarPadding - 24.dp) .fillMaxSize() - .padding(paddingValues) ) { - InfinitePostsGrid(items = viewModel.bookmarkedPostsState.bookmarkedPosts, + InfinitePostsGrid( + items = viewModel.bookmarkedPostsState.bookmarkedPosts, isLoading = viewModel.bookmarkedPostsState.isLoading, isRefreshing = viewModel.bookmarkedPostsState.isRefreshing, error = viewModel.bookmarkedPostsState.error, @@ -61,7 +55,31 @@ fun BookmarkedPostsComposable( emptyMessage = EmptyState(heading = stringResource(Res.string.no_bookmarked_posts)), navController = navController, getItemsPaginated = { /*TODO*/ }, - onRefresh = { viewModel.getBookmarkedPosts(true) }) + onRefresh = { viewModel.getBookmarkedPosts(true) }, + contentPaddingTop = 24.dp + ) } + + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + Text( + stringResource(Res.string.bookmarked_posts), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + }, navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" + ) + } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) } } \ No newline at end of file diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/followed_hashtags/FollowedHashtagsComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/followed_hashtags/FollowedHashtagsComposable.kt index bf069fa8..1384f475 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/followed_hashtags/FollowedHashtagsComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/followed_hashtags/FollowedHashtagsComposable.kt @@ -1,28 +1,34 @@ package com.daniebeler.pfpixelix.ui.composables.settings.followed_hashtags import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.outlined.Tag -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Scaffold +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.ui.composables.CustomHashtag @@ -32,9 +38,7 @@ import com.daniebeler.pfpixelix.ui.composables.states.FullscreenErrorComposable import com.daniebeler.pfpixelix.ui.composables.states.FullscreenLoadingComposable import com.daniebeler.pfpixelix.ui.navigation.Destination import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.followed_hashtags import pixelix.app.generated.resources.no_followed_hashtags @@ -44,57 +48,76 @@ fun FollowedHashtagsComposable( navController: NavController, viewModel: FollowedHashtagsViewModel = injectViewModel(key = "followed-hashtags-key") { followedHashtagsViewModel } ) { - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - Text(stringResource(Res.string.followed_hashtags), fontWeight = FontWeight.Bold) - }, navigationIcon = { - IconButton(onClick = { - navController.popBackStack() - }) { - Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), contentDescription = "" - ) - } - }) + Box(modifier = Modifier.fillMaxSize()) { - }) { paddingValues -> - PullToRefreshBox( - isRefreshing = viewModel.followedHashtagsState.isRefreshing, - onRefresh = { viewModel.getFollowedHashtags(true) }, - modifier = Modifier + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + + Box( + modifier = Modifier.padding(top = TopAppBarDefaults.TopAppBarExpandedHeight + statusBarPadding - 24.dp) .fillMaxSize() - .padding(paddingValues) ) { - LazyColumn(verticalArrangement = Arrangement.spacedBy(4.dp), - modifier = Modifier.fillMaxSize(), - content = { - items(viewModel.followedHashtagsState.followedHashtags) { tag -> - CustomHashtag(hashtag = tag, navController = navController) + PullToRefreshBox( + isRefreshing = viewModel.followedHashtagsState.isRefreshing, + onRefresh = { viewModel.getFollowedHashtags(true) }, + modifier = Modifier.fillMaxSize() + ) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(4.dp), + contentPadding = PaddingValues(top = 24.dp), + modifier = Modifier.fillMaxSize(), + content = { + items(viewModel.followedHashtagsState.followedHashtags) { tag -> + CustomHashtag(hashtag = tag, navController = navController) + } + }) + + if (viewModel.followedHashtagsState.followedHashtags.isEmpty()) { + if (viewModel.followedHashtagsState.isLoading && !viewModel.followedHashtagsState.isRefreshing) { + FullscreenLoadingComposable() } - }) - if (viewModel.followedHashtagsState.followedHashtags.isEmpty()) { - if (viewModel.followedHashtagsState.isLoading && !viewModel.followedHashtagsState.isRefreshing) { - FullscreenLoadingComposable() - } + if (viewModel.followedHashtagsState.error.isNotEmpty()) { + FullscreenErrorComposable(message = viewModel.followedHashtagsState.error) + } - if (viewModel.followedHashtagsState.error.isNotEmpty()) { - FullscreenErrorComposable(message = viewModel.followedHashtagsState.error) + if (!viewModel.followedHashtagsState.isLoading && viewModel.followedHashtagsState.error.isEmpty()) { + FullscreenEmptyStateComposable( + EmptyState( + icon = Icons.Outlined.Tag, + heading = stringResource(Res.string.no_followed_hashtags), + message = "Followed hashtags will appear here", + buttonText = "Explore trending hashtags", + onClick = { + navController.navigate(Destination.Search(2)) + }) + ) + } } + } + + } - if (!viewModel.followedHashtagsState.isLoading && viewModel.followedHashtagsState.error.isEmpty()) { - FullscreenEmptyStateComposable( - EmptyState(icon = Icons.Outlined.Tag, - heading = stringResource(Res.string.no_followed_hashtags), - message = "Followed hashtags will appear here", - buttonText = "Explore trending hashtags", - onClick = { - navController.navigate(Destination.Search(2)) - }) + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + Text( + stringResource(Res.string.followed_hashtags), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + }, navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" ) } - } - } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) } } \ No newline at end of file diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/icon_selection/IconSelectionComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/icon_selection/IconSelectionComposable.kt index e9e3ea4b..2d8652c5 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/icon_selection/IconSelectionComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/icon_selection/IconSelectionComposable.kt @@ -6,8 +6,7 @@ import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.aspectRatio @@ -17,21 +16,23 @@ import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.AlertDialog -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableStateOf @@ -41,20 +42,17 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res import pixelix.app.generated.resources.cancel import pixelix.app.generated.resources.change import pixelix.app.generated.resources.change_app_icon import pixelix.app.generated.resources.change_app_icon_dialog_content -import pixelix.app.generated.resources.chevron_back_outline -import pixelix.app.generated.resources.two_icons_info - @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -65,24 +63,10 @@ fun IconSelectionComposable( val lazyGridState = rememberLazyGridState() val (newIcon, setNewIcon) = remember { mutableStateOf(null) } - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - Text("Icon Selection", fontWeight = FontWeight.Bold) - }, navigationIcon = { - IconButton(onClick = { - navController.popBackStack() - }) { - Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), - contentDescription = "" - ) - } - }) - - }) { paddingValues -> + Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top)) { paddingValues -> Box( - modifier = Modifier - .fillMaxSize() + modifier = Modifier.fillMaxSize() + .padding(top = TopAppBarDefaults.TopAppBarExpandedHeight - 24.dp) .padding(paddingValues) ) { @@ -90,9 +74,8 @@ fun IconSelectionComposable( LazyVerticalGrid( horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp), - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 12.dp), + contentPadding = PaddingValues(top = 30.dp), + modifier = Modifier.fillMaxSize().padding(horizontal = 12.dp), state = lazyGridState, columns = GridCells.Fixed(3) ) { @@ -101,21 +84,15 @@ fun IconSelectionComposable( painterResource(icon), contentDescription = null, contentScale = ContentScale.Crop, - modifier = Modifier - .padding(6.dp) - .fillMaxWidth() - .aspectRatio(1f) - .clip(CircleShape) - .let { + modifier = Modifier.padding(6.dp).fillMaxWidth().aspectRatio(1f) + .clip(CircleShape).let { if (selectedIcon.value == icon) { it.border( BorderStroke(4.dp, MaterialTheme.colorScheme.primary), shape = CircleShape ) } else it - } - .clickable { setNewIcon(icon) } - ) + }.clickable { setNewIcon(icon) }) } } @@ -123,6 +100,24 @@ fun IconSelectionComposable( } + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + Text("Icon Selection", fontWeight = FontWeight.Bold, fontSize = 18.sp) + }, navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" + ) + } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) + if (newIcon != null) { AlertDialog(title = { Text(text = stringResource(Res.string.change_app_icon)) diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/liked_posts/LikedPostsComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/liked_posts/LikedPostsComposable.kt index 18507f96..a116c787 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/liked_posts/LikedPostsComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/liked_posts/LikedPostsComposable.kt @@ -2,30 +2,33 @@ package com.daniebeler.pfpixelix.ui.composables.settings.liked_posts import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.outlined.FavoriteBorder -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Scaffold +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.ui.composables.InfinitePostsGrid import com.daniebeler.pfpixelix.ui.composables.states.EmptyState import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.liked_posts import pixelix.app.generated.resources.no_liked_posts @@ -36,27 +39,13 @@ fun LikedPostsComposable( viewModel: LikedPostsViewModel = injectViewModel(key = "likey-posts-key") { likedPostsViewModel } ) { - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - Text(stringResource(Res.string.liked_posts), fontWeight = FontWeight.Bold) - }, navigationIcon = { - IconButton(onClick = { - navController.popBackStack() - }) { - Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), contentDescription = "" - ) - } - }) + Box(modifier = Modifier.fillMaxSize()) { - }) { paddingValues -> - Box( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - ) { + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() - InfinitePostsGrid(items = viewModel.likedPostsState.likedPosts, + Box(modifier = Modifier.padding(top = TopAppBarDefaults.TopAppBarExpandedHeight + statusBarPadding - 24.dp).fillMaxSize()) { + InfinitePostsGrid( + items = viewModel.likedPostsState.likedPosts, isLoading = viewModel.likedPostsState.isLoading, isRefreshing = viewModel.likedPostsState.isRefreshing, error = viewModel.likedPostsState.error, @@ -65,13 +54,29 @@ fun LikedPostsComposable( heading = stringResource(Res.string.no_liked_posts) ), navController = navController, - getItemsPaginated = { - viewModel.getItemsPaginated() - }, - onRefresh = { - viewModel.getItemsFirstLoad(true) - }) + getItemsPaginated = { viewModel.getItemsPaginated() }, + onRefresh = { viewModel.getItemsFirstLoad(true) }, + contentPaddingTop = 24.dp) } + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + Text( + stringResource(Res.string.liked_posts), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + }, navigationIcon = { + IconButton(onClick = { navController.popBackStack() }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" + ) + } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) } } \ No newline at end of file diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/muted_accounts/MutedAccountsComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/muted_accounts/MutedAccountsComposable.kt index 5189c6a6..39b24f6d 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/muted_accounts/MutedAccountsComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/muted_accounts/MutedAccountsComposable.kt @@ -1,25 +1,33 @@ package com.daniebeler.pfpixelix.ui.composables.settings.muted_accounts +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Scaffold +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.ui.composables.states.EmptyState @@ -27,9 +35,7 @@ import com.daniebeler.pfpixelix.ui.composables.states.FullscreenEmptyStateCompos import com.daniebeler.pfpixelix.ui.composables.states.FullscreenErrorComposable import com.daniebeler.pfpixelix.ui.composables.states.FullscreenLoadingComposable import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.muted_accounts import pixelix.app.generated.resources.no_muted_accounts @@ -39,56 +45,71 @@ fun MutedAccountsComposable( navController: NavController, viewModel: MutedAccountsViewModel = injectViewModel(key = "muted-accounts-key") { mutedAccountsViewModel } ) { - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - Text(text = stringResource(Res.string.muted_accounts), fontWeight = FontWeight.Bold) - }, navigationIcon = { - IconButton(onClick = { - navController.popBackStack() - }) { - Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), contentDescription = "" - ) - } - }) + Box(modifier = Modifier.fillMaxSize()) { + + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() - }) { paddingValues -> - PullToRefreshBox( - onRefresh = {viewModel.getMutedAccounts(true)}, - isRefreshing = viewModel.mutedAccountsState.isRefreshing, - modifier = Modifier + Box( + modifier = Modifier.padding(top = TopAppBarDefaults.TopAppBarExpandedHeight + statusBarPadding - 24.dp) .fillMaxSize() - .padding(paddingValues) ) { - LazyColumn( - modifier = Modifier - .fillMaxSize() + PullToRefreshBox( + onRefresh = { viewModel.getMutedAccounts(true) }, + isRefreshing = viewModel.mutedAccountsState.isRefreshing, + modifier = Modifier.fillMaxSize() ) { - items(viewModel.mutedAccountsState.mutedAccounts, key = { - it.id - }) { - Row { - CustomMutedAccountRow( - account = it, navController = navController, viewModel - ) + LazyColumn( + contentPadding = PaddingValues(top = 24.dp), modifier = Modifier.fillMaxSize() + ) { + items(viewModel.mutedAccountsState.mutedAccounts, key = { + it.id + }) { + Row { + CustomMutedAccountRow( + account = it, navController = navController, viewModel + ) + } } } - } - if (viewModel.mutedAccountsState.mutedAccounts.isEmpty()) { - if (viewModel.mutedAccountsState.isLoading && !viewModel.mutedAccountsState.isRefreshing) { - FullscreenLoadingComposable() - } + if (viewModel.mutedAccountsState.mutedAccounts.isEmpty()) { + if (viewModel.mutedAccountsState.isLoading && !viewModel.mutedAccountsState.isRefreshing) { + FullscreenLoadingComposable() + } - if (viewModel.mutedAccountsState.error.isNotEmpty()) { - FullscreenErrorComposable(message = viewModel.mutedAccountsState.error) - } + if (viewModel.mutedAccountsState.error.isNotEmpty()) { + FullscreenErrorComposable(message = viewModel.mutedAccountsState.error) + } - if (!viewModel.mutedAccountsState.isLoading && viewModel.mutedAccountsState.error.isEmpty()) { - FullscreenEmptyStateComposable(EmptyState(heading = stringResource(Res.string.no_muted_accounts))) + if (!viewModel.mutedAccountsState.isLoading && viewModel.mutedAccountsState.error.isEmpty()) { + FullscreenEmptyStateComposable(EmptyState(heading = stringResource(Res.string.no_muted_accounts))) + } } } + } + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + Text( + text = stringResource(Res.string.muted_accounts), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + }, navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" + ) + } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) + } } \ No newline at end of file diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/PreferencesComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/PreferencesComposable.kt index ab1083e2..bd7caafd 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/PreferencesComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/PreferencesComposable.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.DrawerState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider @@ -19,12 +18,12 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -64,8 +63,8 @@ fun PreferencesComposable( contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - CenterAlignedTopAppBar(scrollBehavior = scrollBehavior, title = { - Text(text = stringResource(Res.string.settings), fontWeight = FontWeight.Bold) + TopAppBar(scrollBehavior = scrollBehavior, title = { + Text(text = stringResource(Res.string.settings), fontWeight = FontWeight.Bold, fontSize = 18.sp) }, navigationIcon = { IconButton(onClick = { closePreferencesDrawer() @@ -78,12 +77,8 @@ fun PreferencesComposable( }) }) { paddingValues -> Column( - Modifier - .padding(paddingValues) - .padding(horizontal = 18.dp) - .padding(bottom = 18.dp) - .fillMaxSize() - .verticalScroll(state = rememberScrollState()), + Modifier.padding(paddingValues).padding(horizontal = 18.dp).padding(bottom = 18.dp) + .fillMaxSize().verticalScroll(state = rememberScrollState()), verticalArrangement = Arrangement.spacedBy(8.dp) ) { diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/single_post/SinglePostComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/single_post/SinglePostComposable.kt index 9701580c..1be5b86f 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/single_post/SinglePostComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/single_post/SinglePostComposable.kt @@ -9,18 +9,24 @@ import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel @@ -29,10 +35,8 @@ import com.daniebeler.pfpixelix.ui.composables.states.ErrorComposable import com.daniebeler.pfpixelix.ui.composables.states.LoadingComposable import com.daniebeler.pfpixelix.ui.navigation.Destination import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.resources.vectorResource import pixelix.app.generated.resources.Res import pixelix.app.generated.resources.by -import pixelix.app.generated.resources.chevron_back_outline import pixelix.app.generated.resources.post @OptIn(ExperimentalMaterial3Api::class) @@ -57,11 +61,36 @@ fun SinglePostComposable( } } - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = { - CenterAlignedTopAppBar(title = { - Column (horizontalAlignment = Alignment.CenterHorizontally) { + Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top)) { paddingValues -> + Box( + modifier = Modifier.padding(paddingValues).padding(top = TopAppBarDefaults.TopAppBarExpandedHeight - 24.dp) + .fillMaxSize() + ) { + Column(modifier = Modifier.verticalScroll(scrollState).padding(top = 28.dp, start = 4.dp, end = 4.dp, bottom = 28.dp)) { + if (viewModel.postState.post != null) { + PostComposable( + viewModel.postState.post!!, navController, postGetsDeleted = { + navController.navigate(Destination.OwnProfile) { + popUpTo(0) { inclusive = true } + } + }, setZindex = { }, openReplies + ) + } + } + + LoadingComposable(isLoading = viewModel.postState.isLoading) + ErrorComposable(message = viewModel.postState.error) + } + + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), title = { + Column { Text( - text = stringResource(Res.string.post), fontWeight = FontWeight.Bold + text = stringResource(Res.string.post), + fontWeight = FontWeight.Bold, + fontSize = 18.sp ) Text( text = stringResource( @@ -74,29 +103,12 @@ fun SinglePostComposable( navController.popBackStack() }) { Icon( - imageVector = vectorResource(Res.drawable.chevron_back_outline), contentDescription = "" + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" ) } - }) - }) { paddingValues -> - Box( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize() - ) { - Column(modifier = Modifier.verticalScroll(scrollState)) { - if (viewModel.postState.post != null) { - PostComposable(viewModel.postState.post!!, navController, postGetsDeleted = { - navController.navigate(Destination.OwnProfile) { - popUpTo(0) { inclusive = true } - } - }, - setZindex = { }, openReplies) - } - } - - LoadingComposable(isLoading = viewModel.postState.isLoading) - ErrorComposable(message = viewModel.postState.error) - } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) } } \ No newline at end of file diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/global_timeline/GlobalTimelineComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/global_timeline/GlobalTimelineComposable.kt index e352b8a8..9e3c6841 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/global_timeline/GlobalTimelineComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/global_timeline/GlobalTimelineComposable.kt @@ -1,11 +1,10 @@ package com.daniebeler.pfpixelix.ui.composables.timelines.global_timeline import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.ui.composables.InfinitePostsList -import com.daniebeler.pfpixelix.ui.composables.profile.ViewEnum -import com.daniebeler.pfpixelix.ui.composables.states.EmptyState @Composable fun GlobalTimelineComposable( @@ -13,6 +12,7 @@ fun GlobalTimelineComposable( viewModel: GlobalTimelineViewModel = injectViewModel(key = "global-timeline-key") { globalTimelineViewModel } ) { InfinitePostsList(items = viewModel.globalTimelineState.globalTimeline, + contentPaddingTop = 30.dp, isLoading = viewModel.globalTimelineState.isLoading, isRefreshing = viewModel.globalTimelineState.refreshing, error = viewModel.globalTimelineState.error, diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/hashtag_timeline/HashtagTimelineComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/hashtag_timeline/HashtagTimelineComposable.kt index 35e1a1cd..9452fa1e 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/hashtag_timeline/HashtagTimelineComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/hashtag_timeline/HashtagTimelineComposable.kt @@ -3,27 +3,29 @@ package com.daniebeler.pfpixelix.ui.composables.timelines.hashtag_timeline import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Scaffold +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.ui.composables.FollowButton @@ -43,18 +45,39 @@ fun HashtagTimelineComposable( viewModel.getRelatedHashtags(hashtag) } - val lazyGridState = rememberLazyListState() + Box(modifier = Modifier.fillMaxSize()) { - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() - Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - CenterAlignedTopAppBar(scrollBehavior = scrollBehavior, title = { + Box(modifier = Modifier.padding(top = TopAppBarDefaults.TopAppBarExpandedHeight + statusBarPadding - 24.dp).fillMaxSize()) { + InfinitePostsList( + contentPaddingTop = 24.dp, + items = viewModel.postsState.hashtagTimeline, + isLoading = viewModel.postsState.isLoading, + isRefreshing = viewModel.postsState.isRefreshing, + error = viewModel.postsState.error, + endReached = viewModel.postsState.endReached, + view = viewModel.view, + changeView = { viewModel.changeView(it) }, + isFirstItemLarge = true, + itemGetsDeleted = { viewModel.postGetsDeleted(it) }, + getItemsPaginated = { viewModel.getItemsPaginated(hashtag) }, + onRefresh = { viewModel.refresh() }, + postsCount = viewModel.hashtagState.hashtag?.count ?: 0, + navController = navController, + postGetsUpdated = { viewModel.postGetsUpdated(it) }) + } + + TopAppBar( + modifier = Modifier.clip( + RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp) + ), + title = { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( "#$hashtag", fontWeight = FontWeight.Bold, + fontSize = 18.sp, overflow = TextOverflow.Ellipsis, maxLines = 1 ) @@ -65,35 +88,21 @@ fun HashtagTimelineComposable( navController.popBackStack() }) { Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "" + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "" ) } }, actions = { - FollowButton(firstLoaded = viewModel.hashtagState.hashtag != null, + FollowButton( + iconButton = true, + firstLoaded = viewModel.hashtagState.hashtag != null, isLoading = viewModel.hashtagState.isLoading, isFollowing = viewModel.hashtagState.hashtag?.following ?: false, onFollowClick = { viewModel.followHashtag(viewModel.hashtagState.hashtag!!.name) }, onUnFollowClick = { viewModel.unfollowHashtag(viewModel.hashtagState.hashtag!!.name) }) - }) - - }) { paddingValues -> - Box(modifier = Modifier.padding(paddingValues)) { - InfinitePostsList( - items = viewModel.postsState.hashtagTimeline, - isLoading = viewModel.postsState.isLoading, - isRefreshing = viewModel.postsState.isRefreshing, - error = viewModel.postsState.error, - endReached = viewModel.postsState.endReached, - view = viewModel.view, - changeView = { viewModel.changeView(it) }, - isFirstItemLarge = true, - itemGetsDeleted = { viewModel.postGetsDeleted(it) }, - getItemsPaginated = { viewModel.getItemsPaginated(hashtag) }, - onRefresh = { viewModel.refresh() }, - postsCount = viewModel.hashtagState.hashtag?.count ?: 0, - navController = navController, - postGetsUpdated = { viewModel.postGetsUpdated(it) } + }, colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer ) - } + ) } } \ No newline at end of file diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/home_timeline/HomeTimelineComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/home_timeline/HomeTimelineComposable.kt index 4334c006..373400d5 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/home_timeline/HomeTimelineComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/home_timeline/HomeTimelineComposable.kt @@ -6,6 +6,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.PhotoLibrary import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.ui.composables.InfinitePostsList @@ -24,6 +25,7 @@ fun HomeTimelineComposable( ) { Box(modifier = Modifier.fillMaxSize()) { InfinitePostsList(items = viewModel.homeTimelineState.homeTimeline, + contentPaddingTop = 32.dp, isLoading = viewModel.homeTimelineState.isLoading, isRefreshing = viewModel.homeTimelineState.refreshing, error = viewModel.homeTimelineState.error, diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/local_timeline/LocalTimelineComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/local_timeline/LocalTimelineComposable.kt index dc67c131..68d2dfe1 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/local_timeline/LocalTimelineComposable.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/local_timeline/LocalTimelineComposable.kt @@ -1,6 +1,7 @@ package com.daniebeler.pfpixelix.ui.composables.timelines.local_timeline import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.daniebeler.pfpixelix.di.injectViewModel import com.daniebeler.pfpixelix.ui.composables.InfinitePostsList @@ -12,6 +13,7 @@ fun LocalTimelineComposable( viewModel: LocalTimelineViewModel = injectViewModel(key = "local-timeline-key") { localTimelineViewModel } ) { InfinitePostsList(items = viewModel.localTimelineState.localTimeline, + contentPaddingTop = 30.dp, isLoading = viewModel.localTimelineState.isLoading, isRefreshing = viewModel.localTimelineState.refreshing, error = viewModel.localTimelineState.error, diff --git a/appstorebadgewhite.svg b/appstorebadgewhite.svg deleted file mode 100644 index 16c0496c..00000000 --- a/appstorebadgewhite.svg +++ /dev/null @@ -1,46 +0,0 @@ - - Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/pixelix_screenshots.png b/assets/pixelix_screenshots.png new file mode 100644 index 00000000..9ec51cdf Binary files /dev/null and b/assets/pixelix_screenshots.png differ diff --git a/github-image.png b/github-image.png deleted file mode 100644 index 28bce9d9..00000000 Binary files a/github-image.png and /dev/null differ diff --git a/google-play-button.png b/google-play-button.png deleted file mode 100644 index 7a06997a..00000000 Binary files a/google-play-button.png and /dev/null differ diff --git a/metadata/de/changelogs/33.txt b/metadata/de/changelogs/33.txt new file mode 100644 index 00000000..854f41d2 --- /dev/null +++ b/metadata/de/changelogs/33.txt @@ -0,0 +1 @@ +- Neues Design 🎨 \ No newline at end of file diff --git a/metadata/en-US/changelogs/33.txt b/metadata/en-US/changelogs/33.txt new file mode 100644 index 00000000..1830d2d9 --- /dev/null +++ b/metadata/en-US/changelogs/33.txt @@ -0,0 +1 @@ +- New Design 🎨 \ No newline at end of file diff --git a/metadata/fr-FR/full_description.txt b/metadata/fr-FR/full_description.txt index 18d07801..56b19023 100644 --- a/metadata/fr-FR/full_description.txt +++ b/metadata/fr-FR/full_description.txt @@ -1 +1 @@ -Pixelix provides a smooth and intuitive interface for interacting with Pixelfed, the federated image-sharing social network. Designed with user experience in mind, Pixelix makes it simple to connect to your Pixelfed instance, upload photos directly from your device, and browse through your feed with ease. Whether you're a seasoned Pixelfed user or just getting started, Pixelix offers a streamlined way to share and discover visual content. \ No newline at end of file +Pixelix offre une interface fluide et intuitive pour interagir avec Pixelfed, le réseau social fédéré dédié au partage d’images. Conçu pour offrir une expérience utilisateur optimale, Pixelix permet de se connecter facilement à votre instance Pixelfed, d’ajouter des photos directement depuis votre appareil, et de parcourir votre fil en toute simplicité. Que vous soyez un·e utilisateur·rice chevronné·e de Pixelfed ou que vous débutiez tout juste, Pixelix offre un moyen simple et fluide de partager et découvrir du contenu visuel. \ No newline at end of file diff --git a/metadata/fr-FR/short_description.txt b/metadata/fr-FR/short_description.txt index 7b39709e..a15ce780 100644 --- a/metadata/fr-FR/short_description.txt +++ b/metadata/fr-FR/short_description.txt @@ -1 +1 @@ -Pixelix: a user-friendly Pixelfed client for photo uploads, browsing, & sharing. \ No newline at end of file +Pixelix : un client Pixelfed pour publier, naviguer & partager des photos. \ No newline at end of file