From 3961371705d53494a625924794bb2dcf474f3709 Mon Sep 17 00:00:00 2001 From: Konstantin Tskhovrebov Date: Fri, 7 Mar 2025 01:42:55 +0100 Subject: [PATCH 1/2] Refactor Platform service and delete LocalContext --- .../com/daniebeler/pfpixelix/AppActivity.kt | 8 +- .../com/daniebeler/pfpixelix/MyApplication.kt | 11 +- .../domain/service/file/AndroidFileService.kt | 228 ++++++++++++++ .../service/icon/AndroidAppIconManager.kt | 69 +++++ .../service/platform/Platform.android.kt | 280 ------------------ .../pfpixelix/ui/theme/Theme.android.kt | 49 +-- .../pfpixelix/utils/KmpPlatform.android.kt | 7 - .../pfpixelix/utils/VideoPlayer.android.kt | 1 + .../daniebeler/pfpixelix/di/AppComponent.kt | 22 +- .../service/editor/PostEditorService.kt | 6 +- .../domain/service/file/FileService.kt | 23 ++ .../domain/service/icon/AppIconService.kt | 20 +- .../domain/service/platform/Platform.kt | 22 -- .../collection/CollectionComposable.kt | 7 +- .../collection/CollectionViewModel.kt | 5 +- .../direct_messages/chat/ChatComposable.kt | 2 - .../edit_post/EditPostComposable.kt | 14 - .../edit_profile/EditProfileComposable.kt | 18 -- .../composables/explore/ExploreComposable.kt | 4 - .../TrendingAccountElement.kt | 4 +- .../TrendingAccountElementViewModel.kt | 5 +- .../composables/newpost/NewPostComposable.kt | 9 +- .../composables/newpost/NewPostViewModel.kt | 15 +- .../notifications/NotificationsComposable.kt | 3 - .../composables/post/CommentsBottomSheet.kt | 6 +- .../ui/composables/post/PostComposable.kt | 14 +- .../ui/composables/post/PostViewModel.kt | 13 +- .../ui/composables/post/ShareBottomSheet.kt | 9 +- .../ui/composables/post/VideoAttachment.kt | 7 +- .../other_profile/OtherProfileComposable.kt | 9 +- .../other_profile/OtherProfileViewModel.kt | 3 +- .../own_profile/OwnProfileComposable.kt | 1 - .../server_stats/ServerStatsComposable.kt | 8 +- .../server_stats/ServerStatsViewModel.kt | 5 +- .../about_instance/AboutInstanceComposable.kt | 8 +- .../about_instance/AboutInstanceViewModel.kt | 5 +- .../about_pixelix/AboutPixelixComposable.kt | 3 - .../icon_selection/IconSelectionComposable.kt | 6 - .../prefs/PreferencesComposable.kt | 2 - .../prefs/prefs/ClearCacheViewModel.kt | 8 +- .../preferences/prefs/prefs/LogoutPref.kt | 2 - .../daniebeler/pfpixelix/ui/theme/Theme.kt | 14 +- .../daniebeler/pfpixelix/utils/KmpPlatform.kt | 5 - .../daniebeler/pfpixelix/AppViewController.kt | 22 +- .../domain/service/file/IosFileService.kt | 173 +++++++++++ .../domain/service/icon/IosAppIconManager.kt | 48 +++ .../domain/service/platform/Platform.ios.kt | 202 ------------- .../pfpixelix/ui/theme/Theme.ios.kt | 18 +- .../pfpixelix/utils/KmpPlatform.ios.kt | 17 -- 49 files changed, 678 insertions(+), 762 deletions(-) create mode 100644 app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/AndroidFileService.kt create mode 100644 app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/AndroidAppIconManager.kt create mode 100644 app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/FileService.kt create mode 100644 app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/IosFileService.kt create mode 100644 app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/icon/IosAppIconManager.kt diff --git a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/AppActivity.kt b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/AppActivity.kt index b020c60b..5ab0ed26 100644 --- a/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/AppActivity.kt +++ b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/AppActivity.kt @@ -12,12 +12,10 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalView import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogWindowProvider -import com.daniebeler.pfpixelix.utils.LocalKmpContext import java.io.File import java.io.FileOutputStream import java.io.InputStream @@ -29,11 +27,7 @@ class AppActivity : ComponentActivity() { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { - CompositionLocalProvider( - LocalKmpContext provides this - ) { - App(MyApplication.appComponent) { finish() } - } + App(MyApplication.appComponent) { finish() } } if (savedInstanceState == null) { handleNewIntent(intent) 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..10a3bc8d --- /dev/null +++ b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/AndroidFileService.kt @@ -0,0 +1,228 @@ +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 + } + + 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() + } + } + } + } + } + + 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..dc7d56b7 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) { @@ -111,237 +64,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/ui/theme/Theme.android.kt b/app/src/androidMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.android.kt index 4de94b36..7a0e61ba 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 @@ -1,41 +1,47 @@ package com.daniebeler.pfpixelix.ui.theme -import android.app.Activity 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 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 +): 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 + } + } } } @@ -47,11 +53,8 @@ actual fun applySystemNightMode(mode: Int) { else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM } ) -} - -@Composable -actual fun ChangeSystemBarColors(mode: Int) { - (LocalKmpContext.current as ComponentActivity).enableEdgeToEdge( + val activity = MyApplication.currentActivity?.get() + activity?.enableEdgeToEdge( when (mode) { LIGHT -> SystemBarStyle.light( Color.Transparent.toArgb(), Color.Transparent.toArgb() @@ -65,7 +68,5 @@ actual fun ChangeSystemBarColors(mode: Int) { Color.Transparent.toArgb() ) } - ) - } \ 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..6632494b 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 @@ -13,6 +13,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 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..fdc8a53e 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/di/AppComponent.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/di/AppComponent.kt @@ -14,6 +14,8 @@ 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.session.AuthService import com.daniebeler.pfpixelix.domain.service.session.Session @@ -24,8 +26,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,7 +54,9 @@ 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 @@ -116,7 +118,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 +127,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 +138,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 +162,7 @@ abstract class AppComponent( .diskCache( DiskCache.Builder() .maxSizeBytes(50L * 1024L * 1024L) - .directory(context.imageCacheDir) + .directory(fileService.imageCacheDir) .build() ) .build() @@ -169,4 +171,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/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/platform/Platform.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/domain/service/platform/Platform.kt index 5931b79e..ae75c277 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,15 @@ 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 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/ui/composables/collection/CollectionComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/collection/CollectionComposable.kt index dc244a2c..57e35ebc 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) } @@ -209,9 +206,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) } }) 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..b021945e 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,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.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.utils.Resource import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import me.tatarka.inject.annotations.Inject @@ -220,7 +219,7 @@ class CollectionViewModel @Inject constructor( 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/direct_messages/chat/ChatComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatComposable.kt index be0bb082..10eee2d9 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,7 +53,6 @@ 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.utils.imeAwareInsets import org.jetbrains.compose.resources.painterResource @@ -74,7 +73,6 @@ fun ChatComposable( viewModel: ChatViewModel = injectViewModel(key = "chat$accountId") { chatViewModel } ) { val lazyListState = rememberLazyListState() - val context = LocalKmpContext.current LaunchedEffect(Unit) { viewModel.getChat(accountId) } 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..331d3cd8 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,16 @@ 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.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 +106,6 @@ fun EditPostComposable( val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.current - val context = LocalKmpContext.current - LaunchedEffect(Unit) { viewModel.loadData(postId) } 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..0b8160dc 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,42 +46,29 @@ 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 import androidx.compose.ui.unit.dp -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.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 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..a6f43ac7 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 @@ -66,8 +66,6 @@ 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.utils.imeAwareInsets import kotlinx.coroutines.launch @@ -88,8 +86,6 @@ fun ExploreComposable( initialPage: Int = 0, viewModel: ExploreViewModel = injectViewModel(key = "search-viewmodel-key") { exploreViewModel } ) { - val context: KmpContext = LocalKmpContext.current - val textFieldState = rememberTextFieldState() var expanded by rememberSaveable { mutableStateOf(false) } 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..f5cb62d1 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,7 +20,6 @@ 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 @Composable @@ -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) } @@ -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/newpost/NewPostComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/newpost/NewPostComposable.kt index 7c91aa00..a95856ec 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 @@ -75,7 +75,6 @@ import com.daniebeler.pfpixelix.ui.composables.states.ErrorComposable 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 +108,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 +115,7 @@ fun NewPostComposable( LaunchedEffect(uris) { uris?.let { - uris.forEach { - viewModel.addImage(uri = it, context = context) - } + uris.forEach { viewModel.addImage(uri = it) } } } @@ -151,7 +146,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( 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..affec643 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,14 +7,13 @@ 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.utils.KmpUri import com.daniebeler.pfpixelix.utils.Navigate import kotlinx.coroutines.Dispatchers @@ -28,7 +27,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, @@ -101,8 +100,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 @@ -145,7 +144,7 @@ class NewPostViewModel @Inject constructor( return } images += ImageItem(uri, fileType, null, "", true) - uploadImage(context, uri, "") + uploadImage(uri, "") } fun deleteMedia(index: Int) { @@ -168,7 +167,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 -> { 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..fd8950ce 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,7 +65,6 @@ 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.utils.TimeAgo import org.jetbrains.compose.resources.stringResource @@ -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) { 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 730da54f..9636b1f8 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 @@ -47,7 +47,6 @@ 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 @@ -76,7 +75,6 @@ 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.injectViewModel @@ -85,11 +83,8 @@ 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.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.TimeAgo -import com.daniebeler.pfpixelix.utils.toKmpUri import com.daniebeler.pfpixelix.utils.zoomable.rememberZoomState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -137,9 +132,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 { @@ -411,7 +403,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() @@ -576,7 +568,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 ) } @@ -625,7 +617,6 @@ fun PostComposable( } else if (showBottomSheet == 2) { if (viewModel.myAccountId != null && post.account.id == viewModel.myAccountId) { ShareBottomSheet( - context, post.url, true, viewModel, @@ -635,7 +626,6 @@ fun PostComposable( ) } else { ShareBottomSheet( - context, post.url, false, viewModel, 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 7f24936e..7ac7ed9a 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,18 +5,18 @@ 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.model.LikedBy import com.daniebeler.pfpixelix.domain.model.Post 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 @@ -32,7 +32,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) @@ -367,12 +368,12 @@ class PostViewModel @Inject constructor( } - 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/ShareBottomSheet.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/post/ShareBottomSheet.kt index 085a9294..cc5922b1 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,7 +25,6 @@ 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 org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.stringResource @@ -51,7 +50,6 @@ import pixelix.app.generated.resources.visibility_x @Composable fun ShareBottomSheet( - context: KmpContext, url: String, minePost: Boolean, viewModel: PostViewModel, @@ -102,7 +100,7 @@ fun ShareBottomSheet( ButtonRowElement(icon = Res.drawable.document_text_outline, text = stringResource( Res.string.license, mediaAttachment.license.title ), onClick = { - viewModel.openUrl(mediaAttachment.license.url, context) + viewModel.openUrl(mediaAttachment.license.url) }) } @@ -111,7 +109,7 @@ fun ShareBottomSheet( ButtonRowElement(icon = Res.drawable.open_outline, text = stringResource( Res.string.open_in_browser ), onClick = { - viewModel.openUrl(url, context) + viewModel.openUrl(url) }) ButtonRowElement(icon = Res.drawable.share_social_outline, @@ -127,8 +125,7 @@ fun ShareBottomSheet( viewModel.saveImage( post.account.username, - viewModel.post!!.mediaAttachments[currentMediaAttachmentNumber].url!!, - context + viewModel.post!!.mediaAttachments[currentMediaAttachmentNumber].url!! ) }) } 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..8592f49a 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,5 @@ 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.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.aspectRatio @@ -32,8 +29,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,8 +39,8 @@ 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) } 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 571dd3c2..b3f1e089 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 @@ -44,7 +44,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi @@ -68,7 +67,6 @@ 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 org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @@ -134,7 +132,6 @@ fun OtherProfileComposable( var showBlockAlert by remember { mutableStateOf(false) } var showUnBlockAlert by remember { mutableStateOf(false) } - val context = LocalKmpContext.current LaunchedEffect(userId) { if (userId != "") { viewModel.loadData(userId, false) @@ -203,7 +200,7 @@ fun OtherProfileComposable( relationship = viewModel.relationshipState.accountRelationship, navController, openUrl = { url -> - viewModel.openUrl(url, context) + viewModel.openUrl(url) }) } @@ -290,7 +287,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)) @@ -389,7 +386,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 1c03cc35..abb220b4 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 @@ -22,7 +22,6 @@ 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 kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -362,7 +361,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/OwnProfileComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/profile/own_profile/OwnProfileComposable.kt index ad67af7d..44bec909 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 @@ -52,7 +52,6 @@ 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 org.jetbrains.compose.resources.stringResource import pixelix.app.generated.resources.Res 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/settings/about_instance/AboutInstanceComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/about_instance/AboutInstanceComposable.kt index d4cdd354..1dade6b4 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,7 +36,6 @@ 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.utils.StringFormat import org.jetbrains.compose.resources.painterResource @@ -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) @@ -179,7 +175,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 +198,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..91265da5 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,7 +42,6 @@ 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 org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @@ -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 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/preferences/prefs/PreferencesComposable.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/settings/preferences/prefs/PreferencesComposable.kt index 89cc04b3..02ce4c11 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,7 +21,6 @@ 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 @@ -42,7 +41,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 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/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/theme/Theme.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/theme/Theme.kt index f61bf532..d32c731d 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 @@ -9,14 +9,11 @@ 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.graphics.Color import com.daniebeler.pfpixelix.di.LocalAppComponent 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 { @@ -141,12 +138,7 @@ fun PixelixTheme( nightModeValue = if (isSystemInDarkTheme()) DARK else LIGHT } - ChangeSystemBarColors(nightModeValue) - - val context = LocalKmpContext.current - val colorScheme = remember(nightModeValue, dynamicColor, lightScheme, darkScheme) { - context.generateColorScheme(nightModeValue, dynamicColor, lightScheme, darkScheme) - } + val colorScheme = generateColorScheme(nightModeValue, dynamicColor, lightScheme, darkScheme) MaterialTheme( colorScheme = colorScheme, @@ -158,9 +150,7 @@ fun PixelixTheme( expect fun applySystemNightMode(mode: Int) @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/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/iosMain/kotlin/com/daniebeler/pfpixelix/AppViewController.kt b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/AppViewController.kt index 2e99e206..0111db7d 100644 --- a/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/AppViewController.kt +++ b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/AppViewController.kt @@ -2,13 +2,13 @@ package com.daniebeler.pfpixelix import androidx.compose.foundation.ComposeFoundationFlags import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.runtime.CompositionLocalProvider 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 @@ -22,11 +22,13 @@ fun AppViewController(urlCallback: IosUrlCallback): UIViewController { 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() @@ -40,11 +42,7 @@ fun AppViewController(urlCallback: IosUrlCallback): UIViewController { val finishApp = {} viewController = ComposeUIViewController { - CompositionLocalProvider( - LocalKmpContext provides context - ) { - App(appComponent, finishApp) - } + 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..69b63fd9 --- /dev/null +++ b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/IosFileService.kt @@ -0,0 +1,173 @@ +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() + 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) { + 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..87b621e7 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 @@ -2,77 +2,17 @@ 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 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 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.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.UIKit.UIActivityViewController import platform.UIKit.UIApplication -import platform.UIKit.alternateIconName -import platform.UIKit.setAlternateIconName -import platform.posix.memcpy @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)) } @@ -87,146 +27,4 @@ actual class Platform actual constructor( } 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() - } - 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 - ) - 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) { - 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 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) - } - } } 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..1cb96977 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) {} @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() - From 4664ee4b3acb5ddc70c27ac43d8c23b3cf0a6b05 Mon Sep 17 00:00:00 2001 From: Konstantin Tskhovrebov Date: Fri, 7 Mar 2025 02:15:25 +0100 Subject: [PATCH 2/2] Handle few build warnings --- app/build.gradle.kts | 4 + .../daniebeler/pfpixelix/di/AppComponent.kt | 1 + .../pfpixelix/ui/composables/CustomHashtag.kt | 17 ++-- .../direct_messages/chat/ChatViewModel.kt | 88 +++++++++---------- .../conversations/ConversationsViewModel.kt | 9 +- .../edit_post/EditPostViewModel.kt | 26 +++--- .../ui/composables/newpost/NewPostPref.kt | 4 +- .../composables/newpost/NewPostViewModel.kt | 22 +++-- .../FollowedHashtagsViewModel.kt | 6 +- .../TextFieldLocationsViewModel.kt | 8 +- .../TextFieldMentionsViewModel.kt | 8 +- .../domain/service/file/IosFileService.kt | 2 + 12 files changed, 84 insertions(+), 111 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ceca0bdb..c28005de 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -128,6 +128,10 @@ kotlin { implementation(libs.ktor.client.darwin) } } + + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } } android { 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 fdc8a53e..b5e4ed38 100644 --- a/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/di/AppComponent.kt +++ b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/di/AppComponent.kt @@ -62,6 +62,7 @@ abstract class AppComponent( abstract val systemFileShare: SystemFileShare abstract val authService: AuthService abstract val widgetService: WidgetService + abstract val preferences: UserPreferences @get:Provides 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..43748003 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 @@ -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/direct_messages/chat/ChatViewModel.kt b/app/src/commonMain/kotlin/com/daniebeler/pfpixelix/ui/composables/direct_messages/chat/ChatViewModel.kt index 1a819ff1..d7c60ba1 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 @@ -45,40 +45,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 +95,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,13 +122,11 @@ 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) } } 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/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/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 affec643..91e61400 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 @@ -56,13 +56,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 -> { @@ -140,7 +134,10 @@ 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) @@ -176,7 +173,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( @@ -283,9 +283,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) - } + Navigate.navigateAndDeleteBackStack("own_profile_screen", navController) CreatePostState(post = result.data, isLoading = true) } 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/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/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/IosFileService.kt b/app/src/iosMain/kotlin/com/daniebeler/pfpixelix/domain/service/file/IosFileService.kt index 69b63fd9..6b7fda13 100644 --- 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 @@ -103,6 +103,7 @@ private class IosFile( 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, @@ -125,6 +126,7 @@ private class IosFile( } 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(