diff --git a/.gitignore b/.gitignore
index 2da05054..af336a98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ captures
xcuserdata/
*.jks
*.gpg
+/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/iosApp 2025-03-27 12-51-49/
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d33521..00000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index fa487d5c..00000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-Pixelix
\ No newline at end of file
diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
deleted file mode 100644
index 09cfc854..00000000
--- a/.idea/appInsightsSettings.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
deleted file mode 100644
index 7643783a..00000000
--- a/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- xmlns:android
-
- ^$
-
-
-
-
-
-
-
-
- xmlns:.*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*:id
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:name
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- name
-
- ^$
-
-
-
-
-
-
-
-
- style
-
- ^$
-
-
-
-
-
-
-
-
- .*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*
-
- http://schemas.android.com/apk/res/android
-
-
- ANDROID_ATTRIBUTE_ORDER
-
-
-
-
-
-
- .*
-
- .*
-
-
- BY_NAME
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index 79ee123c..00000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index b589d56e..00000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
deleted file mode 100644
index 1ce1c320..00000000
--- a/.idea/deploymentTargetDropDown.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
deleted file mode 100644
index ec5f8705..00000000
--- a/.idea/deploymentTargetSelector.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
deleted file mode 100644
index 7b3006b6..00000000
--- a/.idea/gradle.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index e0da3ee5..00000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
deleted file mode 100644
index d0667882..00000000
--- a/.idea/kotlinc.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
deleted file mode 100644
index f8051a6f..00000000
--- a/.idea/migrations.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 3b0be228..00000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
deleted file mode 100644
index 16660f1d..00000000
--- a/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1ddf..00000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ceca0bdb..b2f9b5d8 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,3 +1,5 @@
+import org.jetbrains.compose.desktop.application.dsl.TargetFormat.*
+
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.kotlinMultiplatform)
@@ -11,7 +13,7 @@ plugins {
kotlin {
jvmToolchain(17)
androidTarget()
-
+ jvm()
listOf(
iosX64(),
iosArm64(),
@@ -87,8 +89,6 @@ kotlin {
//image loader
implementation(libs.coil.compose)
- implementation(libs.coil.video)
- implementation(libs.coil.gif)
implementation(libs.coil.network)
//image crop
@@ -104,7 +104,6 @@ kotlin {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.activity.compose)
- implementation(libs.androidx.runtime.livedata)
implementation(libs.androidx.browser)
implementation(libs.accompanist.systemuicontroller)
@@ -116,6 +115,8 @@ kotlin {
implementation(libs.androidx.media3.exoplayer.dash)
implementation(libs.androidx.media3.ui)
implementation(libs.android.image.cropper)
+ implementation(libs.coil.video)
+ implementation(libs.coil.gif)
// widget
implementation(libs.androidx.glance.appwidget)
@@ -127,6 +128,21 @@ kotlin {
iosMain.dependencies {
implementation(libs.ktor.client.darwin)
}
+
+ jvmMain.dependencies {
+ implementation(compose.desktop.currentOs)
+ implementation(libs.kotlinx.coroutines.swing)
+ implementation(libs.ktor.client.okhttp)
+ implementation(libs.appdirs)
+ implementation(libs.slf4j.simple)
+ implementation(libs.vlcj)
+ implementation(libs.jna)
+ implementation(libs.jna.platform)
+ }
+ }
+
+ compilerOptions {
+ freeCompilerArgs.add("-Xexpect-actual-classes")
}
}
@@ -138,8 +154,8 @@ android {
applicationId = "com.daniebeler.pfpixelix"
minSdk = 26
targetSdk = 35
- versionCode = 30
- versionName = "4.0.3"
+ versionCode = 31
+ versionName = "4.1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -177,6 +193,7 @@ android {
dependencies {
listOf(
"kspAndroid",
+ "kspJvm",
"kspIosX64",
"kspIosArm64",
"kspIosSimulatorArm64"
@@ -184,3 +201,45 @@ dependencies {
add(it, libs.kotlin.inject.compiler.ksp)
}
}
+
+compose.desktop {
+ application {
+ mainClass = "com.daniebeler.pfpixelix.MainKt"
+
+ nativeDistributions {
+ targetFormats(Dmg, Msi, Deb)
+ packageName = "Pixelix"
+ packageVersion = "1.0.0"
+
+ //data store https://issuetracker.google.com/280205600
+ modules("jdk.unsupported")
+ modules("jdk.unsupported.desktop")
+
+ linux {
+ iconFile.set(project.file("desktopAppIcons/LinuxIcon.png"))
+ }
+ windows {
+ iconFile.set(project.file("desktopAppIcons/WindowsIcon.ico"))
+ }
+ macOS {
+ iconFile.set(project.file("desktopAppIcons/MacosIcon.icns"))
+ bundleID = "com.daniebeler.pfpixelix"
+ infoPlist {
+ extraKeysRawXml = """
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+ Pixelix auth redirect
+ CFBundleURLSchemes
+
+ pixelix-android-auth
+
+
+
+ """.trimIndent()
+ }
+ }
+ }
+ }
+}
diff --git a/app/desktopAppIcons/LinuxIcon.png b/app/desktopAppIcons/LinuxIcon.png
new file mode 100644
index 00000000..98f2c065
Binary files /dev/null and b/app/desktopAppIcons/LinuxIcon.png differ
diff --git a/app/desktopAppIcons/MacosIcon.icns b/app/desktopAppIcons/MacosIcon.icns
new file mode 100644
index 00000000..6f2c4c36
Binary files /dev/null and b/app/desktopAppIcons/MacosIcon.icns differ
diff --git a/app/desktopAppIcons/WindowsIcon.ico b/app/desktopAppIcons/WindowsIcon.ico
new file mode 100644
index 00000000..020bbf09
Binary files /dev/null and b/app/desktopAppIcons/WindowsIcon.ico differ
diff --git a/app/src/androidMain/AndroidManifest.xml b/app/src/androidMain/AndroidManifest.xml
index c21e23fc..6b3791a3 100644
--- a/app/src/androidMain/AndroidManifest.xml
+++ b/app/src/androidMain/AndroidManifest.xml
@@ -87,6 +87,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Unit,
- properties: DialogProperties,
- content: @Composable () -> Unit
-) {
- Dialog(
- onDismissRequest = onDismissRequest,
- properties = DialogProperties(
- dismissOnBackPress = properties.dismissOnBackPress,
- dismissOnClickOutside = properties.dismissOnClickOutside,
- usePlatformDefaultWidth = true,
- decorFitsSystemWindows = false
- ),
- content = {
- SetUpEdgeToEdgeDialog()
- content()
- }
- )
-}
-
-@Composable
-private fun SetUpEdgeToEdgeDialog() {
- val parentView = LocalView.current.parent as View
- val window = (parentView as DialogWindowProvider).window
-
- window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
-
- window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- window.attributes.fitInsetsTypes = 0
- window.attributes.fitInsetsSides = 0
- }
-}
+actual fun EdgeToEdgeDialogProperties(
+ dismissOnBackPress: Boolean,
+ dismissOnClickOutside: Boolean,
+ usePlatformDefaultWidth: Boolean
+): DialogProperties = DialogProperties(
+ dismissOnBackPress = dismissOnBackPress,
+ dismissOnClickOutside = dismissOnClickOutside,
+ usePlatformDefaultWidth = usePlatformDefaultWidth,
+ decorFitsSystemWindows = false
+)
private fun saveUriToCache(uri: Uri, contentResolver: ContentResolver, cacheDir: File): Uri? {
try {
diff --git a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/MyApplication.kt b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/MyApplication.kt
index bd731dc6..f4d84c86 100644
--- a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/MyApplication.kt
+++ b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/MyApplication.kt
@@ -3,6 +3,7 @@ package com.daniebeler.pfpixelix
import android.app.Activity
import android.app.Application
import android.content.Context
+import androidx.activity.ComponentActivity
import androidx.work.Configuration
import androidx.work.ListenableWorker
import androidx.work.WorkerFactory
@@ -10,6 +11,8 @@ import androidx.work.WorkerParameters
import coil3.SingletonImageLoader
import com.daniebeler.pfpixelix.di.AppComponent
import com.daniebeler.pfpixelix.di.create
+import com.daniebeler.pfpixelix.domain.service.file.AndroidFileService
+import com.daniebeler.pfpixelix.domain.service.icon.AndroidAppIconManager
import com.daniebeler.pfpixelix.utils.configureLogger
import com.daniebeler.pfpixelix.widget.notifications.work_manager.LatestImageTask
import com.daniebeler.pfpixelix.widget.notifications.work_manager.NotificationsTask
@@ -23,7 +26,11 @@ class MyApplication : Application(), Configuration.Provider {
get() = Configuration.Builder().setWorkerFactory(workerFactory).build()
override fun onCreate() {
- appComponent = AppComponent.create(this)
+ appComponent = AppComponent.create(
+ this,
+ AndroidFileService(this),
+ AndroidAppIconManager(this)
+ )
SingletonImageLoader.setSafe {
appComponent.provideImageLoader()
}
@@ -34,7 +41,7 @@ class MyApplication : Application(), Configuration.Provider {
companion object {
lateinit var appComponent: AppComponent
private set
- var currentActivity: WeakReference? = null
+ var currentActivity: WeakReference? = null
}
}
diff --git a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/AndroidFileService.kt b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/AndroidFileService.kt
new file mode 100644
index 00000000..1a31ef1d
--- /dev/null
+++ b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/AndroidFileService.kt
@@ -0,0 +1,226 @@
+package com.daniebeler.pfpixelix.domain.service.file
+
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.content.Context
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.Build
+import android.provider.MediaStore
+import android.provider.OpenableColumns
+import android.webkit.MimeTypeMap
+import android.widget.Toast
+import co.touchlab.kermit.Logger
+import coil3.ImageLoader
+import coil3.SingletonImageLoader
+import coil3.request.ImageRequest
+import coil3.request.SuccessResult
+import coil3.request.allowHardware
+import coil3.toBitmap
+import coil3.video.videoFrameMillis
+import com.daniebeler.pfpixelix.utils.KmpContext
+import com.daniebeler.pfpixelix.utils.KmpUri
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.datetime.Clock
+import okio.Path
+import okio.Path.Companion.toPath
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.FileNotFoundException
+
+class AndroidFileService(
+ private val context: KmpContext
+) : FileService {
+ override val dataStoreDir: Path = context.filesDir.path.toPath().resolve("datastore")
+ override val imageCacheDir: Path = context.cacheDir.path.toPath().resolve("image_cache")
+
+ override fun getFile(uri: KmpUri): PlatformFile? {
+ return AndroidFile(uri, context).takeIf { it.isExist() }
+ }
+
+ override fun downloadFile(name: String?, url: String) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ var uri: Uri? = null
+ val saveImageRoutine = CoroutineScope(Dispatchers.Default).launch {
+
+ val bitmap: Bitmap? = urlToBitmap(url, context)
+ if (bitmap == null) {
+ cancel("an error occured when downloading the image")
+ return@launch
+ }
+
+ uri = saveImageToMediaStore(
+ context,
+ generateUniqueName(name, false, context),
+ bitmap
+ )
+ if (uri == null) {
+ cancel("an error occured when saving the image")
+ return@launch
+ }
+ }
+
+ saveImageRoutine.invokeOnCompletion { throwable ->
+ CoroutineScope(Dispatchers.Main).launch {
+ uri?.let {
+ Toast.makeText(context, "Stored at: " + uri.toString(), Toast.LENGTH_LONG)
+ .show()
+ } ?: throwable?.let {
+ Toast.makeText(
+ context, "an error occurred downloading the image", Toast.LENGTH_LONG
+ ).show()
+ }
+ }
+ }
+ }
+ }
+
+ override fun getCacheSizeInBytes(): Long {
+ return imageCacheDir.toFile().walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
+ }
+
+ override fun cleanCache() {
+ imageCacheDir.toFile().deleteRecursively()
+ }
+
+ private fun generateUniqueName(
+ imageName: String?, returnFullPath: Boolean, context: KmpContext
+ ): String {
+
+ val filename = "${imageName}_${Clock.System.now().epochSeconds}"
+
+ if (returnFullPath) {
+ val directory: File = context.getDir("zest", Context.MODE_PRIVATE)
+ return "$directory/$filename"
+ } else {
+ return filename
+ }
+ }
+
+ private suspend fun urlToBitmap(
+ imageURL: String,
+ context: KmpContext,
+ ): Bitmap? {
+ val loader = ImageLoader(context)
+ val request = ImageRequest.Builder(context).data(imageURL).allowHardware(false).build()
+ val result = loader.execute(request)
+ if (result is SuccessResult) {
+ return result.image.toBitmap()
+ }
+ return null
+ }
+
+ private fun saveImageToMediaStore(
+ context: KmpContext,
+ displayName: String,
+ bitmap: Bitmap
+ ): Uri? {
+ val imageCollections = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
+ } else {
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ }
+
+ val imageDetails = ContentValues().apply {
+ put(MediaStore.Images.Media.DISPLAY_NAME, displayName)
+ put(MediaStore.Images.Media.MIME_TYPE, "image/png")
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ put(MediaStore.Images.Media.IS_PENDING, 1)
+ }
+ }
+
+ val resolver = context.applicationContext.contentResolver
+ val imageContentUri = resolver.insert(imageCollections, imageDetails) ?: return null
+
+ return try {
+ resolver.openOutputStream(imageContentUri, "w").use { os ->
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, os!!)
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ imageDetails.clear()
+ imageDetails.put(MediaStore.Images.Media.IS_PENDING, 0)
+ resolver.update(imageContentUri, imageDetails, null, null)
+ }
+
+ imageContentUri
+ } catch (e: FileNotFoundException) {
+ // Some legacy devices won't create directory for the Uri if dir not exist, resulting in
+ // a FileNotFoundException. To resolve this issue, we should use the File API to save the
+ // image, which allows us to create the directory ourselves.
+ null
+ }
+ }
+}
+
+private class AndroidFile(
+ private val uri: Uri,
+ private val context: Context
+) : PlatformFile {
+ override fun isExist(): Boolean =
+ getName() != "AndroidFile:unknown"
+
+ override fun getName(): String = when (uri.scheme) {
+ ContentResolver.SCHEME_FILE -> uri.pathSegments.last().substringBeforeLast('.')
+ ContentResolver.SCHEME_CONTENT -> context.contentResolver.query(
+ uri, null, null, null, null
+ )?.use {
+ val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ it.moveToFirst()
+ it.getString(nameIndex)
+ }
+
+ else -> null
+ } ?: "AndroidFile:unknown"
+
+ override fun getSize(): Long = when (uri.scheme) {
+ ContentResolver.SCHEME_FILE -> context.contentResolver.openFileDescriptor(uri, "r")
+ ?.use { it.statSize }
+
+ ContentResolver.SCHEME_CONTENT -> context.contentResolver.query(
+ uri, null, null, null, null
+ )?.use {
+ val nameIndex = it.getColumnIndex(OpenableColumns.SIZE)
+ it.moveToFirst()
+ it.getLong(nameIndex)
+ }
+
+ else -> null
+ } ?: 0L
+
+ override fun getMimeType(): String = when (uri.scheme) {
+ ContentResolver.SCHEME_FILE -> {
+ val fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString())
+ MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.lowercase())
+ }
+
+ ContentResolver.SCHEME_CONTENT -> {
+ context.contentResolver.getType(uri)
+ }
+
+ else -> null
+ } ?: "image/*"
+
+ override suspend fun readBytes(): ByteArray = withContext(Dispatchers.IO) {
+ context.contentResolver.openInputStream(uri)!!.readBytes()
+ }
+
+ override suspend fun getThumbnail(): ByteArray? = withContext(Dispatchers.IO) {
+ val bm = try {
+ val req = ImageRequest.Builder(context).data(uri).videoFrameMillis(0).build()
+ val img = SingletonImageLoader.get(context).execute(req)
+ img.image?.toBitmap()
+ } catch (e: Exception) {
+ Logger.e("AndroidFile.getThumbnail error", e)
+ null
+ } ?: return@withContext null
+
+ val stream = ByteArrayOutputStream()
+ bm.compress(Bitmap.CompressFormat.PNG, 100, stream)
+ stream.toByteArray()
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/AndroidAppIconManager.kt b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/AndroidAppIconManager.kt
new file mode 100644
index 00000000..9b8c4c07
--- /dev/null
+++ b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/AndroidAppIconManager.kt
@@ -0,0 +1,69 @@
+package com.daniebeler.pfpixelix.domain.service.icon
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import co.touchlab.kermit.Logger
+import org.jetbrains.compose.resources.DrawableResource
+import pixelix.app.generated.resources.Res
+import pixelix.app.generated.resources.app_icon_00
+import pixelix.app.generated.resources.app_icon_01
+import pixelix.app.generated.resources.app_icon_02
+import pixelix.app.generated.resources.app_icon_03
+import pixelix.app.generated.resources.app_icon_05
+import pixelix.app.generated.resources.app_icon_06
+import pixelix.app.generated.resources.app_icon_07
+import pixelix.app.generated.resources.app_icon_08
+import pixelix.app.generated.resources.app_icon_09
+
+class AndroidAppIconManager(
+ private val context: Context
+) : AppIconManager {
+ private val iconIds = mapOf(
+ Res.drawable.app_icon_00 to "com.daniebeler.pfpixelix.Icon04",
+ Res.drawable.app_icon_01 to "com.daniebeler.pfpixelix.Icon01",
+ Res.drawable.app_icon_02 to "com.daniebeler.pfpixelix.AppActivity",
+ Res.drawable.app_icon_03 to "com.daniebeler.pfpixelix.Icon03",
+ Res.drawable.app_icon_05 to "com.daniebeler.pfpixelix.Icon05",
+ Res.drawable.app_icon_06 to "com.daniebeler.pfpixelix.Icon06",
+ Res.drawable.app_icon_07 to "com.daniebeler.pfpixelix.Icon07",
+ Res.drawable.app_icon_08 to "com.daniebeler.pfpixelix.Icon08",
+ Res.drawable.app_icon_09 to "com.daniebeler.pfpixelix.Icon09",
+ )
+
+ override fun getCurrentIcon(): DrawableResource {
+ for ((res, id) in iconIds.entries) {
+ val i = context.packageManager.getComponentEnabledSetting(ComponentName(context, id))
+ if (i == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ return res
+ }
+ }
+ return Res.drawable.app_icon_02
+ }
+
+ override fun setCustomIcon(icon: DrawableResource) {
+ try {
+ val pm = context.packageManager
+ for ((res, id) in iconIds.entries) {
+ if (res != icon) {
+ val i = pm.getComponentEnabledSetting(ComponentName(context, id))
+ if (i == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ pm.setComponentEnabledSetting(
+ ComponentName(context, id),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP
+ )
+ }
+ } else {
+ pm.setComponentEnabledSetting(
+ ComponentName(context, iconIds[icon]!!),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP
+ )
+ }
+ }
+ } catch (e: Error) {
+ Logger.e("enableCustomIcon", e)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.android.kt b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.android.kt
index 9a7173e1..f1c9b754 100644
--- a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.android.kt
+++ b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.android.kt
@@ -2,68 +2,21 @@ package com.daniebeler.pfpixelix.domain.service.platform
import android.appwidget.AppWidgetManager
import android.content.ComponentName
-import android.content.ContentResolver
-import android.content.ContentValues
-import android.content.Context
import android.content.Intent
-import android.content.pm.PackageManager
-import android.graphics.Bitmap
import android.net.Uri
-import android.os.Build
-import android.provider.MediaStore
-import android.provider.OpenableColumns
-import android.webkit.MimeTypeMap
-import android.widget.Toast
import androidx.browser.customtabs.CustomTabsIntent
import co.touchlab.kermit.Logger
-import coil3.ImageLoader
-import coil3.SingletonImageLoader
-import coil3.request.ImageRequest
-import coil3.request.SuccessResult
-import coil3.request.allowHardware
-import coil3.toBitmap
-import coil3.video.videoFrameMillis
import com.daniebeler.pfpixelix.MyApplication
import com.daniebeler.pfpixelix.domain.service.preferences.UserPreferences
import com.daniebeler.pfpixelix.utils.KmpContext
-import com.daniebeler.pfpixelix.utils.KmpUri
import com.daniebeler.pfpixelix.widget.notifications.NotificationWidgetReceiver
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import kotlinx.datetime.Clock
import me.tatarka.inject.annotations.Inject
-import org.jetbrains.compose.resources.DrawableResource
-import pixelix.app.generated.resources.Res
-import pixelix.app.generated.resources.app_icon_00
-import pixelix.app.generated.resources.app_icon_01
-import pixelix.app.generated.resources.app_icon_02
-import pixelix.app.generated.resources.app_icon_03
-import pixelix.app.generated.resources.app_icon_05
-import pixelix.app.generated.resources.app_icon_06
-import pixelix.app.generated.resources.app_icon_07
-import pixelix.app.generated.resources.app_icon_08
-import pixelix.app.generated.resources.app_icon_09
-import java.io.ByteArrayOutputStream
-import java.io.File
-import java.io.FileNotFoundException
@Inject
actual class Platform actual constructor(
private val context: KmpContext,
private val prefs: UserPreferences
) {
- actual fun getPlatformFile(uri: KmpUri): PlatformFile? {
- val f = AndroidFile(uri, context)
- return if (f.getName() != "AndroidFile:unknown") f else null
- }
-
- actual fun getAppIconManager(): AppIconManager {
- return AndroidAppIconManager(context)
- }
-
actual fun openUrl(url: String) {
val activity = MyApplication.currentActivity?.get()
if (activity != null) {
@@ -94,7 +47,9 @@ actual class Platform actual constructor(
}
}
- actual fun getAppVersion(): String {
+ actual fun dismissBrowser() {}
+
+ actual fun getAppVersion(): String {
return try {
context.packageManager.getPackageInfo(context.packageName, 0).versionName
} catch (e: Exception) {
@@ -111,237 +66,4 @@ actual class Platform actual constructor(
appWidgetManager.requestPinAppWidget(myProvider, null, null)
}
}
-
- actual fun downloadImageToGallery(name: String?, url: String) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- var uri: Uri? = null
- val saveImageRoutine = CoroutineScope(Dispatchers.Default).launch {
-
- val bitmap: Bitmap? = urlToBitmap(url, context)
- if (bitmap == null) {
- cancel("an error occured when downloading the image")
- return@launch
- }
-
- println(bitmap.toString())
-
- uri = saveImageToMediaStore(
- context,
- generateUniqueName(name, false, context),
- bitmap!!
- )
- if (uri == null) {
- cancel("an error occured when saving the image")
- return@launch
- }
- }
-
- saveImageRoutine.invokeOnCompletion { throwable ->
- CoroutineScope(Dispatchers.Main).launch {
- uri?.let {
- Toast.makeText(context, "Stored at: " + uri.toString(), Toast.LENGTH_LONG)
- .show()
- } ?: throwable?.let {
- Toast.makeText(
- context, "an error occurred downloading the image", Toast.LENGTH_LONG
- ).show()
- }
- }
- }
- }
- }
-
- private fun generateUniqueName(
- imageName: String?, returnFullPath: Boolean, context: KmpContext
- ): String {
-
- val filename = "${imageName}_${Clock.System.now().epochSeconds}"
-
- if (returnFullPath) {
- val directory: File = context.getDir("zest", Context.MODE_PRIVATE)
- return "$directory/$filename"
- } else {
- return filename
- }
- }
-
- private suspend fun urlToBitmap(
- imageURL: String,
- context: KmpContext,
- ): Bitmap? {
- val loader = ImageLoader(context)
- val request = ImageRequest.Builder(context).data(imageURL).allowHardware(false).build()
- val result = loader.execute(request)
- if (result is SuccessResult) {
- return result.image.toBitmap()
- }
- return null
- }
-
- private fun saveImageToMediaStore(
- context: KmpContext,
- displayName: String,
- bitmap: Bitmap
- ): Uri? {
- val imageCollections = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
- } else {
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI
- }
-
- val imageDetails = ContentValues().apply {
- put(MediaStore.Images.Media.DISPLAY_NAME, displayName)
- put(MediaStore.Images.Media.MIME_TYPE, "image/png")
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- put(MediaStore.Images.Media.IS_PENDING, 1)
- }
- }
-
- val resolver = context.applicationContext.contentResolver
- val imageContentUri = resolver.insert(imageCollections, imageDetails) ?: return null
-
- return try {
- resolver.openOutputStream(imageContentUri, "w").use { os ->
- bitmap.compress(Bitmap.CompressFormat.PNG, 100, os!!)
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- imageDetails.clear()
- imageDetails.put(MediaStore.Images.Media.IS_PENDING, 0)
- resolver.update(imageContentUri, imageDetails, null, null)
- }
-
- imageContentUri
- } catch (e: FileNotFoundException) {
- // Some legacy devices won't create directory for the Uri if dir not exist, resulting in
- // a FileNotFoundException. To resolve this issue, we should use the File API to save the
- // image, which allows us to create the directory ourselves.
- null
- }
- }
-
- actual fun getCacheSizeInBytes(): Long {
- return context.cacheDir.walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
- }
-
- actual fun cleanCache() {
- context.cacheDir.deleteRecursively()
- }
-}
-
-private class AndroidFile(
- private val uri: Uri,
- private val context: Context
-) : PlatformFile {
- override fun getName(): String = when (uri.scheme) {
- ContentResolver.SCHEME_FILE -> uri.pathSegments.last().substringBeforeLast('.')
- ContentResolver.SCHEME_CONTENT -> context.contentResolver.query(
- uri, null, null, null, null
- )?.use {
- val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
- it.moveToFirst()
- it.getString(nameIndex)
- }
-
- else -> null
- } ?: "AndroidFile:unknown"
-
- override fun getSize(): Long = when (uri.scheme) {
- ContentResolver.SCHEME_FILE -> context.contentResolver.openFileDescriptor(uri, "r")
- ?.use { it.statSize }
-
- ContentResolver.SCHEME_CONTENT -> context.contentResolver.query(
- uri, null, null, null, null
- )?.use {
- val nameIndex = it.getColumnIndex(OpenableColumns.SIZE)
- it.moveToFirst()
- it.getLong(nameIndex)
- }
-
- else -> null
- } ?: 0L
-
- override fun getMimeType(): String = when (uri.scheme) {
- ContentResolver.SCHEME_FILE -> {
- val fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString())
- MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.lowercase())
- }
-
- ContentResolver.SCHEME_CONTENT -> {
- context.contentResolver.getType(uri)
- }
-
- else -> null
- } ?: "image/*"
-
- override suspend fun readBytes(): ByteArray = withContext(Dispatchers.IO) {
- context.contentResolver.openInputStream(uri)!!.readBytes()
- }
-
- override suspend fun getThumbnail(): ByteArray? = withContext(Dispatchers.IO) {
- val bm = try {
- val req = ImageRequest.Builder(context).data(uri).videoFrameMillis(0).build()
- val img = SingletonImageLoader.get(context).execute(req)
- img.image?.toBitmap()
- } catch (e: Exception) {
- Logger.e("AndroidFile.getThumbnail error", e)
- null
- } ?: return@withContext null
-
- val stream = ByteArrayOutputStream()
- bm.compress(Bitmap.CompressFormat.PNG, 100, stream)
- stream.toByteArray()
- }
}
-
-private class AndroidAppIconManager(
- private val context: Context
-) : AppIconManager {
- private val iconIds = mapOf(
- Res.drawable.app_icon_00 to "com.daniebeler.pfpixelix.Icon04",
- Res.drawable.app_icon_01 to "com.daniebeler.pfpixelix.Icon01",
- Res.drawable.app_icon_02 to "com.daniebeler.pfpixelix.AppActivity",
- Res.drawable.app_icon_03 to "com.daniebeler.pfpixelix.Icon03",
- Res.drawable.app_icon_05 to "com.daniebeler.pfpixelix.Icon05",
- Res.drawable.app_icon_06 to "com.daniebeler.pfpixelix.Icon06",
- Res.drawable.app_icon_07 to "com.daniebeler.pfpixelix.Icon07",
- Res.drawable.app_icon_08 to "com.daniebeler.pfpixelix.Icon08",
- Res.drawable.app_icon_09 to "com.daniebeler.pfpixelix.Icon09",
- )
-
- override fun getCurrentIcon(): DrawableResource {
- for ((res, id) in iconIds.entries) {
- val i = context.packageManager.getComponentEnabledSetting(ComponentName(context, id))
- if (i == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
- return res
- }
- }
- return Res.drawable.app_icon_02
- }
-
- override fun setCustomIcon(icon: DrawableResource) {
- try {
- val pm = context.packageManager
- for ((res, id) in iconIds.entries) {
- if (res != icon) {
- val i = pm.getComponentEnabledSetting(ComponentName(context, id))
- if (i == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
- pm.setComponentEnabledSetting(
- ComponentName(context, id),
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
- PackageManager.DONT_KILL_APP
- )
- }
- } else {
- pm.setComponentEnabledSetting(
- ComponentName(context, iconIds[icon]!!),
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
- PackageManager.DONT_KILL_APP
- )
- }
- }
- } catch (e: Error) {
- Logger.e("enableCustomIcon", e)
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.android.kt b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.android.kt
index 94ccf91b..f6ee25f4 100644
--- a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.android.kt
+++ b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.android.kt
@@ -6,4 +6,8 @@ actual object PlatformFeatures {
actual val notificationWidgets = true
actual val inAppBrowser = true
actual val downloadToGallery = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+ actual val customAppIcon = true
+ actual val autoplayVideosPref = true
+ actual val addCollection = true
+ actual val customAccentColors = false
}
\ No newline at end of file
diff --git a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.android.kt b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.android.kt
index 4de94b36..a3e45a53 100644
--- a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.android.kt
+++ b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.android.kt
@@ -5,67 +5,56 @@ import android.os.Build
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
-import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
+import androidx.core.view.WindowInsetsControllerCompat
+import co.touchlab.kermit.Logger
+import com.daniebeler.pfpixelix.MyApplication
+import com.daniebeler.pfpixelix.di.LocalAppComponent
import com.daniebeler.pfpixelix.domain.model.AppThemeMode.AMOLED
import com.daniebeler.pfpixelix.domain.model.AppThemeMode.DARK
import com.daniebeler.pfpixelix.domain.model.AppThemeMode.LIGHT
-import com.daniebeler.pfpixelix.utils.KmpContext
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
-actual fun KmpContext.generateColorScheme(
+@Composable
+actual fun generateColorScheme(
nightModeValue: Int,
dynamicColor: Boolean,
lightScheme: ColorScheme,
darkScheme: ColorScheme
-): ColorScheme = if (dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- when (nightModeValue) {
- AMOLED -> dynamicDarkColorScheme(this).toAmoled()
- DARK -> dynamicDarkColorScheme(this)
- else -> dynamicLightColorScheme(this)
- }
-} else {
- when (nightModeValue) {
- AMOLED -> darkScheme.toAmoled()
- DARK -> darkScheme
- else -> lightScheme
- }
-}
-
-actual fun applySystemNightMode(mode: Int) {
- AppCompatDelegate.setDefaultNightMode(
- when (mode) {
- LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
- AMOLED, DARK -> AppCompatDelegate.MODE_NIGHT_YES
- else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
+): ColorScheme {
+ val context = LocalAppComponent.current.context
+ return remember(
+ nightModeValue, dynamicColor, lightScheme, darkScheme
+ ) {
+ if (dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ when (nightModeValue) {
+ AMOLED -> dynamicDarkColorScheme(context).toAmoled()
+ DARK -> dynamicDarkColorScheme(context)
+ else -> dynamicLightColorScheme(context)
+ }
+ } else {
+ when (nightModeValue) {
+ AMOLED -> darkScheme.toAmoled()
+ DARK -> darkScheme
+ else -> lightScheme
+ }
}
- )
+ }
}
-@Composable
-actual fun ChangeSystemBarColors(mode: Int) {
- (LocalKmpContext.current as ComponentActivity).enableEdgeToEdge(
- when (mode) {
- LIGHT -> SystemBarStyle.light(
- Color.Transparent.toArgb(), Color.Transparent.toArgb()
- )
-
- AMOLED, DARK -> SystemBarStyle.dark(
- Color.Transparent.toArgb()
- )
-
- else -> SystemBarStyle.dark(
- Color.Transparent.toArgb()
- )
- }
-
- )
-
+actual fun applySystemNightMode(isDark: Boolean) {
+ val activity = MyApplication.currentActivity?.get() ?: return
+ val window = activity.window
+ Logger.d { "applySystemNightMode isDark=$isDark" }
+ WindowInsetsControllerCompat(window, window.decorView).apply {
+ isAppearanceLightStatusBars = !isDark
+ isAppearanceLightNavigationBars = !isDark
+ }
}
\ No newline at end of file
diff --git a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.android.kt b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.android.kt
index 3c209fe8..3ce9c589 100644
--- a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.android.kt
+++ b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.android.kt
@@ -5,9 +5,6 @@ import android.net.Uri
import androidx.core.net.toUri
import coil3.PlatformContext
import io.github.vinceglb.filekit.core.PlatformFile
-import okio.Path
-import okio.Path.Companion.toPath
-import java.io.File
actual typealias KmpUri = Uri
actual val EmptyKmpUri: KmpUri = Uri.EMPTY
@@ -17,7 +14,3 @@ actual fun PlatformFile.toKmpUri(): KmpUri = this.uri
actual typealias KmpContext = Context
actual val KmpContext.coilContext: PlatformContext get() = this
-actual val KmpContext.dataStoreDir: Path
- get() = File(applicationContext.filesDir, "datastore").path.toPath()
-actual val KmpContext.imageCacheDir: Path
- get() = cacheDir.path.toPath().resolve("image_cache")
diff --git a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.android.kt b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.android.kt
index 26539f14..86860231 100644
--- a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.android.kt
+++ b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.android.kt
@@ -2,6 +2,7 @@ package com.daniebeler.pfpixelix.utils
import androidx.annotation.OptIn
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.AudioAttributes
@@ -13,6 +14,7 @@ import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView
+import com.daniebeler.pfpixelix.MyApplication
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
@@ -24,6 +26,7 @@ actual class VideoPlayer actual constructor(
) {
actual var progress: ((current: Long, duration: Long) -> Unit)? = null
actual var hasAudio: ((Boolean) -> Unit)? = null
+ actual var isVideoPlaying: ((Boolean) -> Unit)? = null
private val audioAttributes =
AudioAttributes.Builder()
@@ -48,6 +51,10 @@ actual class VideoPlayer actual constructor(
}
}
}
+
+ override fun onIsPlayingChanged(isPlaying: Boolean) {
+ isVideoPlaying?.invoke(isPlaying)
+ }
})
}
@@ -67,6 +74,9 @@ actual class VideoPlayer actual constructor(
@OptIn(UnstableApi::class)
@Composable
actual fun view(modifier: Modifier) {
+ LaunchedEffect(player) {
+ player.isPlaying
+ }
AndroidView(
modifier = modifier,
factory = { ctx ->
diff --git a/app/src/commonMain/composeResources/drawable/blur.xml b/app/src/commonMain/composeResources/drawable/blur.xml
new file mode 100644
index 00000000..939aff7b
--- /dev/null
+++ b/app/src/commonMain/composeResources/drawable/blur.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/commonMain/composeResources/drawable/sync_outline_bold.xml b/app/src/commonMain/composeResources/drawable/sync_outline_bold.xml
new file mode 100644
index 00000000..cf7a297e
--- /dev/null
+++ b/app/src/commonMain/composeResources/drawable/sync_outline_bold.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/app/src/commonMain/composeResources/drawable/trash.xml b/app/src/commonMain/composeResources/drawable/trash.xml
new file mode 100644
index 00000000..76205fce
--- /dev/null
+++ b/app/src/commonMain/composeResources/drawable/trash.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/commonMain/composeResources/drawable/warning.xml b/app/src/commonMain/composeResources/drawable/warning.xml
new file mode 100644
index 00000000..531b7831
--- /dev/null
+++ b/app/src/commonMain/composeResources/drawable/warning.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/app/src/commonMain/composeResources/values-af-rZA/strings.xml b/app/src/commonMain/composeResources/values-af-rZA/strings.xml
index 6cc98a41..260b83ab 100644
--- a/app/src/commonMain/composeResources/values-af-rZA/strings.xml
+++ b/app/src/commonMain/composeResources/values-af-rZA/strings.xml
@@ -18,7 +18,19 @@
Follow
Unfollow
Followers
+
+ - Follower
+ - Followers
+
Following
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +228,55 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-ar-rSA/strings.xml b/app/src/commonMain/composeResources/values-ar-rSA/strings.xml
index 143125e8..a8c6658a 100644
--- a/app/src/commonMain/composeResources/values-ar-rSA/strings.xml
+++ b/app/src/commonMain/composeResources/values-ar-rSA/strings.xml
@@ -18,7 +18,31 @@
متابعة
إلغاء المُتابعة
متابِعون
+
+ - Followers
+ - Follower
+ - Followers
+ - Followers
+ - Followers
+ - Followers
+
يتابع
+
+ - Following
+ - Following
+ - Following
+ - Following
+ - Following
+ - Following
+
+
+ - Posts
+ - Post
+ - Posts
+ - Posts
+ - Posts
+ - Posts
+
المنشورات
الوسوم
reblogged your post
@@ -216,4 +240,83 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - seconds
+ - second
+ - seconds
+ - seconds
+ - seconds
+ - seconds
+
+
+ - minutes
+ - minute
+ - minutes
+ - minutes
+ - minutes
+ - minutes
+
+
+ - hours
+ - hour
+ - hours
+ - hours
+ - hours
+ - hours
+
+
+ - days
+ - day
+ - days
+ - days
+ - days
+ - days
+
+
+ - weeks
+ - week
+ - weeks
+ - weeks
+ - weeks
+ - weeks
+
+
+ - months
+ - month
+ - months
+ - months
+ - months
+ - months
+
+
+ - years
+ - year
+ - years
+ - years
+ - years
+ - years
+
+ 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-ca-rES/strings.xml b/app/src/commonMain/composeResources/values-ca-rES/strings.xml
index efd8ab74..c13171f3 100644
--- a/app/src/commonMain/composeResources/values-ca-rES/strings.xml
+++ b/app/src/commonMain/composeResources/values-ca-rES/strings.xml
@@ -18,7 +18,19 @@
Segueix
Deixa de seguir
Seguidors
+
+ - Follower
+ - Followers
+
Següent
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Entrades
Etiquetes
ha reblogjat la teva publicació
@@ -216,4 +228,55 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-cs-rCZ/strings.xml b/app/src/commonMain/composeResources/values-cs-rCZ/strings.xml
index 9b998259..0eec7024 100644
--- a/app/src/commonMain/composeResources/values-cs-rCZ/strings.xml
+++ b/app/src/commonMain/composeResources/values-cs-rCZ/strings.xml
@@ -18,7 +18,25 @@
Sledovat
Zrušit sledování
Sledující
+
+ - Follower
+ - Followers
+ - Followers
+ - Followers
+
Sleduje
+
+ - Following
+ - Following
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+ - Posts
+ - Posts
+
Příspěvky
Hashtagy
reblogoval tvůj příspěvek
@@ -216,4 +234,69 @@
Schová popisky, liky a tlačítka u příspěvků.
Nastavení repostu
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+ - seconds
+ - seconds
+
+
+ - minute
+ - minutes
+ - minutes
+ - minutes
+
+
+ - hour
+ - hours
+ - hours
+ - hours
+
+
+ - day
+ - days
+ - days
+ - days
+
+
+ - week
+ - weeks
+ - weeks
+ - weeks
+
+
+ - month
+ - months
+ - months
+ - months
+
+
+ - year
+ - years
+ - years
+ - years
+
+ 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-da-rDK/strings.xml b/app/src/commonMain/composeResources/values-da-rDK/strings.xml
index f7ab73d9..995ae8dd 100644
--- a/app/src/commonMain/composeResources/values-da-rDK/strings.xml
+++ b/app/src/commonMain/composeResources/values-da-rDK/strings.xml
@@ -18,7 +18,19 @@
Følg
Følg ikke
Followers
+
+ - Follower
+ - Followers
+
Following
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +228,55 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-de-rDE/strings.xml b/app/src/commonMain/composeResources/values-de-rDE/strings.xml
index 645c1607..3a22ca8a 100644
--- a/app/src/commonMain/composeResources/values-de-rDE/strings.xml
+++ b/app/src/commonMain/composeResources/values-de-rDE/strings.xml
@@ -18,7 +18,19 @@
Folgen
Entfolgen
Follower
+
+ - Follower
+ - Followers
+
Gefolgt
+
+ - Following
+ - Following
+
+
+ - Beitrag
+ - Beiträge
+
Beiträge
Hashtags
hat deinen Beitrag geteilt
@@ -206,14 +218,65 @@
%1$s Version %2$s
Besuche %1$s
Anzeigename
- Bio
+ Über mich
Website
Standort
Erwähnungen
Entdecken
geteilt von %1$s
- Focus Mode
- Hides description, likes and buttons from posts.
+ Fokusmodus
+ Versteckt Beschreibung, Likes und Buttons von Beiträgen.
Boost-Einstellungen
AMOLED
+ Noch keine Follower
+ Sie folgen niemanden
+ Mehr lesen
+ Weniger anzeigen
+ Beigetreten %1$s
+ her
+ Nachricht löschen
+ Video-Player wird nicht unterstützt
+
+ - Sekunde
+ - Sekunden
+
+
+ - Minute
+ - Minuten
+
+
+ - Stunde
+ - Stunden
+
+
+ - Tag
+ - Tage
+
+
+ - Woche
+ - Wochen
+
+
+ - Monat
+ - Monate
+
+
+ - Jahr
+ - Jahre
+
+ 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-el-rGR/strings.xml b/app/src/commonMain/composeResources/values-el-rGR/strings.xml
index e043bc94..6e5c925e 100644
--- a/app/src/commonMain/composeResources/values-el-rGR/strings.xml
+++ b/app/src/commonMain/composeResources/values-el-rGR/strings.xml
@@ -18,7 +18,19 @@
Ακολούθησε
Άρση ακολούθησης
Ακόλουθοι
+
+ - Ακόλουθος
+ - Ακόλουθοι
+
Ακολουθείς
+
+ - Ακολουθεί
+ - Ακολουθούν
+
+
+ - Ανάρτηση
+ - Αναρτήσεις
+
Αναρτήσεις
Ετικέτες
αναδημοσίευσε την ανάρτησή σου
@@ -216,4 +228,55 @@
Κρύβει την περιγραφή, μου αρέσει και κουμπιά από αναρτήσεις.
Ρυθμίσεις αναδημοσίευσης
AMOLED
+ Κανένας ακόλουθος ακόμα
+ Δεν ακολουθείται κανένας
+ Διάβασε περισσότερα
+ Διάβασε Λιγότερα
+ Έγινε Μέλος %1$s
+ πριν
+ Διαγραφή μηνύματος
+ Ο αναπαραγωγέας βίντεο δεν υποστηρίζεται
+
+ - δευτερόλεπτο
+ - δευτερόλεπτα
+
+
+ - λεπτό
+ - λεπτά
+
+
+ - ώρα
+ - ώρες
+
+
+ - ημέρα
+ - ημέρες
+
+
+ - εβδομάδα
+ - εβδομάδες
+
+
+ - μήνας
+ - μήνες
+
+
+ - έτος
+ - έτη
+
+ 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 6cc98a41..260b83ab 100644
--- a/app/src/commonMain/composeResources/values-en-rUS/strings.xml
+++ b/app/src/commonMain/composeResources/values-en-rUS/strings.xml
@@ -18,7 +18,19 @@
Follow
Unfollow
Followers
+
+ - Follower
+ - Followers
+
Following
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +228,55 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-eo-rUY/strings.xml b/app/src/commonMain/composeResources/values-eo-rUY/strings.xml
index 6cc98a41..260b83ab 100644
--- a/app/src/commonMain/composeResources/values-eo-rUY/strings.xml
+++ b/app/src/commonMain/composeResources/values-eo-rUY/strings.xml
@@ -18,7 +18,19 @@
Follow
Unfollow
Followers
+
+ - Follower
+ - Followers
+
Following
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +228,55 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-es-rES/strings.xml b/app/src/commonMain/composeResources/values-es-rES/strings.xml
index 4d3e71d0..467ab369 100644
--- a/app/src/commonMain/composeResources/values-es-rES/strings.xml
+++ b/app/src/commonMain/composeResources/values-es-rES/strings.xml
@@ -18,7 +18,19 @@
Seguir
No seguir
Seguidores
+
+ - Seguidor
+ - Seguidores
+
Siguiendo
+
+ - Siguiendo
+ - Siguiendo
+
+
+ - Publicación
+ - Publicaciones
+
Publicaciones
Hashtags
reblogueó tu publicación
@@ -216,4 +228,55 @@
Oculta la descripción, me gusta y los botones de los mensajes.
Configuración de volver a publicar
AMOLED
+ Todavía no hay seguidores
+ No sigues a nadie
+ Leer más
+ Leer menos
+ Se unió el %1$s
+ hace
+ Eliminar mensaje
+ El reproductor de vídeo no es compatible
+
+ - segundo
+ - segundos
+
+
+ - minuto
+ - minutos
+
+
+ - hora
+ - horas
+
+
+ - día
+ - días
+
+
+ - semana
+ - semanas
+
+
+ - mes
+ - meses
+
+
+ - año
+ - años
+
+ Difuminar contenido sensible
+ Reproducir automáticamente los videos
+ Reportar esta publicación
+ Reportar
+ Spam
+ Contenido para adultos o sensible
+ Abusivo o dañino
+ Cuenta para menores
+ Violencia
+ Violación de derechos de autor
+ Suplantación de la identidad
+ Estafa
+ Terrorismo
+ Reporte enviado
+ Eliminar la cuenta
diff --git a/app/src/commonMain/composeResources/values-fi-rFI/strings.xml b/app/src/commonMain/composeResources/values-fi-rFI/strings.xml
index f2c95dc3..09830f8a 100644
--- a/app/src/commonMain/composeResources/values-fi-rFI/strings.xml
+++ b/app/src/commonMain/composeResources/values-fi-rFI/strings.xml
@@ -18,7 +18,19 @@
Seuraa
Lopeta seuraaminen
Seuraajat
+
+ - Follower
+ - Followers
+
Seurataan
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Julkaisut
Aihetunnisteet
jakoi julkaisusi
@@ -216,4 +228,55 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-fr-rFR/strings.xml b/app/src/commonMain/composeResources/values-fr-rFR/strings.xml
index 403dd5e1..02926154 100644
--- a/app/src/commonMain/composeResources/values-fr-rFR/strings.xml
+++ b/app/src/commonMain/composeResources/values-fr-rFR/strings.xml
@@ -18,7 +18,19 @@
Suivre
Ne plus suivre
Abonné·e·s
+
+ - Abonné
+ - Abonné·e·s
+
Abonnements
+
+ - Abonnements
+ - Abonnements
+
+
+ - Publication
+ - Publications
+
Publications
Hashtags
a republié votre publication
@@ -28,8 +40,8 @@
Tendances
Comptes
Règles
- Version de l\'instance
- Conditions générales d\'utilisation
+ Version de l’instance
+ Conditions générales d’utilisation
Politique de Confidentialité
Administrateur·rice
Utilisateur·rice·s
@@ -54,24 +66,24 @@
tous les ans
tous les mois
tous les jours
- Je n\'ai pas de compte
+ Je n’ai pas de compte
URL du serveur
Voulez-vous vraiment vous déconnecter ?
Se déconnecter ?
Afficher %1$s commentaires
Laisser un commentaire
Aucun commentaire pour l\'instant
- Personne n\'a aimé pour l\'instant
+ Personne n’a aimé pour l’instant
aucune publication aimée
Local
Fédéré
Accueil
- %1$s J\'aime
+ %1$s J’aime
Rechercher
Ne plus masquer
ne plus masquer
Description du média
- Aucune publication pour l\'instant
+ Aucune publication pour l’instant
vous a mentionné·e
Aucune publication favorite
Rien à afficher
@@ -81,7 +93,7 @@
légende
Média suggestif/sensible
avertissement de contenu ou texte caché
- Audience
+ Visibilité
non répertorié
abonné·e·s seulement
public
@@ -102,12 +114,12 @@
Explorer les profils tendances
Personne ne vous suit actuellement
Vide
- Suivez des comptes ou hashtags pour remplir votre fil d\'actualité
+ Suivez des comptes ou hashtags pour remplir votre fil d’actualité
Aucune publication
- Le fil d\'accueil vous montre les publications des personnes et hashtags que vous suivez
+ Le fil d’accueil vous montre les publications des personnes et hashtags que vous suivez
Le fil local affiche les messages de tous les utilisateur·rices de votre serveur
Le fil fédéré affiche les publications des utilisateur·rices de tous les serveurs fédérés au vôtre
- Cet utilisateur·rice n\'a pas posté pour l\'instant
+ Cet utilisateur·rice n’a pas posté pour l\'instant
Aucune nouvelle notification
voir plus
Modifier le profil
@@ -118,31 +130,31 @@
Pixelfed est un réseau de partage de photos et d\'images sur le Fediverse avec une interface orientée photo, qui comprend des albums, des filtres, des moments etc.
Mastodon est une plateforme de microblogging libre et open source
PeerTube est un outil de partage de vidéos en ligne développé par Framasoft, un organisme à but non lucratif français.
- Lemmy est une plate-forme auto-hébergée de discussion et d\'agrégation de liens. Elle est entièrement libre et ouverte, et non contrôlée par aucune entreprise. Cela signifie qu\'il n\'y a pas d\'algorithmes secret, de publicité ou de tracking. Le contenu est organisé en communautés, il est donc facile de s\'abonner à des sujets qui vous intéressent, et d\'ignorer les autres. Le vote est utilisé pour faire remonter les sujets les plus intéressants.
- Threads est une application de Meta avec laquelle vous pouvez voir et partager publiquement des conversations. Vous pouvez également poster des fils, répondre à d\'autres personnes et suivre des profils qui vous intéressent.
+ Lemmy est une plate-forme auto-hébergée de discussion et d\'agrégation de liens. Elle est entièrement libre et ouverte, et non contrôlée par aucune entreprise. Cela signifie qu’il n’y a pas d\'algorithmes secret, de publicité ou de tracking. Le contenu est organisé en communautés, il est donc facile de s’abonner à des sujets qui vous intéressent, et d’ignorer les autres. Le vote est utilisé pour faire remonter les sujets les plus intéressants.
+ Threads est une application de Meta avec laquelle vous pouvez voir et partager publiquement des conversations. Vous pouvez également poster des fils, répondre à d’autres personnes et suivre des profils qui vous intéressent.
Plus d\'informations
- Qu\'est-ce qui rend une publication tendance ?
+ Qu’est-ce qui rend une publication tendance ?
Les publications sont tendances si elles ont été postées pendant une période sélectionnée et ont beaucoup de J\'aime.
- La section \"comptes tendances\" liste les comptes sur votre instance avec le plus d\'abonné·e·s. Les comptes que vous suivez déjà sont cachés.
- Qu\'est-ce qui rend un compte tendance ?
- Qu\'est-ce qui rend un hashtag tendance ?
+ La section “comptes tendances”liste les comptes sur votre instance avec le plus d’abonné·e·s. Les comptes que vous suivez déjà sont cachés.
+ Qu’est-ce qui rend un compte tendance ?
+ Qu’est-ce qui rend un hashtag tendance ?
Tendance des hashtags si beaucoup d\'utilisateur·rice·s les ont utilisés récemment.
Profil privé
- • Vous ne verrez pas l\'utilisateur·rice dans votre fil d\'actualité
- • Vous ne verrez pas d\'autres personnes republier l\'utilisateur
- • Vous ne verrez pas d\'autres personnes mentionner l\'utilisateur·rice
- • Vous ne verrez pas l\'utilisateur·rice dans les fils publics
- L\'utilisateur n\'a aucun moyen de savoir qu\'il a été masqué.
- • Vous ne verrez pas l\'utilisateur·rice dans votre fil d\'actualité
- • Vous ne verrez pas d\'autres personnes republier l\'utilisateur
- • Vous ne verrez pas d\'autres personnes mentionner l\'utilisateur·rice
- • Vous ne verrez pas l\'utilisateur·rice dans les fils publics
+ • Vous ne verrez pas l\'’utilisateur·rice dans votre fil d\'actualité
+ • Vous ne verrez pas d’autres personnes republier l’utilisateur
+ • Vous ne verrez pas d’autres personnes mentionner l’utilisateur·rice
+ • Vous ne verrez pas l’utilisateur·rice dans les fils publics
+ L\'utilisateur n’a aucun moyen de savoir qu\'il a été masqué.
+ • Vous ne verrez pas l’utilisateur·rice dans votre fil d’actualité
+ • Vous ne verrez pas d’autres personnes republier l’utilisateur
+ • Vous ne verrez pas d’autres personnes mentionner l’utilisateur·rice
+ • Vous ne verrez pas l’utilisateur·rice dans les fils publics
• Vous ne verrez pas de notifications provenant de cet utilisateur·rice
- • L\'utilisateur·rice est automatiquement désabonné
- • L\'utilisateur·rice ne peut plus vous suivre
- • L\'utilisateur·rice ne verra pas les republications d\'autres personnes de vous
- • L\'utilisateur·rice ne vous verra pas dans les fils publics
- Si vous et l\'utilisateur·rice bloqué·e sont sur le même serveur, l\'utilisateur·rice bloqué·e ne pourra pas voir vos messages sur votre profil en étant connecté.
+ • L’utilisateur·rice est automatiquement désabonné
+ • L’utilisateur·rice ne peut plus vous suivre
+ • L’utilisateur·rice ne verra pas les republications d’autres personnes de vous
+ • L’utilisateur·rice ne vous verra pas dans les fils publics
+ Si vous et l’utilisateur·rice bloqué·e sont sur le même serveur, l’utilisateur·rice bloqué·e ne pourra pas voir vos messages sur votre profil en étant connecté.
Bloquer
Bloquer le compte ?
Ne plus masquer le compte ?
@@ -157,9 +169,9 @@
Sélectionner un destinataire
Nouveau Message Privé
Avertissement
- "Les messages privés sur Pixelfed ne sont pas chiffrés de bout en bout. Soyez prudent·e·s lorsque vous partagez des données sensibles. "
+ "Les messages privés sur Pixelfed ne sont pas chiffrés de bout en bout. Soyez prudent·e·s lorsque vous partagez des données sensibles."
Confirmer
- Ceci est le début de votre conversation avec cet·te utilisateur·rice. N\'oubliez pas d\'être respectueux.
+ Ceci est le début de votre conversation avec cet·te utilisateur·rice. N’oubliez pas d’être respectueux.
Évaluez-nous
À propos de Pixelix
Développé par
@@ -167,7 +179,7 @@
Partager cette collection
Par %1$s
Nouvelle Collection
- La création de collections n\'est pas encore disponible dans l\'api de Pixelfed, mais une nouvelle collection peut être créée sur l\'application web de Pixelfed :
+ La création de collections n\'est pas encore disponible dans l’API de Pixelfed, mais une nouvelle collection peut être créée sur l’application web de Pixelfed :
Nouveau
Collections
Vous suit
@@ -175,17 +187,17 @@
bloqué·e
Message
Tout
- J\'aime
+ J’aime
Contenu partagé
Modifier cette publication
- Restez à jour avec vos dernières notifications de Pixelfed directement sur votre écran d\'accueil
- Personnaliser l\'icône de l\'application
- Changer l\'icône de l\'application ?
- L\'icône peut mettre un certain temps à s\'afficher.
+ Restez à jour avec vos dernières notifications de Pixelfed directement sur votre écran d’accueil
+ Personnaliser l’icône de l’application
+ Changer l’icône de l’application ?
+ L’icône peut mettre un certain temps à s’afficher.
Modifier
vous a mentionné·e
- jetez un coup d\'œil à la dernière publication de votre fil d\'actualité, directement de votre écran d\'accueil
- Si une icône autre que celle par défaut est sélectionnée, deux icônes s\'afficheront dans votre tiroir d\'applications.
+ jetez un coup d’œil à la dernière publication de votre fil d’actualité, directement de votre écran d\'accueil
+ 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
@@ -201,19 +213,70 @@
Cacher le bouton texte alternatif
Instances :
Nombre total de publications :
- Nombre total d\'utilisateur·rices :
+ Nombre total d’utilisateur·rices :
utilisateur·rices actif·ves :
%1$s version %2$s
Se rendre sur %1$s
Pseudonyme
Description
- Site Internet
+ Site web
Localisation
Mentions
Explorer
reblogged by %1$s
Mode sans distraction
- Masque la description, les J\'aime et les boutons des messages.
- Repost settings
+ Masque la description, les J’aime et les boutons des messages.
+ Paramètres de republication
AMOLED
+ Aucun abonné pour le moment
+ Vous ne suivez personne
+ Lire la suite
+ En voir moins
+ Inscrit·e le %1$s
+ ago
+ Supprimer le message
+ Lecteur vidéo non pris en charge
+
+ - seconde
+ - secondes
+
+
+ - minute
+ - minutes
+
+
+ - heure
+ - heures
+
+
+ - jour
+ - jours
+
+
+ - semaine
+ - semaines
+
+
+ - mois
+ - mois
+
+
+ - année
+ - années
+
+ 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-gl-rES/strings.xml b/app/src/commonMain/composeResources/values-gl-rES/strings.xml
index 8b7a3470..4d4bac14 100644
--- a/app/src/commonMain/composeResources/values-gl-rES/strings.xml
+++ b/app/src/commonMain/composeResources/values-gl-rES/strings.xml
@@ -18,7 +18,19 @@
Seguir
Deixar de seguir
Seguidoras
+
+ - Seguidora
+ - Seguidoras
+
Seguindo
+
+ - Seguimento
+ - Seguimentos
+
+
+ - Publicación
+ - Publicacións
+
Publicacións
Cancelos
compartiu a túa publicación
@@ -211,9 +223,60 @@
Localización
Mencións
Descubrir
- reblogged by %1$s
- Focus Mode
- Hides description, likes and buttons from posts.
- Repost settings
+ compartido por %1$s
+ Modo de Concentración
+ Oculta a descrición, os favorecementos e os botóns nas publicacións.
+ Axustes das promocións
AMOLED
+ Sen seguidoras
+ Sen seguimentos
+ Ler máis
+ Ler menos
+ Creada hai %1$s
+ ago
+ Eliminar mensaxe
+ Video player is not supported
+
+ - segundo
+ - segundos
+
+
+ - minuto
+ - minutos
+
+
+ - hora
+ - horas
+
+
+ - día
+ - días
+
+
+ - semana
+ - semanas
+
+
+ - mes
+ - meses
+
+
+ - ano
+ - anos
+
+ 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-hu-rHU/strings.xml b/app/src/commonMain/composeResources/values-hu-rHU/strings.xml
index 4bfa394c..bbb93f51 100644
--- a/app/src/commonMain/composeResources/values-hu-rHU/strings.xml
+++ b/app/src/commonMain/composeResources/values-hu-rHU/strings.xml
@@ -18,7 +18,19 @@
Követés
Követés vége
Követő
+
+ - Követő
+ - Követő
+
Követett
+
+ - Követett
+ - Követett
+
+
+ - Bejegyzés
+ - Bejegyzés
+
Bejegyzés
Hashtagek
megosztotta a bejegyzésed
@@ -216,4 +228,55 @@
Elrejti a bejegyzések alól a leírást, a kedveléseket és a gombokat.
Megosztások beállítása
AMOLED
+ Még nincsenek követői
+ Nem követ senkit
+ Bővebben
+ Rövidebben
+ Csatlakozás ideje %1$s
+ ezelőtt
+ Üzenet törlése
+ Nem támogatott videólejátszó
+
+ - másodperccel
+ - másodperccel
+
+
+ - perccel
+ - perccel
+
+
+ - órával
+ - órával
+
+
+ - nappal
+ - nappal
+
+
+ - héttel
+ - héttel
+
+
+ - hónappal
+ - hónappal
+
+
+ - évvel
+ - évvel
+
+ Érzékeny tartalom elhomályosítása
+ Videók automatikus lejátszása
+ Bejegyzés jelentése
+ Jelentés
+ Spam
+ Felnőtt vagy érzékeny tartalom
+ Bántalmazó vagy káros
+ Kiskorú fiók
+ Erőszak
+ Szerzői jog megsértése
+ Hamis személyazonosság
+ Csalás
+ Terrorizmus
+ Jelentve
+ Fiók törlése
diff --git a/app/src/commonMain/composeResources/values-io-rEN/strings.xml b/app/src/commonMain/composeResources/values-io-rEN/strings.xml
index 6cc98a41..260b83ab 100644
--- a/app/src/commonMain/composeResources/values-io-rEN/strings.xml
+++ b/app/src/commonMain/composeResources/values-io-rEN/strings.xml
@@ -18,7 +18,19 @@
Follow
Unfollow
Followers
+
+ - Follower
+ - Followers
+
Following
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +228,55 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-it-rIT/strings.xml b/app/src/commonMain/composeResources/values-it-rIT/strings.xml
index 06662c93..015bb83c 100644
--- a/app/src/commonMain/composeResources/values-it-rIT/strings.xml
+++ b/app/src/commonMain/composeResources/values-it-rIT/strings.xml
@@ -18,7 +18,19 @@
Segui
Smetti di seguire
Seguaci
+
+ - Follower
+ - Followers
+
Seguiti
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Post
Hashtag
ha ricondiviso il tuo post
@@ -216,4 +228,55 @@
Nasconde le descrizioni, i \"mi piace\" e i pulsanti dai post.
Impostazioni di ripubblicazione
AMOLED
+ Ancora nessun seguace
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ fa
+ Elimina messaggio
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-iw-rIL/strings.xml b/app/src/commonMain/composeResources/values-iw-rIL/strings.xml
index 6cc98a41..a5c0bc55 100644
--- a/app/src/commonMain/composeResources/values-iw-rIL/strings.xml
+++ b/app/src/commonMain/composeResources/values-iw-rIL/strings.xml
@@ -18,7 +18,25 @@
Follow
Unfollow
Followers
+
+ - Follower
+ - Followers
+ - Followers
+ - Followers
+
Following
+
+ - Following
+ - Following
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+ - Posts
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +234,69 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+ - seconds
+ - seconds
+
+
+ - minute
+ - minutes
+ - minutes
+ - minutes
+
+
+ - hour
+ - hours
+ - hours
+ - hours
+
+
+ - day
+ - days
+ - days
+ - days
+
+
+ - week
+ - weeks
+ - weeks
+ - weeks
+
+
+ - month
+ - months
+ - months
+ - months
+
+
+ - year
+ - years
+ - years
+ - years
+
+ 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-ja-rJP/strings.xml b/app/src/commonMain/composeResources/values-ja-rJP/strings.xml
index 6cc98a41..8ab1324a 100644
--- a/app/src/commonMain/composeResources/values-ja-rJP/strings.xml
+++ b/app/src/commonMain/composeResources/values-ja-rJP/strings.xml
@@ -18,7 +18,16 @@
Follow
Unfollow
Followers
+
+ - Followers
+
Following
+
+ - Following
+
+
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +225,48 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - seconds
+
+
+ - minutes
+
+
+ - hours
+
+
+ - days
+
+
+ - weeks
+
+
+ - months
+
+
+ - years
+
+ 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-ko-rKR/strings.xml b/app/src/commonMain/composeResources/values-ko-rKR/strings.xml
index 6cc98a41..8ab1324a 100644
--- a/app/src/commonMain/composeResources/values-ko-rKR/strings.xml
+++ b/app/src/commonMain/composeResources/values-ko-rKR/strings.xml
@@ -18,7 +18,16 @@
Follow
Unfollow
Followers
+
+ - Followers
+
Following
+
+ - Following
+
+
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +225,48 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - seconds
+
+
+ - minutes
+
+
+ - hours
+
+
+ - days
+
+
+ - weeks
+
+
+ - months
+
+
+ - years
+
+ 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-nl-rNL/strings.xml b/app/src/commonMain/composeResources/values-nl-rNL/strings.xml
index cc036b6b..804d2c8f 100644
--- a/app/src/commonMain/composeResources/values-nl-rNL/strings.xml
+++ b/app/src/commonMain/composeResources/values-nl-rNL/strings.xml
@@ -18,7 +18,19 @@
Volgen
Stop met volgen
Volgers
+
+ - Follower
+ - Followers
+
Gevolgd
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Berichten
Hashtags
deelde je bericht
@@ -216,4 +228,55 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-no-rNO/strings.xml b/app/src/commonMain/composeResources/values-no-rNO/strings.xml
index c4839d4e..c1bc613c 100644
--- a/app/src/commonMain/composeResources/values-no-rNO/strings.xml
+++ b/app/src/commonMain/composeResources/values-no-rNO/strings.xml
@@ -18,7 +18,19 @@
Følg
Avfølg
Følgere
+
+ - Follower
+ - Followers
+
Følger
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Innlegg
Emneknagger
reblogged your post
@@ -216,4 +228,55 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-pl-rPL/strings.xml b/app/src/commonMain/composeResources/values-pl-rPL/strings.xml
index a62144d2..7345ef66 100644
--- a/app/src/commonMain/composeResources/values-pl-rPL/strings.xml
+++ b/app/src/commonMain/composeResources/values-pl-rPL/strings.xml
@@ -18,7 +18,25 @@
Obserwuj
Przestań obserwować
Obserwujące
+
+ - Obserwujący
+ - Obserwujące
+ - Obserwujących
+ - Obserwujących
+
Obserwowane
+
+ - Obserwowany
+ - Obserwowane
+ - Obserwowanych
+ - Obserwowanych
+
+
+ - Wpis
+ - Wpisy
+ - Wpisów
+ - Wpisów
+
Wpisy
Hasztagi
podbił(a) twój wpis
@@ -48,7 +66,7 @@
Usunąć
To już koniec!
oraz
- Następnie
+ Obserwują:
inne
inne
rocznie
@@ -216,4 +234,69 @@
Ukrywa opisy, polubienia i przyciski z wpisów.
Ustawienia podbić
AMOLED
+ Na razie brak obserwujących kont
+ Brak obserwowanych kont
+ Więcej
+ Mniej
+ Data dołączenia: %1$s
+ temu
+ Usuń wiadomość
+ Odtwarzacz wideo nie jest obsługiwany
+
+ - sekundę
+ - sekundy
+ - sekund
+ - sekund
+
+
+ - minutę
+ - minuty
+ - minut
+ - minut
+
+
+ - godzinę
+ - godziny
+ - godzin
+ - godzin
+
+
+ - dzień
+ - dni
+ - dni
+ - dni
+
+
+ - tydzień
+ - tygodnie
+ - tygodni
+ - tygodni
+
+
+ - miesiąc
+ - miesiące
+ - miesięcy
+ - miesięcy
+
+
+ - rok
+ - lata
+ - lat
+ - lat
+
+ Rozmyj drażliwe treści
+ Automatycznie odtwarzaj wideo
+ Zgłoś ten wpis
+ Zgłoś
+ Spam
+ Zawartość drażliwa lub dla dorosłych
+ Obraźliwe lub krzywdzące
+ Konto osoby niepełnoletniej
+ Przemoc
+ Naruszenie praw autorskich
+ Podszywanie się
+ Scam
+ Terroryzm
+ Zgłoszono
+ Delete Account
diff --git a/app/src/commonMain/composeResources/values-pt-rBR/strings.xml b/app/src/commonMain/composeResources/values-pt-rBR/strings.xml
index 56b99a60..9e9decc7 100644
--- a/app/src/commonMain/composeResources/values-pt-rBR/strings.xml
+++ b/app/src/commonMain/composeResources/values-pt-rBR/strings.xml
@@ -18,7 +18,19 @@
Seguir
Deixar de seguir
Seguidores
+
+ - Follower
+ - Followers
+
Seguindo
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Postagens
Hashtags
compartilhou sua publicação
@@ -216,4 +228,55 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-pt-rPT/strings.xml b/app/src/commonMain/composeResources/values-pt-rPT/strings.xml
index 9bb14948..39d4e6db 100644
--- a/app/src/commonMain/composeResources/values-pt-rPT/strings.xml
+++ b/app/src/commonMain/composeResources/values-pt-rPT/strings.xml
@@ -18,7 +18,19 @@
Seguir
Deixar de seguir
Seguidores
+
+ - Follower
+ - Followers
+
Seguindo
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Publicações
Etiquetas
impulsionou a tua publicação
@@ -216,4 +228,55 @@
Oculta a descrição, gostos e botões das publicações.
Configurações de impulsos
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-ro-rRO/strings.xml b/app/src/commonMain/composeResources/values-ro-rRO/strings.xml
index bfab0c7f..6c01831a 100644
--- a/app/src/commonMain/composeResources/values-ro-rRO/strings.xml
+++ b/app/src/commonMain/composeResources/values-ro-rRO/strings.xml
@@ -18,7 +18,22 @@
Follow
Unfollow
Followers
+
+ - Follower
+ - Followers
+ - Followers
+
Following
+
+ - Following
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +231,62 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+ - seconds
+
+
+ - minute
+ - minutes
+ - minutes
+
+
+ - hour
+ - hours
+ - hours
+
+
+ - day
+ - days
+ - days
+
+
+ - week
+ - weeks
+ - weeks
+
+
+ - month
+ - months
+ - months
+
+
+ - year
+ - years
+ - years
+
+ 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-ru-rRU/strings.xml b/app/src/commonMain/composeResources/values-ru-rRU/strings.xml
index fddfc0a2..799814b4 100644
--- a/app/src/commonMain/composeResources/values-ru-rRU/strings.xml
+++ b/app/src/commonMain/composeResources/values-ru-rRU/strings.xml
@@ -18,7 +18,25 @@
Подписаться
Отписаться
Подписчиков
+
+ - Follower
+ - Followers
+ - Followers
+ - Followers
+
В читаемых
+
+ - Following
+ - Following
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+ - Posts
+ - Posts
+
Записей
Хештеги
переопубликовал вашу запись
@@ -216,4 +234,69 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+ - seconds
+ - seconds
+
+
+ - minute
+ - minutes
+ - minutes
+ - minutes
+
+
+ - hour
+ - hours
+ - hours
+ - hours
+
+
+ - day
+ - days
+ - days
+ - days
+
+
+ - week
+ - weeks
+ - weeks
+ - weeks
+
+
+ - month
+ - months
+ - months
+ - months
+
+
+ - year
+ - years
+ - years
+ - years
+
+ 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-sr-rSP/strings.xml b/app/src/commonMain/composeResources/values-sr-rSP/strings.xml
index 6cc98a41..a9d18d17 100644
--- a/app/src/commonMain/composeResources/values-sr-rSP/strings.xml
+++ b/app/src/commonMain/composeResources/values-sr-rSP/strings.xml
@@ -18,7 +18,22 @@
Follow
Unfollow
Followers
+
+ - Follower
+ - Followers
+ - Followers
+
Following
+
+ - Following
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +231,62 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+ - seconds
+
+
+ - minute
+ - minutes
+ - minutes
+
+
+ - hour
+ - hours
+ - hours
+
+
+ - day
+ - days
+ - days
+
+
+ - week
+ - weeks
+ - weeks
+
+
+ - month
+ - months
+ - months
+
+
+ - year
+ - years
+ - years
+
+ 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-sv-rSE/strings.xml b/app/src/commonMain/composeResources/values-sv-rSE/strings.xml
index 95264320..7590d33c 100644
--- a/app/src/commonMain/composeResources/values-sv-rSE/strings.xml
+++ b/app/src/commonMain/composeResources/values-sv-rSE/strings.xml
@@ -18,7 +18,19 @@
Följa
Sluta följa
Anhängare
+
+ - Follower
+ - Followers
+
Följande
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Inlägg
Hashtags
rebloggade ditt inlägg
@@ -216,4 +228,55 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-tlh-rAA/strings.xml b/app/src/commonMain/composeResources/values-tlh-rAA/strings.xml
index 6cc98a41..260b83ab 100644
--- a/app/src/commonMain/composeResources/values-tlh-rAA/strings.xml
+++ b/app/src/commonMain/composeResources/values-tlh-rAA/strings.xml
@@ -18,7 +18,19 @@
Follow
Unfollow
Followers
+
+ - Follower
+ - Followers
+
Following
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +228,55 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-tr-rTR/strings.xml b/app/src/commonMain/composeResources/values-tr-rTR/strings.xml
index 6cc98a41..260b83ab 100644
--- a/app/src/commonMain/composeResources/values-tr-rTR/strings.xml
+++ b/app/src/commonMain/composeResources/values-tr-rTR/strings.xml
@@ -18,7 +18,19 @@
Follow
Unfollow
Followers
+
+ - Follower
+ - Followers
+
Following
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +228,55 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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-uk-rUA/strings.xml b/app/src/commonMain/composeResources/values-uk-rUA/strings.xml
index 41e00c90..8dac5d54 100644
--- a/app/src/commonMain/composeResources/values-uk-rUA/strings.xml
+++ b/app/src/commonMain/composeResources/values-uk-rUA/strings.xml
@@ -18,7 +18,25 @@
Підписатися
Відписатися
Підписники
+
+ - Follower
+ - Followers
+ - Followers
+ - Followers
+
Слідкую
+
+ - Following
+ - Following
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+ - Posts
+ - Posts
+
Публікації
Хештеґи
репост вашого допису
@@ -212,8 +230,73 @@
Згадування
Досліджуйте
репост від %1$s
- Focus Mode
- Hides description, likes and buttons from posts.
- Repost settings
+ Режим фокусу
+ Приховує опис, кнопки \"подобається\" і \".
+ Налаштування репостів
AMOLED
+ Поки немає підписників!
+ Нікого не відстежую
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+ - seconds
+ - seconds
+
+
+ - minute
+ - minutes
+ - minutes
+ - minutes
+
+
+ - hour
+ - hours
+ - hours
+ - hours
+
+
+ - day
+ - days
+ - days
+ - days
+
+
+ - week
+ - weeks
+ - weeks
+ - weeks
+
+
+ - month
+ - months
+ - months
+ - months
+
+
+ - year
+ - years
+ - years
+ - years
+
+ 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-vi-rVN/strings.xml b/app/src/commonMain/composeResources/values-vi-rVN/strings.xml
index 6cc98a41..8ab1324a 100644
--- a/app/src/commonMain/composeResources/values-vi-rVN/strings.xml
+++ b/app/src/commonMain/composeResources/values-vi-rVN/strings.xml
@@ -18,7 +18,16 @@
Follow
Unfollow
Followers
+
+ - Followers
+
Following
+
+ - Following
+
+
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +225,48 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - seconds
+
+
+ - minutes
+
+
+ - hours
+
+
+ - days
+
+
+ - weeks
+
+
+ - months
+
+
+ - years
+
+ 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-zh-rCN/strings.xml b/app/src/commonMain/composeResources/values-zh-rCN/strings.xml
index 6cc98a41..8ab1324a 100644
--- a/app/src/commonMain/composeResources/values-zh-rCN/strings.xml
+++ b/app/src/commonMain/composeResources/values-zh-rCN/strings.xml
@@ -18,7 +18,16 @@
Follow
Unfollow
Followers
+
+ - Followers
+
Following
+
+ - Following
+
+
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +225,48 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - seconds
+
+
+ - minutes
+
+
+ - hours
+
+
+ - days
+
+
+ - weeks
+
+
+ - months
+
+
+ - years
+
+ 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-zh-rTW/strings.xml b/app/src/commonMain/composeResources/values-zh-rTW/strings.xml
index 6cc98a41..8ab1324a 100644
--- a/app/src/commonMain/composeResources/values-zh-rTW/strings.xml
+++ b/app/src/commonMain/composeResources/values-zh-rTW/strings.xml
@@ -18,7 +18,16 @@
Follow
Unfollow
Followers
+
+ - Followers
+
Following
+
+ - Following
+
+
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -216,4 +225,48 @@
Hides description, likes and buttons from posts.
Repost settings
AMOLED
+ No followers yet
+ Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - seconds
+
+
+ - minutes
+
+
+ - hours
+
+
+ - days
+
+
+ - weeks
+
+
+ - months
+
+
+ - years
+
+ 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/strings.xml b/app/src/commonMain/composeResources/values/strings.xml
index 73b2f698..7006afe9 100644
--- a/app/src/commonMain/composeResources/values/strings.xml
+++ b/app/src/commonMain/composeResources/values/strings.xml
@@ -19,7 +19,19 @@
Follow
Unfollow
Followers
+
+ - Follower
+ - Followers
+
Following
+
+ - Following
+ - Following
+
+
+ - Post
+ - Posts
+
Posts
Hashtags
reblogged your post
@@ -219,4 +231,53 @@
AMOLED
No followers yet
Not following anyone
+ Read More
+ Read Less
+ Joined %1$s
+ ago
+ Delete message
+ Video player is not supported
+
+ - second
+ - seconds
+
+
+ - minute
+ - minutes
+
+
+ - hour
+ - hours
+
+
+ - day
+ - days
+
+
+ - week
+ - weeks
+
+
+ - month
+ - months
+
+
+ - year
+ - years
+
+ 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/kotlin/com/daniebeler/pfpixelix/App.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/App.kt
index bfda2d85..edf6c469 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/App.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/App.kt
@@ -1,11 +1,8 @@
package com.daniebeler.pfpixelix
-import androidx.compose.animation.AnimatedContentTransitionScope
-import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
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
@@ -30,12 +27,14 @@ import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
import androidx.compose.material3.rememberDrawerState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -48,61 +47,47 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
-import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavDestination.Companion.hasRoute
+import androidx.navigation.NavDestination.Companion.hierarchy
+import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
-import androidx.navigation.compose.dialog
import androidx.navigation.compose.rememberNavController
-import androidx.navigation.navArgument
import co.touchlab.kermit.Logger
import coil3.compose.AsyncImage
import com.daniebeler.pfpixelix.di.AppComponent
import com.daniebeler.pfpixelix.di.LocalAppComponent
-import com.daniebeler.pfpixelix.ui.composables.HomeComposable
import com.daniebeler.pfpixelix.ui.composables.ReverseModalNavigationDrawer
-import com.daniebeler.pfpixelix.ui.composables.collection.CollectionComposable
-import com.daniebeler.pfpixelix.ui.composables.direct_messages.chat.ChatComposable
-import com.daniebeler.pfpixelix.ui.composables.direct_messages.conversations.ConversationsComposable
-import com.daniebeler.pfpixelix.ui.composables.edit_post.EditPostComposable
-import com.daniebeler.pfpixelix.ui.composables.edit_profile.EditProfileComposable
-import com.daniebeler.pfpixelix.ui.composables.explore.ExploreComposable
-import com.daniebeler.pfpixelix.ui.composables.followers.FollowersMainComposable
-import com.daniebeler.pfpixelix.ui.composables.mention.MentionComposable
-import com.daniebeler.pfpixelix.ui.composables.newpost.NewPostComposable
-import com.daniebeler.pfpixelix.ui.composables.notifications.NotificationsComposable
-import com.daniebeler.pfpixelix.ui.composables.profile.other_profile.OtherProfileComposable
import com.daniebeler.pfpixelix.ui.composables.profile.own_profile.AccountSwitchBottomSheet
-import com.daniebeler.pfpixelix.ui.composables.profile.own_profile.OwnProfileComposable
-import com.daniebeler.pfpixelix.ui.composables.session.LoginComposable
-import com.daniebeler.pfpixelix.ui.composables.settings.about_instance.AboutInstanceComposable
-import com.daniebeler.pfpixelix.ui.composables.settings.about_pixelix.AboutPixelixComposable
-import com.daniebeler.pfpixelix.ui.composables.settings.blocked_accounts.BlockedAccountsComposable
-import com.daniebeler.pfpixelix.ui.composables.settings.bookmarked_posts.BookmarkedPostsComposable
-import com.daniebeler.pfpixelix.ui.composables.settings.followed_hashtags.FollowedHashtagsComposable
-import com.daniebeler.pfpixelix.ui.composables.settings.icon_selection.IconSelectionComposable
-import com.daniebeler.pfpixelix.ui.composables.settings.liked_posts.LikedPostsComposable
-import com.daniebeler.pfpixelix.ui.composables.settings.muted_accounts.MutedAccountsComposable
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.PreferencesComposable
-import com.daniebeler.pfpixelix.ui.composables.single_post.SinglePostComposable
-import com.daniebeler.pfpixelix.ui.composables.timelines.hashtag_timeline.HashtagTimelineComposable
+import com.daniebeler.pfpixelix.ui.navigation.Destination
+import com.daniebeler.pfpixelix.ui.navigation.appGraph
import com.daniebeler.pfpixelix.ui.theme.PixelixTheme
-import com.daniebeler.pfpixelix.utils.Destinations
-import com.daniebeler.pfpixelix.utils.KmpUri
-import com.daniebeler.pfpixelix.utils.Navigate
import com.daniebeler.pfpixelix.utils.end
-import com.daniebeler.pfpixelix.utils.toKmpUri
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
-import kotlinx.serialization.json.Json
+import org.jetbrains.compose.resources.DrawableResource
+import org.jetbrains.compose.resources.StringResource
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.add_circle
+import pixelix.app.generated.resources.add_circle_outline
+import pixelix.app.generated.resources.bookmark_outline
import pixelix.app.generated.resources.default_avatar
+import pixelix.app.generated.resources.home
+import pixelix.app.generated.resources.house
+import pixelix.app.generated.resources.house_fill
+import pixelix.app.generated.resources.new_post
+import pixelix.app.generated.resources.notifications
+import pixelix.app.generated.resources.notifications_outline
+import pixelix.app.generated.resources.profile
+import pixelix.app.generated.resources.search
+import pixelix.app.generated.resources.search_outline
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -166,25 +151,27 @@ fun App(
)
},
content = { paddingValues ->
+ val startDestination =
+ if (activeUser == null) Destination.FirstLogin
+ else Destination.HomeTabFeeds
NavHost(
modifier = Modifier.fillMaxSize().padding(paddingValues)
.consumeWindowInsets(WindowInsets.navigationBars),
navController = navController,
- startDestination = Destinations.FirstLogin.route,
+ startDestination = startDestination,
builder = {
- navigationGraph(
+ appGraph(
navController,
{ scope.launch { drawerState.open() } },
exitApp
)
}
)
+ val launchUser = remember { activeUser }
LaunchedEffect(activeUser) {
- val rootScreen = if (activeUser == null) {
- Destinations.FirstLogin.route
- } else {
- Destinations.HomeScreen.route
- }
+ if (launchUser == activeUser) return@LaunchedEffect
+ val rootScreen =
+ if (activeUser == null) Destination.FirstLogin else Destination.HomeTabFeeds
navController.navigate(rootScreen) {
val root = navController.currentBackStack.value
.firstOrNull { it.destination.route != null }
@@ -196,11 +183,8 @@ fun App(
if (activeUser != null) {
appComponent.systemFileShare.shareFilesRequests.collect { uris ->
- val urisJson = Json.encodeToString(
- uris.map { uri -> uri.toString() }
- )
- Navigate.navigate(
- "new_post_screen?uris=$urisJson", navController
+ navController.navigate(
+ Destination.NewPost(uris.map { it.toString() })
)
}
}
@@ -225,242 +209,49 @@ fun App(
}
}
-private fun NavGraphBuilder.navigationGraph(
- navController: NavHostController,
- openPreferencesDrawer: () -> Unit,
- exitApp: () -> Unit
+private enum class HomeTab(
+ val destination: Destination,
+ val icon: DrawableResource,
+ val activeIcon: DrawableResource,
+ val label: StringResource
) {
- dialog(
- route = Destinations.FirstLogin.route,
- ) {
- EdgeToEdgeDialog(
- onDismissRequest = exitApp,
- properties = DialogProperties(
- dismissOnClickOutside = false,
- usePlatformDefaultWidth = false,
- )
- ) {
- LoginComposable(navController = navController)
- }
- }
- dialog(route = Destinations.NewLogin.route) {
- EdgeToEdgeDialog(
- onDismissRequest = { navController.popBackStack() },
- properties = DialogProperties(
- dismissOnClickOutside = false,
- usePlatformDefaultWidth = false,
- )
- ) {
- LoginComposable(true, navController)
- }
- }
-
- composable(Destinations.HomeScreen.route) {
- HomeComposable(navController, openPreferencesDrawer)
- }
-
- composable(Destinations.NotificationsScreen.route) {
- NotificationsComposable(navController)
- }
-
- composable(Destinations.Profile.route) { navBackStackEntry ->
- /* val uId = navBackStackEntry.arguments?.read {
- if (hasValue("userid")) getString("userid") else null
- }*/
- val uId = navBackStackEntry.arguments?.getString("userid")
-
- uId?.let { id ->
- OtherProfileComposable(navController, userId = id, byUsername = null)
-
- }
- }
-
- composable(Destinations.ProfileByUsername.route) { navBackStackEntry ->
- /*val username = navBackStackEntry.arguments?.read {
- if (hasValue("username")) getString("username") else null
- }
-*/
- val username = navBackStackEntry.arguments?.getString("username")
-
- username?.let {
- OtherProfileComposable(navController, userId = "", byUsername = it)
- }
- }
-
- composable(Destinations.Hashtag.route) { navBackStackEntry ->
- /*val uId = navBackStackEntry.arguments?.read {
- if (hasValue("hashtag")) getString("hashtag") else null
- }*/
- val uId = navBackStackEntry.arguments?.getString("hashtag")
-
- uId?.let { id ->
- HashtagTimelineComposable(navController, id)
- }
- }
-
- composable(Destinations.EditProfile.route) {
- EditProfileComposable(navController)
- }
-
- composable(Destinations.IconSelection.route) {
- IconSelectionComposable(navController)
- }
-
- composable("${Destinations.NewPost.route}?uris={uris}") { navBackStackEntry ->
- /*val urisJson = navBackStackEntry.arguments?.read {
- if (hasValue("uris")) getString("uris") else null
- }
-*/
- val urisJson = navBackStackEntry.arguments?.getString("uris")
-
- val imageUris: List? = urisJson?.let { json ->
- Json.decodeFromString>(json).map { it.toKmpUri() }
- }
- NewPostComposable(navController, imageUris)
- }
-
- composable(Destinations.EditPost.route) { navBackStackEntry ->
- /*val postId = navBackStackEntry.arguments?.read {
- if (hasValue("postId")) getString("postId") else null
- }
-*/
- val postId = navBackStackEntry.arguments?.getString("postId")
- postId?.let { id ->
- EditPostComposable(postId, navController)
- }
- }
-
- composable(Destinations.MutedAccounts.route) {
- MutedAccountsComposable(navController)
- }
-
- composable(Destinations.BlockedAccounts.route) {
- BlockedAccountsComposable(navController)
- }
-
- composable(Destinations.LikedPosts.route) {
- LikedPostsComposable(navController)
- }
-
- composable(Destinations.BookmarkedPosts.route) {
- BookmarkedPostsComposable(navController)
- }
-
- composable(Destinations.FollowedHashtags.route) {
- FollowedHashtagsComposable(navController)
- }
-
- composable(Destinations.AboutInstance.route) {
- AboutInstanceComposable(navController)
- }
-
- composable(Destinations.AboutPixelix.route) {
- AboutPixelixComposable(navController)
- }
-
- composable(Destinations.OwnProfile.route) {
- OwnProfileComposable(navController, openPreferencesDrawer)
- }
-
- composable(Destinations.Followers.route) { navBackStackEntry ->
- /*val uId = navBackStackEntry.arguments?.read {
- if (hasValue("userid")) getString("userid") else null
- }
- val page = navBackStackEntry.arguments?.read {
- if (hasValue("page")) getString("page") else null
- }*/
- val uId = navBackStackEntry.arguments?.getString("userid")
- val page = navBackStackEntry.arguments?.getString("page")
- if (uId != null && page != null) {
- FollowersMainComposable(navController, accountId = uId, page = page)
- }
- }
-
- composable(
- "${Destinations.SinglePost.route}?refresh={refresh}&openReplies={openReplies}",
- arguments = listOf(navArgument("refresh") {
- defaultValue = false
- }, navArgument("openReplies") {
- defaultValue = false
- })
- ) { navBackStackEntry ->
- /*val uId = navBackStackEntry.arguments?.read {
- if (hasValue("postid")) getString("postid") else null
- }
- val refresh = navBackStackEntry.arguments?.read { getBoolean("refresh") }!!
- val openReplies = navBackStackEntry.arguments?.read { getBoolean("openReplies") }!!
- */
- val uId = navBackStackEntry.arguments?.getString("postid")
- val refresh = navBackStackEntry.arguments?.getBoolean("refresh")!!
- val openReplies = navBackStackEntry.arguments?.getBoolean("openReplies")!!
- uId?.let { id ->
- SinglePostComposable(navController, postId = id, refresh, openReplies)
- }
- }
-
- composable(Destinations.Collection.route) { navBackStackEntry ->
- /* val uId = navBackStackEntry.arguments?.read {
- if (hasValue("collectionid")) getString("collectionid") else null
- }*/
- val uId = navBackStackEntry.arguments?.getString("collectionid")
-
- uId?.let { id ->
- CollectionComposable(navController, collectionId = id)
- }
- }
-
- composable(Destinations.Search.route) { navBackStackEntry ->
- /*val initialPage = navBackStackEntry.arguments?.read {
- if (hasValue("initialPage")) getInt("initialPage") else 0
- }*/
- val initialPage = navBackStackEntry.arguments?.getInt("initialPage") ?: 0
-
- initialPage?.let {
- ExploreComposable(navController, initialPage)
- }
- }
-
- composable(Destinations.Conversation.route) {
- ConversationsComposable(navController = navController)
- }
-
- composable(Destinations.Chat.route) { navBackStackEntry ->
- /* val uId = navBackStackEntry.arguments?.read {
- if (hasValue("userid")) getString("userid") else null
- }*/
- val uId = navBackStackEntry.arguments?.getString("userid")
-
- uId?.let { id ->
- ChatComposable(navController = navController, accountId = id)
- }
- }
-
- composable(Destinations.Mention.route) { navBackStackEntry ->
- /* val mentionId = navBackStackEntry.arguments?.read {
- if (hasValue("mentionid")) getString("mentionid") else null
- }*/
- val mentionId = navBackStackEntry.arguments?.getString("mentionid")
-
- mentionId?.let { id ->
- MentionComposable(navController = navController, mentionId = id)
- }
- }
+ Feeds(
+ Destination.HomeTabFeeds,
+ Res.drawable.house,
+ Res.drawable.house_fill,
+ Res.string.home
+ ),
+ Search(
+ Destination.HomeTabSearch,
+ Res.drawable.search_outline,
+ Res.drawable.search,
+ Res.string.search
+ ),
+ NewPost(
+ Destination.HomeTabNewPost,
+ Res.drawable.add_circle_outline,
+ Res.drawable.add_circle,
+ Res.string.new_post
+ ),
+ Notifications(
+ Destination.HomeTabNotifications,
+ Res.drawable.notifications_outline,
+ Res.drawable.notifications,
+ Res.string.notifications
+ ),
+ OwnProfile(
+ Destination.HomeTabOwnProfile,
+ Res.drawable.bookmark_outline,
+ Res.drawable.bookmark_outline,
+ Res.string.profile
+ )
}
-//private fun SavedStateReader.hasValue(key: String) = contains(key) && !isNull(key)
-
@Composable
private fun BottomBar(
navController: NavHostController,
openAccountSwitchBottomSheet: () -> Unit
) {
- val screens = listOf(
- Destinations.HomeScreen,
- Destinations.Search,
- Destinations.NewPost,
- Destinations.NotificationsScreen,
- Destinations.OwnProfile
- )
val systemNavigationBarHeight =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
@@ -478,10 +269,15 @@ private fun BottomBar(
NavigationBar(
modifier = Modifier.height(60.dp + systemNavigationBarHeight)
) {
- val navBackStackEntry by navController.currentBackStackEntryAsState()
- val currentRoute = navBackStackEntry?.destination?.route
+ val navBackStackEntry = navController.currentBackStackEntryAsState().value
+ val currentDestination = navBackStackEntry?.destination ?: return@NavigationBar
+ val tabContainer = currentDestination.parent ?: return@NavigationBar
+
+ HomeTab.entries.forEach { tab ->
+ val isSelected = currentDestination.hierarchy.any {
+ it.hasRoute(tab.destination::class)
+ }
- screens.forEach { screen ->
val interactionSource = remember { MutableInteractionSource() }
val coroutineScope = rememberCoroutineScope()
var isLongPress by remember { mutableStateOf(false) }
@@ -493,7 +289,7 @@ private fun BottomBar(
isLongPress = false // Reset flag before starting detection
coroutineScope.launch {
delay(500L) // Long-press threshold
- if (screen.route == Destinations.OwnProfile.route) {
+ if (tab == HomeTab.OwnProfile) {
openAccountSwitchBottomSheet()
}
isLongPress = true
@@ -506,40 +302,35 @@ private fun BottomBar(
}
}
}
- NavigationBarItem(icon = {
-
-
- if (screen.route == Destinations.OwnProfile.route && avatar != null) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- AsyncImage(
- model = avatar,
- error = painterResource(Res.drawable.default_avatar),
- contentDescription = "",
- modifier = Modifier
- .height(30.dp)
- .width(30.dp)
- .clip(CircleShape)
- )
+ NavigationBarItem(
+ icon = {
+ if (tab == HomeTab.OwnProfile && avatar != null) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ AsyncImage(
+ model = avatar,
+ error = painterResource(Res.drawable.default_avatar),
+ contentDescription = "",
+ modifier = Modifier
+ .height(30.dp)
+ .width(30.dp)
+ .clip(CircleShape)
+ )
+ Icon(
+ Icons.Outlined.UnfoldMore,
+ contentDescription = "long press to switch account"
+ )
+ }
+ } else {
Icon(
- Icons.Outlined.UnfoldMore,
- contentDescription = "long press to switch account"
+ imageVector = vectorResource(
+ if (isSelected) tab.activeIcon else tab.icon
+ ),
+ modifier = Modifier.size(30.dp),
+ contentDescription = stringResource(tab.label)
)
}
- } else if (currentRoute?.startsWith(screen.route) == true) {
- Icon(
- imageVector = vectorResource(screen.activeIcon),
- modifier = Modifier.size(30.dp),
- contentDescription = stringResource(screen.label)
- )
- } else {
- Icon(
- imageVector = vectorResource(screen.icon),
- modifier = Modifier.size(30.dp),
- contentDescription = stringResource(screen.label)
- )
- }
- },
- selected = currentRoute == screen.route,
+ },
+ selected = isSelected,
colors = NavigationBarItemDefaults.colors(
selectedIconColor = MaterialTheme.colorScheme.inverseSurface,
indicatorColor = Color.Transparent
@@ -547,17 +338,38 @@ private fun BottomBar(
interactionSource = interactionSource,
onClick = {
if (!isLongPress) {
- Navigate.navigateWithPopUp(screen.route, navController)
+ if (!isSelected) {
+ //switch tab
+ navController.navigate(tab.destination) {
+ launchSingleTop = true
+ restoreState = true
+ popUpTo(tabContainer.route!!) {
+ inclusive = true
+ saveState = true
+ }
+ }
+ } else {
+ val tabRoot = tabContainer.findStartDestination()
+ val isOnRoot = currentDestination == tabRoot
+ if (!isOnRoot) {
+ //back to root
+ navController.popBackStack(
+ route = tabRoot.route!!,
+ inclusive = false
+ )
+ } else if (currentDestination.hasRoute()) {
+ appComponent.searchFieldFocus.focus()
+ }
+ }
}
- })
+ }
+ )
}
}
}
-//https://partnerissuetracker.corp.google.com/issues/246909281
-@Composable
-expect fun EdgeToEdgeDialog(
- onDismissRequest: () -> Unit,
- properties: DialogProperties,
- content: @Composable () -> Unit
-)
+expect fun EdgeToEdgeDialogProperties(
+ dismissOnBackPress: Boolean = true,
+ dismissOnClickOutside: Boolean = false,
+ usePlatformDefaultWidth: Boolean = false
+): DialogProperties
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/di/AppComponent.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/di/AppComponent.kt
index 3e40a86d..60378d82 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/di/AppComponent.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/di/AppComponent.kt
@@ -14,7 +14,10 @@ import com.daniebeler.pfpixelix.domain.model.SavedSearches
import com.daniebeler.pfpixelix.domain.repository.PixelfedApi
import com.daniebeler.pfpixelix.domain.repository.createPixelfedApi
import com.daniebeler.pfpixelix.domain.repository.serializers.SavedSearchesSerializer
+import com.daniebeler.pfpixelix.domain.service.file.FileService
+import com.daniebeler.pfpixelix.domain.service.icon.AppIconManager
import com.daniebeler.pfpixelix.domain.service.preferences.UserPreferences
+import com.daniebeler.pfpixelix.domain.service.search.SearchFieldFocus
import com.daniebeler.pfpixelix.domain.service.session.AuthService
import com.daniebeler.pfpixelix.domain.service.session.Session
import com.daniebeler.pfpixelix.domain.service.session.SessionStorage
@@ -24,8 +27,6 @@ import com.daniebeler.pfpixelix.domain.service.share.SystemFileShare
import com.daniebeler.pfpixelix.domain.service.widget.WidgetService
import com.daniebeler.pfpixelix.utils.KmpContext
import com.daniebeler.pfpixelix.utils.coilContext
-import com.daniebeler.pfpixelix.utils.dataStoreDir
-import com.daniebeler.pfpixelix.utils.imageCacheDir
import com.russhwolf.settings.ExperimentalSettingsApi
import com.russhwolf.settings.ExperimentalSettingsImplementation
import com.russhwolf.settings.datastore.DataStoreSettings
@@ -54,13 +55,17 @@ annotation class AppSingleton
@AppSingleton
@Component
abstract class AppComponent(
- @get:Provides val context: KmpContext
+ @get:Provides val context: KmpContext,
+ @get:Provides val fileService: FileService,
+ @get:Provides val iconManager: AppIconManager,
) {
abstract val systemUrlHandler: SystemUrlHandler
abstract val systemFileShare: SystemFileShare
abstract val authService: AuthService
abstract val widgetService: WidgetService
+
abstract val preferences: UserPreferences
+ abstract val searchFieldFocus: SearchFieldFocus
@get:Provides
@get:AppSingleton
@@ -116,7 +121,7 @@ abstract class AppComponent(
PreferenceDataStoreFactory.createWithPath(
corruptionHandler = null,
migrations = emptyList(),
- produceFile = { context.dataStoreDir.resolve("settings.preferences_pb") },
+ produceFile = { fileService.dataStoreDir.resolve("settings.preferences_pb") },
)
@Provides
@@ -125,7 +130,7 @@ abstract class AppComponent(
DataStoreFactory.create(
storage = OkioStorage(
fileSystem = FileSystem.SYSTEM,
- producePath = { context.dataStoreDir.resolve("saved_searches.json") },
+ producePath = { fileService.dataStoreDir.resolve("saved_searches.json") },
serializer = SavedSearchesSerializer,
)
)
@@ -136,7 +141,7 @@ abstract class AppComponent(
DataStoreFactory.create(
storage = OkioStorage(
fileSystem = FileSystem.SYSTEM,
- producePath = { context.dataStoreDir.resolve("session_storage_datastore.json") },
+ producePath = { fileService.dataStoreDir.resolve("session_storage_datastore.json") },
serializer = SessionStorageDataSerializer,
)
)
@@ -160,7 +165,7 @@ abstract class AppComponent(
.diskCache(
DiskCache.Builder()
.maxSizeBytes(50L * 1024L * 1024L)
- .directory(context.imageCacheDir)
+ .directory(fileService.imageCacheDir)
.build()
)
.build()
@@ -169,4 +174,8 @@ abstract class AppComponent(
}
@KmpComponentCreate
-expect fun AppComponent.Companion.create(context: KmpContext): AppComponent
\ No newline at end of file
+expect fun AppComponent.Companion.create(
+ context: KmpContext,
+ fileService: FileService,
+ iconManager: AppIconManager,
+): AppComponent
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/AppAccentColor.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/AppAccentColor.kt
new file mode 100644
index 00000000..05d3fdde
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/AppAccentColor.kt
@@ -0,0 +1,8 @@
+package com.daniebeler.pfpixelix.domain.model
+
+object AppAccentColor {
+ const val GREEN = 0xFF2D6A44
+ const val BLUE = 0xFF4C5C92
+ const val RED = 0xFF8F4C38
+ const val White = 0xFFFFFFFF
+}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/NewReport.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/NewReport.kt
new file mode 100644
index 00000000..713b47eb
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/NewReport.kt
@@ -0,0 +1,16 @@
+package com.daniebeler.pfpixelix.domain.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class NewReport (
+ @SerialName("report_type") val reportType: String,
+ @SerialName("object_id") val objectId: String,
+ @SerialName("object_type") val objectType: ReportObjectType,
+)
+
+enum class ReportObjectType {
+ @SerialName("post") POST,
+ @SerialName("user") User
+}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/ReportResponse.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/ReportResponse.kt
new file mode 100644
index 00000000..1422eb91
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/ReportResponse.kt
@@ -0,0 +1,10 @@
+package com.daniebeler.pfpixelix.domain.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ReportResponse (
+ @SerialName("msg") val message: String,
+ @SerialName("code") val code: Int
+)
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/Server.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/Server.kt
new file mode 100644
index 00000000..1f92860a
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/Server.kt
@@ -0,0 +1,15 @@
+package com.daniebeler.pfpixelix.domain.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Server(
+ @SerialName("header_thumbnail") val headerThumbnail: String?,
+ @SerialName("domain") val domain: String,
+ @SerialName("mobile_registration") val mobileRegistrations: Boolean?,
+ @SerialName("version") val version: String,
+ @SerialName("short_description") val shortDescription: String?,
+ @SerialName("user_count") val userCount: Int,
+ @SerialName("last_seen_at") val lastSeenAt: String
+)
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/TargetAccount.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/TargetAccount.kt
new file mode 100644
index 00000000..831b9dc0
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/model/TargetAccount.kt
@@ -0,0 +1,3 @@
+package com.daniebeler.pfpixelix.domain.model
+
+class TargetAccount
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/repository/PixelfedApi.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/repository/PixelfedApi.kt
index 62d08d54..b15df21d 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/repository/PixelfedApi.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/repository/PixelfedApi.kt
@@ -16,7 +16,9 @@ import com.daniebeler.pfpixelix.domain.model.Post
import com.daniebeler.pfpixelix.domain.model.PostContext
import com.daniebeler.pfpixelix.domain.model.RelatedHashtag
import com.daniebeler.pfpixelix.domain.model.Relationship
+import com.daniebeler.pfpixelix.domain.model.ReportResponse
import com.daniebeler.pfpixelix.domain.model.Search
+import com.daniebeler.pfpixelix.domain.model.Server
import com.daniebeler.pfpixelix.domain.model.Settings
import com.daniebeler.pfpixelix.domain.model.Tag
import de.jensklingenberg.ktorfit.Call
@@ -25,6 +27,7 @@ import de.jensklingenberg.ktorfit.http.DELETE
import de.jensklingenberg.ktorfit.http.Field
import de.jensklingenberg.ktorfit.http.FormUrlEncoded
import de.jensklingenberg.ktorfit.http.GET
+import de.jensklingenberg.ktorfit.http.Header
import de.jensklingenberg.ktorfit.http.Headers
import de.jensklingenberg.ktorfit.http.POST
import de.jensklingenberg.ktorfit.http.PUT
@@ -230,8 +233,9 @@ interface PixelfedApi {
@GET("api/v1.1/collections/items/{collectionid}")
suspend fun getPostsOfCollection(
- @Path("collectionid") collectionId: String
- ): List
+ @Path("collectionid") collectionId: String,
+ @Query("page") page: Int,
+ ): List
@POST("api/v1.1/collections/remove")
suspend fun removePostOfCollection(
@@ -355,6 +359,12 @@ interface PixelfedApi {
@Path("id") postid: String
): Post
+ @Headers("Content-Type: application/json")
+ @POST("api/v1.1/report")
+ suspend fun reportPost(
+ @Body reportPostBody: String
+ ): ReportResponse
+
@GET("api/pixelfed/v1/web/settings")
suspend fun getSettings(): Settings
@@ -370,4 +380,9 @@ interface PixelfedApi {
suspend fun getServerFromFediDB(
@Path("slug") domain: String
): FediServerData
+
+ @GET("https://pixelfed.org/api/v1/mobile-app/servers/open.json")
+ suspend fun getOpenServers(
+ @Header("X-Pixelfed-App") pixelfedApp: Int = 1
+ ): List
}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/collection/CollectionService.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/collection/CollectionService.kt
index a8e59fed..b52e7bf2 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/collection/CollectionService.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/collection/CollectionService.kt
@@ -18,8 +18,8 @@ class CollectionService(
api.getCollection(collectionId)
}
- fun getPostsOfCollection(collectionId: String) = loadListResources {
- api.getPostsOfCollection(collectionId)
+ fun getPostsOfCollection(collectionId: String, page: Int = 1) = loadListResources {
+ api.getPostsOfCollection(collectionId, page)
}
fun removePostOfCollection(collectionId: String, postId: String) = loadResource {
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/editor/PostEditorService.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/editor/PostEditorService.kt
index 2dc37806..d72947f4 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/editor/PostEditorService.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/editor/PostEditorService.kt
@@ -3,7 +3,7 @@ package com.daniebeler.pfpixelix.domain.service.editor
import com.daniebeler.pfpixelix.domain.model.NewPost
import com.daniebeler.pfpixelix.domain.model.UpdatePost
import com.daniebeler.pfpixelix.domain.repository.PixelfedApi
-import com.daniebeler.pfpixelix.domain.service.platform.Platform
+import com.daniebeler.pfpixelix.domain.service.file.FileService
import com.daniebeler.pfpixelix.domain.service.utils.loadResource
import com.daniebeler.pfpixelix.utils.KmpUri
import io.ktor.client.request.forms.MultiPartFormDataContent
@@ -16,12 +16,12 @@ import me.tatarka.inject.annotations.Inject
@Inject
class PostEditorService(
private val api: PixelfedApi,
- private val platform: Platform,
+ private val fileService: FileService,
private val json: Json
) {
fun uploadMedia(uri: KmpUri, description: String) = loadResource {
- val file = platform.getPlatformFile(uri) ?: error("File doesn't exist")
+ val file = fileService.getFile(uri) ?: error("File doesn't exist")
val bytes = file.readBytes()
val thumbnail = file.getThumbnail()
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/FileService.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/FileService.kt
new file mode 100644
index 00000000..d08fb0cb
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/FileService.kt
@@ -0,0 +1,23 @@
+package com.daniebeler.pfpixelix.domain.service.file
+
+import com.daniebeler.pfpixelix.utils.KmpUri
+import okio.Path
+
+interface FileService {
+ val dataStoreDir: Path
+ val imageCacheDir: Path
+
+ fun getFile(uri: KmpUri): PlatformFile?
+ fun downloadFile(name: String?, url: String)
+ fun getCacheSizeInBytes(): Long
+ fun cleanCache()
+}
+
+interface PlatformFile {
+ fun isExist(): Boolean
+ fun getName(): String
+ fun getSize(): Long
+ fun getMimeType(): String
+ suspend fun readBytes(): ByteArray
+ suspend fun getThumbnail(): ByteArray?
+}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/AppIconService.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/AppIconService.kt
index 4efb34f4..685f889b 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/AppIconService.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/AppIconService.kt
@@ -1,21 +1,26 @@
package com.daniebeler.pfpixelix.domain.service.icon
import com.daniebeler.pfpixelix.di.AppSingleton
-import com.daniebeler.pfpixelix.domain.service.platform.Platform
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import me.tatarka.inject.annotations.Inject
import org.jetbrains.compose.resources.DrawableResource
import pixelix.app.generated.resources.Res
-import pixelix.app.generated.resources.*
+import pixelix.app.generated.resources.app_icon_00
+import pixelix.app.generated.resources.app_icon_01
+import pixelix.app.generated.resources.app_icon_02
+import pixelix.app.generated.resources.app_icon_03
+import pixelix.app.generated.resources.app_icon_05
+import pixelix.app.generated.resources.app_icon_06
+import pixelix.app.generated.resources.app_icon_07
+import pixelix.app.generated.resources.app_icon_08
+import pixelix.app.generated.resources.app_icon_09
@AppSingleton
@Inject
class AppIconService(
- platform: Platform
+ private val iconManager: AppIconManager
) {
- private val iconManager = platform.getAppIconManager()
-
val icons = listOf(
Res.drawable.app_icon_00,
Res.drawable.app_icon_01,
@@ -35,4 +40,9 @@ class AppIconService(
iconManager.setCustomIcon(icon)
currentIconFlow.value = icon
}
+}
+
+interface AppIconManager {
+ fun getCurrentIcon(): DrawableResource
+ fun setCustomIcon(icon: DrawableResource)
}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/instance/InstanceService.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/instance/InstanceService.kt
index 97b9d455..b713ccec 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/instance/InstanceService.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/instance/InstanceService.kt
@@ -22,6 +22,10 @@ class InstanceService(
}
fun getServerFromFediDB(slug: String) = loadResource {
- api.getServerFromFediDB(slug).data
+ api.getServerFromFediDB(domain = slug).data
+ }
+
+ fun getOpenServers() = loadResource {
+ api.getOpenServers()
}
}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.kt
index 5931b79e..ef4ff335 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.kt
@@ -2,37 +2,16 @@ package com.daniebeler.pfpixelix.domain.service.platform
import com.daniebeler.pfpixelix.domain.service.preferences.UserPreferences
import com.daniebeler.pfpixelix.utils.KmpContext
-import com.daniebeler.pfpixelix.utils.KmpUri
import me.tatarka.inject.annotations.Inject
-import org.jetbrains.compose.resources.DrawableResource
@Inject
expect class Platform(
context: KmpContext,
prefs: UserPreferences
) {
- fun getPlatformFile(uri: KmpUri): PlatformFile?
- fun getAppIconManager(): AppIconManager
fun openUrl(url: String)
+ fun dismissBrowser()
fun shareText(text: String)
fun getAppVersion(): String
fun pinWidget()
-
- fun downloadImageToGallery(name: String?, url: String)
-
- fun getCacheSizeInBytes(): Long
- fun cleanCache()
-}
-
-interface PlatformFile {
- fun getName(): String
- fun getSize(): Long
- fun getMimeType(): String
- suspend fun readBytes(): ByteArray
- suspend fun getThumbnail(): ByteArray?
-}
-
-interface AppIconManager {
- fun getCurrentIcon(): DrawableResource
- fun setCustomIcon(icon: DrawableResource)
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.kt
index 9b1cfcaa..53abb5dd 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.kt
@@ -4,4 +4,8 @@ expect object PlatformFeatures {
val notificationWidgets: Boolean
val inAppBrowser: Boolean
val downloadToGallery: Boolean
+ val customAppIcon: Boolean
+ val autoplayVideosPref: Boolean
+ val addCollection: Boolean
+ val customAccentColors: Boolean
}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/post/PostService.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/post/PostService.kt
index 15335ad3..f31be155 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/post/PostService.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/post/PostService.kt
@@ -2,6 +2,7 @@ package com.daniebeler.pfpixelix.domain.service.post
import com.daniebeler.pfpixelix.domain.model.LikedPostsWithNext
import com.daniebeler.pfpixelix.domain.model.NewReply
+import com.daniebeler.pfpixelix.domain.model.NewReport
import com.daniebeler.pfpixelix.domain.model.Post
import com.daniebeler.pfpixelix.domain.repository.PixelfedApi
import com.daniebeler.pfpixelix.domain.service.preferences.UserPreferences
@@ -29,7 +30,6 @@ class PostService(
private val authService: AuthService,
private val json: Json
) {
-
fun getPostById(postId: String) = loadResource {
api.getPostById(postId)
}
@@ -110,6 +110,10 @@ class PostService(
api.getBookmarkedPosts()
}
+ fun reportPost(reportBody: NewReport) = loadResource {
+ api.reportPost(json.encodeToString(reportBody))
+ }
+
fun getTrendingPosts(range: String) = loadListResources {
api.getTrendingPosts(range)
}.filterSensitive()
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/preferences/UserPreferences.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/preferences/UserPreferences.kt
index a52690ef..14578db5 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/preferences/UserPreferences.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/preferences/UserPreferences.kt
@@ -1,5 +1,6 @@
package com.daniebeler.pfpixelix.domain.service.preferences
+import com.daniebeler.pfpixelix.domain.model.AppAccentColor
import com.daniebeler.pfpixelix.domain.model.AppThemeMode
import com.russhwolf.settings.ExperimentalSettingsApi
import com.russhwolf.settings.ExperimentalSettingsImplementation
@@ -7,6 +8,8 @@ import com.russhwolf.settings.boolean
import com.russhwolf.settings.coroutines.toBlockingSettings
import com.russhwolf.settings.datastore.DataStoreSettings
import com.russhwolf.settings.int
+import com.russhwolf.settings.long
+import com.russhwolf.settings.string
import me.tatarka.inject.annotations.Inject
@OptIn(ExperimentalSettingsApi::class, ExperimentalSettingsImplementation::class)
@@ -15,8 +18,10 @@ class UserPreferences(observableSettings: DataStoreSettings) {
private val settings = observableSettings.toBlockingSettings()
var hideSensitiveContent by settings.boolean("k_hide_sensitive_content", true)
- var useInAppBrowser by settings.boolean("k_use_in_app_browser", true)
+ var blurSensitiveContent by settings.boolean("k_blur_sensitive_content", true)
+ var blurSensitiveContentFlow = observableSettings.getBooleanFlow("k_blur_sensitive_content", blurSensitiveContent)
+ var useInAppBrowser by settings.boolean("k_use_in_app_browser", true)
var hideAltTextButton by settings.boolean("k_hide_alt_text_button", false)
val hideAltTextButtonFlow = observableSettings.getBooleanFlow("k_hide_alt_text_button", hideAltTextButton)
@@ -24,6 +29,10 @@ class UserPreferences(observableSettings: DataStoreSettings) {
var focusMode by settings.boolean("k_focus_mode", false)
val focusModeFlow = observableSettings.getBooleanFlow("k_focus_mode", focusMode)
+ var autoplayVideo by settings.boolean("k_autoplay_mode", true)
+ val autoplayVideoFlow = observableSettings.getBooleanFlow("k_autoplay_mode", autoplayVideo)
+
+
var showUserGridTimeline by settings.boolean("k_grid_timeline", true)
val showUserGridTimelineFlow = observableSettings.getBooleanFlow("k_grid_timeline", showUserGridTimeline)
@@ -32,4 +41,7 @@ class UserPreferences(observableSettings: DataStoreSettings) {
var appThemeMode by settings.int("k_theme_mode", AppThemeMode.FOLLOW_SYSTEM)
val appThemeModeFlow = observableSettings.getIntFlow("k_theme_mode", appThemeMode)
+
+ var accentColor by settings.long("k_accent_color", AppAccentColor.GREEN)
+ val accentColorFlow = observableSettings.getLongFlow("k_accent_color", accentColor)
}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/search/SavedSearchesService.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/search/SavedSearchesService.kt
index 8fe03b1b..c6f1a6e2 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/search/SavedSearchesService.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/search/SavedSearchesService.kt
@@ -1,6 +1,7 @@
package com.daniebeler.pfpixelix.domain.service.search
import androidx.datastore.core.DataStore
+import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.domain.model.Account
import com.daniebeler.pfpixelix.domain.model.SavedSearchItem
import com.daniebeler.pfpixelix.domain.model.SavedSearchType
@@ -38,7 +39,7 @@ class SavedSearchesService(
}
}
} catch (e: Exception) {
- println(e)
+ Logger.e("Add item error", e)
}
}
@@ -50,7 +51,7 @@ class SavedSearchesService(
)
}
} catch (e: Exception) {
- println(e)
+ Logger.e("deleteElement error", e)
}
}
@@ -59,7 +60,7 @@ class SavedSearchesService(
try {
dataStore.updateData { SavedSearches() }
} catch (e: Exception) {
- println(e)
+ Logger.e("clearSavedSearches error", e)
}
}
}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/search/SearchFieldFocus.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/search/SearchFieldFocus.kt
new file mode 100644
index 00000000..479f043c
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/search/SearchFieldFocus.kt
@@ -0,0 +1,19 @@
+package com.daniebeler.pfpixelix.domain.service.search
+
+import com.daniebeler.pfpixelix.di.AppSingleton
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
+import me.tatarka.inject.annotations.Inject
+
+@AppSingleton
+@Inject
+class SearchFieldFocus {
+ private val eventsFlow = MutableSharedFlow()
+ val events = eventsFlow.asSharedFlow()
+
+ fun focus() {
+ GlobalScope.launch { eventsFlow.emit(true) }
+ }
+}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/session/AuthApi.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/session/AuthApi.kt
index f6310eb3..b28b97c4 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/session/AuthApi.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/session/AuthApi.kt
@@ -1,6 +1,7 @@
package com.daniebeler.pfpixelix.domain.service.session
import com.daniebeler.pfpixelix.domain.model.Account
+import com.daniebeler.pfpixelix.domain.model.Server
import de.jensklingenberg.ktorfit.http.Field
import de.jensklingenberg.ktorfit.http.FormUrlEncoded
import de.jensklingenberg.ktorfit.http.GET
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/session/AuthService.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/session/AuthService.kt
index 8f68a7be..b3265735 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/session/AuthService.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/session/AuthService.kt
@@ -3,6 +3,7 @@ package com.daniebeler.pfpixelix.domain.service.session
import androidx.datastore.core.DataStore
import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.di.AppSingleton
+import com.daniebeler.pfpixelix.domain.service.platform.Platform
import com.daniebeler.pfpixelix.domain.service.search.SavedSearchesService
import de.jensklingenberg.ktorfit.Ktorfit
import io.ktor.client.HttpClient
@@ -25,7 +26,8 @@ class AuthService(
private val session: Session,
private val sessionStorage: DataStore,
private val savedSearchesService: SavedSearchesService,
- private val json: Json
+ private val json: Json,
+ private val platform: Platform
) {
companion object {
private const val clientName = "pixelix"
@@ -50,9 +52,11 @@ class AuthService(
}
}.build()
- urlHandler.openBrowser(authUrl.toString())
+ // urlHandler.openBrowser(authUrl.toString())
+ platform.openUrl(authUrl.toString())
val redirect = Url(urlHandler.redirects.first())
+ platform.dismissBrowser()
val code = redirect.parameters["code"] ?: error("Redirect doesn't have a code")
@@ -92,6 +96,7 @@ class AuthService(
}
session.setCredentials(cred)
data.copy(activeUserId = cred?.accountId)
+
}
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/session/Session.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/session/Session.kt
index 37b69cae..da2f6804 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/session/Session.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/session/Session.kt
@@ -27,8 +27,10 @@ class Session {
suspend fun Sender.intercept(request: HttpRequestBuilder): HttpClientCall {
credentials.value?.let { creds ->
request.apply {
- url.set(host = Url(creds.serverUrl).host)
- headers["Authorization"] = "Bearer ${creds.token}"
+ if (url.host != "api.fedidb.org" && url.host != "pixelfed.org") {
+ url.set(host = Url(creds.serverUrl).host)
+ headers["Authorization"] = "Bearer ${creds.token}"
+ }
}
}
return execute(request)
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/CustomHashtag.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/CustomHashtag.kt
index 99f634c8..db16cf89 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/CustomHashtag.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/CustomHashtag.kt
@@ -23,7 +23,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.daniebeler.pfpixelix.domain.model.Tag
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
@Composable
fun CustomHashtag(hashtag: Tag, navController: NavController) {
@@ -43,7 +43,7 @@ private fun CustomHashtagPrivate(hashtag: Tag, onClick: () -> Unit, navControlle
.fillMaxWidth()
.clickable {
onClick()
- Navigate.navigate("hashtag_timeline_screen/${hashtag.name}", navController)
+ navController.navigate(Destination.HashtagTimeline(hashtag.name))
}, verticalAlignment = Alignment.CenterVertically
) {
Box(
@@ -64,18 +64,11 @@ private fun CustomHashtagPrivate(hashtag: Tag, onClick: () -> Unit, navControlle
Column {
Text(text = "#" + hashtag.name)
- if (hashtag.count != null) {
- Text(
- text = hashtag.count.toString() + " posts",
- fontSize = 14.sp,
- color = MaterialTheme.colorScheme.primary
- )
- } else {/*Text(
- text = hashtag.total.toString() + " people are talking",
- fontSize = 14.sp,
- color = MaterialTheme.colorScheme.primary
- )*/
- }
+ Text(
+ text = hashtag.count.toString() + " posts",
+ fontSize = 14.sp,
+ color = MaterialTheme.colorScheme.primary
+ )
}
}
}
\ No newline at end of file
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 a6e1008c..e8d79928 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
@@ -13,17 +13,25 @@ import androidx.compose.material.icons.outlined.VisibilityOff
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+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.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 com.daniebeler.pfpixelix.utils.Navigate
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.vectorResource
import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.stack
@@ -38,14 +46,19 @@ fun CustomPost(
edit: Boolean = false,
editRemove: (postId: String) -> Unit = {}
) {
+ val prefs = LocalAppComponent.current.preferences
+ val blurSensitiveContent = remember { mutableStateOf(prefs.hideSensitiveContent) }
- val blurHashBitmap = BlurHashDecoder.decode(
- if (post.mediaAttachments.isNotEmpty()) {
- post.mediaAttachments[0].blurHash ?: "LEHLk~WB2yk8pyo0adR*.7kCMdnj"
- } else {
- "LEHLk~WB2yk8pyo0adR*.7kCMdnj"
- },
- )
+ val coroutineScope = rememberCoroutineScope()
+ LaunchedEffect(Unit) {
+ coroutineScope.launch {
+ prefs.blurSensitiveContentFlow.collect { blurSensitiveContent.value = it }
+ }
+ }
+
+ val firstBlurHash = post.mediaAttachments.firstOrNull()?.blurHash
+ ?: "LEHLk~WB2yk8pyo0adR*.7kCMdnj"
+ val blurHashBitmap = remember(firstBlurHash) { BlurHashDecoder.decode(firstBlurHash) }
Box(modifier = customModifier.aspectRatio(1f)) {
if (blurHashBitmap != null) {
@@ -57,14 +70,14 @@ fun CustomPost(
)
}
- if (post.sensitive) {
+ if (post.sensitive && blurSensitiveContent.value) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.aspectRatio(1f)
.clickable(onClick = {
if (!edit && onClick == null) {
- Navigate.navigate("single_post_screen/" + post.id, navController)
+ navController.navigate(Destination.Post(post.id))
} else if (onClick != null){
onClick(post.id)
}
@@ -83,7 +96,7 @@ fun CustomPost(
customModifier
.clickable(onClick = {
if (!edit && onClick == null) {
- Navigate.navigate("single_post_screen/" + post.id, navController)
+ navController.navigate(Destination.Post(post.id))
} else if (onClick != null){
onClick(post.id)
}
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 707ac8fa..cea13c2c 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,13 +6,10 @@ 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.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
@@ -41,7 +38,7 @@ import androidx.navigation.NavController
import com.daniebeler.pfpixelix.ui.composables.timelines.global_timeline.GlobalTimelineComposable
import com.daniebeler.pfpixelix.ui.composables.timelines.home_timeline.HomeTimelineComposable
import com.daniebeler.pfpixelix.ui.composables.timelines.local_timeline.LocalTimelineComposable
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.resources.vectorResource
@@ -81,10 +78,7 @@ fun HomeComposable(navController: NavController, openPreferencesDrawer: () -> Un
Row {
IconButton(onClick = {
- Navigate.navigate(
- "conversations",
- navController
- )
+ navController.navigate(Destination.Conversations)
}) {
Icon(
imageVector = vectorResource(Res.drawable.mail_outline),
@@ -168,8 +162,7 @@ fun HomeComposable(navController: NavController, openPreferencesDrawer: () -> Un
onDismissRequest = {
showBottomSheet = false
},
- sheetState = sheetState,
- modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+ sheetState = sheetState
) {
Box(
modifier = Modifier
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/InfiniteListHandler.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/InfiniteListHandler.kt
index 1c665b6b..c665f582 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/InfiniteListHandler.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/InfiniteListHandler.kt
@@ -49,7 +49,11 @@ fun InfiniteGridHandler(
val totalItems = layoutInfo.totalItemsCount
val lastVisibleItemIndex = (layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1
- lastVisibleItemIndex > (totalItems - buffer)
+ if (totalItems != 0) {
+ lastVisibleItemIndex > (totalItems - buffer)
+ } else {
+ false
+ }
}
}
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 dc244a2c..feeb3ccb 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
@@ -48,7 +48,6 @@ import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.ui.composables.ButtonRowElement
import com.daniebeler.pfpixelix.ui.composables.InfinitePostsGrid
import com.daniebeler.pfpixelix.ui.composables.states.EmptyState
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.resources.vectorResource
import pixelix.app.generated.resources.Res
@@ -74,8 +73,6 @@ fun CollectionComposable(
var showBottomSheet by remember { mutableStateOf(false) }
var showAddPostBottomSheet by remember { mutableStateOf(false) }
- val context = LocalKmpContext.current
-
LaunchedEffect(Unit) {
viewModel.loadData(collectionId)
}
@@ -132,13 +129,18 @@ fun CollectionComposable(
}
} else {
- IconButton(onClick = {
- viewModel.toggleEditMode()
- }) {
- Icon(
- imageVector = Icons.Outlined.Edit, contentDescription = ""
- )
+ 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
@@ -168,7 +170,7 @@ fun CollectionComposable(
),
navController = navController,
getItemsPaginated = {
- //viewModel.getItemsPaginated()
+ viewModel.getPostsPaginated(false)
},
after = {
if (viewModel.editState.editMode) {
@@ -198,8 +200,7 @@ fun CollectionComposable(
onDismissRequest = {
showBottomSheet = false
},
- sheetState = sheetState,
- modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+ sheetState = sheetState
) {
Column(
modifier = Modifier.padding(bottom = 32.dp)
@@ -209,9 +210,7 @@ fun CollectionComposable(
Res.string.open_in_browser
), onClick = {
if (viewModel.collectionState.collection != null) {
- viewModel.openUrl(
- viewModel.collectionState.collection!!.url, context
- )
+ viewModel.openUrl(viewModel.collectionState.collection!!.url)
}
})
@@ -229,8 +228,7 @@ fun CollectionComposable(
onDismissRequest = {
showAddPostBottomSheet = false
},
- sheetState = showAddPostBottomSheetState,
- modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+ sheetState = showAddPostBottomSheetState
) {
Column(
modifier = Modifier.padding(bottom = 32.dp)
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/collection/CollectionViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/collection/CollectionViewModel.kt
index a132a85c..542fdfaf 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/collection/CollectionViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/collection/CollectionViewModel.kt
@@ -5,11 +5,13 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.daniebeler.pfpixelix.domain.service.utils.Resource
+import co.touchlab.kermit.Logger
+import com.daniebeler.pfpixelix.domain.model.Post
import com.daniebeler.pfpixelix.domain.service.collection.CollectionService
import com.daniebeler.pfpixelix.domain.service.platform.Platform
import com.daniebeler.pfpixelix.domain.service.post.PostService
-import com.daniebeler.pfpixelix.utils.KmpContext
+import com.daniebeler.pfpixelix.domain.service.session.AuthService
+import com.daniebeler.pfpixelix.domain.service.utils.Resource
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import me.tatarka.inject.annotations.Inject
@@ -17,15 +19,19 @@ import me.tatarka.inject.annotations.Inject
class CollectionViewModel @Inject constructor(
private val platform: Platform,
private val collectionService: CollectionService,
- private val postService: PostService
+ private val postService: PostService,
+ private val authService: AuthService
) : ViewModel() {
var collectionState by mutableStateOf(CollectionState())
var collectionPostsState by mutableStateOf(CollectionPostsState())
var editState by mutableStateOf(EditCollectionState())
+ var myUsername: String? = null
+ var page: Int = 1
fun loadData(collectionId: String) {
if (collectionState.id == null) {
+ myUsername = authService.getCurrentSession()!!.username
collectionState = collectionState.copy(id = collectionId)
getCollection()
getPostsFirstLoad(false)
@@ -64,12 +70,53 @@ class CollectionViewModel @Inject constructor(
private fun getPostsFirstLoad(refreshing: Boolean) {
if (collectionState.id != null) {
- collectionService.getPostsOfCollection(collectionState.id!!).onEach { result ->
+ collectionService.getPostsOfCollection(collectionState.id!!, 1).onEach { result ->
+ when (result) {
+ is Resource.Success -> {
+ val endReached = (result.data?.size ?: 0) == 0
+ collectionPostsState = CollectionPostsState(
+ posts = result.data ?: emptyList(), endReached = endReached
+ )
+ getPostsPaginated(false)
+ }
+
+ is Resource.Error -> {
+ collectionPostsState = CollectionPostsState(
+ error = result.message ?: "An unexpected error occurred"
+ )
+ }
+
+ is Resource.Loading -> {
+ collectionPostsState = CollectionPostsState(
+ isLoading = true,
+ isRefreshing = refreshing,
+ posts = collectionPostsState.posts
+ )
+ }
+ }
+ }.launchIn(viewModelScope)
+ }
+ }
+
+ fun getPostsPaginated(refreshing: Boolean) {
+ if (collectionState.id != null) {
+ if (collectionPostsState.posts.isEmpty()) {
+ return
+ }
+ page += 1
+
+ collectionService.getPostsOfCollection(
+ collectionState.id!!,
+ page
+ ).onEach { result ->
collectionPostsState = when (result) {
is Resource.Success -> {
val endReached = (result.data?.size ?: 0) == 0
+ var newPosts: List = result.data
+ newPosts = newPosts.drop(1);
CollectionPostsState(
- posts = result.data ?: emptyList(), endReached = endReached
+ posts = collectionPostsState.posts + newPosts,
+ endReached = endReached
)
}
@@ -95,7 +142,7 @@ class CollectionViewModel @Inject constructor(
postService.getOwnPosts().onEach { result ->
when (result) {
is Resource.Success -> {
- val posts = result.data!!.filter {!editState.editPosts.contains(it)}
+ val posts = result.data!!.filter { !editState.editPosts.contains(it) }
editState = editState.copy(allPostsExceptCollection = posts)
}
@@ -112,10 +159,14 @@ class CollectionViewModel @Inject constructor(
fun addPostToCollection(id: String) {
val postToAdd = editState.allPostsExceptCollection.find { it.id == id }
- val allPosts = editState.allPostsExceptCollection.filter {it.id != id}
+ val allPosts = editState.allPostsExceptCollection.filter { it.id != id }
postToAdd?.let {
val posts = editState.editPosts + postToAdd
- editState = editState.copy(editPosts = posts, addedIds = editState.addedIds + id, allPostsExceptCollection = allPosts)
+ editState = editState.copy(
+ editPosts = posts,
+ addedIds = editState.addedIds + id,
+ allPostsExceptCollection = allPosts
+ )
}
}
@@ -133,12 +184,18 @@ class CollectionViewModel @Inject constructor(
if (editState.name != collectionState.collection!!.title) {
updateCollection(editState.name)
}
- collectionState = collectionState.copy(collection = collectionState.collection!!.copy(title = editState.name))
+ collectionState =
+ collectionState.copy(collection = collectionState.collection!!.copy(title = editState.name))
}
private fun updateCollection(newName: String) {
if (collectionState.id != null && collectionState.collection != null) {
- collectionService.updateCollection(collectionState.id!!, newName, collectionState.collection!!.description, collectionState.collection!!.visibility).onEach { result ->
+ collectionService.updateCollection(
+ collectionState.id!!,
+ newName,
+ collectionState.collection!!.description,
+ collectionState.collection!!.visibility
+ ).onEach { result ->
when (result) {
is Resource.Success -> {
getCollection()
@@ -178,21 +235,22 @@ class CollectionViewModel @Inject constructor(
private fun removePostOfCollection(postId: String) {
if (collectionState.id != null) {
- collectionService.removePostOfCollection(collectionState.id!!, postId).onEach { result ->
- when (result) {
- is Resource.Success -> {
- getPostsFirstLoad(false)
- }
+ collectionService.removePostOfCollection(collectionState.id!!, postId)
+ .onEach { result ->
+ when (result) {
+ is Resource.Success -> {
+ getPostsFirstLoad(false)
+ }
- is Resource.Error -> {
+ is Resource.Error -> {
- }
+ }
- is Resource.Loading -> {
+ is Resource.Loading -> {
+ }
}
- }
- }.launchIn(viewModelScope)
+ }.launchIn(viewModelScope)
}
}
@@ -217,10 +275,11 @@ class CollectionViewModel @Inject constructor(
}
fun refresh() {
+ page = 1
getPostsFirstLoad(true)
}
- fun openUrl(url: String, context: KmpContext) {
+ fun openUrl(url: String) {
platform.openUrl(url)
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/custom_account/CustomAccount.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/custom_account/CustomAccount.kt
index 6f5f8972..ccd1cfce 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/custom_account/CustomAccount.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/custom_account/CustomAccount.kt
@@ -29,13 +29,15 @@ import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.domain.model.Account
import com.daniebeler.pfpixelix.domain.model.Relationship
import com.daniebeler.pfpixelix.ui.composables.FollowButton
-import com.daniebeler.pfpixelix.utils.Navigate
+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.pluralStringResource
+import org.jetbrains.compose.resources.vectorResource
import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.default_avatar
-import pixelix.app.generated.resources.followers
+import pixelix.app.generated.resources.follower
+import pixelix.app.generated.resources.trash
@Composable
fun CustomAccount(
@@ -123,7 +125,7 @@ private fun CustomAccountPrivate(
Row(modifier = Modifier
.clickable {
onClick()
- Navigate.navigate("profile_screen/" + account.id, navController)
+ navController.navigate(Destination.Profile(account.id))
}
.padding(horizontal = 12.dp, vertical = 8.dp)
.fillMaxWidth(),
@@ -153,7 +155,7 @@ private fun CustomAccountPrivate(
Text(
text = " • " + StringFormat.groupDigits(
account.followersCount
- ) + " " + stringResource(Res.string.followers),
+ ) + " " + pluralStringResource(Res.plurals.follower, account.followersCount),
fontSize = 12.sp,
color = MaterialTheme.colorScheme.primary,
lineHeight = 8.sp
@@ -238,7 +240,7 @@ private fun CustomAccountPrivateNotClickable(
Text(
text = " • " + StringFormat.groupDigits(
account.followersCount
- ) + " " + stringResource(Res.string.followers),
+ ) + " " + pluralStringResource(Res.plurals.follower, account.followersCount),
fontSize = 12.sp,
color = MaterialTheme.colorScheme.primary,
lineHeight = 8.sp
@@ -268,7 +270,7 @@ private fun CustomAccountPrivateNotClickable(
contentAlignment = Alignment.Center
) {
Icon(
- imageVector = Icons.Outlined.Delete,
+ imageVector = vectorResource(Res.drawable.trash),
contentDescription = null,
tint = MaterialTheme.colorScheme.error
)
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 9408fbbf..5ea37bd9 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
@@ -53,8 +53,7 @@ import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.ui.composables.InfiniteListHandler
import com.daniebeler.pfpixelix.ui.composables.states.EndOfListComposable
import com.daniebeler.pfpixelix.ui.composables.states.ErrorComposable
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
-import com.daniebeler.pfpixelix.utils.Navigate
+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
@@ -63,6 +62,7 @@ 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
@OptIn(ExperimentalMaterial3Api::class)
@@ -73,7 +73,6 @@ fun ChatComposable(
viewModel: ChatViewModel = injectViewModel(key = "chat$accountId") { chatViewModel }
) {
val lazyListState = rememberLazyListState()
- val context = LocalKmpContext.current
LaunchedEffect(Unit) {
viewModel.getChat(accountId)
}
@@ -83,7 +82,7 @@ fun ChatComposable(
if (viewModel.chatState.chat != null) {
Row(
modifier = Modifier.clickable {
- Navigate.navigate("profile_screen/$accountId", navController)
+ navController.navigate(Destination.Profile(accountId))
}, verticalAlignment = Alignment.CenterVertically
) {
AsyncImage(
@@ -99,7 +98,7 @@ fun ChatComposable(
Column {
- Text(text = viewModel.chatState.chat!!.username ?: "")
+ Text(text = viewModel.chatState.chat!!.name ?: "")
Text(
text = viewModel.chatState.chat!!.url.substringAfter("https://")
.substringBefore("/"),
@@ -181,6 +180,7 @@ fun ChatComposable(
Text(
text = stringResource(Res.string.beginning_of_chat_note),
textAlign = TextAlign.Center,
+ color = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.fillMaxWidth()
)
}
@@ -192,7 +192,7 @@ fun ChatComposable(
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.Bottom) {
OutlinedTextField(value = viewModel.newMessage,
onValueChange = { viewModel.newMessage = it },
- label = { Text("Message") },
+ label = { Text(stringResource(Res.string.message)) },
singleLine = false,
colors = OutlinedTextFieldDefaults.colors(
focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer,
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatElementComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatElementComposable.kt
index 1133f862..8eece5ad 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatElementComposable.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatElementComposable.kt
@@ -33,7 +33,7 @@ import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.cancel
import pixelix.app.generated.resources.delete
import pixelix.app.generated.resources.this_action_cannot_be_undone
-
+import pixelix.app.generated.resources.delete_message
@Composable
fun ConversationElementComposable(
message: Message, deleteMessage: () -> Unit, navController: NavController
@@ -106,7 +106,7 @@ fun ConversationElementComposable(
tint = MaterialTheme.colorScheme.error
)
}, title = {
- Text(text = "Delete message")
+ Text(text = stringResource(Res.string.delete_message))
}, text = {
Text(text = stringResource(Res.string.this_action_cannot_be_undone))
}, onDismissRequest = {
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatViewModel.kt
index 1a819ff1..efff60c8 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatViewModel.kt
@@ -5,10 +5,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.daniebeler.pfpixelix.domain.service.utils.Resource
+import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.domain.model.Message
import com.daniebeler.pfpixelix.domain.model.NewMessage
import com.daniebeler.pfpixelix.domain.service.dm.DirectMessagesService
+import com.daniebeler.pfpixelix.domain.service.utils.Resource
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import me.tatarka.inject.annotations.Inject
@@ -45,40 +46,44 @@ class ChatViewModel @Inject constructor(
fun getChatPaginated(accountId: String) {
if (chatState.chat != null && !chatState.isLoading && !chatState.endReached) {
if (chatState.chat!!.messages.isNotEmpty()) {
- directMessagesService.getChat(accountId, chatState.chat!!.messages.last().id).onEach { result ->
- chatState = when (result) {
- is Resource.Success -> {
- val endReached = result.data?.messages!!.isEmpty()
-
- val existingMessageIds = chatState.chat?.messages?.map { it.id }?.toSet() ?: emptySet()
- val newMessages = result.data.messages.filter { it.id !in existingMessageIds }
- val messages = (chatState.chat?.messages ?: emptyList()) + newMessages
-
- val chat = chatState.chat?.copy()
- if (chat != null) {
- chat.messages = messages
- ChatState(
- chat = chat,
- endReached = endReached
- )
- } else {
- ChatState(
- chat = result.data
- )
+ directMessagesService.getChat(accountId, chatState.chat!!.messages.last().id)
+ .onEach { result ->
+ chatState = when (result) {
+ is Resource.Success -> {
+ val endReached = result.data?.messages!!.isEmpty()
+
+ val existingMessageIds =
+ chatState.chat?.messages?.map { it.id }?.toSet() ?: emptySet()
+ val newMessages =
+ result.data.messages.filter { it.id !in existingMessageIds }
+ val messages =
+ (chatState.chat?.messages ?: emptyList()) + newMessages
+
+ val chat = chatState.chat?.copy()
+ if (chat != null) {
+ chat.messages = messages
+ ChatState(
+ chat = chat,
+ endReached = endReached
+ )
+ } else {
+ ChatState(
+ chat = result.data
+ )
+ }
}
- }
- is Resource.Error -> {
- ChatState(error = result.message ?: "An unexpected error occurred")
- }
+ is Resource.Error -> {
+ ChatState(error = result.message ?: "An unexpected error occurred")
+ }
- is Resource.Loading -> {
- ChatState(
- isLoading = true, chat = chatState.chat
- )
+ is Resource.Loading -> {
+ ChatState(
+ isLoading = true, chat = chatState.chat
+ )
+ }
}
- }
- }.launchIn(viewModelScope)
+ }.launchIn(viewModelScope)
}
}
}
@@ -91,13 +96,11 @@ class ChatViewModel @Inject constructor(
directMessagesService.sendMessage(newMsg).onEach { result ->
newMessageState = when (result) {
is Resource.Success -> {
- if (result.data != null) {
- val messages = emptyList() + result.data + chatState.chat!!.messages
- val chat = chatState.chat?.copy()
- if (chat != null) {
- chat.messages = messages
- chatState = ChatState(chat = chat)
- }
+ val messages = emptyList() + result.data + chatState.chat!!.messages
+ val chat = chatState.chat?.copy()
+ if (chat != null) {
+ chat.messages = messages
+ chatState = ChatState(chat = chat)
}
NewMessageState(message = result.data)
}
@@ -120,22 +123,20 @@ class ChatViewModel @Inject constructor(
directMessagesService.deleteMessage(id).onEach { result ->
when (result) {
is Resource.Success -> {
- if (result.data != null) {
- val messages = chatState.chat!!.messages.filter { it.reportId != id }
- val chat = chatState.chat?.copy()
- if (chat != null) {
- chat.messages = messages
- chatState = ChatState(chat = chat)
- }
+ val messages = chatState.chat!!.messages.filter { it.reportId != id }
+ val chat = chatState.chat?.copy()
+ if (chat != null) {
+ chat.messages = messages
+ chatState = ChatState(chat = chat)
}
}
is Resource.Error -> {
- println(result.message)
+ Logger.e(result.message)
}
is Resource.Loading -> {
- println("is loading")
+ Logger.v("is loading")
}
}
}.launchIn(viewModelScope)
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/conversations/ConversationElementComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/conversations/ConversationElementComposable.kt
index 1e9f756a..b9916177 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/conversations/ConversationElementComposable.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/conversations/ConversationElementComposable.kt
@@ -21,7 +21,7 @@ import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import coil3.compose.AsyncImage
import com.daniebeler.pfpixelix.domain.model.Conversation
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import org.jetbrains.compose.resources.painterResource
import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.default_avatar
@@ -33,7 +33,7 @@ fun ConversationElementComposable(conversation: Conversation, navController: Nav
Modifier
.fillMaxWidth()
.clickable {
- Navigate.navigate("chat/" + conversation.accounts.first().id, navController)
+ navController.navigate(Destination.Chat(conversation.accounts.first().id))
}
.padding(horizontal = 12.dp, vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically) {
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 23639131..7a88c217 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
@@ -56,7 +56,7 @@ import com.daniebeler.pfpixelix.ui.composables.states.EndOfListComposable
import com.daniebeler.pfpixelix.ui.composables.states.ErrorComposable
import com.daniebeler.pfpixelix.ui.composables.states.FullscreenEmptyStateComposable
import com.daniebeler.pfpixelix.ui.composables.states.LoadingComposable
-import com.daniebeler.pfpixelix.utils.Navigate
+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
@@ -176,8 +176,7 @@ fun ConversationsComposable(
onDismissRequest = {
showBottomSheet = false
},
- sheetState = sheetState,
- modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+ sheetState = sheetState
) {
Box(
modifier = Modifier
@@ -263,9 +262,7 @@ private fun CreateNewConversation(
}, confirmButton = {
TextButton(enabled = viewModel.newConversationSelectedAccount != null, onClick = {
if (viewModel.newConversationSelectedAccount != null) {
- Navigate.navigate(
- "chat/" + viewModel.newConversationSelectedAccount!!.id, navController
- )
+ navController.navigate(Destination.Chat(viewModel.newConversationSelectedAccount!!.id))
close()
}
}) {
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/conversations/ConversationsViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/conversations/ConversationsViewModel.kt
index 0bcff754..5ac47853 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/conversations/ConversationsViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/conversations/ConversationsViewModel.kt
@@ -23,6 +23,7 @@ class ConversationsViewModel @Inject constructor(
var newConversationUsername by mutableStateOf(TextFieldValue())
var newConversationState by mutableStateOf(NewConversationState())
var newConversationSelectedAccount by mutableStateOf(null)
+
init {
getConversationsFirstLoad(false)
}
@@ -58,13 +59,7 @@ class ConversationsViewModel @Inject constructor(
searchService.search(newUsername.text, "accounts").onEach { result ->
newConversationState = when (result) {
is Resource.Success -> {
- if (result.data != null) {
- NewConversationState(suggestions = result.data.accounts)
- } else {
- NewConversationState(
- error = result.message ?: "An unexpected error occurred"
- )
- }
+ NewConversationState(suggestions = result.data.accounts)
}
is Resource.Error -> {
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/edit_post/EditPostComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/edit_post/EditPostComposable.kt
index 14d3aead..b96726ea 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/edit_post/EditPostComposable.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/edit_post/EditPostComposable.kt
@@ -1,6 +1,5 @@
package com.daniebeler.pfpixelix.ui.composables.edit_post
-import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -12,7 +11,6 @@ import androidx.compose.foundation.layout.WindowInsetsSides
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.ime
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
@@ -32,8 +30,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.outlined.ArrowLeft
import androidx.compose.material.icons.automirrored.outlined.ArrowRight
-import androidx.compose.material.icons.outlined.ArrowDownward
-import androidx.compose.material.icons.outlined.ArrowUpward
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
@@ -72,24 +68,17 @@ import androidx.navigation.NavController
import coil3.compose.AsyncImage
import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.domain.model.MediaAttachment
-import com.daniebeler.pfpixelix.ui.composables.newpost.ImagesPager
-import com.daniebeler.pfpixelix.ui.composables.newpost.NewPostViewModel
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
import com.daniebeler.pfpixelix.ui.composables.textfield_mentions.TextFieldMentionsComposable
-import com.daniebeler.pfpixelix.utils.KmpUri
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
import com.daniebeler.pfpixelix.utils.getPlatformUriObject
import com.daniebeler.pfpixelix.utils.toKmpUri
-import io.github.vinceglb.filekit.compose.rememberFilePickerLauncher
-import io.github.vinceglb.filekit.core.PickerMode
-import io.github.vinceglb.filekit.core.PickerType
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.add_outline
import pixelix.app.generated.resources.alt_text
import pixelix.app.generated.resources.cancel
import pixelix.app.generated.resources.caption
@@ -118,8 +107,6 @@ fun EditPostComposable(
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
- val context = LocalKmpContext.current
-
LaunchedEffect(Unit) {
viewModel.loadData(postId)
}
@@ -335,7 +322,11 @@ fun EditPostComposable(
}
LoadingComposable(isLoading = viewModel.editPostState.isLoading)
- ErrorComposable(message = viewModel.editPostState.error)
+ ErrorComposableDialog(
+ errorMessage = viewModel.editPostState.error,
+ onDismiss = { viewModel.editPostState = viewModel.editPostState.copy(error = "") }
+ )
+
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}
@@ -469,7 +460,8 @@ fun ImagesPagerEditPost(
AsyncImage(
model = image.url.toKmpUri().getPlatformUriObject(),
contentDescription = "video thumbnail",
- modifier = Modifier.width(100.dp)
+ modifier = Modifier.fillMaxWidth(),
+ contentScale = ContentScale.Inside
)
} else {
AsyncImage(
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/edit_post/EditPostViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/edit_post/EditPostViewModel.kt
index 74af4b41..129b980a 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/edit_post/EditPostViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/edit_post/EditPostViewModel.kt
@@ -47,21 +47,19 @@ class EditPostViewModel @Inject constructor(
postService.getPostById(postId).onEach { result ->
editPostState = when (result) {
is Resource.Success -> {
- if (result.data != null) {
- caption = TextFieldValue(result.data.content)
- result.data.place?.let {
- location = it
- }
- sensitive = result.data.sensitive
- sensitiveText = result.data.spoilerText
- mediaAttachmentsEdit.addAll(result.data.mediaAttachments)
- mediaAttachmentsBefore.addAll(result.data.mediaAttachments)
- mediaDescriptionItems.addAll(result.data.mediaAttachments.map {
- MediaDescriptionItem(
- it.id, it.description ?: "", false
- )
- })
+ caption = TextFieldValue(result.data.content)
+ result.data.place?.let {
+ location = it
}
+ sensitive = result.data.sensitive
+ sensitiveText = result.data.spoilerText
+ mediaAttachmentsEdit.addAll(result.data.mediaAttachments)
+ mediaAttachmentsBefore.addAll(result.data.mediaAttachments)
+ mediaDescriptionItems.addAll(result.data.mediaAttachments.map {
+ MediaDescriptionItem(
+ it.id, it.description ?: "", false
+ )
+ })
EditPostState(post = result.data)
}
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 d9fde62f..cb4c14be 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
@@ -3,9 +3,7 @@ package com.daniebeler.pfpixelix.ui.composables.edit_profile
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
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
@@ -17,15 +15,12 @@ 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.systemBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
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.Surface
-import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Done
@@ -51,9 +46,7 @@ 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.draw.clipToBounds
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.decodeToImageBitmap
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.text.font.FontWeight
@@ -62,31 +55,21 @@ 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.CircleCropShape
import com.attafitamim.krop.core.crop.CropResult
-import com.attafitamim.krop.core.crop.CropShape
import com.attafitamim.krop.core.crop.CropState
-import com.attafitamim.krop.core.crop.CropperStyle
import com.attafitamim.krop.core.crop.DefaultCropperStyle
import com.attafitamim.krop.core.crop.LocalCropperStyle
import com.attafitamim.krop.core.crop.rememberImageCropper
import com.attafitamim.krop.core.images.ImageBitmapSrc
-import com.attafitamim.krop.ui.CropperDialogProperties
import com.attafitamim.krop.ui.CropperPreview
import com.attafitamim.krop.ui.DefaultControls
-import com.attafitamim.krop.ui.DefaultTopBar
-import com.attafitamim.krop.ui.ImageCropperDialog
-import com.daniebeler.pfpixelix.EdgeToEdgeDialog
+import com.daniebeler.pfpixelix.EdgeToEdgeDialogProperties
import com.daniebeler.pfpixelix.di.injectViewModel
-import com.daniebeler.pfpixelix.ui.theme.PixelixTheme
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
-import com.daniebeler.pfpixelix.utils.getPlatformUriObject
import com.daniebeler.pfpixelix.utils.imeAwareInsets
import io.github.vinceglb.filekit.compose.rememberFilePickerLauncher
import io.github.vinceglb.filekit.core.PickerMode
import io.github.vinceglb.filekit.core.PickerType
import kotlinx.coroutines.launch
-import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.resources.vectorResource
import pixelix.app.generated.resources.Res
@@ -348,12 +331,9 @@ private fun ImageCropperFullscreenDialog(
}
CompositionLocalProvider(LocalCropperStyle provides style) {
- EdgeToEdgeDialog(
+ Dialog(
onDismissRequest = { state.done(accept = false) },
- properties = DialogProperties(
- dismissOnClickOutside = false,
- usePlatformDefaultWidth = false,
- )
+ properties = EdgeToEdgeDialogProperties()
) {
Scaffold(
contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top),
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 8f72a962..745224c2 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
@@ -46,18 +46,23 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.semantics.isTraversalGroup
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.traversalIndex
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
+import co.touchlab.kermit.Logger
import coil3.compose.AsyncImage
+import com.daniebeler.pfpixelix.di.LocalAppComponent
import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.domain.model.Account
import com.daniebeler.pfpixelix.domain.model.SavedSearchItem
@@ -66,9 +71,7 @@ import com.daniebeler.pfpixelix.ui.composables.CustomHashtag
import com.daniebeler.pfpixelix.ui.composables.custom_account.CustomAccount
import com.daniebeler.pfpixelix.ui.composables.explore.trending.TrendingComposable
import com.daniebeler.pfpixelix.ui.composables.states.FullscreenLoadingComposable
-import com.daniebeler.pfpixelix.utils.KmpContext
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import com.daniebeler.pfpixelix.utils.imeAwareInsets
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.painterResource
@@ -88,13 +91,21 @@ fun ExploreComposable(
initialPage: Int = 0,
viewModel: ExploreViewModel = injectViewModel(key = "search-viewmodel-key") { exploreViewModel }
) {
- val context: KmpContext = LocalKmpContext.current
-
+ val focusRequester = remember { FocusRequester() }
val textFieldState = rememberTextFieldState()
var expanded by rememberSaveable { mutableStateOf(false) }
+ val appComponent = LocalAppComponent.current
+ LaunchedEffect(Unit) {
+ appComponent.searchFieldFocus.events.collect {
+ Logger.d("search") { "Request search focus" }
+ focusRequester.requestFocus()
+ }
+ }
+
Box(Modifier
.fillMaxSize()
+ .background(MaterialTheme.colorScheme.surface)
.semantics { isTraversalGroup = true }) {
SearchBar(
modifier = Modifier
@@ -112,6 +123,7 @@ fun ExploreComposable(
expanded = expanded,
onExpandedChange = { expanded = it },
placeholder = { Text(stringResource(Res.string.explore)) },
+ modifier = Modifier.focusRequester(focusRequester),
leadingIcon = {
if (!expanded) {
Icon(vectorResource(Res.drawable.search_outline), contentDescription = null)
@@ -296,13 +308,9 @@ private fun PastSearchItem(
.fillMaxWidth()
.clickable {
when (item.savedSearchType) {
- SavedSearchType.Account -> Navigate.navigate(
- "profile_screen/" + item.account!!.id, navController
- )
+ SavedSearchType.Account -> navController.navigate(Destination.Profile(item.account!!.id))
- SavedSearchType.Hashtag -> Navigate.navigate(
- "hashtag_timeline_screen/${item.value}", navController
- )
+ SavedSearchType.Hashtag -> navController.navigate(Destination.HashtagTimeline(item.value))
SavedSearchType.Search -> setSearchText(item.value)
}
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 d9c2197d..2e76b331 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
@@ -125,8 +125,7 @@ fun TrendingComposable(navController: NavController, initialPage: Int) {
onDismissRequest = {
showBottomSheet = false
},
- sheetState = sheetState,
- modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+ sheetState = sheetState
) {
Box(
modifier = Modifier
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 bf22f3e1..55035291 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
@@ -20,8 +20,7 @@ import com.daniebeler.pfpixelix.domain.model.Account
import com.daniebeler.pfpixelix.ui.composables.CustomPost
import com.daniebeler.pfpixelix.ui.composables.custom_account.CustomAccount
import com.daniebeler.pfpixelix.ui.composables.hashtagMentionText.HashtagsMentionsTextView
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
@Composable
fun TrendingAccountElement(
@@ -29,7 +28,6 @@ fun TrendingAccountElement(
navController: NavController,
viewModel: TrendingAccountElementViewModel = injectViewModel(key = account.id) { trendingAccountElementViewModel }
) {
- val context = LocalKmpContext.current
LaunchedEffect(account) {
viewModel.loadItems(account.id)
}
@@ -39,7 +37,7 @@ fun TrendingAccountElement(
.padding(12.dp)
.fillMaxWidth()
.clickable {
- Navigate.navigate("profile_screen/" + account.id, navController)
+ navController.navigate(Destination.Profile(account.id))
}) {
CustomAccount(account = account)
@@ -49,7 +47,7 @@ fun TrendingAccountElement(
text = account.note,
mentions = null,
navController = navController,
- modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), openUrl = { url -> viewModel.openUrl(url, context) }
+ modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), openUrl = { url -> viewModel.openUrl(url) }
)
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_accounts/TrendingAccountElementViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_accounts/TrendingAccountElementViewModel.kt
index 50ebf473..aafc3626 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_accounts/TrendingAccountElementViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/explore/trending/trending_accounts/TrendingAccountElementViewModel.kt
@@ -5,10 +5,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.daniebeler.pfpixelix.domain.service.utils.Resource
import com.daniebeler.pfpixelix.domain.service.platform.Platform
import com.daniebeler.pfpixelix.domain.service.post.PostService
-import com.daniebeler.pfpixelix.utils.KmpContext
+import com.daniebeler.pfpixelix.domain.service.utils.Resource
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import me.tatarka.inject.annotations.Inject
@@ -47,7 +46,7 @@ class TrendingAccountElementViewModel @Inject constructor(
}
}
- fun openUrl(url: String, context: KmpContext) {
+ fun openUrl(url: String) {
platform.openUrl(url)
}
}
\ No newline at end of file
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 0bdf21f7..8a1e85be 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
@@ -29,7 +29,7 @@ import androidx.navigation.NavController
import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.domain.model.Tag
import com.daniebeler.pfpixelix.ui.composables.CustomPost
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import com.daniebeler.pfpixelix.utils.StringFormat
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
@@ -52,7 +52,7 @@ fun TrendingHashtagElement(
.padding(vertical = 8.dp)
.fillMaxWidth()
.clickable {
- Navigate.navigate("hashtag_timeline_screen/${hashtag.name}", navController)
+ navController.navigate(Destination.HashtagTimeline(hashtag.name))
}) {
Row(
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowerElementComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowerElementComposable.kt
index f0d888c4..0be0c2e6 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowerElementComposable.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/followers/FollowerElementComposable.kt
@@ -20,7 +20,7 @@ import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import coil3.compose.AsyncImage
import com.daniebeler.pfpixelix.domain.model.Account
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import org.jetbrains.compose.resources.painterResource
import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.default_avatar
@@ -32,7 +32,7 @@ fun FollowerElementComposable(
Row(
modifier = Modifier
.clickable {
- Navigate.navigate("profile_screen/" + account.id, navController, false)
+ navController.navigate(Destination.Profile(account.id))
}
.padding(horizontal = 12.dp, vertical = 8.dp)
.fillMaxWidth()
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 0cdeec62..2b08cf37 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
@@ -32,7 +32,6 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
-import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.di.injectViewModel
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource
@@ -47,7 +46,7 @@ import pixelix.app.generated.resources.following
fun FollowersMainComposable(
navController: NavController,
accountId: String,
- page: String,
+ isFollowers: Boolean,
viewModel: FollowersViewModel = injectViewModel(key = "followers-viewmodel-key") { followersViewModel }
) {
@@ -59,7 +58,7 @@ fun FollowersMainComposable(
viewModel.setLoggedInAccountIdValue()
}
- val pageId = if (page == "followers") 0 else 1
+ val pageId = if (isFollowers) 0 else 1
val pagerState = rememberPagerState(initialPage = pageId, pageCount = { 2 })
val scope = rememberCoroutineScope()
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 364a2712..af02847d 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
@@ -10,15 +10,11 @@ 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.material3.Text
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.key.Key.Companion.R
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
-import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.ui.composables.InfiniteListHandler
import com.daniebeler.pfpixelix.ui.composables.states.EmptyState
@@ -26,7 +22,7 @@ import com.daniebeler.pfpixelix.ui.composables.states.EndOfListComposable
import com.daniebeler.pfpixelix.ui.composables.states.ErrorComposable
import com.daniebeler.pfpixelix.ui.composables.states.FullscreenEmptyStateComposable
import com.daniebeler.pfpixelix.ui.composables.states.LoadingComposable
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.empty
@@ -80,7 +76,7 @@ fun FollowingComposable(
message = message,
buttonText = stringResource(Res.string.explore_trending_profiles),
onClick = {
- Navigate.navigate("search_screen/1", navController)
+ navController.navigate(Destination.Search(1))
})
)
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/hashtagMentionText/TextWithClickableHashtagsAndMentionsComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/hashtagMentionText/TextWithClickableHashtagsAndMentionsComposable.kt
index 68471f41..c7243c23 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/hashtagMentionText/TextWithClickableHashtagsAndMentionsComposable.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/hashtagMentionText/TextWithClickableHashtagsAndMentionsComposable.kt
@@ -24,11 +24,14 @@ import androidx.compose.ui.unit.TextUnit
import androidx.navigation.NavController
import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.domain.model.Account
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
+import org.jetbrains.compose.resources.stringResource
+import pixelix.app.generated.resources.Res
+import pixelix.app.generated.resources.read_less
+import pixelix.app.generated.resources.read_more
@Composable
fun HashtagsMentionsTextView(
@@ -127,42 +130,32 @@ fun HashtagsMentionsTextView(
}
},
modifier = modifier, onClick = { position ->
- CoroutineScope(Dispatchers.Default).launch {
+ CoroutineScope(Dispatchers.Main).launch {
val annotatedStringRange =
annotatedStringList.firstOrNull { it.start <= position && position < it.end }
if (annotatedStringRange != null) {
if (annotatedStringRange.tag == "tag" || annotatedStringRange.tag == "account") {
val newItem = annotatedStringRange.item.drop(1)
- val route = if (annotatedStringRange.tag == "tag") {
- "hashtag_timeline_screen/$newItem"
+ if (annotatedStringRange.tag == "tag") {
+ navController.navigate(Destination.HashtagTimeline(newItem))
} else {
if (mentions == null) {
- "profile_screen/byUsername/${annotatedStringRange.item.drop(1)}"
+ navController.navigate(Destination.ProfileByUsername(annotatedStringRange.item.drop(1)))
} else {
- var account =
- mentions.find { account: Account -> account.acct == newItem }
+ var account = mentions.find { account: Account -> account.acct == newItem }
if (account == null) {
- account =
- mentions.find { account: Account -> account.username == newItem }
+ account = mentions.find { account: Account -> account.username == newItem }
}
if (account != null) {
//get my account id and check if it is mine account
val myAccountId = viewModel.getMyAccountId()
if (account.id == myAccountId) {
- "own_profile_screen"
+ navController.navigate(Destination.OwnProfile)
} else {
- "profile_screen/${account.id}"
+ navController.navigate(Destination.Profile(account.id))
}
- } else {
- ""
}
}
-
- }
- withContext(Dispatchers.Main) {
- if (route.isNotBlank() && route.isNotEmpty()) {
- Navigate.navigate(route, navController)
- }
}
} else if (annotatedStringRange.tag == "link") {
openUrl(annotatedStringRange.item)
@@ -172,7 +165,7 @@ fun HashtagsMentionsTextView(
})
if (showReadMoreButtonState) {
Text(
- text = if (expanded) "Read Less" else "Read More",
+ text = if (expanded) stringResource(Res.string.read_less) else stringResource(Res.string.read_more),
color = Color.Gray,
modifier = Modifier.clickable {
expanded = !expanded
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 084aab8f..fa4ea8f7 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
@@ -36,13 +36,12 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
-import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.domain.model.Post
import com.daniebeler.pfpixelix.ui.composables.post.PostComposable
import com.daniebeler.pfpixelix.ui.composables.states.ErrorComposable
import com.daniebeler.pfpixelix.ui.composables.states.LoadingComposable
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.resources.vectorResource
@@ -115,7 +114,7 @@ fun MentionComposable(
post,
navController,
postGetsDeleted = {
- Navigate.navigateAndDeleteBackStack("own_profile_screen", navController)
+ navController.navigate(Destination.OwnProfile)
},
setZindex = { },
openReplies = false,
@@ -146,15 +145,15 @@ private fun SubPosts(posts: List, navController: NavController) {
ancestor,
navController,
postGetsDeleted = {
- Navigate.navigateAndDeleteBackStack(
- "own_profile_screen", navController
- )
+ navController.navigate(Destination.OwnProfile) {
+ popUpTo(0) { inclusive = true }
+ }
},
setZindex = { },
openReplies = false,
showReplies = false,
modifier = Modifier.clickable {
- Navigate.navigate("mention/" + ancestor.id, navController)
+ navController.navigate(Destination.Mention(ancestor.id))
}
)
}
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 7c91aa00..29e2d839 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
@@ -72,10 +72,10 @@ 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
import com.daniebeler.pfpixelix.utils.KmpUri
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
import com.daniebeler.pfpixelix.utils.getPlatformUriObject
import com.daniebeler.pfpixelix.utils.imeAwareInsets
import com.daniebeler.pfpixelix.utils.toKmpUri
@@ -109,8 +109,6 @@ fun NewPostComposable(
uris: List? = null,
viewModel: NewPostViewModel = injectViewModel(key = "new-post-viewmodel-key") { newPostViewModel }
) {
- val context = LocalKmpContext.current
-
var expanded by remember { mutableStateOf(false) }
var showReleaseAlert by remember {
mutableStateOf(false)
@@ -118,9 +116,7 @@ fun NewPostComposable(
LaunchedEffect(uris) {
uris?.let {
- uris.forEach {
- viewModel.addImage(uri = it, context = context)
- }
+ uris.forEach { viewModel.addImage(uri = it) }
}
}
@@ -151,7 +147,7 @@ fun NewPostComposable(
{ index -> viewModel.moveMediaAttachmentUp(index) },
{ index -> viewModel.moveMediaAttachmentDown(index) },
{ index -> viewModel.deleteMedia(index) },
- { kmpUri: KmpUri -> viewModel.addImage(kmpUri, context) })
+ { kmpUri: KmpUri -> viewModel.addImage(kmpUri) })
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
NewPostTextField(
@@ -282,8 +278,15 @@ fun NewPostComposable(
LoadingComposable(isLoading = viewModel.createPostState.isLoading)
//LoadingComposable(isLoading = viewModel.mediaUploadState.isLoading)
- ErrorComposable(message = viewModel.mediaUploadState.error)
- ErrorComposable(message = viewModel.createPostState.error)
+ ErrorComposableDialog(
+ errorMessage = viewModel.mediaUploadState.error,
+ onDismiss = { viewModel.mediaUploadState = viewModel.mediaUploadState.copy(error = "") }
+ )
+
+ ErrorComposableDialog(
+ errorMessage = viewModel.createPostState.error,
+ onDismiss = { viewModel.createPostState = viewModel.createPostState.copy(error = "") }
+ )
}
}
}
@@ -392,7 +395,8 @@ fun ImagesPager(
AsyncImage(
model = image.imageUri.getPlatformUriObject(),
contentDescription = "video thumbnail",
- modifier = Modifier.width(100.dp)
+ modifier = Modifier.fillMaxWidth(),
+ contentScale = ContentScale.Inside
)
} else {
AsyncImage(
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/newpost/NewPostPref.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/newpost/NewPostPref.kt
index 7ad8ee30..4e40bf5e 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/newpost/NewPostPref.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/newpost/NewPostPref.kt
@@ -49,9 +49,7 @@ fun NewPostPref(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.defaultMinSize(minHeight = 54.dp)
) {
- if (leadingIcon != null) {
- Icon(vectorResource(leadingIcon), contentDescription = null)
- }
+ Icon(vectorResource(leadingIcon), contentDescription = null)
Column(
modifier = Modifier
.weight(1f)
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/newpost/NewPostViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/newpost/NewPostViewModel.kt
index 26242b90..e8679d61 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/newpost/NewPostViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/newpost/NewPostViewModel.kt
@@ -7,16 +7,16 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
-import com.daniebeler.pfpixelix.domain.service.utils.Resource
import com.daniebeler.pfpixelix.domain.model.Instance
import com.daniebeler.pfpixelix.domain.model.NewPost
import com.daniebeler.pfpixelix.domain.model.Visibility
import com.daniebeler.pfpixelix.domain.service.editor.PostEditorService
+import com.daniebeler.pfpixelix.domain.service.file.FileService
import com.daniebeler.pfpixelix.domain.service.instance.InstanceService
-import com.daniebeler.pfpixelix.domain.service.platform.Platform
-import com.daniebeler.pfpixelix.utils.KmpContext
+import com.daniebeler.pfpixelix.domain.service.utils.Resource
+import com.daniebeler.pfpixelix.ui.navigation.Destination
+import com.daniebeler.pfpixelix.utils.EmptyKmpUri
import com.daniebeler.pfpixelix.utils.KmpUri
-import com.daniebeler.pfpixelix.utils.Navigate
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.flowOn
@@ -28,7 +28,7 @@ import me.tatarka.inject.annotations.Inject
class NewPostViewModel @Inject constructor(
private val postEditorService: PostEditorService,
private val instanceService: InstanceService,
- private val platform: Platform
+ private val fileService: FileService
) : ViewModel() {
data class ImageItem(
val imageUri: KmpUri,
@@ -57,13 +57,7 @@ class NewPostViewModel @Inject constructor(
instanceService.getInstance().onEach { result ->
when (result) {
is Resource.Success -> {
- if (result.data != null) {
- instance = result.data
- } else {
- createPostState = CreatePostState(
- error = result.message ?: "An unexpected error occurred"
- )
- }
+ instance = result.data
}
is Resource.Error -> {
@@ -101,8 +95,8 @@ class NewPostViewModel @Inject constructor(
}
}
- fun addImage(uri: KmpUri, context: KmpContext) {
- val file = platform.getPlatformFile(uri) ?: return
+ fun addImage(uri: KmpUri) {
+ val file = fileService.getFile(uri) ?: return
val fileType = file.getMimeType()
if (instance != null && !instance!!.configuration.mediaAttachmentConfig.supportedMimeTypes.contains(
fileType
@@ -141,11 +135,14 @@ class NewPostViewModel @Inject constructor(
}
val imagesNumber = images.size + 1
if (instance != null && imagesNumber > instance!!.configuration.statusConfig.maxMediaAttachments) {
- addImageError = Pair("To many images", "You have added to many images, your Server does only allow ${instance!!.configuration.statusConfig.maxMediaAttachments} images per post")
+ addImageError = Pair(
+ "To many images",
+ "You have added to many images, your Server does only allow ${instance!!.configuration.statusConfig.maxMediaAttachments} images per post"
+ )
return
}
images += ImageItem(uri, fileType, null, "", true)
- uploadImage(context, uri, "")
+ uploadImage(uri, "")
}
fun deleteMedia(index: Int) {
@@ -168,7 +165,7 @@ class NewPostViewModel @Inject constructor(
}
}
- private fun uploadImage(context: KmpContext, uri: KmpUri, text: String) {
+ private fun uploadImage(uri: KmpUri, text: String) {
postEditorService.uploadMedia(uri, text).onEach { result ->
mediaUploadState = when (result) {
is Resource.Success -> {
@@ -177,7 +174,10 @@ class NewPostViewModel @Inject constructor(
}
val index = images.indexOfFirst { it.imageUri == uri }
if (index != -1) {
- images[index] = images[index].copy(isLoading = false, id = result.data?.id) // Replacing the object forces recomposition
+ images[index] = images[index].copy(
+ isLoading = false,
+ id = result.data?.id
+ ) // Replacing the object forces recomposition
}
mediaUploadState.copy(
@@ -187,11 +187,11 @@ class NewPostViewModel @Inject constructor(
}
is Resource.Error -> {
- if (!result.message.isNullOrEmpty()) {
- MediaUploadState(error = result.message)
- } else {
- MediaUploadState(error = "An unexpected error occured")
+ val index = images.indexOfFirst { it.imageUri == uri }
+ if (index != -1) {
+ images.removeAt(index)
}
+ MediaUploadState(error = "An unexpected error occured")
}
is Resource.Loading -> {
@@ -284,9 +284,7 @@ class NewPostViewModel @Inject constructor(
postEditorService.createPost(createPostDto).onEach { result ->
createPostState = when (result) {
is Resource.Success -> {
- if (result.data != null) {
- Navigate.navigateAndDeleteBackStack("own_profile_screen", navController)
- }
+ navController.navigate(Destination.OwnProfile)
CreatePostState(post = result.data, isLoading = true)
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/notifications/CustomNotification.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/notifications/CustomNotification.kt
index 028a244e..710639eb 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/notifications/CustomNotification.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/notifications/CustomNotification.kt
@@ -11,23 +11,30 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.ClickableText
+import androidx.compose.material.Colors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.produceState
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.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import coil3.compose.AsyncImage
import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.domain.model.Notification
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import com.daniebeler.pfpixelix.utils.TimeAgo
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
@@ -78,63 +85,97 @@ fun CustomNotification(
}
}
+ val timeAgoText = produceState(initialValue = "") {
+ value = TimeAgo.convertTimeToText(notification.createdAt)
+ }
+
Row(
- Modifier
- .padding(horizontal = 12.dp, vertical = 8.dp)
- .fillMaxWidth().clickable {
- if (notification.post != null && notification.post.mediaAttachments.isEmpty()) {
- Navigate.navigate("mention/" + notification.post.id, navController)
- }
- },
- verticalAlignment = Alignment.CenterVertically
+ Modifier.padding(horizontal = 12.dp, vertical = 8.dp).fillMaxWidth().clickable {
+ if (notification.post != null && notification.post.mediaAttachments.isEmpty()) {
+ navController.navigate(Destination.Mention(notification.post.id))
+ } else if (notification.post != null && notification.post.mediaAttachments.isNotEmpty()) {
+ navController.navigate(Destination.Post(notification.post.id))
+ } else if (notification.post == null) {
+ navController.navigate(Destination.Profile(notification.account.id))
+ }
+ }, verticalAlignment = Alignment.CenterVertically
) {
- AsyncImage(model = notification.account?.avatar,
+ AsyncImage(
+ model = notification.account.avatar,
error = painterResource(Res.drawable.default_avatar),
contentDescription = "",
- modifier = Modifier
- .height(46.dp)
- .width(46.dp)
- .clip(CircleShape)
- .clickable {
- Navigate.navigate("profile_screen/" + notification.account?.id, navController)
+ modifier = Modifier.height(46.dp).width(46.dp).clip(CircleShape).clickable {
+ navController.navigate(Destination.Profile(notification.account.id))
})
Spacer(modifier = Modifier.width(10.dp))
Column(Modifier.weight(1f)) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- Text(text = notification.account?.username.orEmpty(), fontWeight = FontWeight.Bold, modifier = Modifier.clickable {
- Navigate.navigate("profile_screen/" + notification.account?.id, navController)
+ val annotatedText = buildAnnotatedString {
+ pushStringAnnotation(tag = "username", annotation = notification.account.id)
+ withStyle(
+ style = SpanStyle(
+ fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onBackground
+ )
+ ) {
+ append(notification.account.username)
+ }
+ pop()
+ append(" ")
+ withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.onBackground)) {
+ append(text)
+ }
+ }
+
+ ClickableText(
+ text = annotatedText,
+ style = MaterialTheme.typography.bodyMedium,
+ onClick = { offset ->
+ annotatedText.getStringAnnotations(
+ tag = "username", start = offset, end = offset
+ ).firstOrNull()?.let { annotation ->
+ if (annotation.tag == "username") {
+ navController.navigate(Destination.Profile(annotation.item))
+ }
+ } ?: kotlin.run {
+ if (notification.post != null && notification.post.mediaAttachments.isEmpty()) {
+ navController.navigate(Destination.Mention(notification.post.id))
+ } else if (notification.post != null && notification.post.mediaAttachments.isNotEmpty()) {
+ navController.navigate(Destination.Post(notification.post.id))
+ } else if (notification.post == null) {
+ navController.navigate(Destination.Profile(notification.account.id))
+ }
+ }
})
- Text(text = text, overflow = TextOverflow.Ellipsis)
- }
Text(
- text = TimeAgo.convertTimeToText(notification.createdAt),
+ text = timeAgoText.value,
fontSize = 14.sp,
color = MaterialTheme.colorScheme.primary
)
}
- val doesMediaAttachmentExsist = (notification.post?.mediaAttachments?.size
- ?: 0) > 0
- if (showImage && (doesMediaAttachmentExsist || (viewModel.ancestor != null && viewModel.ancestor!!.mediaAttachments.isNotEmpty()))
- ) {
+ val doesMediaAttachmentExsist = (notification.post?.mediaAttachments?.size ?: 0) > 0
+ if (showImage && (doesMediaAttachmentExsist || (viewModel.ancestor != null && viewModel.ancestor!!.mediaAttachments.isNotEmpty()))) {
val previewUrl = if (doesMediaAttachmentExsist) {
notification.post?.mediaAttachments?.get(0)?.previewUrl
} else {
viewModel.ancestor?.mediaAttachments?.get(0)?.previewUrl
}
- //Spacer(modifier = Modifier.weight(1f))
- AsyncImage(model = previewUrl,
+ Spacer(modifier = Modifier.width(10.dp))
+ AsyncImage(
+ model = previewUrl,
contentDescription = "",
contentScale = ContentScale.Crop,
- modifier = Modifier
- .height(36.dp)
- .aspectRatio(1f)
- .clip(RoundedCornerShape(4.dp))
+ modifier = Modifier.height(36.dp).aspectRatio(1f).clip(RoundedCornerShape(4.dp))
.clickable {
- Navigate.navigate(
- "single_post_screen/" + if (doesMediaAttachmentExsist) {notification.post!!.id} else {viewModel.ancestor!!.id + "?openReplies=true"}, navController
+ navController.navigate(
+ Destination.Post(
+ id = if (doesMediaAttachmentExsist) {
+ notification.account.id
+ } else {
+ viewModel.ancestor!!.id
+ }, openReplies = true
+ )
)
})
}
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 73912c72..91557215 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
@@ -47,8 +47,6 @@ import com.daniebeler.pfpixelix.ui.composables.states.EndOfListComposable
import com.daniebeler.pfpixelix.ui.composables.states.ErrorComposable
import com.daniebeler.pfpixelix.ui.composables.states.FullscreenEmptyStateComposable
import com.daniebeler.pfpixelix.ui.composables.states.LoadingComposable
-import com.daniebeler.pfpixelix.utils.KmpContext
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.resources.vectorResource
import pixelix.app.generated.resources.Res
@@ -70,7 +68,6 @@ fun NotificationsComposable(
val lazyListState = rememberLazyListState()
val scrollState = rememberScrollState()
- val context: KmpContext = LocalKmpContext.current
Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = {
CenterAlignedTopAppBar(title = {
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/CommentsBottomSheet.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/CommentsBottomSheet.kt
index ad4b701b..592704b9 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/CommentsBottomSheet.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/CommentsBottomSheet.kt
@@ -65,8 +65,7 @@ import com.daniebeler.pfpixelix.ui.composables.post.reply.ReplyElementViewModel
import com.daniebeler.pfpixelix.ui.composables.states.ErrorComposable
import com.daniebeler.pfpixelix.ui.composables.states.FixedHeightLoadingComposable
import com.daniebeler.pfpixelix.ui.composables.textfield_mentions.TextFieldMentionsComposable
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import com.daniebeler.pfpixelix.utils.TimeAgo
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
@@ -82,7 +81,6 @@ fun CommentsBottomSheet(
post: Post, navController: NavController, viewModel: PostViewModel
) {
var replyText by remember { mutableStateOf(TextFieldValue("")) }
- val context = LocalKmpContext.current
Box(
modifier = Modifier
.fillMaxSize()
@@ -122,7 +120,7 @@ fun CommentsBottomSheet(
navController = navController,
{},
viewModel.myAccountId,
- { url -> viewModel.openUrl(url, context) })
+ { url -> viewModel.openUrl(url) })
}
TextFieldMentionsComposable(submit = { text ->
@@ -182,7 +180,7 @@ fun CommentsBottomSheet(
navController = navController,
{ viewModel.deleteReply(reply.id) },
viewModel.myAccountId,
- { url -> viewModel.openUrl(url, context) })
+ { url -> viewModel.openUrl(url) })
}
if (viewModel.repliesState.isLoading) {
@@ -262,9 +260,7 @@ private fun ReplyElement(
.width(42.dp)
.clip(CircleShape)
.clickable {
- Navigate.navigate(
- "profile_screen/" + reply.account.id, navController
- )
+ navController.navigate(Destination.Profile(reply.account.id))
})
Spacer(modifier = Modifier.width(12.dp))
@@ -275,9 +271,7 @@ private fun ReplyElement(
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.clickable {
- Navigate.navigate(
- "profile_screen/" + reply.account.id, navController
- )
+ navController.navigate(Destination.Profile(reply.account.id))
})
Text(
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/LikesBottomSheet.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/LikesBottomSheet.kt
index 5f6b5a7d..df28d095 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/LikesBottomSheet.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/LikesBottomSheet.kt
@@ -28,10 +28,11 @@ import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import coil3.compose.AsyncImage
import com.daniebeler.pfpixelix.domain.model.Account
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
+import org.jetbrains.compose.resources.pluralStringResource
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
-import pixelix.app.generated.resources.followers
+import pixelix.app.generated.resources.follower
import pixelix.app.generated.resources.liked_by
import pixelix.app.generated.resources.no_likes_yet
@@ -92,7 +93,7 @@ private fun LikedByAccountElement(account: Account, navController: NavController
.padding(horizontal = 12.dp, vertical = 8.dp)
.fillMaxWidth()
.clickable {
- Navigate.navigate("profile_screen/" + account.id, navController)
+ navController.navigate(Destination.Profile(account.id))
}, verticalAlignment = Alignment.CenterVertically
) {
AsyncImage(
@@ -107,7 +108,7 @@ private fun LikedByAccountElement(account: Account, navController: NavController
Column {
Text(text = "@${account.username}")
Text(
- text = "${account.followersCount} " + stringResource(Res.string.followers),
+ text = "${account.followersCount} " + pluralStringResource(Res.plurals.follower, account.followersCount),
fontSize = 14.sp,
color = MaterialTheme.colorScheme.primary
)
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 e9e9f210..2bbb9827 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
@@ -31,6 +31,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.outlined.Cached
+import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Description
import androidx.compose.material.icons.outlined.LocationOn
@@ -46,11 +47,11 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@@ -74,25 +75,24 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.zIndex
import androidx.navigation.NavController
-import co.touchlab.kermit.Logger
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
import com.daniebeler.pfpixelix.ui.composables.hashtagMentionText.HashtagsMentionsTextView
import com.daniebeler.pfpixelix.ui.composables.states.LoadingComposable
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import com.daniebeler.pfpixelix.utils.BlurHashDecoder
-import com.daniebeler.pfpixelix.utils.KmpUri
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
-import com.daniebeler.pfpixelix.utils.Navigate
-import com.daniebeler.pfpixelix.utils.toKmpUri
+import com.daniebeler.pfpixelix.utils.TimeAgo
import com.daniebeler.pfpixelix.utils.zoomable.rememberZoomState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.engawapg.lib.zoomable.snapBackZoomable
+import net.engawapg.lib.zoomable.zoomable
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.resources.vectorResource
@@ -115,6 +115,7 @@ 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
@@ -132,9 +133,6 @@ fun PostComposable(
updatePost: (post: Post) -> Unit = {},
viewModel: PostViewModel = injectViewModel(key = "post" + post.id) { postViewModel }
) {
-
- val context = LocalKmpContext.current
-
var postId by remember { mutableStateOf(post.id) }
val sheetState = rememberModalBottomSheetState()
var showBottomSheet by remember {
@@ -147,9 +145,8 @@ fun PostComposable(
)
}
- DisposableEffect(post.createdAt) {
- viewModel.convertTime(post.createdAt)
- onDispose {}
+ val timeAgoText = produceState(initialValue = "") {
+ value = TimeAgo.convertTimeToText(post.createdAt)
}
LaunchedEffect(Unit) {
@@ -197,7 +194,8 @@ fun PostComposable(
var animateHeart by remember { mutableStateOf(false) }
- val heartScale by animateFloatAsState(targetValue = if (animateHeart) 1.3f else 1f,
+ val heartScale by animateFloatAsState(
+ targetValue = if (animateHeart) 1.3f else 1f,
animationSpec = tween(durationMillis = 200, easing = LinearEasing),
finishedListener = {
animateHeart = false
@@ -211,9 +209,7 @@ fun PostComposable(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.padding(start = 16.dp, end = 12.dp).clickable(onClick = {
- Navigate.navigate(
- "profile_screen/" + reblogAccount.id, navController
- )
+ navController.navigate(Destination.Profile(reblogAccount.id))
})
) {
Icon(Icons.Outlined.Cached, contentDescription = "reblogged by")
@@ -229,9 +225,7 @@ fun PostComposable(
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(start = 16.dp, end = 12.dp).clickable(onClick = {
- Navigate.navigate(
- "profile_screen/" + viewModel.post!!.account.id, navController
- )
+ navController.navigate(Destination.Profile(viewModel.post!!.account.id))
})
) {
AsyncImage(
@@ -287,9 +281,9 @@ fun PostComposable(
Spacer(modifier = Modifier.height(6.dp))
if (viewModel.post!!.mediaAttachments.isNotEmpty()) {
- if (viewModel.post!!.sensitive && !viewModel.showPost) {
+ if (viewModel.post!!.sensitive && !viewModel.showPost && viewModel.blurSensitiveContent) {
- Box {
+ Box(modifier.padding(start = 12.dp, end = 12.dp).clip(RoundedCornerShape(16.dp))) {
val blurHashBitmap = BlurHashDecoder.decode(
viewModel.post!!.mediaAttachments[0].blurHash
)
@@ -333,11 +327,13 @@ fun PostComposable(
} else {
if (viewModel.post!!.mediaAttachments.count() > 1) {
- Box(
-
- ) {
+ val smallestAspectRatio = viewModel.post!!.mediaAttachments
+ .minByOrNull { it.meta?.original?.aspect ?: 1.0 }
+ Box {
HorizontalPager(
- state = pagerState, modifier = Modifier.zIndex(50f)
+ state = pagerState, modifier = Modifier.zIndex(50f).aspectRatio(
+ smallestAspectRatio?.meta?.original?.aspect?.toFloat() ?: 1f
+ )
) { page ->
Box(
modifier = Modifier.zIndex(10f)
@@ -406,7 +402,7 @@ fun PostComposable(
mentions = viewModel.post!!.mentions,
navController = navController,
textSize = 18.sp,
- openUrl = { url -> viewModel.openUrl(url, context) },
+ openUrl = { url -> viewModel.openUrl(url) },
modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
)
HorizontalDivider()
@@ -483,7 +479,7 @@ fun PostComposable(
viewModel.unreblogPost(postId, updatePost)
}) {
Icon(
- imageVector = vectorResource(Res.drawable.sync_outline),
+ imageVector = vectorResource(Res.drawable.sync_outline_bold),
contentDescription = "undo reblog post",
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.rotate(boostRotation)
@@ -530,23 +526,22 @@ fun PostComposable(
Text(
text = stringResource(Res.string.liked_by) + " ", fontSize = 14.sp
)
- Text(text = viewModel.post!!.likedBy!!.username!!,
+ Text(
+ text = viewModel.post!!.likedBy!!.username!!,
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.clickable {
- Navigate.navigate(
- "profile_screen/" + viewModel.post!!.likedBy!!.id,
- navController
- )
+ navController.navigate(Destination.Profile(viewModel.post!!.likedBy!!.id!!))
})
if (post.favouritesCount > 1) {
Text(
text = " " + stringResource(Res.string.and) + " ",
fontSize = 14.sp
)
- Text(text = (viewModel.post!!.favouritesCount - 1).toString() + " " + stringResource(
- Res.string.others
- ),
+ Text(
+ text = (viewModel.post!!.favouritesCount - 1).toString() + " " + stringResource(
+ Res.string.others
+ ),
fontWeight = FontWeight.Bold,
fontSize = 14.sp,
modifier = Modifier.clickable {
@@ -569,7 +564,7 @@ fun PostComposable(
text = viewModel.post!!.content,
mentions = viewModel.post!!.mentions,
navController = navController,
- openUrl = { url -> viewModel.openUrl(url, context) },
+ openUrl = { url -> viewModel.openUrl(url) },
maximumLines = 4
)
}
@@ -579,9 +574,10 @@ fun PostComposable(
Spacer(modifier = Modifier.height(6.dp))
- Text(text = stringResource(
- Res.string.view_comments, viewModel.post!!.replyCount
- ),
+ Text(
+ text = stringResource(
+ Res.string.view_comments, viewModel.post!!.replyCount
+ ),
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.clickable {
viewModel.loadReplies(
@@ -592,7 +588,7 @@ fun PostComposable(
}
Text(
- text = viewModel.timeAgoString,
+ text = timeAgoText.value,
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
@@ -609,15 +605,13 @@ fun PostComposable(
onDismissRequest = {
showBottomSheet = 0
},
- sheetState = sheetState,
- modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+ sheetState = sheetState
) {
if (showBottomSheet == 1) {
CommentsBottomSheet(post, navController, viewModel)
} else if (showBottomSheet == 2) {
if (viewModel.myAccountId != null && post.account.id == viewModel.myAccountId) {
ShareBottomSheet(
- context,
post.url,
true,
viewModel,
@@ -627,7 +621,6 @@ fun PostComposable(
)
} else {
ShareBottomSheet(
- context,
post.url,
false,
viewModel,
@@ -737,14 +730,13 @@ fun PostImage(
})
}) {
if (mediaAttachment.type == "image") {
- ImageWrapper(mediaAttachment,
+ ImageWrapper(
+ mediaAttachment,
{ zoomState.setContentSize(it.painter.intrinsicSize) },
{ imageLoaded = true })
} else {
VideoAttachment(
- mediaAttachment,
- viewModel,
- { imageLoaded = true })
+ mediaAttachment, viewModel, { imageLoaded = true })
}
}
@@ -800,7 +792,8 @@ private fun ImageWrapper(
setContentSize: (painter: AsyncImagePainter.State.Success) -> Unit,
onSuccess: () -> Unit
) {
- AsyncImage(model = mediaAttachment.url,
+ AsyncImage(
+ model = mediaAttachment.url,
contentDescription = "",
Modifier.fillMaxWidth(),
contentScale = ContentScale.FillWidth,
@@ -847,18 +840,24 @@ fun MediaDialog(
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Box(
- modifier = Modifier.fillMaxSize().background(Color.Black.copy(alpha = 0.8f))
- .clickable(onClick = closeDialog), // Close on background tap
+ modifier = Modifier.fillMaxSize().background(Color.Black.copy(alpha = 0.8f)).clickable {
+ closeDialog()
+ },
contentAlignment = Alignment.Center
) {
- Box(modifier = Modifier.zIndex(2f).snapBackZoomable(zoomState)) {
+ Box(modifier = Modifier.zIndex(2f).zoomable(zoomState).clickable { }) {
if (mediaAttachment.type == "image") {
- ImageWrapper(mediaAttachment,
+ ImageWrapper(
+ mediaAttachment,
{ zoomState.setContentSize(it.painter.intrinsicSize) },
{})
} else {
VideoAttachment(mediaAttachment, postViewModel, {})
-
+ }
+ }
+ Box(Modifier.align(Alignment.TopEnd).padding(20.dp).zIndex(2f)) {
+ IconButton(onClick = { closeDialog() }) {
+ Icon(Icons.Outlined.Close, "", tint = Color.White)
}
}
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/PostViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/PostViewModel.kt
index dd3b71e7..ce250f83 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/PostViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/PostViewModel.kt
@@ -5,19 +5,21 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.daniebeler.pfpixelix.domain.service.utils.Resource
+import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.domain.model.LikedBy
+import com.daniebeler.pfpixelix.domain.model.NewReport
import com.daniebeler.pfpixelix.domain.model.Post
+import com.daniebeler.pfpixelix.domain.model.ReportObjectType
import com.daniebeler.pfpixelix.domain.service.account.AccountService
import com.daniebeler.pfpixelix.domain.service.editor.PostEditorService
+import com.daniebeler.pfpixelix.domain.service.file.FileService
import com.daniebeler.pfpixelix.domain.service.platform.Platform
import com.daniebeler.pfpixelix.domain.service.post.PostService
import com.daniebeler.pfpixelix.domain.service.preferences.UserPreferences
import com.daniebeler.pfpixelix.domain.service.session.AuthService
+import com.daniebeler.pfpixelix.domain.service.utils.Resource
import com.daniebeler.pfpixelix.ui.composables.post.reply.OwnReplyState
import com.daniebeler.pfpixelix.ui.composables.post.reply.RepliesState
-import com.daniebeler.pfpixelix.utils.KmpContext
-import com.daniebeler.pfpixelix.utils.TimeAgo
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
@@ -32,7 +34,8 @@ class PostViewModel @Inject constructor(
private val postEditorService: PostEditorService,
private val authService: AuthService,
private val accountService: AccountService,
- private val platform: Platform
+ private val platform: Platform,
+ private val fileService: FileService
) : ViewModel() {
var post: Post? by mutableStateOf(null)
@@ -45,16 +48,16 @@ class PostViewModel @Inject constructor(
var deleteState by mutableStateOf(DeleteState())
var deleteDialog: String? by mutableStateOf(null)
- var timeAgoString: String by mutableStateOf("")
-
+ var reportState by mutableStateOf(null)
var showPost: Boolean by mutableStateOf(false)
var myAccountId: String? = null
var myUsername: String? = null
-
var isAltTextButtonHidden by mutableStateOf(false)
var isInFocusMode by mutableStateOf(false)
+ var isAutoplayVideos by mutableStateOf(true)
+ var blurSensitiveContent by mutableStateOf(false)
var volume by mutableStateOf(prefs.enableVolume)
@@ -70,6 +73,12 @@ class PostViewModel @Inject constructor(
viewModelScope.launch {
prefs.focusModeFlow.collect { isInFocusMode = it }
}
+ viewModelScope.launch {
+ prefs.autoplayVideoFlow.collect { isAutoplayVideos = it }
+ }
+ viewModelScope.launch {
+ prefs.blurSensitiveContentFlow.collect { blurSensitiveContent = it }
+ }
}
fun toggleVolume(newVolume: Boolean) {
@@ -113,10 +122,6 @@ class PostViewModel @Inject constructor(
showPost = !showPost
}
- fun convertTime(createdAt: String) {
- timeAgoString = TimeAgo.convertTimeToText(createdAt)
- }
-
fun loadReplies(postId: String) {
postService.getReplies(postId).onEach { result ->
repliesState = when (result) {
@@ -167,11 +172,11 @@ class PostViewModel @Inject constructor(
}
is Resource.Error -> {
- println(result.message)
+ Logger.e(result.message)
}
is Resource.Loading -> {
- println("is loading")
+ Logger.v("is loading")
}
}
}.launchIn(viewModelScope)
@@ -242,10 +247,27 @@ class PostViewModel @Inject constructor(
return
}
post = post?.copy(
- favourited = false, favouritesCount = post?.favouritesCount?.minus(
+ favourited = false,
+ favouritesCount = post?.favouritesCount?.minus(
1
- ) ?: 0
+ ) ?: 0,
)
+
+ post?.likedBy?.let {
+ if (it.username == myUsername) {
+ post = post!!.copy(
+ likedBy = post!!.likedBy!!.copy(
+ username = null,
+ totalCount = post!!.likedBy!!.totalCount - 1
+ )
+ )
+ } else {
+ post = post!!.copy(
+ likedBy = post!!.likedBy!!.copy(totalCount = post!!.likedBy!!.totalCount - 1)
+ )
+ }
+ }
+
post?.let { updatePost(it) }
CoroutineScope(Dispatchers.Default).launch {
@@ -253,7 +275,7 @@ class PostViewModel @Inject constructor(
when (result) {
is Resource.Success -> {
post = post?.copy(favourited = result.data?.favourited ?: false)
- result.data?.let { updatePost(result.data) }
+ post?.let { updatePost(it) }
}
is Resource.Error -> {
@@ -280,7 +302,7 @@ class PostViewModel @Inject constructor(
when (result) {
is Resource.Success -> {
post = post?.copy(reblogged = result.data?.reblogged ?: false)
- result.data?.let { updatePost(result.data) }
+ post?.let { updatePost(it) }
}
is Resource.Error -> {
@@ -305,7 +327,7 @@ class PostViewModel @Inject constructor(
when (result) {
is Resource.Success -> {
post = post?.copy(reblogged = result.data?.reblogged ?: false)
- result.data?.let { updatePost(result.data) }
+ post?.let { updatePost(it) }
}
is Resource.Error -> {
@@ -329,8 +351,8 @@ class PostViewModel @Inject constructor(
postService.bookmarkPost(postId).onEach { result ->
when (result) {
is Resource.Success -> {
- post = post?.copy(bookmarked = result.data?.bookmarked ?: false)
- result.data?.let { updatePost(result.data) }
+ post = post?.copy(bookmarked = result.data.bookmarked)
+ post?.let { updatePost(it) }
}
is Resource.Error -> {
@@ -355,7 +377,7 @@ class PostViewModel @Inject constructor(
when (result) {
is Resource.Success -> {
post = post?.copy(bookmarked = result.data?.bookmarked ?: false)
- result.data?.let { updatePost(result.data) }
+ post?.let { updatePost(it) }
}
is Resource.Error -> {
@@ -369,15 +391,50 @@ class PostViewModel @Inject constructor(
}.launchIn(viewModelScope)
}
}
+ }
+
+ fun reportPost(category: String) {
+ reportState = ReportState(isLoading = true, reported = false)
+ if (post == null) {
+ reportState = ReportState(isLoading = false, reported = false, error = "an unexpected error occurred")
+ return
+ }
+ val newReport = NewReport(
+ reportType = category,
+ objectType = ReportObjectType.POST,
+ objectId = post!!.id
+ )
+ CoroutineScope(Dispatchers.Default).launch {
+ postService.reportPost(newReport).onEach { result ->
+ reportState = when (result) {
+ is Resource.Success -> {
+ ReportState(
+ reported = true
+ )
+ }
+ is Resource.Error -> {
+ ReportState(
+ error = "an unexpected error occured"
+ )
+ }
+
+ is Resource.Loading -> {
+ ReportState(
+ isLoading = true
+ )
+ }
+ }
+ }.launchIn(viewModelScope)
+ }
}
- fun openUrl(url: String, context: KmpContext) {
+ fun openUrl(url: String) {
platform.openUrl(url)
}
- fun saveImage(name: String?, url: String, context: KmpContext) {
- platform.downloadImageToGallery(name, url)
+ fun saveImage(name: String?, url: String) {
+ fileService.downloadFile(name, url)
}
fun shareText(text: String) {
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/ReportDialog.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/ReportDialog.kt
new file mode 100644
index 00000000..a1d5c508
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/ReportDialog.kt
@@ -0,0 +1,112 @@
+package com.daniebeler.pfpixelix.ui.composables.post
+
+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.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Card
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import org.jetbrains.compose.resources.StringResource
+import org.jetbrains.compose.resources.stringResource
+import org.jetbrains.compose.resources.vectorResource
+import pixelix.app.generated.resources.Res
+import pixelix.app.generated.resources.abusive_or_harmful
+import pixelix.app.generated.resources.adult_or_sensitive_content
+import pixelix.app.generated.resources.cancel
+import pixelix.app.generated.resources.chevron_forward_outline
+import pixelix.app.generated.resources.copyright_infringement
+import pixelix.app.generated.resources.impersonation
+import pixelix.app.generated.resources.ok
+import pixelix.app.generated.resources.scam
+import pixelix.app.generated.resources.spam
+import pixelix.app.generated.resources.terrorism
+import pixelix.app.generated.resources.underage_account
+import pixelix.app.generated.resources.violence
+import pixelix.app.generated.resources.report
+import pixelix.app.generated.resources.reported
+
+@Composable
+fun ReportDialog(
+ dismissDialog: () -> Unit,
+ reportState: ReportState?,
+ report: (category: String) -> Unit
+) {
+ Dialog(
+ onDismissRequest = dismissDialog,
+ ) {
+ Card(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ shape = RoundedCornerShape(16.dp),
+ ) {
+ Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ if (reportState != null) {
+ if (reportState.isLoading) {
+ CircularProgressIndicator()
+ } else if (reportState.error.isNotBlank()) {
+ Text("an unexpected error occurred")
+ } else {
+ Text(stringResource(Res.string.reported))
+ }
+ HorizontalDivider()
+ Row (modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
+ TextButton(onClick = { dismissDialog() }) {
+ Text(stringResource(Res.string.ok))
+ }
+ }
+ } else {
+ Text(stringResource(Res.string.report), style = MaterialTheme.typography.headlineSmall)
+ HorizontalDivider()
+ ReportCategoryButton(Res.string.spam, { report("spam") })
+ ReportCategoryButton(Res.string.adult_or_sensitive_content, { report("sensitive") })
+ ReportCategoryButton(Res.string.abusive_or_harmful, { report("abusive") })
+ ReportCategoryButton(Res.string.underage_account, { report("underage") })
+ ReportCategoryButton(Res.string.violence, { report("violence") })
+ ReportCategoryButton(Res.string.copyright_infringement, { report("copyright") })
+ ReportCategoryButton(Res.string.impersonation, { report("impersonation") })
+ ReportCategoryButton(Res.string.scam, { report("scam") })
+ ReportCategoryButton(Res.string.terrorism, { report("terrorism") })
+
+ HorizontalDivider()
+ Row (modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
+ TextButton(onClick = { dismissDialog() }) {
+ Text(stringResource(Res.string.cancel))
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun ReportCategoryButton(category: StringResource, onClick: () -> Unit) {
+ Row(Modifier.fillMaxWidth().clickable { onClick() }) {
+ Text(stringResource(category))
+ Icon(
+ imageVector = vectorResource(Res.drawable.chevron_forward_outline),
+ contentDescription = stringResource(category),
+ tint = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier.padding(start = 4.dp)
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/ReportState.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/ReportState.kt
new file mode 100644
index 00000000..09e9384a
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/ReportState.kt
@@ -0,0 +1,7 @@
+package com.daniebeler.pfpixelix.ui.composables.post
+
+data class ReportState(
+ val isLoading: Boolean = false,
+ val reported: Boolean = false,
+ val error: String = ""
+)
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/ShareBottomSheet.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/ShareBottomSheet.kt
index 085a9294..20aacb7f 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/ShareBottomSheet.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/ShareBottomSheet.kt
@@ -25,8 +25,7 @@ import com.daniebeler.pfpixelix.domain.model.Post
import com.daniebeler.pfpixelix.domain.model.Visibility
import com.daniebeler.pfpixelix.domain.service.platform.PlatformFeatures
import com.daniebeler.pfpixelix.ui.composables.ButtonRowElement
-import com.daniebeler.pfpixelix.utils.KmpContext
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.resources.vectorResource
@@ -48,10 +47,11 @@ import pixelix.app.generated.resources.share_this_post
import pixelix.app.generated.resources.trash_outline
import pixelix.app.generated.resources.unlisted
import pixelix.app.generated.resources.visibility_x
+import pixelix.app.generated.resources.warning
+import pixelix.app.generated.resources.report_this_post
@Composable
fun ShareBottomSheet(
- context: KmpContext,
url: String,
minePost: Boolean,
viewModel: PostViewModel,
@@ -64,6 +64,8 @@ fun ShareBottomSheet(
mutableStateOf("")
}
+ var isReportDialogOpen by remember { mutableStateOf(false) }
+
val mediaAttachment: MediaAttachment? = viewModel.post?.mediaAttachments?.let { attachments ->
if (attachments.isNotEmpty() && currentMediaAttachmentNumber in attachments.indices) {
attachments[currentMediaAttachmentNumber]
@@ -99,36 +101,39 @@ fun ShareBottomSheet(
Text(text = stringResource(Res.string.visibility_x, humanReadableVisibility))
}
if (mediaAttachment?.license != null) {
- ButtonRowElement(icon = Res.drawable.document_text_outline, text = stringResource(
- Res.string.license, mediaAttachment.license.title
- ), onClick = {
- viewModel.openUrl(mediaAttachment.license.url, context)
- })
+ ButtonRowElement(
+ icon = Res.drawable.document_text_outline, text = stringResource(
+ Res.string.license, mediaAttachment.license.title
+ ), onClick = {
+ viewModel.openUrl(mediaAttachment.license.url)
+ })
}
HorizontalDivider(Modifier.padding(12.dp))
- ButtonRowElement(icon = Res.drawable.open_outline, text = stringResource(
- Res.string.open_in_browser
- ), onClick = {
- viewModel.openUrl(url, context)
- })
+ ButtonRowElement(
+ icon = Res.drawable.open_outline, text = stringResource(
+ Res.string.open_in_browser
+ ), onClick = {
+ viewModel.openUrl(url)
+ })
- ButtonRowElement(icon = Res.drawable.share_social_outline,
+ ButtonRowElement(
+ icon = Res.drawable.share_social_outline,
text = stringResource(Res.string.share_this_post),
onClick = {
viewModel.shareText(url)
})
if (mediaAttachment != null && PlatformFeatures.downloadToGallery && mediaAttachment.type == "image") {
- ButtonRowElement(icon = Res.drawable.cloud_download_outline,
+ ButtonRowElement(
+ icon = Res.drawable.cloud_download_outline,
text = stringResource(Res.string.download_image),
onClick = {
viewModel.saveImage(
post.account.username,
- viewModel.post!!.mediaAttachments[currentMediaAttachmentNumber].url!!,
- context
+ viewModel.post!!.mediaAttachments[currentMediaAttachmentNumber].url!!
)
})
}
@@ -140,7 +145,7 @@ fun ShareBottomSheet(
icon = Res.drawable.pencil_outline,
text = stringResource(Res.string.edit_post),
onClick = {
- Navigate.navigate("edit_post_screen/${post.id}", navController = navController)
+ navController.navigate(Destination.EditPost(post.id))
}
)
ButtonRowElement(
@@ -151,6 +156,30 @@ fun ShareBottomSheet(
},
color = MaterialTheme.colorScheme.error
)
+ } else {
+ HorizontalDivider(Modifier.padding(12.dp))
+
+ ButtonRowElement(
+ icon = Res.drawable.warning,
+ text = stringResource(Res.string.report_this_post),
+ onClick = {
+ isReportDialogOpen = true
+ },
+ color = MaterialTheme.colorScheme.error
+ )
+ }
+ }
+
+ if (isReportDialogOpen) {
+ ReportDialog(
+ dismissDialog = {
+ isReportDialogOpen = false
+ viewModel.reportState = null
+ },
+ reportState = viewModel.reportState
+ ) { category ->
+ viewModel.reportPost(category)
+ viewModel.reportState = null
}
}
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/VideoAttachment.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/VideoAttachment.kt
index cb006c6b..7b6bbf7b 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/VideoAttachment.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/VideoAttachment.kt
@@ -1,8 +1,6 @@
package com.daniebeler.pfpixelix.ui.composables.post
-import androidx.compose.animation.core.LinearEasing
-import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.core.tween
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
@@ -32,8 +30,8 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
+import com.daniebeler.pfpixelix.di.LocalAppComponent
import com.daniebeler.pfpixelix.domain.model.MediaAttachment
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
import com.daniebeler.pfpixelix.utils.VideoPlayer
@Composable
@@ -42,16 +40,23 @@ fun VideoAttachment(
viewModel: PostViewModel,
onReady: () -> Unit
) {
- val context = LocalKmpContext.current
val coroutineScope = rememberCoroutineScope()
+ val context = LocalAppComponent.current.context
val player = remember { VideoPlayer(context, coroutineScope) }
var progress by remember { mutableFloatStateOf(0f) }
var hasAudio by remember { mutableStateOf(false) }
+ var isPlaying by remember { mutableStateOf(false) }
var videoFrameIsVisible by remember { mutableStateOf(false) }
Column {
- Box {
+ Box(Modifier.clickable {
+ if (isPlaying) {
+ player.pause()
+ } else {
+ player.play()
+ }
+ }) {
player.view(
modifier = Modifier
.fillMaxWidth()
@@ -109,6 +114,7 @@ fun VideoAttachment(
progress = current.toFloat() / duration.toFloat()
}
player.hasAudio = { hasAudio = it }
+ player.isVideoPlaying = { isPlaying = it }
onDispose {
player.progress = null
@@ -118,7 +124,7 @@ fun VideoAttachment(
}
LaunchedEffect(videoFrameIsVisible) {
- if (videoFrameIsVisible) {
+ if (videoFrameIsVisible && viewModel.isAutoplayVideos) {
player.play()
} else {
player.pause()
@@ -130,10 +136,11 @@ fun VideoAttachment(
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> {
- if (videoFrameIsVisible) {
+ if (videoFrameIsVisible && viewModel.isAutoplayVideos) {
player.play()
}
}
+
Lifecycle.Event.ON_PAUSE -> {
player.pause()
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/reply/ReplyElementViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/reply/ReplyElementViewModel.kt
index 6572c17c..3921c9c6 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/reply/ReplyElementViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/reply/ReplyElementViewModel.kt
@@ -5,10 +5,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.daniebeler.pfpixelix.domain.service.utils.Resource
+import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.domain.model.Post
import com.daniebeler.pfpixelix.domain.service.editor.PostEditorService
import com.daniebeler.pfpixelix.domain.service.post.PostService
+import com.daniebeler.pfpixelix.domain.service.utils.Resource
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import me.tatarka.inject.annotations.Inject
@@ -74,10 +75,10 @@ class ReplyElementViewModel @Inject constructor(
}
is Resource.Error -> {
- println(result.message)
+ Logger.e(result.message)
}
is Resource.Loading -> {
- println("is loading")
+ Logger.v("is loading")
}
}
}.launchIn(viewModelScope)
@@ -95,7 +96,7 @@ class ReplyElementViewModel @Inject constructor(
}
is Resource.Loading -> {
- println("is loading")
+ Logger.v("is loading")
}
}
}.launchIn(viewModelScope)
@@ -113,7 +114,7 @@ class ReplyElementViewModel @Inject constructor(
}
is Resource.Loading -> {
- println("is loading")
+ Logger.v("is loading")
}
}
}.launchIn(viewModelScope)
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 ca511495..615c5109 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
@@ -34,7 +34,7 @@ import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import coil3.compose.AsyncImage
import com.daniebeler.pfpixelix.ui.composables.InfiniteListHandler
-import com.daniebeler.pfpixelix.utils.Navigate
+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
@@ -74,7 +74,7 @@ fun CollectionsComposable(
Modifier
.padding(12.dp)
.clickable {
- Navigate.navigate("collection_screen/" + it.id, navController)
+ navController.navigate(Destination.Collection(it.id))
}, horizontalAlignment = Alignment.CenterHorizontally
) {
AsyncImage(
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/MutualFollowersComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/MutualFollowersComposable.kt
index 6ad2819a..8f3bbaad 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/MutualFollowersComposable.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/MutualFollowersComposable.kt
@@ -36,7 +36,7 @@ import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import coil3.compose.AsyncImage
import com.daniebeler.pfpixelix.ui.composables.followers.FollowerElementComposable
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.and
@@ -203,8 +203,7 @@ fun MutualFollowersComposable(
annotatedString.getStringAnnotations("account", it, it)
.firstOrNull()?.let { annotation ->
- println(annotation.item)
- Navigate.navigate("profile_screen/" + annotation.item, navController, false)
+ navController.navigate(Destination.Profile(annotation.item))
}
})
}
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 b0a086cd..38f9ce20 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
@@ -33,20 +33,22 @@ import coil3.compose.AsyncImage
import com.daniebeler.pfpixelix.domain.model.Account
import com.daniebeler.pfpixelix.domain.model.Relationship
import com.daniebeler.pfpixelix.ui.composables.hashtagMentionText.HashtagsMentionsTextView
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import com.daniebeler.pfpixelix.utils.StringFormat
import kotlinx.datetime.LocalDate
import kotlinx.datetime.format.MonthNames
import kotlinx.datetime.format.char
import org.jetbrains.compose.resources.painterResource
+import org.jetbrains.compose.resources.pluralStringResource
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.admin
import pixelix.app.generated.resources.blocked
import pixelix.app.generated.resources.default_avatar
-import pixelix.app.generated.resources.followers
+import pixelix.app.generated.resources.follower
import pixelix.app.generated.resources.following
import pixelix.app.generated.resources.follows_you
+import pixelix.app.generated.resources.joined_date
import pixelix.app.generated.resources.muted
import pixelix.app.generated.resources.posts
@@ -80,35 +82,31 @@ fun ProfileTopSection(
fontWeight = FontWeight.Bold,
fontSize = 18.sp
)
- Text(text = stringResource(Res.string.posts), fontSize = 12.sp)
+ Text(text = pluralStringResource(Res.plurals.posts, account.postsCount), fontSize = 12.sp)
}
Column(horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.clickable {
- Navigate.navigate(
- "followers_screen/" + "followers/" + account.id, navController
- )
+ navController.navigate(Destination.Followers(account.id, true))
}) {
Text(
text = StringFormat.groupDigits(account.followersCount),
fontWeight = FontWeight.Bold,
fontSize = 18.sp
)
- Text(text = stringResource(Res.string.followers), fontSize = 12.sp)
+ Text(text = pluralStringResource(Res.plurals.follower, account.followersCount), fontSize = 12.sp)
}
Column(horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.clickable {
- Navigate.navigate(
- "followers_screen/" + "following/" + account.id, navController
- )
+ navController.navigate(Destination.Followers(account.id, false))
}) {
Text(
text = StringFormat.groupDigits(account.followingCount),
fontWeight = FontWeight.Bold,
fontSize = 18.sp
)
- Text(text = stringResource(Res.string.following), fontSize = 12.sp)
+ Text(text = pluralStringResource(Res.plurals.following, account.followingCount), fontSize = 12.sp)
}
}
}
@@ -194,7 +192,10 @@ fun ProfileTopSection(
year()
}
Text(
- text = "Joined ${formatter.format(date)}",
+ text = stringResource(
+ Res.string.joined_date,
+ formatter.format(date)
+ ),
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 10.sp
)
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 c9d794fe..5f495f86 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
@@ -67,8 +67,7 @@ import com.daniebeler.pfpixelix.ui.composables.profile.ProfileTopSection
import com.daniebeler.pfpixelix.ui.composables.profile.SwitchViewComposable
import com.daniebeler.pfpixelix.ui.composables.profile.server_stats.DomainSoftwareComposable
import com.daniebeler.pfpixelix.ui.composables.states.EmptyState
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
-import com.daniebeler.pfpixelix.utils.Navigate
+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
@@ -122,7 +121,6 @@ fun OtherProfileComposable(
byUsername: String?,
viewModel: OtherProfileViewModel = injectViewModel(key = "other-profile$userId$byUsername") { otherProfileViewModel }
) {
-
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val lazyGridState = rememberLazyListState()
@@ -133,13 +131,11 @@ fun OtherProfileComposable(
var showBlockAlert by remember { mutableStateOf(false) }
var showUnBlockAlert by remember { mutableStateOf(false) }
- val context = LocalKmpContext.current
-
- LaunchedEffect(Unit) {
+ LaunchedEffect(userId) {
if (userId != "") {
- viewModel.loadData(userId, false)
+ viewModel.loadData(userId, false, navController)
} else {
- viewModel.loadDataByUsername(byUsername!!, false)
+ viewModel.loadDataByUsername(byUsername!!, false, navController)
}
}
@@ -186,7 +182,7 @@ fun OtherProfileComposable(
}) { paddingValues ->
PullToRefreshBox (
isRefreshing = viewModel.accountState.refreshing || viewModel.postsState.refreshing,
- onRefresh = { viewModel.loadData(userId, true) },
+ onRefresh = { viewModel.loadData(userId, true, navController) },
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
@@ -203,7 +199,7 @@ fun OtherProfileComposable(
relationship = viewModel.relationshipState.accountRelationship,
navController,
openUrl = { url ->
- viewModel.openUrl(url, context)
+ viewModel.openUrl(url)
})
}
@@ -268,9 +264,7 @@ fun OtherProfileComposable(
Button(
onClick = {
viewModel.accountState.account?.let { account ->
- Navigate.navigate(
- "chat/" + account.id, navController
- )
+ navController.navigate(Destination.Chat(account.id))
}
},
modifier = Modifier.weight(1f),
@@ -290,7 +284,7 @@ fun OtherProfileComposable(
getMoreCollections = {viewModel.getCollections(account.id, true)},
navController = navController,
instanceDomain = viewModel.domain,
- openUrl = { url -> viewModel.openUrl(url, context) })
+ openUrl = { url -> viewModel.openUrl(url) })
}
HorizontalDivider(Modifier.padding(bottom = 12.dp, top = 12.dp))
@@ -335,7 +329,7 @@ fun OtherProfileComposable(
}
}
- ToTopButton(listState = lazyGridState, refresh = {viewModel.loadData(userId, true)})
+ ToTopButton(listState = lazyGridState, refresh = {viewModel.loadData(userId, true, navController)})
InfiniteListHandler(lazyListState = lazyGridState) {
viewModel.getPostsPaginated(viewModel.userId)
@@ -389,7 +383,7 @@ fun OtherProfileComposable(
ButtonRowElement(icon = Res.drawable.browsers_outline, text = stringResource(
Res.string.open_in_browser
), onClick = {
- viewModel.openUrl(viewModel.accountState.account!!.url, context)
+ viewModel.openUrl(viewModel.accountState.account!!.url)
})
ButtonRowElement(icon = Res.drawable.share_social_outline,
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/other_profile/OtherProfileViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/other_profile/OtherProfileViewModel.kt
index aba83096..8183e12d 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/other_profile/OtherProfileViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/other_profile/OtherProfileViewModel.kt
@@ -6,6 +6,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import androidx.navigation.NavController
import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.domain.model.Post
import com.daniebeler.pfpixelix.domain.repository.PixelfedApi
@@ -15,6 +16,7 @@ import com.daniebeler.pfpixelix.domain.service.hashtag.SearchService
import com.daniebeler.pfpixelix.domain.service.platform.Platform
import com.daniebeler.pfpixelix.domain.service.post.PostService
import com.daniebeler.pfpixelix.domain.service.preferences.UserPreferences
+import com.daniebeler.pfpixelix.domain.service.session.AuthService
import com.daniebeler.pfpixelix.domain.service.utils.Resource
import com.daniebeler.pfpixelix.ui.composables.profile.AccountState
import com.daniebeler.pfpixelix.ui.composables.profile.CollectionsState
@@ -22,7 +24,7 @@ import com.daniebeler.pfpixelix.ui.composables.profile.MutualFollowersState
import com.daniebeler.pfpixelix.ui.composables.profile.PostsState
import com.daniebeler.pfpixelix.ui.composables.profile.RelationshipState
import com.daniebeler.pfpixelix.ui.composables.profile.ViewEnum
-import com.daniebeler.pfpixelix.utils.KmpContext
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -36,6 +38,7 @@ class OtherProfileViewModel(
private val platform: Platform,
private val prefs: UserPreferences,
private val collectionService: CollectionService,
+ private val authService: AuthService
) : ViewModel() {
var userId: String = ""
var accountState by mutableStateOf(AccountState())
@@ -48,10 +51,18 @@ class OtherProfileViewModel(
var domain by mutableStateOf("")
var view by mutableStateOf(ViewEnum.Grid)
- fun loadData(_userId: String, refreshing: Boolean) {
+ fun loadData(_userId: String, refreshing: Boolean, navController: NavController) {
+ val myAccountId = authService.getCurrentSession()!!.accountId
+
+ if (_userId == myAccountId) {
+ navController.popBackStack()
+ navController.navigate(Destination.OwnProfile)
+ }
+
userId = _userId
getAccount(userId, refreshing)
loadDataExceptAccount(refreshing)
+
}
private fun loadDataExceptAccount(refreshing: Boolean) {
@@ -69,7 +80,12 @@ class OtherProfileViewModel(
}
}
- fun loadDataByUsername(username: String, refreshing: Boolean) {
+ fun loadDataByUsername(username: String, refreshing: Boolean, navController: NavController) {
+ val myUsername = authService.getCurrentSession()!!.username
+ if (username == myUsername) {
+ navController.popBackStack()
+ navController.navigate(Destination.OwnProfile)
+ }
getAccountByUsername(username, refreshing)
}
@@ -132,7 +148,11 @@ class OtherProfileViewModel(
}
is Resource.Loading -> {
- AccountState(isLoading = true, account = accountState.account, refreshing = refreshing)
+ AccountState(
+ isLoading = true,
+ account = accountState.account,
+ refreshing = refreshing
+ )
}
}
@@ -157,7 +177,11 @@ class OtherProfileViewModel(
}
is Resource.Loading -> {
- AccountState(isLoading = true, account = accountState.account, refreshing = refreshing)
+ AccountState(
+ isLoading = true,
+ account = accountState.account,
+ refreshing = refreshing
+ )
}
}
@@ -184,12 +208,16 @@ class OtherProfileViewModel(
CollectionsState(collections = result.data ?: emptyList())
} else {
val endReached = result.data!!.isEmpty()
- CollectionsState(collections = collectionsState.collections + result.data, endReached = endReached)
+ CollectionsState(
+ collections = collectionsState.collections + result.data,
+ endReached = endReached
+ )
}
}
is Resource.Error -> {
- collectionsState = CollectionsState(error = result.message ?: "An unexpected error occurred")
+ collectionsState =
+ CollectionsState(error = result.message ?: "An unexpected error occurred")
}
is Resource.Loading -> {
@@ -202,6 +230,9 @@ class OtherProfileViewModel(
}
private fun getPostsFirstLoad(userId: String, refreshing: Boolean) {
+ if (postsState.posts.isNotEmpty() && !refreshing) {
+ return
+ }
postService.getPostsOfAccount(userId).onEach { result ->
postsState = when (result) {
is Resource.Success -> {
@@ -358,7 +389,7 @@ class OtherProfileViewModel(
}.launchIn(viewModelScope)
}
- fun openUrl(url: String, context: KmpContext) {
+ fun openUrl(url: String) {
platform.openUrl(url)
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/AccountSwitchBottomSheet.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/AccountSwitchBottomSheet.kt
index 102734ff..dd38eafc 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/AccountSwitchBottomSheet.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/AccountSwitchBottomSheet.kt
@@ -20,6 +20,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -29,10 +30,11 @@ 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.utils.Destinations
+import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.domain.model.credentialsToAccount
import com.daniebeler.pfpixelix.ui.composables.custom_account.CustomAccount
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -54,7 +56,9 @@ fun AccountSwitchBottomSheet(
viewModel: AccountSwitchViewModel = injectViewModel(key = "account_switcher_viewmodel") { accountSwitchViewModel }
) {
val showRemoveLoginDataAlert = remember { mutableStateOf("") }
-
+ LaunchedEffect(Unit) {
+ viewModel.loadData()
+ }
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
val sessionStorage = viewModel.sessionStorage
if (viewModel.currentCredentials != null) {
@@ -98,7 +102,7 @@ fun AccountSwitchBottomSheet(
.padding(horizontal = 12.dp, vertical = 8.dp)
.fillMaxWidth()
.clickable {
- navController.navigate(Destinations.NewLogin.route)
+ navController.navigate(Destination.NewLogin)
closeBottomSheet()
},
verticalAlignment = Alignment.CenterVertically
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/AccountSwitchViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/AccountSwitchViewModel.kt
index 0d790122..5ec2e8e9 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/AccountSwitchViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/AccountSwitchViewModel.kt
@@ -8,7 +8,6 @@ import androidx.lifecycle.viewModelScope
import com.daniebeler.pfpixelix.domain.service.session.AuthService
import com.daniebeler.pfpixelix.domain.service.session.Credentials
import com.daniebeler.pfpixelix.domain.service.session.SessionStorage
-import com.daniebeler.pfpixelix.utils.Navigate
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import me.tatarka.inject.annotations.Inject
@@ -18,7 +17,8 @@ class AccountSwitchViewModel @Inject constructor(
) : ViewModel() {
var sessionStorage by mutableStateOf(null)
var currentCredentials by mutableStateOf(null)
- init {
+
+ fun loadData() {
viewModelScope.launch {
loadCurrentCredentials()
}
@@ -45,7 +45,6 @@ class AccountSwitchViewModel @Inject constructor(
}
coroutine.invokeOnCompletion {
- Navigate.changeAccount()
changedAccount()
loadAccounts()
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/ModalBottomSheetContent.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/ModalBottomSheetContent.kt
index a53588a4..f9847a63 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/ModalBottomSheetContent.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/ModalBottomSheetContent.kt
@@ -8,12 +8,11 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.daniebeler.pfpixelix.ui.composables.ButtonRowElement
import com.daniebeler.pfpixelix.ui.composables.ButtonRowElementWithRoundedImage
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.imageResource
import org.jetbrains.compose.resources.stringResource
@@ -29,7 +28,6 @@ import pixelix.app.generated.resources.heart_outline
import pixelix.app.generated.resources.liked_posts
import pixelix.app.generated.resources.muted_accounts
import pixelix.app.generated.resources.pixelfed_logo
-import pixelix.app.generated.resources.pixelix_logo
import pixelix.app.generated.resources.remove_circle_outline
import pixelix.app.generated.resources.settings
import pixelix.app.generated.resources.settings_outline
@@ -64,7 +62,7 @@ fun ModalBottomSheetContent(
text = stringResource(Res.string.liked_posts),
onClick = {
closeBottomSheet()
- Navigate.navigate("liked_posts_screen", navController)
+ navController.navigate(Destination.LikedPosts)
})
ButtonRowElement(
@@ -72,7 +70,7 @@ fun ModalBottomSheetContent(
text = stringResource(Res.string.bookmarked_posts),
onClick = {
closeBottomSheet()
- Navigate.navigate("bookmarked_posts_screen", navController)
+ navController.navigate(Destination.BookmarkedPosts)
})
ButtonRowElement(
@@ -80,7 +78,7 @@ fun ModalBottomSheetContent(
text = stringResource(Res.string.followed_hashtags),
onClick = {
closeBottomSheet()
- Navigate.navigate("followed_hashtags_screen", navController)
+ navController.navigate(Destination.FollowedHashtags)
})
ButtonRowElement(
@@ -88,7 +86,7 @@ fun ModalBottomSheetContent(
text = stringResource(Res.string.muted_accounts),
onClick = {
closeBottomSheet()
- Navigate.navigate("muted_accounts_screen", navController)
+ navController.navigate(Destination.MutedAccounts)
})
ButtonRowElement(
@@ -96,7 +94,7 @@ fun ModalBottomSheetContent(
text = stringResource(Res.string.blocked_accounts),
onClick = {
closeBottomSheet()
- Navigate.navigate("blocked_accounts_screen", navController)
+ navController.navigate(Destination.BlockedAccounts)
})
HorizontalDivider(Modifier.padding(12.dp))
@@ -106,7 +104,7 @@ fun ModalBottomSheetContent(
text = stringResource(Res.string.about_x, instanceDomain),
onClick = {
closeBottomSheet()
- Navigate.navigate("about_instance_screen", navController)
+ navController.navigate(Destination.AboutInstance)
})
ButtonRowElement(
@@ -114,7 +112,7 @@ fun ModalBottomSheetContent(
text = stringResource(Res.string.about_pixelix),
onClick = {
closeBottomSheet()
- Navigate.navigate("about_pixelix_screen", navController)
+ navController.navigate(Destination.AboutPixelix)
})
}
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 ad67af7d..3fa0b30b 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
@@ -43,7 +43,9 @@ 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
import com.daniebeler.pfpixelix.ui.composables.profile.CollectionsComposable
import com.daniebeler.pfpixelix.ui.composables.profile.PostsWrapperComposable
@@ -52,8 +54,7 @@ import com.daniebeler.pfpixelix.ui.composables.profile.SwitchViewComposable
import com.daniebeler.pfpixelix.ui.composables.profile.server_stats.DomainSoftwareComposable
import com.daniebeler.pfpixelix.ui.composables.states.EmptyState
import com.daniebeler.pfpixelix.ui.composables.states.FullscreenErrorComposable
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.edit_profile
@@ -65,7 +66,6 @@ fun OwnProfileComposable(
openPreferencesDrawer: () -> Unit,
viewModel: OwnProfileViewModel = injectViewModel(key = "own-profile-key") { ownProfileViewModel }
) {
-
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
var showBottomSheet by remember { mutableStateOf(0) }
@@ -130,9 +130,7 @@ fun OwnProfileComposable(
) {
Button(
onClick = {
- Navigate.navigate(
- "edit_profile_screen", navController
- )
+ navController.navigate(Destination.EditProfile)
},
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
@@ -158,7 +156,7 @@ fun OwnProfileComposable(
}
},
navController = navController,
- addNewButton = true,
+ addNewButton = PlatformFeatures.addCollection,
instanceDomain = viewModel.ownDomain,
) { url -> viewModel.openUrl(url) }
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/OwnProfileViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/OwnProfileViewModel.kt
index 7260f849..1a682c5b 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/OwnProfileViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/OwnProfileViewModel.kt
@@ -98,6 +98,9 @@ class OwnProfileViewModel @Inject constructor(
}
private fun getPostsFirstLoad(refreshing: Boolean) {
+ if (postsState.posts.isNotEmpty() && !refreshing) {
+ return
+ }
postService.getOwnPosts().onEach { result ->
postsState = when (result) {
is Resource.Success -> {
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/server_stats/ServerStatsComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/server_stats/ServerStatsComposable.kt
index ec4b77b4..03c344b5 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/server_stats/ServerStatsComposable.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/server_stats/ServerStatsComposable.kt
@@ -39,7 +39,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.daniebeler.pfpixelix.di.injectViewModel
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
import com.daniebeler.pfpixelix.utils.StringFormat
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
@@ -56,9 +55,6 @@ import pixelix.app.generated.resources.visit_url
fun DomainSoftwareComposable(
domain: String, viewModel: ServerStatsViewModel = injectViewModel(key = "serverstats$domain") { serverStatsViewModel }
) {
-
- val context = LocalKmpContext.current
-
var showBottomSheet by remember { mutableStateOf(false) }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
@@ -176,7 +172,7 @@ fun DomainSoftwareComposable(
TextButton(
onClick = {
viewModel.openUrl(
- viewModel.statsState.fediSoftware!!.website, context
+ viewModel.statsState.fediSoftware!!.website
)
},
shape = RoundedCornerShape(12.dp),
@@ -319,7 +315,7 @@ fun DomainSoftwareComposable(
TextButton(
onClick = {
viewModel.openUrl(
- "https://" + viewModel.statsState.fediServer!!.domain, context
+ "https://" + viewModel.statsState.fediServer!!.domain
)
},
shape = RoundedCornerShape(12.dp),
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/server_stats/ServerStatsViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/server_stats/ServerStatsViewModel.kt
index deab9b07..0a1edf18 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/server_stats/ServerStatsViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/server_stats/ServerStatsViewModel.kt
@@ -5,11 +5,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.daniebeler.pfpixelix.domain.service.utils.Resource
import com.daniebeler.pfpixelix.domain.service.instance.InstanceService
import com.daniebeler.pfpixelix.domain.service.platform.Platform
+import com.daniebeler.pfpixelix.domain.service.utils.Resource
import com.daniebeler.pfpixelix.ui.composables.profile.DomainSoftwareState
-import com.daniebeler.pfpixelix.utils.KmpContext
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import me.tatarka.inject.annotations.Inject
@@ -84,7 +83,7 @@ class ServerStatsViewModel @Inject constructor(
}.launchIn(viewModelScope)
}
- fun openUrl(url: String, context: KmpContext) {
+ fun openUrl(url: String) {
platform.openUrl(url)
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/session/LoginComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/session/LoginComposable.kt
index 10cbb2f8..412bf3ca 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/session/LoginComposable.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/session/LoginComposable.kt
@@ -1,7 +1,9 @@
package com.daniebeler.pfpixelix.ui.composables.session
+import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -22,6 +24,8 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -32,7 +36,10 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowForwardIos
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -42,15 +49,23 @@ import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+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.graphics.luminance
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
@@ -69,12 +84,20 @@ import pixelix.app.generated.resources.pixelix_logo_black_xxl
import pixelix.app.generated.resources.pixelix_logo_white_xxl
import pixelix.app.generated.resources.server_url
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LoginComposable(
isCloseable: Boolean = false,
navController: NavController,
viewModel: LoginViewModel = injectViewModel("LoginViewModel") { loginViewModel }
) {
+ val dark = MaterialTheme.colorScheme.background.luminance() < 0.5
+
+ var expanded by remember { mutableStateOf(false) }
+
+ val filteredServers = viewModel.openServers
+ .filter { it.domain.startsWith(viewModel.serverHost.text, ignoreCase = true) }
+
Scaffold(Modifier.fillMaxSize()) { paddingValues ->
Column(
modifier = Modifier.fillMaxWidth()
@@ -85,7 +108,7 @@ fun LoginComposable(
modifier = Modifier
.fillMaxWidth()
.background(
- if (isSystemInDarkTheme()) Color.White else Color.Black
+ if (dark) Color.White else Color.Black
)
.windowInsetsPadding(
WindowInsets.systemBars.only(
@@ -105,7 +128,7 @@ fun LoginComposable(
}) {
Icon(
imageVector = vectorResource(Res.drawable.close_outline),
- tint = if (isSystemInDarkTheme()) Color.Black else Color.White,
+ tint = if (dark) Color.Black else Color.White,
contentDescription = ""
)
}
@@ -116,7 +139,7 @@ fun LoginComposable(
.size(150.dp)
.clip(CircleShape),
painter = painterResource(
- if (isSystemInDarkTheme()) {
+ if (dark) {
Res.drawable.pixelix_logo_black_xxl
} else {
Res.drawable.pixelix_logo_white_xxl
@@ -131,7 +154,7 @@ fun LoginComposable(
text = "PIXELIX",
fontSize = 38.sp,
fontWeight = FontWeight.Black,
- color = if (isSystemInDarkTheme()) Color.Black else Color.White
+ color = if (dark) Color.Black else Color.White
)
Spacer(modifier = Modifier.height(24.dp))
@@ -139,7 +162,7 @@ fun LoginComposable(
Image(
painterResource(
- if (isSystemInDarkTheme()) {
+ if (dark) {
Res.drawable.login_wave_light
} else {
Res.drawable.login_wave_dark
@@ -177,12 +200,16 @@ fun LoginComposable(
keyboardController?.hide()
focusManager.clearFocus()
viewModel.auth()
+ expanded = false
}
Row(verticalAlignment = Alignment.Bottom) {
TextField(
value = viewModel.serverHost,
- onValueChange = { viewModel.updateServerHost(it) },
+ onValueChange = {
+ viewModel.updateServerHost(it)
+ expanded = true
+ },
prefix = { Text("https://") },
singleLine = true,
modifier = Modifier.weight(1f),
@@ -197,6 +224,7 @@ fun LoginComposable(
keyboardActions = KeyboardActions(onDone = { login() })
)
+
Spacer(Modifier.width(12.dp))
if (viewModel.isLoading) {
Box(
@@ -216,7 +244,10 @@ fun LoginComposable(
}
} else {
Button(
- onClick = { login() },
+ onClick = {
+ login()
+ expanded = false
+ },
Modifier
.height(56.dp)
.width(56.dp)
@@ -240,6 +271,43 @@ fun LoginComposable(
}
}
}
+
+ Spacer(Modifier.height(24.dp))
+
+ AnimatedVisibility(
+ visible = expanded,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Card(
+ shape = RoundedCornerShape(10.dp),
+ ) {
+
+ LazyColumn(
+ modifier = Modifier.padding(horizontal = 12.dp).heightIn(max = 200.dp),
+ ) {
+ items(
+ filteredServers.take(5)
+ ) {
+ Row(Modifier.padding(12.dp).fillParentMaxWidth().clickable {
+ viewModel.updateServerHost(
+ TextFieldValue(
+ it.domain,
+ selection = TextRange(it.domain.length)
+ )
+ )
+ expanded = false
+ }) {
+ Text(it.domain, fontSize = 20.sp)
+ }
+ }
+
+ }
+
+ }
+
+ }
+
+
Spacer(modifier = Modifier.height(24.dp))
TextButton(onClick = {
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/session/LoginViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/session/LoginViewModel.kt
index 73047f97..a0c15234 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/session/LoginViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/session/LoginViewModel.kt
@@ -3,20 +3,30 @@ package com.daniebeler.pfpixelix.ui.composables.session
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.daniebeler.pfpixelix.domain.model.Server
+import com.daniebeler.pfpixelix.domain.service.instance.InstanceService
import com.daniebeler.pfpixelix.domain.service.platform.Platform
import com.daniebeler.pfpixelix.domain.service.session.AuthService
+import com.daniebeler.pfpixelix.domain.service.utils.Resource
+import com.daniebeler.pfpixelix.ui.composables.settings.about_instance.InstanceState
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import me.tatarka.inject.annotations.Inject
+import pixelix.app.generated.resources.Res
@Inject
class LoginViewModel(
private val authService: AuthService,
+ private val instanceService: InstanceService,
private val platform: Platform
) : ViewModel() {
- var serverHost by mutableStateOf("")
+ var serverHost by mutableStateOf(TextFieldValue())
private set
var isLoading by mutableStateOf(false)
@@ -28,9 +38,32 @@ class LoginViewModel(
var error by mutableStateOf(null)
private set
- fun updateServerHost(host: String) {
+ var openServers by mutableStateOf>(emptyList())
+ private set
+
+ init {
+ getOpenServers()
+ }
+
+ private fun getOpenServers() {
+ instanceService.getOpenServers().onEach { result ->
+ when (result) {
+ is Resource.Success -> {
+ openServers = result.data
+ }
+
+ is Resource.Error -> {
+ }
+
+ is Resource.Loading -> {
+ }
+ }
+ }.launchIn(viewModelScope)
+ }
+
+ fun updateServerHost(host: TextFieldValue) {
serverHost = host
- isValidHost = authService.isValidHost(serverHost)
+ isValidHost = authService.isValidHost(serverHost.text)
}
fun auth() {
@@ -38,7 +71,7 @@ class LoginViewModel(
try {
isLoading = true
error = null
- authService.auth(serverHost)
+ authService.auth(serverHost.text)
} catch (e: Exception) {
error = e.message
} finally {
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 d4cdd354..04720463 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
@@ -36,8 +36,7 @@ import coil3.compose.AsyncImage
import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.ui.composables.states.FullscreenErrorComposable
import com.daniebeler.pfpixelix.ui.composables.states.FullscreenLoadingComposable
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
-import com.daniebeler.pfpixelix.utils.Navigate
+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
@@ -62,9 +61,6 @@ fun AboutInstanceComposable(
) {
val lazyListState = rememberLazyListState()
-
- val context = LocalKmpContext.current
-
Scaffold(contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top), topBar = {
CenterAlignedTopAppBar(title = {
Text(text = viewModel.ownInstanceDomain, fontWeight = FontWeight.Bold)
@@ -137,9 +133,7 @@ fun AboutInstanceComposable(
Row(modifier = Modifier
.clickable {
- Navigate.navigate(
- "profile_screen/" + account.id, navController
- )
+ navController.navigate(Destination.Profile(account.id))
}
.padding(horizontal = 12.dp, vertical = 8.dp)
.fillMaxWidth(),
@@ -179,7 +173,7 @@ fun AboutInstanceComposable(
.clickable {
if (viewModel.instanceState.instance != null) {
viewModel.openUrl(
- url = "https://" + viewModel.instanceState.instance!!.domain + "/site/privacy", context
+ url = "https://" + viewModel.instanceState.instance!!.domain + "/site/privacy"
)
}
})
@@ -202,7 +196,7 @@ fun AboutInstanceComposable(
.clickable {
if (viewModel.instanceState.instance != null) {
viewModel.openUrl(
- url = "https://" + viewModel.instanceState.instance!!.domain + "/site/terms", context
+ url = "https://" + viewModel.instanceState.instance!!.domain + "/site/terms"
)
}
})
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/about_instance/AboutInstanceViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/about_instance/AboutInstanceViewModel.kt
index 500c459e..39412488 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/about_instance/AboutInstanceViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/about_instance/AboutInstanceViewModel.kt
@@ -5,11 +5,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.daniebeler.pfpixelix.domain.service.utils.Resource
import com.daniebeler.pfpixelix.domain.service.instance.InstanceService
import com.daniebeler.pfpixelix.domain.service.platform.Platform
import com.daniebeler.pfpixelix.domain.service.session.AuthService
-import com.daniebeler.pfpixelix.utils.KmpContext
+import com.daniebeler.pfpixelix.domain.service.utils.Resource
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import me.tatarka.inject.annotations.Inject
@@ -47,7 +46,7 @@ class AboutInstanceViewModel @Inject constructor(
}.launchIn(viewModelScope)
}
- fun openUrl(url: String, context: KmpContext) {
+ fun openUrl(url: String) {
platform.openUrl(url)
}
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 03594ce6..996852eb 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
@@ -30,7 +30,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -43,8 +42,7 @@ import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.ui.composables.ButtonRowElement
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
-import com.daniebeler.pfpixelix.utils.Navigate
+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
@@ -56,7 +54,6 @@ import pixelix.app.generated.resources.code_slash_outline
import pixelix.app.generated.resources.developed_by
import pixelix.app.generated.resources.mastodon_logo
import pixelix.app.generated.resources.pixelfed_logo
-import pixelix.app.generated.resources.pixelix_logo
import pixelix.app.generated.resources.shield_outline
import pixelix.app.generated.resources.star_outline
@@ -175,9 +172,7 @@ fun AboutPixelixComposable(
.width(32.dp)
.height(32.dp)
.clickable {
- Navigate.navigate(
- "profile_screen/677938259497057424", navController
- )
+ navController.navigate(Destination.ProfileByUsername("hiebeler05@pixelix.social"))
})
Spacer(modifier = Modifier.width(16.dp))
@@ -224,9 +219,7 @@ fun AboutPixelixComposable(
.width(32.dp)
.height(32.dp)
.clickable {
- Navigate.navigate(
- "profile_screen/497910174831013185", navController
- )
+ navController.navigate(Destination.ProfileByUsername("daniebeler@pixelix.social"))
})
Spacer(modifier = Modifier.width(16.dp))
@@ -255,6 +248,53 @@ fun AboutPixelixComposable(
)
}
}
+
+ 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))
+
+ 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))
+
+ Icon(
+ imageVector = Icons.Outlined.Language,
+ contentDescription = "",
+ Modifier
+ .size(32.dp)
+ .clickable {
+ viewModel.openUrl("https://github.com/terrakok")
+ },
+ tint = Color(0xFF4793FF)
+ )
+ }
+ }
}
}
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/blocked_accounts/CustomBlockedAccountRow.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/blocked_accounts/CustomBlockedAccountRow.kt
index e2a89f97..06dd17ce 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/blocked_accounts/CustomBlockedAccountRow.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/blocked_accounts/CustomBlockedAccountRow.kt
@@ -20,7 +20,7 @@ import androidx.navigation.NavController
import coil3.compose.AsyncImage
import com.daniebeler.pfpixelix.domain.model.Account
import com.daniebeler.pfpixelix.ui.composables.profile.other_profile.UnBlockAccountAlert
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
@@ -39,7 +39,7 @@ fun CustomBlockedAccountRow(
) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable {
- Navigate.navigate("profile_screen/" + account.id, navController)
+ navController.navigate(Destination.Profile(account.id))
}) {
AsyncImage(
model = account.avatar,
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 b8db2e69..bf069fa8 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
@@ -30,7 +30,7 @@ import com.daniebeler.pfpixelix.ui.composables.states.EmptyState
import com.daniebeler.pfpixelix.ui.composables.states.FullscreenEmptyStateComposable
import com.daniebeler.pfpixelix.ui.composables.states.FullscreenErrorComposable
import com.daniebeler.pfpixelix.ui.composables.states.FullscreenLoadingComposable
-import com.daniebeler.pfpixelix.utils.Navigate
+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
@@ -89,7 +89,7 @@ fun FollowedHashtagsComposable(
message = "Followed hashtags will appear here",
buttonText = "Explore trending hashtags",
onClick = {
- Navigate.navigate("search_screen/2", navController)
+ navController.navigate(Destination.Search(2))
})
)
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/followed_hashtags/FollowedHashtagsViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/followed_hashtags/FollowedHashtagsViewModel.kt
index aa4df17e..f3bbeab5 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/followed_hashtags/FollowedHashtagsViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/followed_hashtags/FollowedHashtagsViewModel.kt
@@ -56,11 +56,7 @@ class FollowedHashtagsViewModel @Inject constructor(
searchService.getHashtag(tag.name).onEach { result ->
followedHashtagsState = when (result) {
is Resource.Success -> {
- if (result.data != null) {
- FollowedHashtagsState(followedHashtags = followedHashtagsState.followedHashtags + result.data)
- } else {
- FollowedHashtagsState(followedHashtags = followedHashtagsState.followedHashtags)
- }
+ FollowedHashtagsState(followedHashtags = followedHashtagsState.followedHashtags + result.data)
}
is Resource.Error -> {
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 091b5f77..280fde85 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
@@ -2,7 +2,6 @@ package com.daniebeler.pfpixelix.ui.composables.settings.icon_selection
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@@ -34,21 +33,16 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
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.unit.dp
import androidx.navigation.NavController
import com.daniebeler.pfpixelix.di.injectViewModel
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/muted_accounts/CustomMutedAccountRow.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/muted_accounts/CustomMutedAccountRow.kt
index d16fdea1..8ea45225 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/muted_accounts/CustomMutedAccountRow.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/muted_accounts/CustomMutedAccountRow.kt
@@ -20,7 +20,7 @@ import androidx.navigation.NavController
import coil3.compose.AsyncImage
import com.daniebeler.pfpixelix.domain.model.Account
import com.daniebeler.pfpixelix.ui.composables.profile.other_profile.UnMuteAccountAlert
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
@@ -41,7 +41,7 @@ fun CustomMutedAccountRow(
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable {
- Navigate.navigate("profile_screen/" + account.id, navController)
+ navController.navigate(Destination.Profile(account.id))
}
) {
AsyncImage(
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 89cc04b3..8db93f34 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
@@ -21,10 +21,10 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
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
@@ -32,8 +32,10 @@ import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.domain.service.platform.PlatformFeatures
+import com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.AutoplayVideoPref
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.ClearCachePref
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.CustomizeAppIconPref
+import com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.DeleteAccountPref
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.FocusModePref
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.HideAltTextButtonPref
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.HideSensitiveContentPref
@@ -42,7 +44,6 @@ import com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.MoreSe
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.RepostSettingsPref
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.ThemePref
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.UseInAppBrowserPref
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.resources.vectorResource
import pixelix.app.generated.resources.Res
@@ -96,14 +97,20 @@ fun PreferencesComposable(
UseInAppBrowserPref()
}
+ if (PlatformFeatures.autoplayVideosPref) {
+ AutoplayVideoPref()
+ }
+
RepostSettingsPref { viewModel.openRepostSettings() }
HorizontalDivider(modifier = Modifier.padding(12.dp))
ThemePref()
- val icon = viewModel.appIcon.collectAsState()
- CustomizeAppIconPref(navController, closePreferencesDrawer, icon.value)
+ if (PlatformFeatures.customAppIcon) {
+ val icon = viewModel.appIcon.collectAsState()
+ CustomizeAppIconPref(navController, closePreferencesDrawer, icon.value)
+ }
HorizontalDivider(modifier = Modifier.padding(12.dp))
@@ -113,6 +120,8 @@ fun PreferencesComposable(
LogoutPref { viewModel.logout() }
+ DeleteAccountPref { viewModel.openDeleteAccountPage() }
+
HorizontalDivider(modifier = Modifier.padding(12.dp))
Text(
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/PreferencesViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/PreferencesViewModel.kt
index 27a06942..a402073f 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/PreferencesViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/PreferencesViewModel.kt
@@ -1,5 +1,6 @@
package com.daniebeler.pfpixelix.ui.composables.settings.preferences
+import androidx.compose.ui.platform.UriHandler
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.daniebeler.pfpixelix.domain.service.icon.AppIconService
@@ -34,4 +35,10 @@ class PreferencesViewModel(
platform.openUrl("${it.serverUrl}settings/timeline")
}
}
+
+ fun openDeleteAccountPage() {
+ authService.getCurrentSession()?.let {
+ platform.openUrl("${it.serverUrl}settings/remove/request/permanent")
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/AutoplayVideoPref.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/AutoplayVideoPref.kt
new file mode 100644
index 00000000..3cd4649a
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/AutoplayVideoPref.kt
@@ -0,0 +1,26 @@
+package com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import com.daniebeler.pfpixelix.di.LocalAppComponent
+import com.daniebeler.pfpixelix.ui.composables.settings.preferences.basic.SwitchPref
+import org.jetbrains.compose.resources.stringResource
+import pixelix.app.generated.resources.Res
+import pixelix.app.generated.resources.autoplay_videos
+import pixelix.app.generated.resources.square_outline
+
+@Composable
+fun AutoplayVideoPref() {
+ val prefs = LocalAppComponent.current.preferences
+ val state = remember { mutableStateOf(prefs.autoplayVideo) }
+ LaunchedEffect(state.value) {
+ prefs.autoplayVideo = state.value
+ }
+ SwitchPref(
+ leadingIcon = Res.drawable.square_outline,
+ title = stringResource(Res.string.autoplay_videos),
+ state = state
+ )
+}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/ClearCacheViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/ClearCacheViewModel.kt
index 24cd9ecf..24320948 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/ClearCacheViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/ClearCacheViewModel.kt
@@ -1,15 +1,15 @@
package com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.prefs
import androidx.lifecycle.ViewModel
-import com.daniebeler.pfpixelix.domain.service.platform.Platform
+import com.daniebeler.pfpixelix.domain.service.file.FileService
import me.tatarka.inject.annotations.Inject
@Inject
class ClearCacheViewModel(
- private val platform: Platform
+ private val fileService: FileService
): ViewModel() {
- fun getCacheSizeInBytes() = platform.getCacheSizeInBytes()
+ fun getCacheSizeInBytes() = fileService.getCacheSizeInBytes()
fun cleanCache() {
- platform.cleanCache()
+ fileService.cleanCache()
}
}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/CustomAccentColorPref.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/CustomAccentColorPref.kt
new file mode 100644
index 00000000..65ffbc83
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/CustomAccentColorPref.kt
@@ -0,0 +1,68 @@
+package com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.prefs
+
+import androidx.compose.animation.animateContentSize
+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.defaultMinSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Switch
+import androidx.compose.material3.Text
+import androidx.compose.material3.surfaceColorAtElevation
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+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.graphics.luminance
+import androidx.compose.ui.unit.dp
+import com.daniebeler.pfpixelix.di.LocalAppComponent
+import com.daniebeler.pfpixelix.domain.model.AppAccentColor
+
+@Composable
+fun CustomAccentColorPref() {
+ val prefs = LocalAppComponent.current.preferences
+ val state = remember { mutableStateOf(prefs.accentColor) }
+ LaunchedEffect(state.value) {
+ prefs.accentColor = state.value
+ }
+
+ Card(
+ shape = MaterialTheme.shapes.medium,
+ colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .defaultMinSize(minHeight = 48.dp)
+ .padding(start = 12.dp, top = 8.dp, end = 12.dp, bottom = 8.dp)
+ ) {
+ Row(Modifier.weight(1f), horizontalArrangement = Arrangement.SpaceEvenly) {
+ Box(Modifier.height(32.dp).width(32.dp).clip(CircleShape).background(Color(AppAccentColor.GREEN)).clickable {
+ state.value = AppAccentColor.GREEN
+ })
+ Box(Modifier.height(32.dp).width(32.dp).clip(CircleShape).background(Color(AppAccentColor.RED)).clickable {
+ state.value = AppAccentColor.RED
+ })
+ Box(Modifier.height(32.dp).width(32.dp).clip(CircleShape).background(Color(AppAccentColor.BLUE)).clickable {
+ state.value = AppAccentColor.BLUE
+ })
+ val dark = MaterialTheme.colorScheme.background.luminance() < 0.5
+ Box(Modifier.height(32.dp).width(32.dp).clip(CircleShape).background(if (dark) {Color(AppAccentColor.White)} else {Color(0xFF5D5F5F)}).clickable {
+ state.value = AppAccentColor.White
+ })}
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/CustomizeAppIconPref.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/CustomizeAppIconPref.kt
index c67d7045..045d3297 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/CustomizeAppIconPref.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/CustomizeAppIconPref.kt
@@ -1,18 +1,15 @@
package com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs
import androidx.compose.runtime.Composable
-import androidx.compose.ui.graphics.ImageBitmap
import androidx.navigation.NavController
-import androidx.navigation.compose.rememberNavController
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.basic.SettingPref
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.chevron_forward_outline
import pixelix.app.generated.resources.customize_app_icon
-import pixelix.app.generated.resources.pixelix_logo
@Composable
fun CustomizeAppIconPref(navController: NavController, closePreferenceDrawer: () -> Unit, logo: DrawableResource) {
@@ -21,6 +18,6 @@ fun CustomizeAppIconPref(navController: NavController, closePreferenceDrawer: ()
trailingContent = Res.drawable.chevron_forward_outline,
onClick = {
closePreferenceDrawer()
- Navigate.navigate("icon_selection_screen", navController)
+ navController.navigate(Destination.IconSelection)
})
}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/DeleteAccountPref.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/DeleteAccountPref.kt
new file mode 100644
index 00000000..cb5fd215
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/DeleteAccountPref.kt
@@ -0,0 +1,22 @@
+package com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import com.daniebeler.pfpixelix.ui.composables.settings.preferences.basic.SettingPref
+import org.jetbrains.compose.resources.stringResource
+import pixelix.app.generated.resources.Res
+import pixelix.app.generated.resources.open_outline
+import pixelix.app.generated.resources.trash
+import pixelix.app.generated.resources.delete_account
+
+
+@Composable
+fun DeleteAccountPref(openUrl: () -> Unit) {
+ SettingPref(
+ leadingIcon = Res.drawable.trash,
+ title = stringResource(Res.string.delete_account),
+ trailingContent = Res.drawable.open_outline,
+ onClick = openUrl,
+ textColor = MaterialTheme.colorScheme.error
+ )
+}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/HideSensitiveContentPref.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/HideSensitiveContentPref.kt
index 2242002a..856cea48 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/HideSensitiveContentPref.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/HideSensitiveContentPref.kt
@@ -1,28 +1,62 @@
package com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.slideInVertically
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.BlurOn
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
import com.daniebeler.pfpixelix.di.LocalAppComponent
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.basic.SwitchPref
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.eye_off_outline
+import pixelix.app.generated.resources.blur
import pixelix.app.generated.resources.hide_sensitive_content
+import pixelix.app.generated.resources.blur_sensitive_content
@Composable
fun HideSensitiveContentPref() {
val prefs = LocalAppComponent.current.preferences
- val state = remember { mutableStateOf(prefs.hideSensitiveContent) }
- LaunchedEffect(state.value) {
- prefs.hideSensitiveContent = state.value
+ val hideState = remember { mutableStateOf(prefs.hideSensitiveContent) }
+ LaunchedEffect(hideState.value) {
+ prefs.hideSensitiveContent = hideState.value
+ }
+
+ val blurState = remember { mutableStateOf(prefs.blurSensitiveContent) }
+ LaunchedEffect(blurState.value) {
+ prefs.blurSensitiveContent = blurState.value
+ }
+ Column {
+ SwitchPref(
+ leadingIcon = Res.drawable.eye_off_outline,
+ title = stringResource(Res.string.hide_sensitive_content),
+ state = hideState
+ )
+
+ AnimatedVisibility(
+ modifier = Modifier.padding(top = 8.dp),
+ visible = !hideState.value,
+ enter = slideInVertically() + fadeIn(),
+ exit = shrinkVertically(animationSpec = spring(stiffness = Spring.StiffnessMedium)) + fadeOut(),
+ ) {
+ SwitchPref(
+ leadingIcon = Res.drawable.blur,
+ title = stringResource(Res.string.blur_sensitive_content),
+ state = blurState
+ )
+ }
}
- SwitchPref(
- leadingIcon = Res.drawable.eye_off_outline,
- title = stringResource(Res.string.hide_sensitive_content),
- state = state
- )
}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/LogoutPref.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/LogoutPref.kt
index c76e5218..ffdc7828 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/LogoutPref.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/LogoutPref.kt
@@ -9,7 +9,6 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.basic.SettingPref
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -38,7 +37,6 @@ fun LogoutPref(logout: () -> Unit) {
@Composable
fun LogoutAlert(show: MutableState, logout: () -> Unit) {
- val context = LocalKmpContext.current
if (show.value) {
AlertDialog(title = {
Text(text = stringResource(Res.string.logout_questionmark))
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/MoreSettingsPref.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/MoreSettingsPref.kt
index 51306b34..d60e23cb 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/MoreSettingsPref.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/MoreSettingsPref.kt
@@ -1,6 +1,7 @@
package com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs
import androidx.compose.runtime.Composable
+import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.basic.SettingPref
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
@@ -21,6 +22,6 @@ fun MoreSettingsPref(openUrl: () -> Unit) {
@Composable
private fun MoreSettingsPrefPreview() {
MoreSettingsPref(openUrl = {
- println("URL opened: url")
+ Logger.v("URL opened: url")
})
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/RepostSettingsPref.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/RepostSettingsPref.kt
index da96ec3f..7325410a 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/RepostSettingsPref.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/RepostSettingsPref.kt
@@ -18,10 +18,3 @@ fun RepostSettingsPref(openUrl: () -> Unit) {
onClick = openUrl
)
}
-
-@Composable
-private fun RepostSettingsPrefPreview() {
- RepostSettingsPref(openUrl = {
- println("URL opened: url")
- })
-}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/ThemePref.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/ThemePref.kt
index b8fd3b9b..7563d243 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/ThemePref.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/prefs/ThemePref.kt
@@ -1,5 +1,7 @@
package com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.BrightnessAuto
import androidx.compose.material.icons.rounded.Contrast
@@ -8,16 +10,20 @@ import androidx.compose.material.icons.rounded.LightMode
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
import com.daniebeler.pfpixelix.di.LocalAppComponent
import com.daniebeler.pfpixelix.domain.model.AppThemeMode.AMOLED
import com.daniebeler.pfpixelix.domain.model.AppThemeMode.DARK
import com.daniebeler.pfpixelix.domain.model.AppThemeMode.FOLLOW_SYSTEM
import com.daniebeler.pfpixelix.domain.model.AppThemeMode.LIGHT
+import com.daniebeler.pfpixelix.domain.service.platform.PlatformFeatures
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.basic.ExpandOptionsPref
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.basic.OptionShapes
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.basic.ValueOption
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.basic.imageVectorIconBlock
import com.daniebeler.pfpixelix.ui.composables.settings.preferences.basic.radioButtonBlock
+import com.daniebeler.pfpixelix.ui.composables.settings.preferences.prefs.prefs.CustomAccentColorPref
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.amoled
@@ -85,5 +91,10 @@ fun ThemePref() {
onOptionClick = onOptionClick,
)
+ if (PlatformFeatures.customAccentColors) {
+ Spacer(modifier = Modifier.height(1.dp))
+
+ CustomAccentColorPref()
+ }
}
}
\ No newline at end of file
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 3d17393a..9701580c 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
@@ -27,7 +27,7 @@ import com.daniebeler.pfpixelix.di.injectViewModel
import com.daniebeler.pfpixelix.ui.composables.post.PostComposable
import com.daniebeler.pfpixelix.ui.composables.states.ErrorComposable
import com.daniebeler.pfpixelix.ui.composables.states.LoadingComposable
-import com.daniebeler.pfpixelix.utils.Navigate
+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
@@ -87,7 +87,9 @@ fun SinglePostComposable(
Column(modifier = Modifier.verticalScroll(scrollState)) {
if (viewModel.postState.post != null) {
PostComposable(viewModel.postState.post!!, navController, postGetsDeleted = {
- Navigate.navigateAndDeleteBackStack("own_profile_screen", navController)
+ navController.navigate(Destination.OwnProfile) {
+ popUpTo(0) { inclusive = true }
+ }
},
setZindex = { }, openReplies)
}
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/states/ErrorComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/states/ErrorComposable.kt
index 612656bc..ff3c3d7a 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/states/ErrorComposable.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/states/ErrorComposable.kt
@@ -11,10 +11,17 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.WarningAmber
+import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+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.text.font.FontWeight
@@ -30,6 +37,23 @@ fun ErrorComposable(message: String) {
}
}
+@Composable
+fun ErrorComposableDialog(errorMessage: String?, onDismiss: () -> Unit) {
+ if (!errorMessage.isNullOrBlank()) {
+ AlertDialog(
+ onDismissRequest = onDismiss,
+ title = { Text("Error") },
+ text = { Text(errorMessage) },
+ confirmButton = {
+ TextButton(onClick = onDismiss) {
+ Text("OK")
+ }
+ }
+ )
+ }
+}
+
+
@Composable
fun FullscreenErrorComposable(message: String) {
if (message.isNotBlank()) {
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/textfield_location/TextFieldLocationsViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/textfield_location/TextFieldLocationsViewModel.kt
index 25feecd3..ff6f75cf 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/textfield_location/TextFieldLocationsViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/textfield_location/TextFieldLocationsViewModel.kt
@@ -39,13 +39,7 @@ class TextFieldLocationsViewModel @Inject constructor(
searchService.searchLocations(location).onEach { result ->
locationsSuggestions = when (result) {
is Resource.Success -> {
- if (result.data != null) {
- LocationsState(locations = result.data)
- } else {
- LocationsState(
- error = result.message ?: "An unexpected error occurred"
- )
- }
+ LocationsState(locations = result.data)
}
is Resource.Error -> {
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/textfield_mentions/TextFieldMentionsViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/textfield_mentions/TextFieldMentionsViewModel.kt
index 1dd6e3d8..2295030c 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/textfield_mentions/TextFieldMentionsViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/textfield_mentions/TextFieldMentionsViewModel.kt
@@ -46,13 +46,7 @@ class TextFieldMentionsViewModel @Inject constructor(
searchService.search(searchShortened).onEach { result ->
mentionSuggestions = when (result) {
is Resource.Success -> {
- if (result.data != null) {
- SuggestionsState(suggestions = if (type == "accounts") {result.data.accounts.map { "@" + it.acct }} else {result.data.tags.map { "#" + it.name }})
- } else {
- SuggestionsState(
- error = result.message ?: "An unexpected error occurred"
- )
- }
+ SuggestionsState(suggestions = if (type == "accounts") {result.data.accounts.map { "@" + it.acct }} else {result.data.tags.map { "#" + it.name }})
}
is Resource.Error -> {
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/global_timeline/GlobalTimelineViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/global_timeline/GlobalTimelineViewModel.kt
index 4d3112ad..90a48110 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/global_timeline/GlobalTimelineViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/global_timeline/GlobalTimelineViewModel.kt
@@ -23,6 +23,9 @@ class GlobalTimelineViewModel @Inject constructor(
}
private fun getItemsFirstLoad(refreshing: Boolean) {
+ if (globalTimelineState.globalTimeline.isNotEmpty() && !refreshing) {
+ return
+ }
timelineService.getGlobalTimeline().onEach { result ->
globalTimelineState = when (result) {
is Resource.Success -> {
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 ed8a9152..35e1a1cd 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
@@ -1,35 +1,22 @@
package com.daniebeler.pfpixelix.ui.composables.timelines.hashtag_timeline
-import androidx.compose.foundation.layout.Arrangement
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.WindowInsetsSides
-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.width
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
-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.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.TopAppBarDefaults
-import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
@@ -37,26 +24,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
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
-import com.daniebeler.pfpixelix.ui.composables.InfiniteListHandler
-import com.daniebeler.pfpixelix.ui.composables.InfinitePostsGrid
import com.daniebeler.pfpixelix.ui.composables.InfinitePostsList
-import com.daniebeler.pfpixelix.ui.composables.ToTopButton
-import com.daniebeler.pfpixelix.ui.composables.profile.AccountState
-import com.daniebeler.pfpixelix.ui.composables.profile.PostsState
-import com.daniebeler.pfpixelix.ui.composables.profile.PostsWrapperComposable
-import com.daniebeler.pfpixelix.ui.composables.profile.SwitchViewComposable
-import com.daniebeler.pfpixelix.ui.composables.profile.ViewEnum
-import com.daniebeler.pfpixelix.ui.composables.states.EmptyState
-import com.daniebeler.pfpixelix.utils.Navigate
-import com.daniebeler.pfpixelix.utils.StringFormat
-import org.jetbrains.compose.resources.stringResource
-import pixelix.app.generated.resources.Res
-import pixelix.app.generated.resources.posts
@OptIn(ExperimentalMaterial3Api::class)
@Composable
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/hashtag_timeline/HashtagTimelineViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/hashtag_timeline/HashtagTimelineViewModel.kt
index beba551a..93109587 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/hashtag_timeline/HashtagTimelineViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/hashtag_timeline/HashtagTimelineViewModel.kt
@@ -5,6 +5,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.domain.model.Post
import com.daniebeler.pfpixelix.domain.model.RelatedHashtag
import com.daniebeler.pfpixelix.domain.repository.PixelfedApi
@@ -53,6 +54,9 @@ class HashtagTimelineViewModel @Inject constructor(
}
fun getItemsFirstLoad(hashtag: String, refreshing: Boolean = false) {
+ if (postsState.hashtagTimeline.isNotEmpty() && !refreshing) {
+ return
+ }
timelineService.getHashtagTimeline(hashtag).onEach { result ->
postsState = when (result) {
is Resource.Success -> {
@@ -132,10 +136,10 @@ class HashtagTimelineViewModel @Inject constructor(
fun getRelatedHashtags(hashtag: String) {
searchService.getRelatedHashtags(hashtag).onEach { result ->
if (result is Resource.Success) {
- relatedHashtags = result.data ?: emptyList()
- println("juhuu" + result.data)
+ relatedHashtags = result.data
+ Logger.v("juhuu" + result.data)
} else {
- println("fief" + result.message)
+ Logger.v("fief" + result.message)
}
}.launchIn(viewModelScope)
}
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 05ca0bec..4334c006 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
@@ -9,9 +9,8 @@ import androidx.compose.ui.Modifier
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
-import com.daniebeler.pfpixelix.utils.Navigate
+import com.daniebeler.pfpixelix.ui.navigation.Destination
import org.jetbrains.compose.resources.stringResource
import pixelix.app.generated.resources.Res
import pixelix.app.generated.resources.explore_trending_profiles
@@ -35,7 +34,7 @@ fun HomeTimelineComposable(
message = stringResource(Res.string.follow_accounts_or_hashtags_to_fill_your_home_timeline),
buttonText = stringResource(Res.string.explore_trending_profiles),
onClick = {
- Navigate.navigate("search_screen/1", navController)
+ navController.navigate(Destination.Search(1))
}),
getItemsPaginated = {
viewModel.getItemsPaginated()
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/home_timeline/HomeTimelineViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/home_timeline/HomeTimelineViewModel.kt
index b30b5a02..8e2e458c 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/home_timeline/HomeTimelineViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/home_timeline/HomeTimelineViewModel.kt
@@ -48,6 +48,9 @@ class HomeTimelineViewModel @Inject constructor(
}
private fun getItemsFirstLoad(refreshing: Boolean, enableReblogs: Boolean) {
+ if (homeTimelineState.homeTimeline.isNotEmpty() && !refreshing) {
+ return
+ }
timelineService.getHomeTimeline(
enableReblogs = enableReblogs
).onEach { result ->
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/local_timeline/LocalTimelineViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/local_timeline/LocalTimelineViewModel.kt
index abe0de38..2a7ad6c1 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/local_timeline/LocalTimelineViewModel.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/timelines/local_timeline/LocalTimelineViewModel.kt
@@ -23,6 +23,9 @@ class LocalTimelineViewModel @Inject constructor(
}
private fun getItemsFirstLoad(refreshing: Boolean) {
+ if (localTimelineState.localTimeline.isNotEmpty() && !refreshing) {
+ return
+ }
timelineService.getLocalTimeline().onEach { result ->
localTimelineState = when (result) {
is Resource.Success -> {
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/navigation/Navigation.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/navigation/Navigation.kt
new file mode 100644
index 00000000..b31fc873
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/navigation/Navigation.kt
@@ -0,0 +1,290 @@
+package com.daniebeler.pfpixelix.ui.navigation
+
+import androidx.compose.animation.AnimatedContentTransitionScope
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavDestination.Companion.hasRoute
+import androidx.navigation.NavDestination.Companion.hierarchy
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.dialog
+import androidx.navigation.compose.navigation
+import androidx.navigation.toRoute
+import com.daniebeler.pfpixelix.EdgeToEdgeDialogProperties
+import com.daniebeler.pfpixelix.ui.composables.HomeComposable
+import com.daniebeler.pfpixelix.ui.composables.collection.CollectionComposable
+import com.daniebeler.pfpixelix.ui.composables.direct_messages.chat.ChatComposable
+import com.daniebeler.pfpixelix.ui.composables.direct_messages.conversations.ConversationsComposable
+import com.daniebeler.pfpixelix.ui.composables.edit_post.EditPostComposable
+import com.daniebeler.pfpixelix.ui.composables.edit_profile.EditProfileComposable
+import com.daniebeler.pfpixelix.ui.composables.explore.ExploreComposable
+import com.daniebeler.pfpixelix.ui.composables.followers.FollowersMainComposable
+import com.daniebeler.pfpixelix.ui.composables.mention.MentionComposable
+import com.daniebeler.pfpixelix.ui.composables.newpost.NewPostComposable
+import com.daniebeler.pfpixelix.ui.composables.notifications.NotificationsComposable
+import com.daniebeler.pfpixelix.ui.composables.profile.other_profile.OtherProfileComposable
+import com.daniebeler.pfpixelix.ui.composables.profile.own_profile.OwnProfileComposable
+import com.daniebeler.pfpixelix.ui.composables.session.LoginComposable
+import com.daniebeler.pfpixelix.ui.composables.settings.about_instance.AboutInstanceComposable
+import com.daniebeler.pfpixelix.ui.composables.settings.about_pixelix.AboutPixelixComposable
+import com.daniebeler.pfpixelix.ui.composables.settings.blocked_accounts.BlockedAccountsComposable
+import com.daniebeler.pfpixelix.ui.composables.settings.bookmarked_posts.BookmarkedPostsComposable
+import com.daniebeler.pfpixelix.ui.composables.settings.followed_hashtags.FollowedHashtagsComposable
+import com.daniebeler.pfpixelix.ui.composables.settings.icon_selection.IconSelectionComposable
+import com.daniebeler.pfpixelix.ui.composables.settings.liked_posts.LikedPostsComposable
+import com.daniebeler.pfpixelix.ui.composables.settings.muted_accounts.MutedAccountsComposable
+import com.daniebeler.pfpixelix.ui.composables.single_post.SinglePostComposable
+import com.daniebeler.pfpixelix.ui.composables.timelines.hashtag_timeline.HashtagTimelineComposable
+import com.daniebeler.pfpixelix.utils.KmpUri
+import com.daniebeler.pfpixelix.utils.toKmpUri
+import kotlinx.serialization.Serializable
+import kotlin.jvm.JvmSuppressWildcards
+
+sealed interface Destination {
+ @Serializable data class Hashtag(val hashtag: String) : Destination
+ @Serializable data class HashtagTimeline(val hashtag: String) : Destination
+ @Serializable data class Post(
+ val id: String,
+ val refresh: Boolean = false,
+ val openReplies: Boolean = false
+ ) : Destination
+ @Serializable data class EditPost(val id: String) : Destination
+ @Serializable data class Collection(val id: String) : Destination
+ @Serializable data class Followers(val userId: String, val isFollowers: Boolean) : Destination
+ @Serializable data object Conversations : Destination
+ @Serializable data class Chat(val id: String) : Destination
+ @Serializable data class Mention(val id: String) : Destination
+ @Serializable data object EditProfile : Destination
+ @Serializable data object IconSelection : Destination
+ @Serializable data object MutedAccounts : Destination
+ @Serializable data object BlockedAccounts : Destination
+ @Serializable data object LikedPosts : Destination
+ @Serializable data object BookmarkedPosts : Destination
+ @Serializable data object FollowedHashtags : Destination
+ @Serializable data object AboutInstance : Destination
+ @Serializable data object AboutPixelix : Destination
+ @Serializable data class Profile(val userId: String) : Destination
+ @Serializable data class ProfileByUsername(val userName: String) : Destination
+ @Serializable data object FirstLogin : Destination
+ @Serializable data object NewLogin : Destination
+ @Serializable data class Search(val page: Int = 0) : Destination
+ @Serializable data object OwnProfile : Destination
+ @Serializable data object Feeds : Destination
+ @Serializable data class NewPost(val uris: List = emptyList()) : Destination
+ @Serializable data object Notifications : Destination
+
+ @Serializable data object HomeTabFeeds : Destination
+ @Serializable data object HomeTabSearch : Destination
+ @Serializable data object HomeTabNewPost : Destination
+ @Serializable data object HomeTabNotifications : Destination
+ @Serializable data object HomeTabOwnProfile : Destination
+}
+
+internal fun NavGraphBuilder.appGraph(
+ navController: NavHostController,
+ openPreferencesDrawer: () -> Unit,
+ exitApp: () -> Unit
+) {
+ composable(
+ enterTransition = { EnterTransition.None },
+ exitTransition = { ExitTransition.None }
+ ) {
+ Dialog(
+ onDismissRequest = exitApp,
+ properties = EdgeToEdgeDialogProperties()
+ ) {
+ LoginComposable(navController = navController)
+ }
+ }
+
+ //home tabs (with no transition animations)
+ //more info: https://issuetracker.google.com/408010634
+ navigation(
+ startDestination = Destination.Feeds,
+ enterTransition = { tabEnterTransition() },
+ exitTransition = { tabExitTransition() }
+ ) {
+ tabGraph(navController, openPreferencesDrawer)
+ }
+
+ navigation(
+ startDestination = Destination.Search(),
+ enterTransition = { tabEnterTransition() },
+ exitTransition = { tabExitTransition() }
+ ) {
+ tabGraph(navController, openPreferencesDrawer)
+ }
+
+ navigation(
+ startDestination = Destination.NewPost(),
+ enterTransition = { tabEnterTransition() },
+ exitTransition = { tabExitTransition() }
+ ) {
+
+ composable { navBackStackEntry ->
+ val args = navBackStackEntry.toRoute()
+ val imageUris: List? = args.uris.map { it.toKmpUri() }
+ NewPostComposable(navController, imageUris)
+ }
+
+ tabGraph(navController, openPreferencesDrawer)
+ }
+
+ navigation(
+ startDestination = Destination.Notifications,
+ enterTransition = { tabEnterTransition() },
+ exitTransition = { tabExitTransition() }
+ ) {
+ tabGraph(navController, openPreferencesDrawer)
+ }
+
+ navigation(
+ startDestination = Destination.OwnProfile,
+ enterTransition = { tabEnterTransition() },
+ exitTransition = { tabExitTransition() }
+ ) {
+ tabGraph(navController, openPreferencesDrawer)
+ }
+}
+
+private inline fun AnimatedContentTransitionScope.tabEnterTransition(): EnterTransition? {
+ val initialHierarchy = initialState.destination.hierarchy
+ return if (initialHierarchy.none { it.hasRoute() }) EnterTransition.None else null
+}
+
+private inline fun AnimatedContentTransitionScope.tabExitTransition(): ExitTransition? {
+ val targetHierarchy = targetState.destination.hierarchy
+ return if (targetHierarchy.none { it.hasRoute() }) ExitTransition.None else null
+}
+
+private fun NavGraphBuilder.tabGraph(
+ navController: NavHostController,
+ openPreferencesDrawer: () -> Unit
+) {
+ dialog(
+ dialogProperties = EdgeToEdgeDialogProperties()
+ ) {
+ LoginComposable(true, navController)
+ }
+
+ composable {
+ HomeComposable(navController, openPreferencesDrawer)
+ }
+
+ composable {
+ NotificationsComposable(navController)
+ }
+
+ composable { navBackStackEntry ->
+ val args = navBackStackEntry.toRoute()
+ HashtagTimelineComposable(navController, args.hashtag)
+ }
+
+ composable { navBackStackEntry ->
+ val args = navBackStackEntry.toRoute()
+ OtherProfileComposable(navController, userId = args.userId, byUsername = null)
+ }
+
+ composable { navBackStackEntry ->
+ val args = navBackStackEntry.toRoute()
+ OtherProfileComposable(navController, userId = "", byUsername = args.userName)
+ }
+
+ composable { navBackStackEntry ->
+ val args = navBackStackEntry.toRoute()
+ HashtagTimelineComposable(navController, args.hashtag)
+ }
+
+ composable {
+ EditProfileComposable(navController)
+ }
+
+ composable {
+ IconSelectionComposable(navController)
+ }
+
+ composable { navBackStackEntry ->
+ val args = navBackStackEntry.toRoute()
+ EditPostComposable(args.id, navController)
+ }
+
+ composable {
+ MutedAccountsComposable(navController)
+ }
+
+ composable {
+ BlockedAccountsComposable(navController)
+ }
+
+ composable {
+ LikedPostsComposable(navController)
+ }
+
+ composable {
+ BookmarkedPostsComposable(navController)
+ }
+
+ composable {
+ FollowedHashtagsComposable(navController)
+ }
+
+ composable {
+ AboutInstanceComposable(navController)
+ }
+
+ composable {
+ AboutPixelixComposable(navController)
+ }
+
+ composable {
+ OwnProfileComposable(navController, openPreferencesDrawer)
+ }
+
+ composable { navBackStackEntry ->
+ val args = navBackStackEntry.toRoute()
+ FollowersMainComposable(
+ navController,
+ accountId = args.userId,
+ isFollowers = args.isFollowers
+ )
+ }
+
+ composable { navBackStackEntry ->
+ val args = navBackStackEntry.toRoute()
+ SinglePostComposable(navController, postId = args.id, args.refresh, args.openReplies)
+ }
+
+ composable { navBackStackEntry ->
+ val args = navBackStackEntry.toRoute()
+ CollectionComposable(navController, collectionId = args.id)
+ }
+
+ composable { navBackStackEntry ->
+ val args = navBackStackEntry.toRoute()
+ ExploreComposable(navController, args.page)
+ }
+
+ composable {
+ ConversationsComposable(navController = navController)
+ }
+
+ composable { navBackStackEntry ->
+ val args = navBackStackEntry.toRoute()
+ ChatComposable(navController = navController, accountId = args.id)
+ }
+
+ composable { navBackStackEntry ->
+ val args = navBackStackEntry.toRoute()
+ MentionComposable(navController = navController, mentionId = args.id)
+ }
+}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/ColorBlue.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/ColorBlue.kt
new file mode 100644
index 00000000..0d90cfab
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/ColorBlue.kt
@@ -0,0 +1,74 @@
+package com.daniebeler.pfpixelix.ui.theme
+import androidx.compose.ui.graphics.Color
+
+val bluePrimaryLight = Color(0xFF4C5C92)
+val blueOnPrimaryLight = Color(0xFFFFFFFF)
+val bluePrimaryContainerLight = Color(0xFFDBE1FF)
+val blueOnPrimaryContainerLight = Color(0xFF334479)
+val blueSecondaryLight = Color(0xFF595E72)
+val blueOnSecondaryLight = Color(0xFFFFFFFF)
+val blueSecondaryContainerLight = Color(0xFFDDE1F9)
+val blueOnSecondaryContainerLight = Color(0xFF414659)
+val blueTertiaryLight = Color(0xFF745470)
+val blueOnTertiaryLight = Color(0xFFFFFFFF)
+val blueTertiaryContainerLight = Color(0xFFFFD6F7)
+val blueOnTertiaryContainerLight = Color(0xFF5B3D57)
+val blueErrorLight = Color(0xFFBA1A1A)
+val blueOnErrorLight = Color(0xFFFFFFFF)
+val blueErrorContainerLight = Color(0xFFFFDAD6)
+val blueOnErrorContainerLight = Color(0xFF93000A)
+val blueBackgroundLight = Color(0xFFFAF8FF)
+val blueOnBackgroundLight = Color(0xFF1A1B21)
+val blueSurfaceLight = Color(0xFFFAF8FF)
+val blueOnSurfaceLight = Color(0xFF1A1B21)
+val blueSurfaceVariantLight = Color(0xFFE2E1EC)
+val blueOnSurfaceVariantLight = Color(0xFF45464F)
+val blueOutlineLight = Color(0xFF767680)
+val blueOutlineVariantLight = Color(0xFFC6C6D0)
+val blueScrimLight = Color(0xFF000000)
+val blueInverseSurfaceLight = Color(0xFF2F3036)
+val blueInverseOnSurfaceLight = Color(0xFFF1F0F7)
+val blueInversePrimaryLight = Color(0xFFB5C4FF)
+val blueSurfaceDimLight = Color(0xFFDAD9E0)
+val blueSurfaceBrightLight = Color(0xFFFAF8FF)
+val blueSurfaceContainerLowestLight = Color(0xFFFFFFFF)
+val blueSurfaceContainerLowLight = Color(0xFFF4F3FA)
+val blueSurfaceContainerLight = Color(0xFFEEEDF4)
+val blueSurfaceContainerHighLight = Color(0xFFE9E7EF)
+val blueSurfaceContainerHighestLight = Color(0xFFE3E1E9)
+
+val bluePrimaryDark = Color(0xFFB5C4FF)
+val blueOnPrimaryDark = Color(0xFF1C2D61)
+val bluePrimaryContainerDark = Color(0xFF334479)
+val blueOnPrimaryContainerDark = Color(0xFFDBE1FF)
+val blueSecondaryDark = Color(0xFFC1C5DD)
+val blueOnSecondaryDark = Color(0xFF2B3042)
+val blueSecondaryContainerDark = Color(0xFF414659)
+val blueOnSecondaryContainerDark = Color(0xFFDDE1F9)
+val blueTertiaryDark = Color(0xFFE2BBDB)
+val blueOnTertiaryDark = Color(0xFF432740)
+val blueTertiaryContainerDark = Color(0xFF5B3D57)
+val blueOnTertiaryContainerDark = Color(0xFFFFD6F7)
+val blueErrorDark = Color(0xFFFFB4AB)
+val blueOnErrorDark = Color(0xFF690005)
+val blueErrorContainerDark = Color(0xFF93000A)
+val blueOnErrorContainerDark = Color(0xFFFFDAD6)
+val blueBackgroundDark = Color(0xFF121318)
+val blueOnBackgroundDark = Color(0xFFE3E1E9)
+val blueSurfaceDark = Color(0xFF121318)
+val blueOnSurfaceDark = Color(0xFFE3E1E9)
+val blueSurfaceVariantDark = Color(0xFF45464F)
+val blueOnSurfaceVariantDark = Color(0xFFC6C6D0)
+val blueOutlineDark = Color(0xFF8F909A)
+val blueOutlineVariantDark = Color(0xFF45464F)
+val blueScrimDark = Color(0xFF000000)
+val blueInverseSurfaceDark = Color(0xFFE3E1E9)
+val blueInverseOnSurfaceDark = Color(0xFF2F3036)
+val blueInversePrimaryDark = Color(0xFF4C5C92)
+val blueSurfaceDimDark = Color(0xFF121318)
+val blueSurfaceBrightDark = Color(0xFF38393F)
+val blueSurfaceContainerLowestDark = Color(0xFF0D0E13)
+val blueSurfaceContainerLowDark = Color(0xFF1A1B21)
+val blueSurfaceContainerDark = Color(0xFF1E1F25)
+val blueSurfaceContainerHighDark = Color(0xFF292A2F)
+val blueSurfaceContainerHighestDark = Color(0xFF34343A)
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/ColorRed.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/ColorRed.kt
new file mode 100644
index 00000000..1c9263b9
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/ColorRed.kt
@@ -0,0 +1,74 @@
+package com.daniebeler.pfpixelix.ui.theme
+import androidx.compose.ui.graphics.Color
+
+val redPrimaryLight = Color(0xFF8F4C38)
+val redOnPrimaryLight = Color(0xFFFFFFFF)
+val redPrimaryContainerLight = Color(0xFFFFDBD1)
+val redOnPrimaryContainerLight = Color(0xFF723523)
+val redSecondaryLight = Color(0xFF77574E)
+val redOnSecondaryLight = Color(0xFFFFFFFF)
+val redSecondaryContainerLight = Color(0xFFFFDBD1)
+val redOnSecondaryContainerLight = Color(0xFF5D4037)
+val redTertiaryLight = Color(0xFF6C5D2F)
+val redOnTertiaryLight = Color(0xFFFFFFFF)
+val redTertiaryContainerLight = Color(0xFFF5E1A7)
+val redOnTertiaryContainerLight = Color(0xFF534619)
+val redErrorLight = Color(0xFFBA1A1A)
+val redOnErrorLight = Color(0xFFFFFFFF)
+val redErrorContainerLight = Color(0xFFFFDAD6)
+val redOnErrorContainerLight = Color(0xFF93000A)
+val redBackgroundLight = Color(0xFFFFF8F6)
+val redOnBackgroundLight = Color(0xFF231917)
+val redSurfaceLight = Color(0xFFFFF8F6)
+val redOnSurfaceLight = Color(0xFF231917)
+val redSurfaceVariantLight = Color(0xFFF5DED8)
+val redOnSurfaceVariantLight = Color(0xFF53433F)
+val redOutlineLight = Color(0xFF85736E)
+val redOutlineVariantLight = Color(0xFFD8C2BC)
+val redScrimLight = Color(0xFF000000)
+val redInverseSurfaceLight = Color(0xFF392E2B)
+val redInverseOnSurfaceLight = Color(0xFFFFEDE8)
+val redInversePrimaryLight = Color(0xFFFFB5A0)
+val redSurfaceDimLight = Color(0xFFE8D6D2)
+val redSurfaceBrightLight = Color(0xFFFFF8F6)
+val redSurfaceContainerLowestLight = Color(0xFFFFFFFF)
+val redSurfaceContainerLowLight = Color(0xFFFFF1ED)
+val redSurfaceContainerLight = Color(0xFFFCEAE5)
+val redSurfaceContainerHighLight = Color(0xFFF7E4E0)
+val redSurfaceContainerHighestLight = Color(0xFFF1DFDA)
+
+val redPrimaryDark = Color(0xFFFFB5A0)
+val redOnPrimaryDark = Color(0xFF561F0F)
+val redPrimaryContainerDark = Color(0xFF723523)
+val redOnPrimaryContainerDark = Color(0xFFFFDBD1)
+val redSecondaryDark = Color(0xFFE7BDB2)
+val redOnSecondaryDark = Color(0xFF442A22)
+val redSecondaryContainerDark = Color(0xFF5D4037)
+val redOnSecondaryContainerDark = Color(0xFFFFDBD1)
+val redTertiaryDark = Color(0xFFD8C58D)
+val redOnTertiaryDark = Color(0xFF3B2F05)
+val redTertiaryContainerDark = Color(0xFF534619)
+val redOnTertiaryContainerDark = Color(0xFFF5E1A7)
+val redErrorDark = Color(0xFFFFB4AB)
+val redOnErrorDark = Color(0xFF690005)
+val redErrorContainerDark = Color(0xFF93000A)
+val redOnErrorContainerDark = Color(0xFFFFDAD6)
+val redBackgroundDark = Color(0xFF1A110F)
+val redOnBackgroundDark = Color(0xFFF1DFDA)
+val redSurfaceDark = Color(0xFF1A110F)
+val redOnSurfaceDark = Color(0xFFF1DFDA)
+val redSurfaceVariantDark = Color(0xFF53433F)
+val redOnSurfaceVariantDark = Color(0xFFD8C2BC)
+val redOutlineDark = Color(0xFFA08C87)
+val redOutlineVariantDark = Color(0xFF53433F)
+val redScrimDark = Color(0xFF000000)
+val redInverseSurfaceDark = Color(0xFFF1DFDA)
+val redInverseOnSurfaceDark = Color(0xFF392E2B)
+val redInversePrimaryDark = Color(0xFF8F4C38)
+val redSurfaceDimDark = Color(0xFF1A110F)
+val redSurfaceBrightDark = Color(0xFF423734)
+val redSurfaceContainerLowestDark = Color(0xFF140C0A)
+val redSurfaceContainerLowDark = Color(0xFF231917)
+val redSurfaceContainerDark = Color(0xFF271D1B)
+val redSurfaceContainerHighDark = Color(0xFF322825)
+val redSurfaceContainerHighestDark = Color(0xFF3D322F)
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/ColorWhite.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/ColorWhite.kt
new file mode 100644
index 00000000..2c3abee9
--- /dev/null
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/ColorWhite.kt
@@ -0,0 +1,74 @@
+package com.daniebeler.pfpixelix.ui.theme
+import androidx.compose.ui.graphics.Color
+
+val whitePrimaryLight = Color(0xFF5D5F5F)
+val whiteOnPrimaryLight = Color(0xFFFFFFFF)
+val whitePrimaryContainerLight = Color(0xFFFFFFFF)
+val whiteOnPrimaryContainerLight = Color(0xFF747676)
+val whiteSecondaryLight = Color(0xFF5E5E5E)
+val whiteOnSecondaryLight = Color(0xFFFFFFFF)
+val whiteSecondaryContainerLight = Color(0xFFE4E2E2)
+val whiteOnSecondaryContainerLight = Color(0xFF646464)
+val whiteTertiaryLight = Color(0xFF5D5F5F)
+val whiteOnTertiaryLight = Color(0xFFFFFFFF)
+val whiteTertiaryContainerLight = Color(0xFFFFFFFF)
+val whiteOnTertiaryContainerLight = Color(0xFF747676)
+val whiteErrorLight = Color(0xFFBA1A1A)
+val whiteOnErrorLight = Color(0xFFFFFFFF)
+val whiteErrorContainerLight = Color(0xFFFFDAD6)
+val whiteOnErrorContainerLight = Color(0xFF93000A)
+val whiteBackgroundLight = Color(0xFFFCF8F8)
+val whiteOnBackgroundLight = Color(0xFF1C1B1B)
+val whiteSurfaceLight = Color(0xFFFCF8F8)
+val whiteOnSurfaceLight = Color(0xFF1C1B1B)
+val whiteSurfaceVariantLight = Color(0xFFE0E3E3)
+val whiteOnSurfaceVariantLight = Color(0xFF444748)
+val whiteOutlineLight = Color(0xFF747878)
+val whiteOutlineVariantLight = Color(0xFFC4C7C8)
+val whiteScrimLight = Color(0xFF000000)
+val whiteInverseSurfaceLight = Color(0xFF313030)
+val whiteInverseOnSurfaceLight = Color(0xFFF4F0EF)
+val whiteInversePrimaryLight = Color(0xFFC6C6C7)
+val whiteSurfaceDimLight = Color(0xFFDDD9D9)
+val whiteSurfaceBrightLight = Color(0xFFFCF8F8)
+val whiteSurfaceContainerLowestLight = Color(0xFFFFFFFF)
+val whiteSurfaceContainerLowLight = Color(0xFFF6F3F2)
+val whiteSurfaceContainerLight = Color(0xFFF1EDEC)
+val whiteSurfaceContainerHighLight = Color(0xFFEBE7E7)
+val whiteSurfaceContainerHighestLight = Color(0xFFE5E2E1)
+
+val whitePrimaryDark = Color(0xFFFFFFFF)
+val whiteOnPrimaryDark = Color(0xFF2F3131)
+val whitePrimaryContainerDark = Color(0xFFE2E2E2)
+val whiteOnPrimaryContainerDark = Color(0xFF636565)
+val whiteSecondaryDark = Color(0xFFC8C6C6)
+val whiteOnSecondaryDark = Color(0xFF303030)
+val whiteSecondaryContainerDark = Color(0xFF494949)
+val whiteOnSecondaryContainerDark = Color(0xFFB9B8B8)
+val whiteTertiaryDark = Color(0xFFFFFFFF)
+val whiteOnTertiaryDark = Color(0xFF2F3131)
+val whiteTertiaryContainerDark = Color(0xFFE2E2E2)
+val whiteOnTertiaryContainerDark = Color(0xFF636565)
+val whiteErrorDark = Color(0xFFFFB4AB)
+val whiteOnErrorDark = Color(0xFF690005)
+val whiteErrorContainerDark = Color(0xFF93000A)
+val whiteOnErrorContainerDark = Color(0xFFFFDAD6)
+val whiteBackgroundDark = Color(0xFF141313)
+val whiteOnBackgroundDark = Color(0xFFE5E2E1)
+val whiteSurfaceDark = Color(0xFF141313)
+val whiteOnSurfaceDark = Color(0xFFE5E2E1)
+val whiteSurfaceVariantDark = Color(0xFF444748)
+val whiteOnSurfaceVariantDark = Color(0xFFC4C7C8)
+val whiteOutlineDark = Color(0xFF8E9192)
+val whiteOutlineVariantDark = Color(0xFF444748)
+val whiteScrimDark = Color(0xFF000000)
+val whiteInverseSurfaceDark = Color(0xFFE5E2E1)
+val whiteInverseOnSurfaceDark = Color(0xFF313030)
+val whiteInversePrimaryDark = Color(0xFF5D5F5F)
+val whiteSurfaceDimDark = Color(0xFF141313)
+val whiteSurfaceBrightDark = Color(0xFF3A3939)
+val whiteSurfaceContainerLowestDark = Color(0xFF0E0E0E)
+val whiteSurfaceContainerLowDark = Color(0xFF1C1B1B)
+val whiteSurfaceContainerDark = Color(0xFF201F1F)
+val whiteSurfaceContainerHighDark = Color(0xFF2A2A2A)
+val whiteSurfaceContainerHighestDark = Color(0xFF353434)
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.kt
index f61bf532..354bb3e7 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.kt
@@ -1,22 +1,23 @@
package com.daniebeler.pfpixelix.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.daniebeler.pfpixelix.di.LocalAppComponent
+import com.daniebeler.pfpixelix.domain.model.AppAccentColor
import com.daniebeler.pfpixelix.domain.model.AppThemeMode.DARK
import com.daniebeler.pfpixelix.domain.model.AppThemeMode.FOLLOW_SYSTEM
import com.daniebeler.pfpixelix.domain.model.AppThemeMode.LIGHT
-import com.daniebeler.pfpixelix.utils.KmpContext
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
fun ColorScheme.toAmoled(): ColorScheme {
@@ -126,6 +127,237 @@ private val darkScheme = darkColorScheme(
surfaceContainerHighest = surfaceContainerHighestDark,
)
+
+private val blueLightScheme = lightColorScheme(
+ primary = bluePrimaryLight,
+ onPrimary = blueOnPrimaryLight,
+ primaryContainer = bluePrimaryContainerLight,
+ onPrimaryContainer = blueOnPrimaryContainerLight,
+ secondary = blueSecondaryLight,
+ onSecondary = blueOnSecondaryLight,
+ secondaryContainer = blueSecondaryContainerLight,
+ onSecondaryContainer = blueOnSecondaryContainerLight,
+ tertiary = blueTertiaryLight,
+ onTertiary = blueOnTertiaryLight,
+ tertiaryContainer = blueTertiaryContainerLight,
+ onTertiaryContainer = blueOnTertiaryContainerLight,
+ error = blueErrorLight,
+ onError = blueOnErrorLight,
+ errorContainer = blueErrorContainerLight,
+ onErrorContainer = blueOnErrorContainerLight,
+ background = blueBackgroundLight,
+ onBackground = blueOnBackgroundLight,
+ surface = blueSurfaceLight,
+ onSurface = blueOnSurfaceLight,
+ surfaceVariant = blueSurfaceVariantLight,
+ onSurfaceVariant = blueOnSurfaceVariantLight,
+ outline = blueOutlineLight,
+ outlineVariant = blueOutlineVariantLight,
+ scrim = blueScrimLight,
+ inverseSurface = blueInverseSurfaceLight,
+ inverseOnSurface = blueInverseOnSurfaceLight,
+ inversePrimary = blueInversePrimaryLight,
+ surfaceDim = blueSurfaceDimLight,
+ surfaceBright = blueSurfaceBrightLight,
+ surfaceContainerLowest = blueSurfaceContainerLowestLight,
+ surfaceContainerLow = blueSurfaceContainerLowLight,
+ surfaceContainer = blueSurfaceContainerLight,
+ surfaceContainerHigh = blueSurfaceContainerHighLight,
+ surfaceContainerHighest = blueSurfaceContainerHighestLight,
+)
+
+private val blueDarkScheme = darkColorScheme(
+ primary = bluePrimaryDark,
+ onPrimary = blueOnPrimaryDark,
+ primaryContainer = bluePrimaryContainerDark,
+ onPrimaryContainer = blueOnPrimaryContainerDark,
+ secondary = blueSecondaryDark,
+ onSecondary = blueOnSecondaryDark,
+ secondaryContainer = blueSecondaryContainerDark,
+ onSecondaryContainer = blueOnSecondaryContainerDark,
+ tertiary = blueTertiaryDark,
+ onTertiary = blueOnTertiaryDark,
+ tertiaryContainer = blueTertiaryContainerDark,
+ onTertiaryContainer = blueOnTertiaryContainerDark,
+ error = blueErrorDark,
+ onError = blueOnErrorDark,
+ errorContainer = blueErrorContainerDark,
+ onErrorContainer = blueOnErrorContainerDark,
+ background = blueBackgroundDark,
+ onBackground = blueOnBackgroundDark,
+ surface = blueSurfaceDark,
+ onSurface = blueOnSurfaceDark,
+ surfaceVariant = blueSurfaceVariantDark,
+ onSurfaceVariant = blueOnSurfaceVariantDark,
+ outline = blueOutlineDark,
+ outlineVariant = blueOutlineVariantDark,
+ scrim = blueScrimDark,
+ inverseSurface = blueInverseSurfaceDark,
+ inverseOnSurface = blueInverseOnSurfaceDark,
+ inversePrimary = blueInversePrimaryDark,
+ surfaceDim = blueSurfaceDimDark,
+ surfaceBright = blueSurfaceBrightDark,
+ surfaceContainerLowest = blueSurfaceContainerLowestDark,
+ surfaceContainerLow = blueSurfaceContainerLowDark,
+ surfaceContainer = blueSurfaceContainerDark,
+ surfaceContainerHigh = blueSurfaceContainerHighDark,
+ surfaceContainerHighest = blueSurfaceContainerHighestDark,
+)
+
+private val redLightScheme = lightColorScheme(
+ primary = redPrimaryLight,
+ onPrimary = redOnPrimaryLight,
+ primaryContainer = redPrimaryContainerLight,
+ onPrimaryContainer = redOnPrimaryContainerLight,
+ secondary = redSecondaryLight,
+ onSecondary = redOnSecondaryLight,
+ secondaryContainer = redSecondaryContainerLight,
+ onSecondaryContainer = redOnSecondaryContainerLight,
+ tertiary = redTertiaryLight,
+ onTertiary = redOnTertiaryLight,
+ tertiaryContainer = redTertiaryContainerLight,
+ onTertiaryContainer = redOnTertiaryContainerLight,
+ error = redErrorLight,
+ onError = redOnErrorLight,
+ errorContainer = redErrorContainerLight,
+ onErrorContainer = redOnErrorContainerLight,
+ background = redBackgroundLight,
+ onBackground = redOnBackgroundLight,
+ surface = redSurfaceLight,
+ onSurface = redOnSurfaceLight,
+ surfaceVariant = redSurfaceVariantLight,
+ onSurfaceVariant = redOnSurfaceVariantLight,
+ outline = redOutlineLight,
+ outlineVariant = redOutlineVariantLight,
+ scrim = redScrimLight,
+ inverseSurface = redInverseSurfaceLight,
+ inverseOnSurface = redInverseOnSurfaceLight,
+ inversePrimary = redInversePrimaryLight,
+ surfaceDim = redSurfaceDimLight,
+ surfaceBright = redSurfaceBrightLight,
+ surfaceContainerLowest = redSurfaceContainerLowestLight,
+ surfaceContainerLow = redSurfaceContainerLowLight,
+ surfaceContainer = redSurfaceContainerLight,
+ surfaceContainerHigh = redSurfaceContainerHighLight,
+ surfaceContainerHighest = redSurfaceContainerHighestLight,
+)
+
+private val redDarkScheme = darkColorScheme(
+ primary = redPrimaryDark,
+ onPrimary = redOnPrimaryDark,
+ primaryContainer = redPrimaryContainerDark,
+ onPrimaryContainer = redOnPrimaryContainerDark,
+ secondary = redSecondaryDark,
+ onSecondary = redOnSecondaryDark,
+ secondaryContainer = redSecondaryContainerDark,
+ onSecondaryContainer = redOnSecondaryContainerDark,
+ tertiary = redTertiaryDark,
+ onTertiary = redOnTertiaryDark,
+ tertiaryContainer = redTertiaryContainerDark,
+ onTertiaryContainer = redOnTertiaryContainerDark,
+ error = redErrorDark,
+ onError = redOnErrorDark,
+ errorContainer = redErrorContainerDark,
+ onErrorContainer = redOnErrorContainerDark,
+ background = redBackgroundDark,
+ onBackground = redOnBackgroundDark,
+ surface = redSurfaceDark,
+ onSurface = redOnSurfaceDark,
+ surfaceVariant = redSurfaceVariantDark,
+ onSurfaceVariant = redOnSurfaceVariantDark,
+ outline = redOutlineDark,
+ outlineVariant = redOutlineVariantDark,
+ scrim = redScrimDark,
+ inverseSurface = redInverseSurfaceDark,
+ inverseOnSurface = redInverseOnSurfaceDark,
+ inversePrimary = redInversePrimaryDark,
+ surfaceDim = redSurfaceDimDark,
+ surfaceBright = redSurfaceBrightDark,
+ surfaceContainerLowest = redSurfaceContainerLowestDark,
+ surfaceContainerLow = redSurfaceContainerLowDark,
+ surfaceContainer = redSurfaceContainerDark,
+ surfaceContainerHigh = redSurfaceContainerHighDark,
+ surfaceContainerHighest = redSurfaceContainerHighestDark,
+)
+
+private val whiteLightScheme = lightColorScheme(
+ primary = whitePrimaryLight,
+ onPrimary = whiteOnPrimaryLight,
+ primaryContainer = whitePrimaryContainerLight,
+ onPrimaryContainer = whiteOnPrimaryContainerLight,
+ secondary = whiteSecondaryLight,
+ onSecondary = whiteOnSecondaryLight,
+ secondaryContainer = whiteSecondaryContainerLight,
+ onSecondaryContainer = whiteOnSecondaryContainerLight,
+ tertiary = whiteTertiaryLight,
+ onTertiary = whiteOnTertiaryLight,
+ tertiaryContainer = whiteTertiaryContainerLight,
+ onTertiaryContainer = whiteOnTertiaryContainerLight,
+ error = whiteErrorLight,
+ onError = whiteOnErrorLight,
+ errorContainer = whiteErrorContainerLight,
+ onErrorContainer = whiteOnErrorContainerLight,
+ background = whiteBackgroundLight,
+ onBackground = whiteOnBackgroundLight,
+ surface = whiteSurfaceLight,
+ onSurface = whiteOnSurfaceLight,
+ surfaceVariant = whiteSurfaceVariantLight,
+ onSurfaceVariant = whiteOnSurfaceVariantLight,
+ outline = whiteOutlineLight,
+ outlineVariant = whiteOutlineVariantLight,
+ scrim = whiteScrimLight,
+ inverseSurface = whiteInverseSurfaceLight,
+ inverseOnSurface = whiteInverseOnSurfaceLight,
+ inversePrimary = whiteInversePrimaryLight,
+ surfaceDim = whiteSurfaceDimLight,
+ surfaceBright = whiteSurfaceBrightLight,
+ surfaceContainerLowest = whiteSurfaceContainerLowestLight,
+ surfaceContainerLow = whiteSurfaceContainerLowLight,
+ surfaceContainer = whiteSurfaceContainerLight,
+ surfaceContainerHigh = whiteSurfaceContainerHighLight,
+ surfaceContainerHighest = whiteSurfaceContainerHighestLight,
+)
+
+private val whiteDarkScheme = darkColorScheme(
+ primary = whitePrimaryDark,
+ onPrimary = whiteOnPrimaryDark,
+ primaryContainer = whitePrimaryContainerDark,
+ onPrimaryContainer = whiteOnPrimaryContainerDark,
+ secondary = whiteSecondaryDark,
+ onSecondary = whiteOnSecondaryDark,
+ secondaryContainer = whiteSecondaryContainerDark,
+ onSecondaryContainer = whiteOnSecondaryContainerDark,
+ tertiary = whiteTertiaryDark,
+ onTertiary = whiteOnTertiaryDark,
+ tertiaryContainer = whiteTertiaryContainerDark,
+ onTertiaryContainer = whiteOnTertiaryContainerDark,
+ error = whiteErrorDark,
+ onError = whiteOnErrorDark,
+ errorContainer = whiteErrorContainerDark,
+ onErrorContainer = whiteOnErrorContainerDark,
+ background = whiteBackgroundDark,
+ onBackground = whiteOnBackgroundDark,
+ surface = whiteSurfaceDark,
+ onSurface = whiteOnSurfaceDark,
+ surfaceVariant = whiteSurfaceVariantDark,
+ onSurfaceVariant = whiteOnSurfaceVariantDark,
+ outline = whiteOutlineDark,
+ outlineVariant = whiteOutlineVariantDark,
+ scrim = whiteScrimDark,
+ inverseSurface = whiteInverseSurfaceDark,
+ inverseOnSurface = whiteInverseOnSurfaceDark,
+ inversePrimary = whiteInversePrimaryDark,
+ surfaceDim = whiteSurfaceDimDark,
+ surfaceBright = whiteSurfaceBrightDark,
+ surfaceContainerLowest = whiteSurfaceContainerLowestDark,
+ surfaceContainerLow = whiteSurfaceContainerLowDark,
+ surfaceContainer = whiteSurfaceContainerDark,
+ surfaceContainerHigh = whiteSurfaceContainerHighDark,
+ surfaceContainerHighest = whiteSurfaceContainerHighestDark,
+)
+
+
+
@Composable
fun PixelixTheme(
dynamicColor: Boolean = true,
@@ -134,33 +366,46 @@ fun PixelixTheme(
val prefs = LocalAppComponent.current.preferences
val theme by prefs.appThemeModeFlow.collectAsState(prefs.appThemeMode)
- LaunchedEffect(theme) { applySystemNightMode(theme) }
-
var nightModeValue = theme
if (nightModeValue == FOLLOW_SYSTEM) {
nightModeValue = if (isSystemInDarkTheme()) DARK else LIGHT
}
- ChangeSystemBarColors(nightModeValue)
+ LaunchedEffect(nightModeValue) { applySystemNightMode(nightModeValue != LIGHT) }
- val context = LocalKmpContext.current
- val colorScheme = remember(nightModeValue, dynamicColor, lightScheme, darkScheme) {
- context.generateColorScheme(nightModeValue, dynamicColor, lightScheme, darkScheme)
+ val accentColor by prefs.accentColorFlow.collectAsState(prefs.accentColor)
+
+ val darkColorScheme = when (accentColor) {
+ AppAccentColor.GREEN -> darkScheme
+ AppAccentColor.RED -> redDarkScheme
+ AppAccentColor.BLUE -> blueDarkScheme
+ AppAccentColor.White -> whiteDarkScheme
+ else -> darkScheme
+ }
+
+ val lightColorScheme = when (accentColor) {
+ AppAccentColor.GREEN -> lightScheme
+ AppAccentColor.RED -> redLightScheme
+ AppAccentColor.BLUE -> blueLightScheme
+ AppAccentColor.White -> whiteLightScheme
+ else -> darkScheme
}
+ val colorScheme = generateColorScheme(nightModeValue, dynamicColor, lightColorScheme, darkColorScheme)
+
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
- content = content
+ content = {
+ Surface(modifier = Modifier.fillMaxSize(), content = content)
+ }
)
}
-expect fun applySystemNightMode(mode: Int)
+expect fun applySystemNightMode(isDark: Boolean)
@Composable
-expect fun ChangeSystemBarColors(mode: Int)
-
-expect fun KmpContext.generateColorScheme(
+expect fun generateColorScheme(
nightModeValue: Int,
dynamicColor: Boolean,
lightScheme: ColorScheme,
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/Destinations.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/Destinations.kt
deleted file mode 100644
index 4215a97a..00000000
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/Destinations.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-package com.daniebeler.pfpixelix.utils
-
-import org.jetbrains.compose.resources.DrawableResource
-import org.jetbrains.compose.resources.StringResource
-import pixelix.app.generated.resources.Res
-import pixelix.app.generated.resources.add_circle
-import pixelix.app.generated.resources.add_circle_outline
-import pixelix.app.generated.resources.alerts
-import pixelix.app.generated.resources.bookmark_outline
-import pixelix.app.generated.resources.home
-import pixelix.app.generated.resources.house
-import pixelix.app.generated.resources.house_fill
-import pixelix.app.generated.resources.notifications
-import pixelix.app.generated.resources.notifications_outline
-import pixelix.app.generated.resources.profile
-import pixelix.app.generated.resources.search
-import pixelix.app.generated.resources.search_outline
-
-sealed class Destinations(
- val route: String,
- val icon: DrawableResource = Res.drawable.bookmark_outline,
- val activeIcon: DrawableResource = Res.drawable.bookmark_outline,
- val label: StringResource = Res.string.home
-) {
- data object FirstLogin : Destinations(
- route = "first_login_screen"
- )
-
- data object NewLogin : Destinations(
- route = "new_login_screen"
- )
-
- data object HomeScreen : Destinations(
- route = "home_screen",
- icon = Res.drawable.house,
- activeIcon = Res.drawable.house_fill,
- label = Res.string.home
- )
-
- data object NotificationsScreen : Destinations(
- route = "notifications_screen",
- icon = Res.drawable.notifications_outline,
- activeIcon = Res.drawable.notifications,
- label = Res.string.alerts
- )
-
- data object OwnProfile : Destinations(
- route = "own_profile_screen",
- icon = Res.drawable.bookmark_outline,
- activeIcon = Res.drawable.bookmark_outline,
- label = Res.string.profile
- )
-
- data object NewPost : Destinations(
- route = "new_post_screen", icon = Res.drawable.add_circle_outline,
- activeIcon = Res.drawable.add_circle
- )
-
- data object Search : Destinations(
- route = "search_screen/{initialPage}",
- icon = Res.drawable.search_outline,
- activeIcon = Res.drawable.search,
- label = Res.string.search
- )
-
- data object Profile : Destinations(
- route = "profile_screen/{userid}"
- )
-
- data object ProfileByUsername : Destinations(
- route = "profile_screen/byUsername/{username}"
- )
-
- data object EditProfile : Destinations(
- route = "edit_profile_screen"
- )
-
- data object IconSelection : Destinations(
- route = "icon_selection_screen"
- )
-
- data object MutedAccounts : Destinations(
- route = "muted_accounts_screen"
- )
-
- data object BlockedAccounts : Destinations(
- route = "blocked_accounts_screen"
- )
-
- data object LikedPosts : Destinations(
- route = "liked_posts_screen"
- )
-
- data object BookmarkedPosts : Destinations(
- route = "bookmarked_posts_screen"
- )
-
- data object FollowedHashtags : Destinations(
- route = "followed_hashtags_screen"
- )
-
- data object AboutInstance : Destinations(
- route = "about_instance_screen"
- )
-
- data object AboutPixelix : Destinations(
- route = "about_pixelix_screen"
- )
-
- data object EditPost : Destinations(
- route = "edit_post_screen/{postId}"
- )
-
- data object Hashtag : Destinations(
- route = "hashtag_timeline_screen/{hashtag}"
- )
-
- data object SinglePost : Destinations(
- route = "single_post_screen/{postid}"
- )
-
- data object Collection : Destinations(
- route = "collection_screen/{collectionid}"
- )
-
- data object Followers : Destinations(
- route = "followers_screen/{page}/{userid}"
- )
-
-
-
- data object Conversation : Destinations(
- route = "conversations"
- )
-
- data object Chat : Destinations(
- route = "chat/{userid}"
- )
-
- data object Mention : Destinations(
- route = "mention/{mentionid}"
- )
-}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.kt
index 04fa414a..f6a4e070 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.kt
@@ -1,9 +1,7 @@
package com.daniebeler.pfpixelix.utils
-import androidx.compose.runtime.staticCompositionLocalOf
import coil3.PlatformContext
import io.github.vinceglb.filekit.core.PlatformFile
-import okio.Path
expect abstract class KmpUri {
abstract override fun toString(): String
@@ -14,8 +12,5 @@ expect fun String.toKmpUri(): KmpUri
expect fun PlatformFile.toKmpUri(): KmpUri
expect abstract class KmpContext
-val LocalKmpContext = staticCompositionLocalOf { error("no KmpContext") }
expect val KmpContext.coilContext: PlatformContext
-expect val KmpContext.dataStoreDir: Path
-expect val KmpContext.imageCacheDir: Path
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/Navigate.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/Navigate.kt
deleted file mode 100644
index 5c491c67..00000000
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/Navigate.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.daniebeler.pfpixelix.utils
-
-import androidx.navigation.NavController
-
-object Navigate {
- private var currentBottomBarRoute: String? = null
- private var restoreStateRoutes: List = listOf("home_screen")
-
- fun changeAccount() {
- restoreStateRoutes = emptyList()
- }
-
- fun navigate(route: String, navController: NavController, singleTop: Boolean = true) {
- if (navController.currentDestination!!.route == route) {
- return
- }
- val alreadySaved = restoreStateRoutes.indexOf(route) != -1
- if (!alreadySaved) {
- restoreStateRoutes = restoreStateRoutes + route
- }
- navController.navigate(route) {
- launchSingleTop = singleTop
- restoreState = alreadySaved
- }
- }
-
- fun navigateWithPopUp(newRoute: String, navController: NavController) {
- if (navController.currentDestination!!.route == newRoute) {
- return
- }
- val alreadySaved = restoreStateRoutes.indexOf(newRoute) != -1
- if (!alreadySaved) {
- restoreStateRoutes = restoreStateRoutes + newRoute
- }
- if (newRoute == currentBottomBarRoute) {
- navController.navigate(newRoute) {
- popUpTo(currentBottomBarRoute!!)
- launchSingleTop = true
- }
- } else {
- navController.navigate(newRoute) {
- popUpTo(0) {
- saveState = true
- }
- launchSingleTop = true
- restoreState = alreadySaved
- }
- }
- currentBottomBarRoute = newRoute
- }
-
- fun navigateAndDeleteBackStack(route: String, navController: NavController) {
- navController.navigate(route) {
- popUpTo(0) {
- inclusive = true
- }
-
- launchSingleTop = true
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/TimeAgo.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/TimeAgo.kt
index 42916f6b..b1eb6abf 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/TimeAgo.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/TimeAgo.kt
@@ -2,12 +2,23 @@ package com.daniebeler.pfpixelix.utils
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
+import org.jetbrains.compose.resources.getPluralString
+import org.jetbrains.compose.resources.getString
+import pixelix.app.generated.resources.Res
+import pixelix.app.generated.resources.ago
+import pixelix.app.generated.resources.second
+import pixelix.app.generated.resources.minute
+import pixelix.app.generated.resources.hour
+import pixelix.app.generated.resources.day
+import pixelix.app.generated.resources.week
+import pixelix.app.generated.resources.month
+import pixelix.app.generated.resources.year
object TimeAgo {
- fun convertTimeToText(dataDate: String): String {
+ suspend fun convertTimeToText(dataDate: String): String {
var convTime: String = ""
- val suffix = "ago"
+ val suffix = getString(Res.string.ago)
try {
val pasTime: Instant = Instant.parse(dataDate)
val nowTime: Instant = Clock.System.now()
@@ -18,21 +29,38 @@ object TimeAgo {
val hour: Long = dateDiff.inWholeHours
val day: Long = dateDiff.inWholeDays
if (second < 60) {
- convTime = "$second seconds $suffix"
+ convTime =
+ "$second ${getPluralString(Res.plurals.second, second.toInt(), 1)} $suffix"
} else if (minute < 60) {
- convTime = "$minute minutes $suffix"
+ convTime =
+ "$minute ${getPluralString(Res.plurals.minute, minute.toInt(), 1)} $suffix"
} else if (hour < 24) {
- convTime = "$hour hours $suffix"
+ convTime = "$hour ${getPluralString(Res.plurals.hour, hour.toInt(), 1)} $suffix"
} else if (day >= 7) {
- convTime = if (day > 360) {
- (day / 360).toString() + " years " + suffix
+ if (day > 360) {
+ convTime = "${(day / 360)} ${
+ getPluralString(
+ Res.plurals.year,
+ (day / 360).toInt()
+ )
+ } $suffix"
} else if (day > 30) {
- (day / 30).toString() + " months " + suffix
+ convTime = "${(day / 30)} ${
+ getPluralString(
+ Res.plurals.month,
+ (day / 30).toInt()
+ )
+ } $suffix"
} else {
- (day / 7).toString() + " week " + suffix
+ convTime = "${(day / 7)} ${
+ getPluralString(
+ Res.plurals.week,
+ (day / 7).toInt()
+ )
+ } $suffix"
}
} else if (day < 7) {
- convTime = "$day days $suffix"
+ convTime = "$day ${getPluralString(Res.plurals.day, day.toInt())} $suffix"
}
} catch (e: IllegalArgumentException) {
e.printStackTrace()
diff --git a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.kt
index 7171e692..588d321d 100644
--- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.kt
+++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.kt
@@ -10,6 +10,7 @@ expect class VideoPlayer(
) {
var progress: ((current: Long, duration: Long) -> Unit)?
var hasAudio: ((Boolean) -> Unit)?
+ var isVideoPlaying: ((Boolean) -> Unit)?
@Composable
fun view(modifier: Modifier)
diff --git a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/App.ios.kt b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/App.ios.kt
index c9a5ed80..c591f76a 100644
--- a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/App.ios.kt
+++ b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/App.ios.kt
@@ -6,18 +6,13 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
@OptIn(ExperimentalComposeUiApi::class)
-@Composable
-actual fun EdgeToEdgeDialog(
- onDismissRequest: () -> Unit,
- properties: DialogProperties,
- content: @Composable () -> Unit
-) = Dialog(
- onDismissRequest = onDismissRequest,
- properties = DialogProperties(
- dismissOnBackPress = properties.dismissOnBackPress,
- dismissOnClickOutside = properties.dismissOnClickOutside,
- usePlatformDefaultWidth = properties.usePlatformDefaultWidth,
- usePlatformInsets = false
- ),
- content = content
+actual fun EdgeToEdgeDialogProperties(
+ dismissOnBackPress: Boolean,
+ dismissOnClickOutside: Boolean,
+ usePlatformDefaultWidth: Boolean
+): DialogProperties = DialogProperties(
+ dismissOnBackPress = dismissOnBackPress,
+ dismissOnClickOutside = dismissOnClickOutside,
+ usePlatformDefaultWidth = usePlatformDefaultWidth,
+ usePlatformInsets = false
)
diff --git a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/AppViewController.kt b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/AppViewController.kt
index 2e99e206..9b796e37 100644
--- a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/AppViewController.kt
+++ b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/AppViewController.kt
@@ -2,13 +2,14 @@ package com.daniebeler.pfpixelix
import androidx.compose.foundation.ComposeFoundationFlags
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.window.ComposeUIViewController
import coil3.SingletonImageLoader
import com.daniebeler.pfpixelix.di.AppComponent
import com.daniebeler.pfpixelix.di.create
+import com.daniebeler.pfpixelix.domain.service.file.IosFileService
+import com.daniebeler.pfpixelix.domain.service.icon.IosAppIconManager
import com.daniebeler.pfpixelix.utils.KmpContext
-import com.daniebeler.pfpixelix.utils.LocalKmpContext
import com.daniebeler.pfpixelix.utils.configureLogger
import platform.UIKit.UIViewController
@@ -16,17 +17,19 @@ class IosUrlCallback {
var onRedirect: (String) -> Unit = {}
}
-@OptIn(ExperimentalFoundationApi::class)
+@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
fun AppViewController(urlCallback: IosUrlCallback): UIViewController {
//https://youtrack.jetbrains.com/issue/CMP-7623 iOS - Gesture handling is incorrect in 1.8.0-alpha03
ComposeFoundationFlags.DragGesturePickUpEnabled = false
var viewController: UIViewController? = null
- val context = object : KmpContext() {
- override val viewController: UIViewController
- get() = viewController!!
- }
- val appComponent = AppComponent.Companion.create(context)
+ val appComponent = AppComponent.Companion.create(
+ object : KmpContext() {
+ override val viewController get() = viewController!!
+ },
+ IosFileService(),
+ IosAppIconManager()
+ )
configureLogger()
@@ -39,12 +42,12 @@ fun AppViewController(urlCallback: IosUrlCallback): UIViewController {
}
val finishApp = {}
- viewController = ComposeUIViewController {
- CompositionLocalProvider(
- LocalKmpContext provides context
- ) {
- App(appComponent, finishApp)
+ viewController = ComposeUIViewController(
+ configure = {
+ parallelRendering = true
}
+ ) {
+ App(appComponent, finishApp)
}
return viewController
diff --git a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/IosFileService.kt b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/IosFileService.kt
new file mode 100644
index 00000000..6b7fda13
--- /dev/null
+++ b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/IosFileService.kt
@@ -0,0 +1,175 @@
+package com.daniebeler.pfpixelix.domain.service.file
+
+import com.daniebeler.pfpixelix.utils.KmpUri
+import kotlinx.cinterop.ExperimentalForeignApi
+import kotlinx.cinterop.get
+import kotlinx.cinterop.refTo
+import kotlinx.cinterop.usePinned
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.IO
+import kotlinx.coroutines.withContext
+import okio.Path
+import okio.Path.Companion.toPath
+import platform.CoreFoundation.CFDataGetBytePtr
+import platform.CoreFoundation.CFDataGetLength
+import platform.CoreFoundation.CFDictionaryAddValue
+import platform.CoreFoundation.CFDictionaryCreateMutable
+import platform.CoreFoundation.CFRelease
+import platform.CoreFoundation.CFStringRef
+import platform.CoreFoundation.CFURLRef
+import platform.CoreGraphics.CGDataProviderCopyData
+import platform.CoreGraphics.CGImageGetDataProvider
+import platform.CoreServices.UTTypeCopyPreferredTagWithClass
+import platform.CoreServices.UTTypeCreatePreferredIdentifierForTag
+import platform.CoreServices.kUTTagClassFilenameExtension
+import platform.CoreServices.kUTTagClassMIMEType
+import platform.Foundation.CFBridgingRelease
+import platform.Foundation.CFBridgingRetain
+import platform.Foundation.NSData
+import platform.Foundation.NSDictionary
+import platform.Foundation.NSDocumentDirectory
+import platform.Foundation.NSFileManager
+import platform.Foundation.NSFileSize
+import platform.Foundation.NSNumber
+import platform.Foundation.NSString
+import platform.Foundation.NSUserDomainMask
+import platform.Foundation.dataWithContentsOfURL
+import platform.Foundation.fileSize
+import platform.ImageIO.CGImageSourceCreateThumbnailAtIndex
+import platform.ImageIO.CGImageSourceCreateWithURL
+import platform.ImageIO.kCGImageSourceCreateThumbnailFromImageAlways
+import platform.ImageIO.kCGImageSourceCreateThumbnailWithTransform
+import platform.ImageIO.kCGImageSourceThumbnailMaxPixelSize
+import platform.posix.memcpy
+
+@OptIn(ExperimentalForeignApi::class)
+class IosFileService : FileService {
+ private fun appDocDir() = NSFileManager.defaultManager.URLForDirectory(
+ directory = NSDocumentDirectory,
+ inDomain = NSUserDomainMask,
+ appropriateForURL = null,
+ create = false,
+ error = null,
+ )!!.path!!.toPath()
+
+ override val dataStoreDir: Path = appDocDir().resolve("dataStore")
+ override val imageCacheDir: Path = appDocDir().resolve("imageCache")
+
+
+ override fun getFile(uri: KmpUri): PlatformFile? {
+ return IosFile(uri).takeIf { it.isExist() }
+ }
+
+ override fun downloadFile(name: String?, url: String) {
+ }
+
+ override fun getCacheSizeInBytes(): Long {
+ val fm = NSFileManager.defaultManager()
+ val files = fm.subpathsOfDirectoryAtPath(imageCacheDir.toString(), null).orEmpty()
+ var result = 0uL
+ files.map { file ->
+ val dict = fm.fileAttributesAtPath(
+ imageCacheDir.resolve(file.toString()).toString(),
+ true
+ ) as NSDictionary
+ result += dict.fileSize()
+ }
+ return result.toLong()
+ }
+
+ override fun cleanCache() {
+ val fm = NSFileManager.defaultManager()
+ fm.removeItemAtPath(imageCacheDir.toString(), null)
+ }
+}
+
+@OptIn(ExperimentalForeignApi::class)
+private class IosFile(
+ private val uri: KmpUri
+) : PlatformFile {
+ override fun isExist(): Boolean =
+ getName() != "IosFile:unknown"
+
+ override fun getName(): String {
+ return uri.url.lastPathComponent() ?: "IosFile:unknown"
+ }
+
+ override fun getSize(): Long {
+ val path = uri.url.path ?: return 0L
+ val fm = NSFileManager.defaultManager
+ val attr = fm.attributesOfItemAtPath(path, null) ?: return 0L
+ return attr.getValue(NSFileSize) as Long
+ }
+
+ override fun getMimeType(): String {
+ val fileExtension = uri.url.pathExtension()
+ @Suppress("UNCHECKED_CAST", "CAST_NEVER_SUCCEEDS")
+ val fileExtensionRef = CFBridgingRetain(fileExtension as NSString) as CFStringRef
+ val uti = UTTypeCreatePreferredIdentifierForTag(
+ kUTTagClassFilenameExtension,
+ fileExtensionRef,
+ null
+ )
+ CFRelease(fileExtensionRef)
+ val mimeType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)
+ CFRelease(uti)
+ return CFBridgingRelease(mimeType) as String
+ }
+
+ override suspend fun readBytes(): ByteArray = withContext(Dispatchers.IO) {
+ val data = NSData.dataWithContentsOfURL(uri.url)!!
+ ByteArray(data.length.toInt()).apply {
+ data.usePinned {
+ memcpy(refTo(0), data.bytes, data.length)
+ }
+ }
+ }
+
+ override suspend fun getThumbnail(): ByteArray? = withContext(Dispatchers.IO) {
+ @Suppress("UNCHECKED_CAST")
+ val urlRef = CFBridgingRetain(uri.url) as CFURLRef
+ val imageSource = CGImageSourceCreateWithURL(urlRef, null)!!
+ val thumbnailOptions = CFDictionaryCreateMutable(
+ null,
+ 3,
+ null,
+ null
+ ).apply {
+ CFDictionaryAddValue(
+ this,
+ kCGImageSourceCreateThumbnailWithTransform,
+ CFBridgingRetain(NSNumber(bool = true))
+ )
+ CFDictionaryAddValue(
+ this,
+ kCGImageSourceCreateThumbnailFromImageAlways,
+ CFBridgingRetain(NSNumber(bool = true))
+ )
+ CFDictionaryAddValue(
+ this,
+ kCGImageSourceThumbnailMaxPixelSize,
+ CFBridgingRetain(NSNumber(512))
+ )
+ }
+
+ val thumbnailSource = CGImageSourceCreateThumbnailAtIndex(
+ imageSource,
+ 0u,
+ thumbnailOptions
+ )
+
+ val data = CGDataProviderCopyData(CGImageGetDataProvider(thumbnailSource))
+ val bytePointer = CFDataGetBytePtr(data)!!
+ val length = CFDataGetLength(data)
+
+ val byteArray = ByteArray(length.toInt()) { index ->
+ bytePointer[index].toByte()
+ }
+
+ CFRelease(urlRef)
+ CFRelease(data)
+ CFRelease(thumbnailSource)
+
+ byteArray
+ }
+}
\ No newline at end of file
diff --git a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/IosAppIconManager.kt b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/IosAppIconManager.kt
new file mode 100644
index 00000000..f2241a67
--- /dev/null
+++ b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/IosAppIconManager.kt
@@ -0,0 +1,48 @@
+package com.daniebeler.pfpixelix.domain.service.icon
+
+import org.jetbrains.compose.resources.DrawableResource
+import pixelix.app.generated.resources.Res
+import pixelix.app.generated.resources.app_icon_00
+import pixelix.app.generated.resources.app_icon_01
+import pixelix.app.generated.resources.app_icon_02
+import pixelix.app.generated.resources.app_icon_03
+import pixelix.app.generated.resources.app_icon_05
+import pixelix.app.generated.resources.app_icon_06
+import pixelix.app.generated.resources.app_icon_07
+import pixelix.app.generated.resources.app_icon_08
+import pixelix.app.generated.resources.app_icon_09
+import platform.UIKit.UIApplication
+import platform.UIKit.alternateIconName
+import platform.UIKit.setAlternateIconName
+
+class IosAppIconManager : AppIconManager {
+ private val iconIds = mapOf(
+ Res.drawable.app_icon_00 to "AppIcon_02",
+ Res.drawable.app_icon_01 to "AppIcon_01",
+ Res.drawable.app_icon_02 to "AppIcon",
+ Res.drawable.app_icon_03 to "AppIcon_03",
+ Res.drawable.app_icon_05 to "AppIcon_04",
+ Res.drawable.app_icon_06 to "AppIcon_05",
+ Res.drawable.app_icon_07 to "AppIcon_06",
+ Res.drawable.app_icon_08 to "AppIcon_07",
+ Res.drawable.app_icon_09 to "AppIcon_08",
+ )
+
+ override fun getCurrentIcon(): DrawableResource {
+ val currentId = UIApplication.sharedApplication.alternateIconName
+ for ((res, id) in iconIds.entries) {
+ if (currentId == id) {
+ return res
+ }
+ }
+ return Res.drawable.app_icon_02
+ }
+
+ override fun setCustomIcon(icon: DrawableResource) {
+ if (icon == Res.drawable.app_icon_02) {
+ UIApplication.sharedApplication.setAlternateIconName(null, null)
+ } else {
+ UIApplication.sharedApplication.setAlternateIconName(iconIds[icon]!!, null)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.ios.kt b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.ios.kt
index f6adfdc9..26b28d6f 100644
--- a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.ios.kt
+++ b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.ios.kt
@@ -1,232 +1,79 @@
package com.daniebeler.pfpixelix.domain.service.platform
+import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.domain.service.preferences.UserPreferences
import com.daniebeler.pfpixelix.utils.KmpContext
-import com.daniebeler.pfpixelix.utils.KmpUri
-import com.daniebeler.pfpixelix.utils.imageCacheDir
import kotlinx.cinterop.ExperimentalForeignApi
-import kotlinx.cinterop.get
-import kotlinx.cinterop.refTo
-import kotlinx.cinterop.usePinned
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.IO
-import kotlinx.coroutines.withContext
+import kotlinx.cinterop.useContents
import me.tatarka.inject.annotations.Inject
-import org.jetbrains.compose.resources.DrawableResource
-import pixelix.app.generated.resources.Res
-import pixelix.app.generated.resources.app_icon_00
-import pixelix.app.generated.resources.app_icon_01
-import pixelix.app.generated.resources.app_icon_02
-import pixelix.app.generated.resources.app_icon_03
-import pixelix.app.generated.resources.app_icon_05
-import pixelix.app.generated.resources.app_icon_06
-import pixelix.app.generated.resources.app_icon_07
-import pixelix.app.generated.resources.app_icon_08
-import pixelix.app.generated.resources.app_icon_09
-import platform.CoreFoundation.CFDataGetBytePtr
-import platform.CoreFoundation.CFDataGetLength
-import platform.CoreFoundation.CFDictionaryAddValue
-import platform.CoreFoundation.CFDictionaryCreateMutable
-import platform.CoreFoundation.CFRelease
-import platform.CoreFoundation.CFStringRef
-import platform.CoreFoundation.CFURLRef
-import platform.CoreGraphics.CGDataProviderCopyData
-import platform.CoreGraphics.CGImageGetDataProvider
-import platform.CoreServices.UTTypeCopyPreferredTagWithClass
-import platform.CoreServices.UTTypeCreatePreferredIdentifierForTag
-import platform.CoreServices.kUTTagClassFilenameExtension
-import platform.CoreServices.kUTTagClassMIMEType
-import platform.Foundation.CFBridgingRelease
-import platform.Foundation.CFBridgingRetain
+import platform.CoreGraphics.CGRectMake
import platform.Foundation.NSBundle
-import platform.Foundation.NSData
-import platform.Foundation.NSDictionary
-import platform.Foundation.NSFileManager
-import platform.Foundation.NSFileSize
-import platform.Foundation.NSNumber
-import platform.Foundation.NSString
import platform.Foundation.NSURL
-import platform.Foundation.dataWithContentsOfURL
-import platform.Foundation.fileSize
-import platform.ImageIO.CGImageSourceCreateThumbnailAtIndex
-import platform.ImageIO.CGImageSourceCreateWithURL
-import platform.ImageIO.kCGImageSourceCreateThumbnailFromImageAlways
-import platform.ImageIO.kCGImageSourceCreateThumbnailWithTransform
-import platform.ImageIO.kCGImageSourceThumbnailMaxPixelSize
+import platform.Foundation.NSURL.Companion.URLWithString
+import platform.SafariServices.SFSafariViewController
import platform.UIKit.UIActivityViewController
import platform.UIKit.UIApplication
-import platform.UIKit.alternateIconName
-import platform.UIKit.setAlternateIconName
-import platform.posix.memcpy
+import platform.UIKit.UIDevice
+import platform.UIKit.UIUserInterfaceIdiomPad
+import platform.UIKit.popoverPresentationController
@Inject
actual class Platform actual constructor(
private val context: KmpContext,
private val prefs: UserPreferences
) {
- actual fun getPlatformFile(uri: KmpUri): PlatformFile? {
- val f = IosFile(uri)
- return if (f.getName() != "IosFile:unknown") f else null
- }
-
- actual fun getAppIconManager(): AppIconManager {
- return IosAppIconManager()
- }
-
actual fun openUrl(url: String) {
- UIApplication.sharedApplication.openURL(NSURL(string = url))
- }
-
- actual fun shareText(text: String) {
- val vc = UIActivityViewController(listOf(text), null)
- context.viewController.presentViewController(vc, true, null)
- }
+ if (prefs.useInAppBrowser) {
+ val safariViewController = SFSafariViewController(uRL = NSURL(string = url))
+ val self = context.viewController
+ self.presentViewController(
+ viewControllerToPresent = safariViewController,
+ animated = true,
+ completion = null
+ )
- actual fun getAppVersion(): String {
- return NSBundle.mainBundle.infoDictionary?.get("CFBundleShortVersionString").toString()
+ } else {
+ UIApplication.sharedApplication.openURL(
+ url = URLWithString(url)!!,
+ options = emptyMap(),
+ completionHandler = null
+ )
+ }
}
- actual fun pinWidget() {}
-
- actual fun downloadImageToGallery(name: String?, url: String) {}
-
- @OptIn(ExperimentalForeignApi::class)
- actual fun getCacheSizeInBytes(): Long {
- val fm = NSFileManager.defaultManager()
- val cacheDir = context.imageCacheDir
- val files = fm.subpathsOfDirectoryAtPath(cacheDir.toString(), null).orEmpty()
- var result = 0uL
- files.map { file ->
- val dict = fm.fileAttributesAtPath(
- cacheDir.resolve(file.toString()).toString(),
- true
- ) as NSDictionary
- result += dict.fileSize()
+ actual fun dismissBrowser() {
+ if (prefs.useInAppBrowser) {
+ val self = context.viewController
+ self.dismissModalViewControllerAnimated(true)
}
- return result.toLong()
}
@OptIn(ExperimentalForeignApi::class)
- actual fun cleanCache() {
- val fm = NSFileManager.defaultManager()
- fm.removeItemAtPath(context.imageCacheDir.toString(), null)
- }
-}
-
-@OptIn(ExperimentalForeignApi::class)
-private class IosFile(
- private val uri: KmpUri
-) : PlatformFile {
- override fun getName(): String {
- return uri.url.lastPathComponent() ?: "IosFile:unknown"
- }
-
- override fun getSize(): Long {
- val path = uri.url.path ?: return 0L
- val fm = NSFileManager.defaultManager
- val attr = fm.attributesOfItemAtPath(path, null) ?: return 0L
- return attr.getValue(NSFileSize) as Long
- }
-
- override fun getMimeType(): String {
- val fileExtension = uri.url.pathExtension()
- val fileExtensionRef = CFBridgingRetain(fileExtension as NSString) as CFStringRef
- val uti = UTTypeCreatePreferredIdentifierForTag(
- kUTTagClassFilenameExtension,
- fileExtensionRef,
- null
+ actual fun shareText(text: String) {
+ val self = context.viewController
+ val vc = UIActivityViewController(
+ activityItems = listOf(text),
+ applicationActivities = null
)
- CFRelease(fileExtensionRef)
- val mimeType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)
- CFRelease(uti)
- return CFBridgingRelease(mimeType) as String
- }
-
- override suspend fun readBytes(): ByteArray = withContext(Dispatchers.IO) {
- val data = NSData.dataWithContentsOfURL(uri.url)!!
- ByteArray(data.length.toInt()).apply {
- data.usePinned {
- memcpy(refTo(0), data.bytes, data.length)
+ if (isIpad()) {
+ Logger.d("share on iPad")
+ vc.popoverPresentationController?.apply {
+ sourceView = self.view
+ sourceRect = self.view.center.useContents { CGRectMake(x, y, 0.0, 0.0) }
+ permittedArrowDirections = 0uL
}
}
+ self.presentViewController(vc, true, null)
}
- override suspend fun getThumbnail(): ByteArray? = withContext(Dispatchers.IO) {
- val urlRef = CFBridgingRetain(uri.url) as CFURLRef
- val imageSource = CGImageSourceCreateWithURL(urlRef, null)!!
- val thumbnailOptions = CFDictionaryCreateMutable(
- null,
- 3,
- null,
- null
- ).apply {
- CFDictionaryAddValue(
- this,
- kCGImageSourceCreateThumbnailWithTransform,
- CFBridgingRetain(NSNumber(bool = true))
- )
- CFDictionaryAddValue(
- this,
- kCGImageSourceCreateThumbnailFromImageAlways,
- CFBridgingRetain(NSNumber(bool = true))
- )
- CFDictionaryAddValue(
- this,
- kCGImageSourceThumbnailMaxPixelSize,
- CFBridgingRetain(NSNumber(512))
- )
- }
-
- val thumbnailSource = CGImageSourceCreateThumbnailAtIndex(
- imageSource,
- 0u,
- thumbnailOptions
- )
-
- val data = CGDataProviderCopyData(CGImageGetDataProvider(thumbnailSource))
- val bytePointer = CFDataGetBytePtr(data)!!
- val length = CFDataGetLength(data)
-
- val byteArray = ByteArray(length.toInt()) { index ->
- bytePointer[index].toByte()
- }
-
- CFRelease(urlRef)
- CFRelease(data)
- CFRelease(thumbnailSource)
-
- byteArray
+ private fun isIpad(): Boolean {
+ val device = UIDevice.currentDevice
+ return device.userInterfaceIdiom == UIUserInterfaceIdiomPad
}
-}
-private class IosAppIconManager : AppIconManager {
- private val iconIds = mapOf(
- Res.drawable.app_icon_00 to "AppIcon_02",
- Res.drawable.app_icon_01 to "AppIcon_01",
- Res.drawable.app_icon_02 to "AppIcon",
- Res.drawable.app_icon_03 to "AppIcon_03",
- Res.drawable.app_icon_05 to "AppIcon_04",
- Res.drawable.app_icon_06 to "AppIcon_05",
- Res.drawable.app_icon_07 to "AppIcon_06",
- Res.drawable.app_icon_08 to "AppIcon_07",
- Res.drawable.app_icon_09 to "AppIcon_08",
- )
-
- override fun getCurrentIcon(): DrawableResource {
- val currentId = UIApplication.sharedApplication.alternateIconName
- for ((res, id) in iconIds.entries) {
- if (currentId == id) {
- return res
- }
- }
- return Res.drawable.app_icon_02
+ actual fun getAppVersion(): String {
+ return NSBundle.mainBundle.infoDictionary?.get("CFBundleShortVersionString").toString()
}
- override fun setCustomIcon(icon: DrawableResource) {
- if (icon == Res.drawable.app_icon_02) {
- UIApplication.sharedApplication.setAlternateIconName(null, null)
- } else {
- UIApplication.sharedApplication.setAlternateIconName(iconIds[icon]!!, null)
- }
- }
+ actual fun pinWidget() {}
}
diff --git a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.ios.kt b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.ios.kt
index 8976de5d..d44c2ceb 100644
--- a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.ios.kt
+++ b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.ios.kt
@@ -2,6 +2,10 @@ package com.daniebeler.pfpixelix.domain.service.platform
actual object PlatformFeatures {
actual val notificationWidgets = false
- actual val inAppBrowser = false
+ actual val inAppBrowser = true
actual val downloadToGallery = false
+ actual val customAppIcon = true
+ actual val autoplayVideosPref = false
+ actual val addCollection = false
+ actual val customAccentColors = true
}
\ No newline at end of file
diff --git a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.ios.kt b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.ios.kt
index 0463fdbe..d9885023 100644
--- a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.ios.kt
+++ b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.ios.kt
@@ -2,25 +2,27 @@ package com.daniebeler.pfpixelix.ui.theme
import androidx.compose.material3.ColorScheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import com.daniebeler.pfpixelix.domain.model.AppThemeMode
-import com.daniebeler.pfpixelix.utils.KmpContext
-actual fun applySystemNightMode(mode: Int) {}
+actual fun applySystemNightMode(isDark: Boolean) {}
@Composable
-actual fun ChangeSystemBarColors(mode: Int) {}
-
-actual fun KmpContext.generateColorScheme(
+actual fun generateColorScheme(
nightModeValue: Int,
dynamicColor: Boolean,
lightScheme: ColorScheme,
darkScheme: ColorScheme
): ColorScheme {
//TODO dynamicColor
- return when (nightModeValue) {
- AppThemeMode.AMOLED -> darkScheme.toAmoled()
- AppThemeMode.DARK -> darkScheme
- else -> lightScheme
+ return remember(
+ nightModeValue, dynamicColor, lightScheme, darkScheme
+ ) {
+ when (nightModeValue) {
+ AppThemeMode.AMOLED -> darkScheme.toAmoled()
+ AppThemeMode.DARK -> darkScheme
+ else -> lightScheme
+ }
}
}
\ No newline at end of file
diff --git a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.ios.kt b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.ios.kt
index 9bef7c96..a17bae93 100644
--- a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.ios.kt
+++ b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.ios.kt
@@ -2,12 +2,7 @@ package com.daniebeler.pfpixelix.utils
import coil3.PlatformContext
import io.github.vinceglb.filekit.core.PlatformFile
-import kotlinx.cinterop.ExperimentalForeignApi
-import okio.Path.Companion.toPath
-import platform.Foundation.NSDocumentDirectory
-import platform.Foundation.NSFileManager
import platform.Foundation.NSURL
-import platform.Foundation.NSUserDomainMask
import platform.UIKit.UIViewController
private data class IosUri(override val url: NSURL) : KmpUri() {
@@ -27,15 +22,3 @@ actual abstract class KmpContext {
abstract val viewController: UIViewController
}
actual val KmpContext.coilContext get() = PlatformContext.INSTANCE
-actual val KmpContext.dataStoreDir get() = appDocDir().resolve("dataStore")
-actual val KmpContext.imageCacheDir get() = appDocDir().resolve("imageCache")
-
-@OptIn(ExperimentalForeignApi::class)
-private fun appDocDir() = NSFileManager.defaultManager.URLForDirectory(
- directory = NSDocumentDirectory,
- inDomain = NSUserDomainMask,
- appropriateForURL = null,
- create = false,
- error = null,
-)!!.path!!.toPath()
-
diff --git a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.ios.kt b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.ios.kt
index 3e3bbdf6..4d667e5b 100644
--- a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.ios.kt
+++ b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.ios.kt
@@ -28,6 +28,7 @@ import platform.AVFoundation.tracksWithMediaType
import platform.AVKit.AVPlayerViewController
import platform.CoreMedia.CMTimeGetSeconds
import platform.Foundation.NSURL
+import platform.Foundation.observeValueForKeyPath
import platform.UIKit.UIView
@OptIn(ExperimentalForeignApi::class)
@@ -37,6 +38,7 @@ actual class VideoPlayer actual constructor(
) {
actual var progress: ((current: Long, duration: Long) -> Unit)? = null
actual var hasAudio: ((Boolean) -> Unit)? = null
+ actual var isVideoPlaying: ((Boolean) -> Unit)? = null
private var extractAudioInfoJob: Job? = null
@@ -86,7 +88,6 @@ actual class VideoPlayer actual constructor(
actual fun prepare(url: String) {
release()
-
val item = AVPlayerItem(NSURL(string = url))
player.replaceCurrentItemWithPlayerItem(item)
diff --git a/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/App.jvm.kt b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/App.jvm.kt
new file mode 100644
index 00000000..6ce16a38
--- /dev/null
+++ b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/App.jvm.kt
@@ -0,0 +1,16 @@
+package com.daniebeler.pfpixelix
+
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.window.DialogProperties
+
+@OptIn(ExperimentalComposeUiApi::class)
+actual fun EdgeToEdgeDialogProperties(
+ dismissOnBackPress: Boolean,
+ dismissOnClickOutside: Boolean,
+ usePlatformDefaultWidth: Boolean
+): DialogProperties = DialogProperties(
+ dismissOnBackPress = dismissOnBackPress,
+ dismissOnClickOutside = dismissOnClickOutside,
+ usePlatformDefaultWidth = usePlatformDefaultWidth,
+ usePlatformInsets = false
+)
diff --git a/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/Main.kt b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/Main.kt
new file mode 100644
index 00000000..ffc09aca
--- /dev/null
+++ b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/Main.kt
@@ -0,0 +1,50 @@
+package com.daniebeler.pfpixelix
+
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Window
+import androidx.compose.ui.window.application
+import androidx.compose.ui.window.rememberWindowState
+import coil3.SingletonImageLoader
+import com.daniebeler.pfpixelix.di.AppComponent
+import com.daniebeler.pfpixelix.di.create
+import com.daniebeler.pfpixelix.domain.service.file.DesktopFileService
+import com.daniebeler.pfpixelix.domain.service.icon.DesktopAppIconManager
+import com.daniebeler.pfpixelix.utils.KmpContext
+import com.daniebeler.pfpixelix.utils.configureJavaLogger
+import com.daniebeler.pfpixelix.utils.configureLogger
+import java.awt.Desktop
+import java.awt.Dimension
+
+fun main() {
+ //https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-desktop-swing-interoperability.html
+ System.setProperty("compose.swing.render.on.graphics", "true")
+ System.setProperty("compose.interop.blending", "true")
+ application {
+ val appComponent = AppComponent.Companion.create(
+ object : KmpContext() {},
+ DesktopFileService(),
+ DesktopAppIconManager()
+ )
+
+ configureJavaLogger()
+
+ SingletonImageLoader.setSafe {
+ appComponent.provideImageLoader()
+ }
+
+ Desktop.getDesktop().setOpenURIHandler { url ->
+ appComponent.systemUrlHandler.onRedirect(
+ url.uri.toString()
+ )
+ }
+
+ Window(
+ title = "Pixelix",
+ state = rememberWindowState(width = 600.dp, height = 1000.dp),
+ onCloseRequest = ::exitApplication,
+ ) {
+ window.minimumSize = Dimension(400, 600)
+ App(appComponent) { exitApplication() }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/DesktopFileService.kt b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/DesktopFileService.kt
new file mode 100644
index 00000000..580bd0ae
--- /dev/null
+++ b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/DesktopFileService.kt
@@ -0,0 +1,79 @@
+package com.daniebeler.pfpixelix.domain.service.file
+
+import ca.gosyer.appdirs.AppDirs
+import co.touchlab.kermit.Logger
+import com.daniebeler.pfpixelix.utils.KmpUri
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import okio.Path
+import okio.Path.Companion.toPath
+import java.awt.Image
+import java.awt.image.BufferedImage
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.nio.file.Files
+import javax.imageio.ImageIO
+
+class DesktopFileService : FileService {
+ private val appDirs = AppDirs("com.daniebeler.pfpixelix", null)
+ private fun appDocDir() = appDirs.getUserDataDir().toPath()
+
+ override val dataStoreDir: Path = appDocDir().resolve("dataStore")
+ override val imageCacheDir: Path = appDocDir().resolve("imageCache")
+
+ override fun getFile(uri: KmpUri): PlatformFile? {
+ return DesktopFile(uri).takeIf { it.isExist() }
+ }
+
+ override fun downloadFile(name: String?, url: String) {
+ }
+
+ override fun getCacheSizeInBytes(): Long {
+ return imageCacheDir.toFile().walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
+ }
+
+ override fun cleanCache() {
+ imageCacheDir.toFile().deleteRecursively()
+ }
+}
+
+private class DesktopFile(
+ uri: KmpUri
+) : PlatformFile {
+ private val file = File(uri.uri)
+
+ override fun isExist(): Boolean = file.exists()
+ override fun getName(): String = file.name
+ override fun getSize(): Long = file.length()
+ override fun getMimeType(): String = Files.probeContentType(file.toPath())
+
+ override suspend fun readBytes(): ByteArray = withContext(Dispatchers.IO) {
+ file.readBytes()
+ }
+
+ override suspend fun getThumbnail(): ByteArray? = withContext(Dispatchers.IO) {
+ val thumbnail = try {
+ val size = 512
+ val originalImage = ImageIO.read(file)
+ val aspectRatio = originalImage.width.toDouble() / originalImage.height
+ val (width, height) = if (aspectRatio > 1) {
+ size to (size / aspectRatio).toInt()
+ } else {
+ (size * aspectRatio).toInt() to size
+ }
+ val image = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH)
+ val bufferedImage = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
+ val graphics = bufferedImage.createGraphics()
+ graphics.drawImage(image, 0, 0, null)
+ graphics.dispose()
+ bufferedImage
+ } catch (e: Exception) {
+ Logger.e("Failed to create thumbnail for file: ${file.name}", e)
+ null
+ } ?: return@withContext null
+
+ val outputStream = ByteArrayOutputStream()
+ ImageIO.write(thumbnail, "png", outputStream)
+ outputStream.toByteArray()
+ }
+}
\ No newline at end of file
diff --git a/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/DesktopAppIconManager.kt b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/DesktopAppIconManager.kt
new file mode 100644
index 00000000..6c593b40
--- /dev/null
+++ b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/DesktopAppIconManager.kt
@@ -0,0 +1,14 @@
+package com.daniebeler.pfpixelix.domain.service.icon
+
+import org.jetbrains.compose.resources.DrawableResource
+import pixelix.app.generated.resources.Res
+import pixelix.app.generated.resources.app_icon_02
+
+class DesktopAppIconManager : AppIconManager {
+
+ override fun getCurrentIcon(): DrawableResource {
+ return Res.drawable.app_icon_02
+ }
+
+ override fun setCustomIcon(icon: DrawableResource) {}
+}
\ No newline at end of file
diff --git a/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.jvm.kt b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.jvm.kt
new file mode 100644
index 00000000..f917a235
--- /dev/null
+++ b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.jvm.kt
@@ -0,0 +1,27 @@
+package com.daniebeler.pfpixelix.domain.service.platform
+
+import com.daniebeler.pfpixelix.domain.service.preferences.UserPreferences
+import com.daniebeler.pfpixelix.utils.KmpContext
+import me.tatarka.inject.annotations.Inject
+import java.awt.Desktop
+import java.net.URI
+
+@Inject
+actual class Platform actual constructor(
+ private val context: KmpContext,
+ private val prefs: UserPreferences
+) {
+ actual fun openUrl(url: String) {
+ Desktop.getDesktop().browse(URI(url))
+ }
+
+ actual fun dismissBrowser() {}
+
+ actual fun shareText(text: String) {}
+
+ actual fun getAppVersion(): String {
+ return "1.0.0"
+ }
+
+ actual fun pinWidget() {}
+}
diff --git a/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.jvm.kt b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.jvm.kt
new file mode 100644
index 00000000..a44074ef
--- /dev/null
+++ b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/PlatformFeatures.jvm.kt
@@ -0,0 +1,11 @@
+package com.daniebeler.pfpixelix.domain.service.platform
+
+actual object PlatformFeatures {
+ actual val notificationWidgets = false
+ actual val inAppBrowser = false
+ actual val downloadToGallery = false
+ actual val customAppIcon = false
+ actual val autoplayVideosPref = false
+ actual val addCollection = true
+ actual val customAccentColors = true
+}
\ No newline at end of file
diff --git a/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.jvm.kt b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.jvm.kt
new file mode 100644
index 00000000..d9885023
--- /dev/null
+++ b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.jvm.kt
@@ -0,0 +1,28 @@
+package com.daniebeler.pfpixelix.ui.theme
+
+import androidx.compose.material3.ColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import com.daniebeler.pfpixelix.domain.model.AppThemeMode
+
+
+actual fun applySystemNightMode(isDark: Boolean) {}
+
+@Composable
+actual fun generateColorScheme(
+ nightModeValue: Int,
+ dynamicColor: Boolean,
+ lightScheme: ColorScheme,
+ darkScheme: ColorScheme
+): ColorScheme {
+ //TODO dynamicColor
+ return remember(
+ nightModeValue, dynamicColor, lightScheme, darkScheme
+ ) {
+ when (nightModeValue) {
+ AppThemeMode.AMOLED -> darkScheme.toAmoled()
+ AppThemeMode.DARK -> darkScheme
+ else -> lightScheme
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/utils/BlurHashDecoder.jvm.kt b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/utils/BlurHashDecoder.jvm.kt
new file mode 100644
index 00000000..137d0c3e
--- /dev/null
+++ b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/utils/BlurHashDecoder.jvm.kt
@@ -0,0 +1,31 @@
+package com.daniebeler.pfpixelix.utils
+
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.ImageBitmapConfig
+import androidx.compose.ui.graphics.asSkiaBitmap
+import org.jetbrains.skia.EncodedImageFormat
+import org.jetbrains.skia.Image
+
+actual fun createBitmap(
+ pixels: IntArray,
+ width: Int,
+ height: Int
+) = ImageBitmap(width, height, ImageBitmapConfig.Argb8888).also { bitmap ->
+ val pixelBytes = ByteArray(pixels.size * 4) { i ->
+ val pixel = pixels[i / 4]
+ val color = when (i % 4) {
+ 0 -> (pixel shr 16 and 0xFF)// Red
+ 1 -> (pixel shr 8 and 0xFF)// Green
+ 2 -> (pixel and 0xFF)// Blue
+ else -> (pixel shr 24 and 0xFF)// Alpha
+ }
+ color.toByte()
+ }
+ bitmap.asSkiaBitmap().installPixels(pixelBytes)
+}
+
+actual fun ImageBitmap.encodeToPngBytes(quality: Int): ByteArray? {
+ return Image.makeFromBitmap(this.asSkiaBitmap())
+ .encodeToData(EncodedImageFormat.PNG, quality)
+ ?.bytes
+}
\ No newline at end of file
diff --git a/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/utils/JvmLogger.kt b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/utils/JvmLogger.kt
new file mode 100644
index 00000000..7cb490ac
--- /dev/null
+++ b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/utils/JvmLogger.kt
@@ -0,0 +1,34 @@
+package com.daniebeler.pfpixelix.utils
+
+import co.touchlab.kermit.LogWriter
+import co.touchlab.kermit.Logger
+import co.touchlab.kermit.Severity
+import org.slf4j.LoggerFactory
+import org.slf4j.event.Level
+
+fun configureJavaLogger(isDebug: Boolean = false) {
+ configureLogger(isDebug)
+ Logger.addLogWriter(Slf4jLogWriter)
+}
+
+private object Slf4jLogWriter : LogWriter() {
+ override fun log(severity: Severity, message: String, tag: String, throwable: Throwable?) {
+ val logger = LoggerFactory.getLogger(tag).atLevel(severity.slf4jLevel)
+
+ if (throwable != null) {
+ logger.setCause(throwable)
+ }
+
+ logger.log(message)
+ }
+
+ private val Severity.slf4jLevel: Level
+ get() = when (this) {
+ Severity.Verbose -> Level.TRACE
+ Severity.Debug -> Level.DEBUG
+ Severity.Info -> Level.INFO
+ Severity.Warn -> Level.WARN
+ Severity.Error -> Level.ERROR
+ Severity.Assert -> Level.ERROR
+ }
+}
\ No newline at end of file
diff --git a/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.jvm.kt b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.jvm.kt
new file mode 100644
index 00000000..c945d542
--- /dev/null
+++ b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/utils/KmpPlatform.jvm.kt
@@ -0,0 +1,21 @@
+package com.daniebeler.pfpixelix.utils
+
+import coil3.PlatformContext
+import io.github.vinceglb.filekit.core.PlatformFile
+import java.net.URI
+
+private data class DesktopUri(override val uri: URI) : KmpUri() {
+ override fun toString(): String = uri.toString()
+}
+
+actual abstract class KmpUri {
+ abstract val uri: URI
+ actual abstract override fun toString(): String
+}
+actual val EmptyKmpUri: KmpUri = DesktopUri(URI(""))
+actual fun KmpUri.getPlatformUriObject(): Any = uri.toString()
+actual fun String.toKmpUri(): KmpUri = DesktopUri(URI(this))
+actual fun PlatformFile.toKmpUri(): KmpUri = DesktopUri(file.toURI())
+
+actual abstract class KmpContext
+actual val KmpContext.coilContext get() = PlatformContext.INSTANCE
diff --git a/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.jvm.kt b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.jvm.kt
new file mode 100644
index 00000000..f46fd721
--- /dev/null
+++ b/app/src/jvmMain/kotlin/com/daniebeler/pfpixelix/utils/VideoPlayer.jvm.kt
@@ -0,0 +1,101 @@
+package com.daniebeler.pfpixelix.utils
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.awt.SwingPanel
+import androidx.compose.ui.graphics.Color
+import kotlinx.coroutines.CoroutineScope
+import uk.co.caprica.vlcj.factory.discovery.NativeDiscovery
+import uk.co.caprica.vlcj.factory.discovery.strategy.OsxNativeDiscoveryStrategy
+import uk.co.caprica.vlcj.player.base.MediaPlayer
+import uk.co.caprica.vlcj.player.base.MediaPlayerEventAdapter
+import uk.co.caprica.vlcj.player.component.CallbackMediaPlayerComponent
+import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent
+import uk.co.caprica.vlcj.player.component.InputEvents
+import java.awt.Component
+import java.util.Locale
+
+actual class VideoPlayer actual constructor(
+ context: KmpContext,
+ private val coroutineScope: CoroutineScope
+) {
+ private val mpComponent = initializeMediaPlayerComponent()
+ private val player = mpComponent.mediaPlayer()
+ actual var isVideoPlaying: ((Boolean) -> Unit)? = null
+
+ actual var progress: ((current: Long, duration: Long) -> Unit)? = null
+ actual var hasAudio: ((Boolean) -> Unit)? = null
+
+ private val listener = object : MediaPlayerEventAdapter() {
+ override fun mediaPlayerReady(mediaPlayer: MediaPlayer?) {
+ hasAudio?.invoke(player.audio().trackCount() > 0)
+ }
+
+ override fun positionChanged(mediaPlayer: MediaPlayer?, newPosition: Float) {
+ val status = player.status()
+ progress?.invoke((status.length() * status.position()).toLong(), status.length())
+ }
+
+ override fun playing(mediaPlayer: MediaPlayer?) {
+ isVideoPlaying?.invoke(true)
+ }
+
+ override fun paused(mediaPlayer: MediaPlayer?) {
+ isVideoPlaying?.invoke(false)
+ }
+ }
+
+ init {
+ player.events().addMediaPlayerEventListener(listener)
+ }
+
+ @Composable
+ actual fun view(modifier: Modifier) {
+ SwingPanel(
+ factory = { mpComponent },
+ background = Color.Transparent,
+ modifier = modifier
+ )
+ }
+
+ actual fun prepare(url: String) {
+ player.media().prepare(url)
+ }
+
+ actual fun play() {
+ player.controls().play()
+ }
+
+ actual fun pause() {
+ player.controls().pause()
+ }
+
+ actual fun release() {
+ player.events().removeMediaPlayerEventListener(listener)
+ player.release()
+ }
+
+ actual fun audio(enable: Boolean) {
+ player.audio().isMute = !enable
+ }
+
+ private fun Component.mediaPlayer() = when (this) {
+ is CallbackMediaPlayerComponent -> mediaPlayer()
+ is EmbeddedMediaPlayerComponent -> mediaPlayer()
+ else -> error("mediaPlayer() can only be called on vlcj player components")
+ }
+
+ private fun initializeMediaPlayerComponent(): Component {
+ NativeDiscovery(OsxNativeDiscoveryStrategy()).discover()
+ return if (isMacOS()) {
+ CallbackMediaPlayerComponent(null, null, InputEvents.NONE, true, null)
+ } else {
+ EmbeddedMediaPlayerComponent(null, null, null, InputEvents.NONE, null)
+ }
+ }
+
+ private fun isMacOS(): Boolean {
+ val os = System.getProperty("os.name", "generic").lowercase(Locale.ENGLISH)
+ return "mac" in os || "darwin" in os
+ }
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 139046b5..3022f950 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,57 +1,61 @@
[versions]
-kotlin = "2.1.10"
-ksp = "2.1.10-1.0.29"
-agp = "8.8.0"
+kotlin = "2.1.20"
+ksp = "2.1.20-1.0.31"
+agp = "8.9.1"
#https://github.com/JetBrains/compose-multiplatform/releases
-composeMultiplatform = "1.8.0-alpha03"
-lifecycle = "2.9.0-alpha03"
-lifecycleMultiplatform = "2.9.0-alpha03"
-navigationMultiplatform = "2.8.0-alpha13"
+composeMultiplatform = "1.8.0-beta02"
+lifecycleMultiplatform = "2.9.0-alpha06"
+navigationMultiplatform = "2.9.0-alpha16"
#JetBrains
kotlinx-coroutines = "1.10.1"
-kotlinxCollectionsImmutable = "0.3.5"
+kotlinxCollectionsImmutable = "0.3.8"
kotlinxSerializationJson = "1.8.0"
-ktor = "3.1.0"
-kotlinx-datetime = "0.6.1"
+ktor = "3.1.1"
+kotlinx-datetime = "0.6.2"
#multiplatform
-ksoup = "0.2.1"
+ksoup = "0.2.2"
kermit = "2.0.5"
-ktorfit = "2.2.0"
+ktorfit = "2.4.1"
kotlinInject = "0.7.2"
androidx-annotation = "1.9.1"
coil = "3.1.0"
-datastorePreferences = "1.1.2"
+datastorePreferences = "1.1.4"
multiplatformSettings = "1.3.0"
filekitCompose = "0.8.8"
krop = "0.2.0-alpha01"
#android
-accompanistSystemuicontroller = "0.34.0"
-activityCompose = "1.10.0"
-androidImageCropper = "4.5.0"
+accompanistSystemuicontroller = "0.36.0"
+activityCompose = "1.10.1"
+androidImageCropper = "4.6.0"
browser = "1.8.0"
-coreKtx = "1.15.0"
+coreKtx = "1.16.0"
glance = "1.1.1"
material = "1.12.0"
-media3 = "1.5.1"
-livedata = "1.7.7"
+media3 = "1.6.0"
okio = "3.10.2"
workRuntimeKtx = "2.10.0"
+#desktop
+appdirs = "1.2.0"
+slf4jSimple = "2.0.17"
+vlcj = "4.10.1"
+jna = "5.17.0"
+
[libraries]
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
+kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
compose-ui-graphics = { module = "org.jetbrains.compose.ui:ui-graphics", version.ref = "composeMultiplatform" }
-androidx-lifecycle-compiler = { module = "androidx.lifecycle:lifecycle-compiler", version.ref = "lifecycle" }
androidx-lifecycle-runtime-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycleMultiplatform" }
androidx-lifecycle-viewmodel = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel", version.ref = "lifecycleMultiplatform" }
androidx-lifecycle-viewmodel-savedstate = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycleMultiplatform" }
@@ -81,6 +85,9 @@ coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" }
ktorfit = { module = "de.jensklingenberg.ktorfit:ktorfit-lib", version.ref = "ktorfit" }
ktorfit-call = { module = "de.jensklingenberg.ktorfit:ktorfit-converters-call", version.ref = "ktorfit" }
+jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
+jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" }
+
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" }
android-image-cropper = { module = "com.vanniktech:android-image-cropper", version.ref = "androidImageCropper" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
@@ -91,7 +98,6 @@ androidx-glance-material3 = { module = "androidx.glance:glance-material3", versi
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
androidx-media3-exoplayer-dash = { module = "androidx.media3:media3-exoplayer-dash", version.ref = "media3" }
androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3" }
-androidx-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "livedata" }
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" }
material = { module = "com.google.android.material:material", version.ref = "material" }
ksoup = { module = "com.fleeksoft.ksoup:ksoup", version.ref = "ksoup" }
@@ -102,6 +108,9 @@ multiplatform-settings-datastore = { module = "com.russhwolf:multiplatform-setti
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
filekit-compose = { module = "io.github.vinceglb:filekit-compose", version.ref = "filekitCompose" }
krop = { module = "com.attafitamim.krop:ui", version.ref = "krop" }
+appdirs = { module = "ca.gosyer:kotlin-multiplatform-appdirs", version.ref = "appdirs" }
+slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4jSimple" }
+vlcj = { module = "uk.co.caprica:vlcj", version.ref = "vlcj" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index c57be855..33918ab3 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Mon Dec 11 12:09:16 CET 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj
index 8ccc32b9..9921c142 100644
--- a/iosApp/iosApp.xcodeproj/project.pbxproj
+++ b/iosApp/iosApp.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 56;
+ objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
@@ -99,8 +99,9 @@
A93A952F29CC810C00F8E227 /* Project object */ = {
isa = PBXProject;
attributes = {
+ BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1420;
- LastUpgradeCheck = 1420;
+ LastUpgradeCheck = 1620;
TargetAttributes = {
A93A953629CC810C00F8E227 = {
CreatedOnToolsVersion = 14.2;
@@ -108,7 +109,6 @@
};
};
buildConfigurationList = A93A953229CC810C00F8E227 /* Build configuration list for PBXProject "iosApp" */;
- compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -116,6 +116,7 @@
Base,
);
mainGroup = A93A952E29CC810C00F8E227;
+ preferredProjectObjectVersion = 77;
productRefGroup = A93A953829CC810C00F8E227 /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -205,6 +206,7 @@
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
@@ -265,6 +267,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -291,17 +294,20 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
+ DEVELOPMENT_TEAM = 4FA7X6639Y;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = iosApp/Info.plist;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UIRequiresFullScreen = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0;
+ MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.daniebeler.pfpixelix.iosApp;
PRODUCT_NAME = Pixelix;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -318,17 +324,20 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
+ DEVELOPMENT_TEAM = 4FA7X6639Y;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = iosApp/Info.plist;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UIRequiresFullScreen = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0;
+ MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.daniebeler.pfpixelix.iosApp;
PRODUCT_NAME = Pixelix;
SWIFT_EMIT_LOC_STRINGS = YES;
diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/AppIcon@2x 1.png
similarity index 100%
rename from iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png
rename to iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/AppIcon@2x 1.png
diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
index c77c4af5..61e90c68 100644
--- a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -1,50 +1,104 @@
{
"images" : [
{
- "idiom" : "iphone",
+ "idiom" : "universal",
+ "platform" : "ios",
"scale" : "2x",
"size" : "20x20"
},
{
- "idiom" : "iphone",
+ "idiom" : "universal",
+ "platform" : "ios",
"scale" : "3x",
"size" : "20x20"
},
{
- "idiom" : "iphone",
+ "idiom" : "universal",
+ "platform" : "ios",
"scale" : "2x",
"size" : "29x29"
},
{
- "idiom" : "iphone",
+ "idiom" : "universal",
+ "platform" : "ios",
"scale" : "3x",
"size" : "29x29"
},
{
- "idiom" : "iphone",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "38x38"
+ },
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "3x",
+ "size" : "38x38"
+ },
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
"scale" : "2x",
"size" : "40x40"
},
{
- "idiom" : "iphone",
+ "idiom" : "universal",
+ "platform" : "ios",
"scale" : "3x",
"size" : "40x40"
},
{
- "filename" : "AppIcon@2x.png",
- "idiom" : "iphone",
+ "filename" : "AppIcon@2x 1.png",
+ "idiom" : "universal",
+ "platform" : "ios",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "AppIcon@3x.png",
- "idiom" : "iphone",
+ "idiom" : "universal",
+ "platform" : "ios",
"scale" : "3x",
"size" : "60x60"
},
{
- "idiom" : "ios-marketing",
- "scale" : "1x",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "64x64"
+ },
+ {
+ "filename" : "pixelix_logo_192.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "3x",
+ "size" : "64x64"
+ },
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "68x68"
+ },
+ {
+ "filename" : "pixelix_logo_152.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "pixelix_logo_167.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "filename" : "pixelix_logo_black.png",
+ "idiom" : "universal",
+ "platform" : "ios",
"size" : "1024x1024"
}
],
diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/pixelix_logo_152.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/pixelix_logo_152.png
new file mode 100644
index 00000000..94439779
Binary files /dev/null and b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/pixelix_logo_152.png differ
diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/pixelix_logo_167.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/pixelix_logo_167.png
new file mode 100644
index 00000000..06bc3634
Binary files /dev/null and b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/pixelix_logo_167.png differ
diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/pixelix_logo_192.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/pixelix_logo_192.png
new file mode 100644
index 00000000..2fc41a62
Binary files /dev/null and b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/pixelix_logo_192.png differ
diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/pixelix_logo_black.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/pixelix_logo_black.png
new file mode 100644
index 00000000..4661593c
Binary files /dev/null and b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/pixelix_logo_black.png differ
diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist
index a37c4ab0..c4f50d8f 100644
--- a/iosApp/iosApp/Info.plist
+++ b/iosApp/iosApp/Info.plist
@@ -5,13 +5,13 @@
CADisableMinimumFrameDurationOnPhone
CFBundleURLTypes
-
-
- CFBundleURLSchemes
-
- pixelix-android-auth
-
-
-
+
+
+ CFBundleURLSchemes
+
+ pixelix-android-auth
+
+
+
diff --git a/metadata/ar-SA/full_description.txt b/metadata/ar-SA/full_description.txt
index 18d07801..697f5096 100644
--- a/metadata/ar-SA/full_description.txt
+++ b/metadata/ar-SA/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
+يوفر بيكسيليكس واجهة سلسة وبديهية للتفاعل مع بيكسلفد، الشبكة الإجتماعية الاتحادية لتشارك الصور. 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. سواء كنت مستخدما بيكسلفيد قديم أو فقط مبتدئ، يوفر بيكسيلكس طريقة مبسطة لمشاركة واكتشاف المحتوى البصري.
\ No newline at end of file
diff --git a/metadata/ar-SA/short_description.txt b/metadata/ar-SA/short_description.txt
index 7b39709e..f675510e 100644
--- a/metadata/ar-SA/short_description.txt
+++ b/metadata/ar-SA/short_description.txt
@@ -1 +1 @@
-Pixelix: a user-friendly Pixelfed client for photo uploads, browsing, & sharing.
\ No newline at end of file
+Pixelix: عميل Pixelfed سهل الاستخدام لتحميل الصور والتصفح والمشاركة.
\ No newline at end of file
diff --git a/metadata/da-DK/full_description.txt b/metadata/da-DK/full_description.txt
index 18d07801..d0cb6546 100644
--- a/metadata/da-DK/full_description.txt
+++ b/metadata/da-DK/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 giver en smidig og intuitiv grænseflade til at interagere med Pixelfed, det fødererede sociale netværk til billeddeling. Pixelix er designet med brugeroplevelsen i tankerne og gør det nemt at oprette forbindelse til din Pixelfed-instans, uploade fotos direkte fra din enhed og gennemse dit feed med lethed. Uanset om du er en erfaren Pixelfed-bruger eller lige er kommet i gang, tilbyder Pixelix en strømlinet måde at dele og opdage visuelt indhold på.
\ No newline at end of file
diff --git a/metadata/da-DK/short_description.txt b/metadata/da-DK/short_description.txt
index 7b39709e..cb0e997e 100644
--- a/metadata/da-DK/short_description.txt
+++ b/metadata/da-DK/short_description.txt
@@ -1 +1 @@
-Pixelix: a user-friendly Pixelfed client for photo uploads, browsing, & sharing.
\ No newline at end of file
+Pixelix: en brugervenlig Billedklient til fotooverførsler, gennemsyn og deling.
\ No newline at end of file
diff --git a/metadata/de-DE/short_description.txt b/metadata/de-DE/short_description.txt
index 10dd00d5..bb19032e 100644
--- a/metadata/de-DE/short_description.txt
+++ b/metadata/de-DE/short_description.txt
@@ -1 +1 @@
-Pixelix: Ein benutzerfreundlicher Pixelfed-Client zum Hochladen, Durchstöbern und Teilen von Fotos.
\ No newline at end of file
+Pixelix: Ein benutzerfreundlicher Pixelfed Client.
\ No newline at end of file
diff --git a/metadata/de/changelogs/25.txt b/metadata/de/changelogs/25.txt
index a1471545..1ff01a1d 100644
--- a/metadata/de/changelogs/25.txt
+++ b/metadata/de/changelogs/25.txt
@@ -6,5 +6,4 @@ Fehlerbehebungen:
- Ein Problem wurde behoben, bei dem keine Beiträge angezeigt wurden, wenn private Profile betrachtet wurden.
- Liken und Boost von rebloggeden Beiträgen:
- Ein Fehler wurde behoben, durch den das Liken und Boosten von erneut geposteten Beiträgen nicht funktionierte.
- - Toolbar-Insets im Einstellungsbildschirm:
- - Falsche Fenster-Insets für die Toolbar im Einstellungsbildschirm wurden korrigiert.
\ No newline at end of file
+ - Toolbar-Insets im Einstellungsbildschirm fixes
diff --git a/metadata/de/changelogs/31.txt b/metadata/de/changelogs/31.txt
new file mode 100644
index 00000000..994168e2
--- /dev/null
+++ b/metadata/de/changelogs/31.txt
@@ -0,0 +1,8 @@
+ - Sammlungs-Bug behoben
+ - Mehrere Bilder behalten dasselbe Seitenverhältnis
+ - Einstellung zum automatischen Anzeigen sensibler Inhalte
+ - Doppelklick auf das Suchsymbol fokussiert die Suchleiste
+ - Autovervollständigung für Server-URL am Login-Bildschirm
+ - Auffälligere Boost-Schaltfläche
+ - Verbesserte Handhabung von Bild-Popups
+ - Viele Fehlerbehebungen
\ No newline at end of file
diff --git a/metadata/el-GR/full_description.txt b/metadata/el-GR/full_description.txt
index 18d07801..d4853493 100644
--- a/metadata/el-GR/full_description.txt
+++ b/metadata/el-GR/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 παρέχει μια ομαλή και διαισθητική διεπαφή για την αλληλεπίδραση με το Pixelfed, το ομοσπονδιακό κοινωνικό δίκτυο διαμοιρασμού εικόνων. Σχεδιασμένο με γνώμονα την εμπειρία χρήστη, το Pixelix καθιστά εύκολη τη σύνδεση με την Pixelfed οντότητά σας, να ανεβάστε φωτογραφίες απευθείας από τη συσκευή σας και να περιηγηθείτε στην ροή σας με ευκολία. Είτε είστε έμπειρος χρήστης Pixelfed είτε μόλις ξεκινήσατε, το Pixelix προσφέρει έναν βελτιωμένο τρόπο να μοιραστείτε και να ανακαλύψετε οπτικό περιεχόμενο.
\ No newline at end of file
diff --git a/metadata/el-GR/short_description.txt b/metadata/el-GR/short_description.txt
index 7b39709e..8fdcbcac 100644
--- a/metadata/el-GR/short_description.txt
+++ b/metadata/el-GR/short_description.txt
@@ -1 +1 @@
-Pixelix: a user-friendly Pixelfed client for photo uploads, browsing, & sharing.
\ No newline at end of file
+Pixelix: ένας φιλικός προς το χρήστη πελάτης Pixelfed για μεταφόρτωση φωτογραφιών, περιήγηση & κοινή χρήση.
\ No newline at end of file
diff --git a/metadata/en-US/changelogs/31.txt b/metadata/en-US/changelogs/31.txt
new file mode 100644
index 00000000..3a1c1fa0
--- /dev/null
+++ b/metadata/en-US/changelogs/31.txt
@@ -0,0 +1,8 @@
+ - collection bug fix
+ - multiple images keep same aspect ratio
+ - setting to automatically show sensitive content
+ - double click on search icon focuses search bar
+ - autocomplete for server url at login screen
+ - more visible boosted button
+ - improved image popup handling
+ - many bug fixes
diff --git a/metadata/es-ES/full_description.txt b/metadata/es-ES/full_description.txt
index 18d07801..a8f85ced 100644
--- a/metadata/es-ES/full_description.txt
+++ b/metadata/es-ES/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 proporciona una interfaz fluida e intuitiva para interactuar con Pixelfed, la red social de intercambio de imágenes federada. Diseñado pensando en la experiencia del usuario, Pixelix simplifica la conexión a tu instancia de Pixelfed, la carga de fotos directamente desde tu dispositivo y la navegación por tu feed con facilidad. Ya seas un usuario experimentado de Pixelfed o solo estés empezando, Pixelix ofrece una forma simplificada de compartir y descubrir contenido visual.
\ No newline at end of file
diff --git a/metadata/es-ES/short_description.txt b/metadata/es-ES/short_description.txt
index 7b39709e..b2592bae 100644
--- a/metadata/es-ES/short_description.txt
+++ b/metadata/es-ES/short_description.txt
@@ -1 +1 @@
-Pixelix: a user-friendly Pixelfed client for photo uploads, browsing, & sharing.
\ No newline at end of file
+Pixelix: un cliente de Pixelfed fácil de usar para subir fotos, navegar y compartir.
\ No newline at end of file
diff --git a/metadata/it-IT/full_description.txt b/metadata/it-IT/full_description.txt
index 18d07801..c469a09c 100644
--- a/metadata/it-IT/full_description.txt
+++ b/metadata/it-IT/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 un'interfaccia intuitiva e scorrevole per interagire con Pixelfed, il social network federato di condivisione foto. Progettato con in mente l'esperienza utente, Pixelix rende semplice la connessione alla tua istanza Pixelfed, caricare direttamente le foto dal tuo dispositivo e navigare con facilità nel tuo feed. Che tu sia un utente Pixelfed con esperienza o che tua abbia appena iniziato, Pixelix offre un modo semplice per scoprire e condividere contenuti visivi.
\ No newline at end of file
diff --git a/metadata/it-IT/short_description.txt b/metadata/it-IT/short_description.txt
index 7b39709e..4961f5cb 100644
--- a/metadata/it-IT/short_description.txt
+++ b/metadata/it-IT/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 di Pixelfed facile da usare per navigare, caricare e condividere foto.
\ No newline at end of file
diff --git a/metadata/pl-PL/full_description.txt b/metadata/pl-PL/full_description.txt
index 925a60ad..8d86c73e 100644
--- a/metadata/pl-PL/full_description.txt
+++ b/metadata/pl-PL/full_description.txt
@@ -1 +1 @@
-Pixelix zapewnia płynny i intuicyjny interfejs do interakcji z Pixelfed, Federacyjną siecią społecznościową do udostępniania obrazów. Zaprojektowany z myślą o doświadczeniu użytkownika, Pixelux ułatwia łączenie się z Pixelfed instance, przesyłaj zdjęcia bezpośrednio z urządzenia i z łatwością przeglądaj swój kanał. Niezależnie od tego, czy jesteś doświadczonym użytkownikiem Pixelfed, czy dopiero zaczynasz, Pixelix oferuje usprawniony sposób udostępniania i odkrywania treści wizualnych.
\ No newline at end of file
+Pixelix zapewnia płynny i intuicyjny interfejs do interakcji z Pixelfed, sfederowaną siecią społecznościową do udostępniania obrazów. Zaprojektowany z myślą o doświadczeniu użytkownika, Pixelix ułatwia łączenie się z twoim serwerem Pixelfed, przesyłanie zdjęć bezpośrednio z urządzenia i przeglądanie osi czasu. Niezależnie od tego, czy jesteś doświadczonym użytkownikiem Pixelfed, czy dopiero zaczynasz, Pixelix oferuje prosty sposób udostępniania i odkrywania treści wizualnych.
\ No newline at end of file
diff --git a/metadata/pl-PL/short_description.txt b/metadata/pl-PL/short_description.txt
index 020cdc84..1771272c 100644
--- a/metadata/pl-PL/short_description.txt
+++ b/metadata/pl-PL/short_description.txt
@@ -1 +1 @@
-Pixelix: przyjazny dla użytkownika Klient Pixelfed do przesyłania zdjęć, przeglądania i udostępniania.
\ No newline at end of file
+Pixelix: przyjazny klient Pixelfed do udostępniania i przeglądania zdjęć.
\ No newline at end of file
diff --git a/metadata/pt-PT/full_description.txt b/metadata/pt-PT/full_description.txt
index 18d07801..40c809cf 100644
--- a/metadata/pt-PT/full_description.txt
+++ b/metadata/pt-PT/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
+O Pixelix fornece uma interface suave e intuitiva para interagir com o Pixelfed, a rede social federada de partilha de imagens. Concebido a pensar na experiência do utilizador, o Pixelix facilita a ligação à sua instância Pixelfed, o o envio de fotografias diretamente do seu dispositivo e a navegação pela sua cronologia com facilidade. Quer seja um utilizador experiente do Pixelfed ou esteja apenas a começar, o Pixelix oferece uma forma simplificada de partilhar e descobrir conteúdos visuais.
\ No newline at end of file
diff --git a/metadata/pt-PT/short_description.txt b/metadata/pt-PT/short_description.txt
index 7b39709e..d5a6a0a2 100644
--- a/metadata/pt-PT/short_description.txt
+++ b/metadata/pt-PT/short_description.txt
@@ -1 +1 @@
-Pixelix: a user-friendly Pixelfed client for photo uploads, browsing, & sharing.
\ No newline at end of file
+Pixelix: um cliente Pixelfed de fácil utilização para carregar, navegar e partilhar fotografias.
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 8f3d6c23..53ccade0 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -3,7 +3,6 @@ pluginManagement {
google()
mavenCentral()
gradlePluginPortal()
- maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
dependencyResolutionManagement {
@@ -11,7 +10,6 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
- maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}