diff --git a/.fleet/receipt.json b/.fleet/receipt.json new file mode 100644 index 0000000..11a2883 --- /dev/null +++ b/.fleet/receipt.json @@ -0,0 +1,19 @@ +// Project generated by Kotlin Multiplatform Wizard +{ + "spec": { + "template_id": "kmt", + "targets": { + "android": { + "ui": [ + "compose" + ] + }, + "ios": { + "ui": [ + "compose" + ] + } + } + }, + "timestamp": "2024-04-15T08:24:49.936562548Z" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 98816d3..33e4c75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,17 @@ *.iml .gradle -/local.properties -/.idea +**/build/ +xcuserdata +!src/**/build/ +local.properties +.idea .DS_Store -build/ -/captures +captures .externalNativeBuild .cxx -iosApp/iosApp.xcworkspace/* -iosApp/iosApp.xcodeproj/* -!iosApp/iosApp.xcodeproj/project.pbxproj +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +**/xcshareddata/WorkspaceSettings.xcsettings diff --git a/README.md b/README.md index 158cc2d..1ba257b 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,14 @@ -# DecomposeNavigation -Compose Multiplatform Bottom Navigation with Decompose
-libraries -#Decompose
-What is Decompose?¶
-Decompose is a Kotlin Multiplatform library for breaking down your code into lifecycle-aware business logic components (aka BLoC), with routing functionality and pluggable UI (Jetpack Compose, Android Views, SwiftUI, JS React, etc.).
-https://arkivanov.github.io/Decompose/getting-started/quick-start/
-https://github.com/arkivanov/Decompose
-Also includes Depedency injection with Koin and examples of how to access Device Specific APIs +This is a Kotlin Multiplatform project targeting Android, iOS. - - - - - - - - - - - - -
HomeBuy
AboutAbout
Call

+* `/composeApp` is for code that will be shared across your Compose Multiplatform applications. + It contains several subfolders: + - `commonMain` is for code that’s common for all targets. + - Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name. + For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app, + `iosMain` would be the right folder for such calls. +* `/iosApp` contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform, + you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project. + +Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html)… \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 10ab54a..52cf9fa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,23 +1,8 @@ -buildscript { - dependencies { - classpath(deps.jetbrains.kotlinx.binaryCompatibilityValidator) - classpath(deps.parcelizeDarwin.gradlePlug) - classpath(deps.kotlin.kotlinGradlePlug) - classpath("com.android.tools.build:gradle:8.1.1") - //classpath(deps.plugins.spotless) - // classpath(deps.plugins.ktlint) - //classpath(deps.plugins.detekt) - } -} - plugins { // this is necessary to avoid the plugins to be loaded multiple times // in each subproject's classloader - kotlin("multiplatform").apply(false) - id("com.android.application").apply(false) - id("com.android.library").apply(false) - id("org.jetbrains.compose").apply(false) - -} - - + alias(libs.plugins.androidApplication) apply false + alias(libs.plugins.androidLibrary) apply false + alias(libs.plugins.jetbrainsCompose) apply false + alias(libs.plugins.kotlinMultiplatform) apply false +} \ No newline at end of file diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts new file mode 100644 index 0000000..9b78622 --- /dev/null +++ b/composeApp/build.gradle.kts @@ -0,0 +1,180 @@ +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.androidApplication) + alias(libs.plugins.jetbrainsCompose) + id("com.arkivanov.parcelize.darwin") version "0.2.3" + id("kotlin-parcelize") + id("app.cash.sqldelight") version "2.0.0" + kotlin("plugin.serialization") version "1.9.21" +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = "17" + } + } + } + + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true + export(libs.decompose) + + export(libs.essenty.lifecycle) + + // Optional, only if you need state preservation on Darwin (Apple) targets + export(libs.essenty.stateKeeper) + + // Optional, only if you need state preservation on Darwin (Apple) targets + export(libs.parcelizeDarwin.runtime) + + } + } + + sourceSets { + + androidMain.dependencies { + implementation(libs.compose.ui.tooling.preview) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.appcompat.appcompat) + + // Ktor + implementation(libs.ktor.clientAndroid) + + // SqlDelight + implementation(libs.sqlDelight.androidDriver) + + // Koin + implementation(libs.koin.android) + + implementation("com.google.android.gms:play-services-auth:21.0.0") + + implementation("com.google.android.gms:play-services-auth-api-phone:18.0.2") + + implementation("androidx.biometric:biometric:1.2.0-alpha05") + //Firebase + implementation ("com.google.firebase:firebase-bom:32.1.1") + implementation (libs.analytics.firebase) + implementation (libs.crashlytics.firebase) + implementation("com.googlecode.libphonenumber:libphonenumber:8.2.0") + // This dependency is downloaded from the Google’s Maven repository. + // So, make sure you also include that repository in your project's build.gradle file. + implementation("com.google.android.play:app-update:2.1.0") + + // For Kotlin users also import the Kotlin extensions library for Play In-App Update: + implementation("com.google.android.play:app-update-ktx:2.1.0") + + api ("com.github.atwa:filepicker:1.0.7") + + } + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material) + implementation(compose.ui) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(compose.material3) + @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) + implementation(compose.materialIconsExtended) + + implementation(libs.decompose) + implementation(libs.decompose.jetbrains) + api(libs.essenty.lifecycle) + api(libs.essenty.stateKeeper) + + api(libs.ktor.clientCore) + api(libs.ktor.serializationKotlinxJson) + api(libs.ktor.clientContentNegotiation) + api(libs.ktor.clientLogging) + + // Logback for ktor logging + implementation(libs.logbackClassic) + + //sqldelight + api(libs.sqlDelight.coroutinesExtensions) + api(libs.sqlDelight.primitiveAdapters) + + //Koin + api(libs.koin.core) + api(libs.koin.test) + +// // KotlinX Serialization Json + implementation(libs.jetbrains.kotlinx.kotlinxSerializationJson) +// +// // Coroutines + implementation(libs.jetbrains.kotlinx.kotlinxCoroutinesCore) +// +// // MVIKotlin + api(libs.mvikotlin) + api(libs.mviKotlin.mvikotlinMain) + api(libs.mviKotlin.mvikotlinExtensionsCoroutines) +// // settings + implementation(libs.russhwolf.settings.core) + implementation(libs.russhwolf.settings.serialization) + //insets + implementation("com.moriatsushi.insetsx:insetsx:0.1.0-alpha10") + + } + + iosMain.dependencies { + //ios dependencies + // Ktor + implementation(libs.ktor.clientDarwin) + + // SqlDelight + implementation(libs.sqlDelight.nativeDriver) + + api(libs.decompose) + + api(libs.essenty.lifecycle) + //Modified + api(libs.parcelizeDarwin.runtime) + + + + } + } +} + +android { + namespace = "org.example.project" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + sourceSets["main"].res.srcDirs("src/androidMain/res") + sourceSets["main"].resources.srcDirs("src/commonMain/resources") + + defaultConfig { + applicationId = "org.example.project" + minSdk = libs.versions.android.minSdk.get().toInt() + targetSdk = libs.versions.android.targetSdk.get().toInt() + versionCode = 1 + versionName = "1.0" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + dependencies { + debugImplementation(libs.compose.ui.tooling) + } +} + diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml new file mode 100644 index 0000000..6b0f60b --- /dev/null +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/composeApp/src/androidMain/kotlin/Platform.android.kt b/composeApp/src/androidMain/kotlin/Platform.android.kt new file mode 100644 index 0000000..9428cd8 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/Platform.android.kt @@ -0,0 +1,13 @@ +import android.os.Build +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import rootBottomStack.RootBottomComponent + +class AndroidPlatform : Platform { + override val name: String = "Android ${Build.VERSION.SDK_INT}" +} + +@Composable +fun MainView(component: RootBottomComponent, modifier: Modifier = Modifier) = App(component, modifier) + +actual fun getPlatform(): Platform = AndroidPlatform() \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/PlatformSpecific.android.kt b/composeApp/src/androidMain/kotlin/PlatformSpecific.android.kt new file mode 100644 index 0000000..ef1ee35 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/PlatformSpecific.android.kt @@ -0,0 +1,193 @@ +import androidx.compose.ui.graphics.ImageBitmap + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.os.Build +import android.provider.DocumentsContract +import android.provider.OpenableColumns +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.RequiresApi +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.ui.graphics.asImageBitmap +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.atwa.filepicker.core.FilePicker +import java.io.File +import java.io.IOException + +actual open class PlatformSpecific(private val context: Context) : AppCompatActivity() { + private val PICK_FILE_REQUEST_CODE = 123 + private val currentActivity: AppCompatActivity = (context as AppCompatActivity) + private val filePicker = FilePicker.getInstance(currentActivity) + private val PICK_IMAGE_REQUEST_CODE = 123 + private val CAMERA_PERMISSION_REQUEST_CODE = 123 + val filePickerLauncher = + currentActivity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + handleFileSelectionResult(result.resultCode, result.data?.data) + } + + //Todo api modifications + //Modified to support android version below and above 13 + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + actual fun loadFiles(callback: (ImageBitmap?) -> Unit) { + if (ContextCompat.checkSelfPermission( + currentActivity, + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { + Manifest.permission.READ_EXTERNAL_STORAGE + } else { + Manifest.permission.READ_MEDIA_IMAGES + } + ) == PackageManager.PERMISSION_GRANTED + ) { + loadDeviceFiles(callback) + } else { + ActivityCompat.requestPermissions( + currentActivity, + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { + arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) + } else { + arrayOf(Manifest.permission.READ_MEDIA_IMAGES) + }, + PICK_FILE_REQUEST_CODE + ) + } + } + + actual fun loadImages(callback: (ImageBitmap?) -> Unit) { + if (ContextCompat.checkSelfPermission( + currentActivity, + Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED + ) { + // Permission is already granted, proceed with capturing the image + captureImage(callback) + } else { + // Request camera permission + ActivityCompat.requestPermissions( + currentActivity, + arrayOf(Manifest.permission.CAMERA), + CAMERA_PERMISSION_REQUEST_CODE + ) + } + } + + fun openImagePicker() { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "image/*" // Set the MIME type to allow only image files + + currentActivity.startActivityForResult(intent, PICK_IMAGE_REQUEST_CODE) + } + + @Deprecated("Deprecated in Java") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + if (requestCode == PICK_IMAGE_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + // Handle the selected image URI + val selectedImageUri: Uri? = data?.data + selectedImageUri?.let { uri -> + // Convert URI to Bitmap + val bitmap: Bitmap? = uriToBitmap(uri) + + if (bitmap != null) { + // Do something with the bitmap + + } + } + } + } + + private fun uriToBitmap(uri: Uri): Bitmap? { + return try { + val inputStream = currentActivity.contentResolver.openInputStream(uri) + BitmapFactory.decodeStream(inputStream) + } catch (e: IOException) { + e.printStackTrace() + null + } + } + + private fun captureImage(callback: (ImageBitmap?) -> Unit) { + filePicker.captureCameraImage { meta -> + val name: String? = meta?.name + val sizeKb: Int? = meta?.sizeKb + val file: File? = meta?.file + val bitmap: Bitmap? = meta?.bitmap + val bitmapimage: ImageBitmap? = meta?.bitmap?.asImageBitmap() + + println("Picked Image:::$bitmap") + + // Invoke the callback with the captured image details + callback(bitmapimage) + } + } + + private fun loadDeviceFiles(callback: (ImageBitmap?) -> Unit) { + filePicker.pickImage() { meta -> + val name: String? = meta?.name + val sizeKb: Int? = meta?.sizeKb + val file: File? = meta?.file + val bitmapimage: ImageBitmap? = meta?.bitmap?.asImageBitmap() + callback(bitmapimage) + } + } + + + @RequiresApi(Build.VERSION_CODES.O) + fun openFile(pickerInitialUri: Uri) { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/pdf" + putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) + } + + filePickerLauncher.launch(intent) + } + + private fun pickPdf() { + filePicker.pickPdf() { meta -> + val name: String? = meta?.name + val sizeKb: Int? = meta?.sizeKb + val file: File? = meta?.file + println("Loaded File name:::::" + name) + } + } + + private fun getFileName(uri: Uri): String { + contentResolver.query(uri, null, null, null, null)?.use { cursor -> + val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + cursor.moveToFirst() + return cursor.getString(nameIndex) + } + return "" + } + + private fun handleFileSelectionResult(resultCode: Int, selectedFileUri: Uri?) { + if (resultCode == Activity.RESULT_OK) { + selectedFileUri?.let { uri -> + // Get the file name from the URI + val fileName = getFileName(uri) + + // Print or use the file name as needed + println("Selected file name: $fileName") + } + } else { + println("Error opening or user canceled") + } + } + + actual fun launchDialer(phoneNumber: String) { + val intent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:$phoneNumber")) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + context.startActivity(intent) + + } + +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/example/project/MainActivity.kt b/composeApp/src/androidMain/kotlin/org/example/project/MainActivity.kt new file mode 100644 index 0000000..1be7840 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/org/example/project/MainActivity.kt @@ -0,0 +1,52 @@ +package org.example.project + +import MainView +import PlatformSpecific +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import com.arkivanov.decompose.defaultComponentContext +import di.initKoin +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.stopKoin +import org.koin.core.error.KoinAppAlreadyStartedException +import rootBottomStack.DefaultRootBottomComponent + +class MainActivity : AppCompatActivity() { + private var platform: PlatformSpecific? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + platform =PlatformSpecific(this) + val root = DefaultRootBottomComponent( + componentContext = defaultComponentContext(), + ) + try { + val koinApplication = initKoin( + // add to build configuration, false in prod + enableNetworkLogs = true, + platform = platform + ) + println("Koin app Started::;::::") + koinApplication.androidContext(applicationContext) + } catch (e: KoinAppAlreadyStartedException) { + println("Koin app Failed::;::::") + e.printStackTrace() + } + setContent { + MainView(root) + } + } + + override fun onDestroy() { + stopKoin() + super.onDestroy() + } +} + +@Preview +@Composable +fun AppAndroidPreview() { + // App() +} \ No newline at end of file diff --git a/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml b/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml b/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a571e60 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..61da551 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c41dd28 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..db5080a Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6dba46d Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..da31a87 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..15ac681 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b216f2d Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..f25a419 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e96783c Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/values/strings.xml b/composeApp/src/androidMain/res/values/strings.xml new file mode 100644 index 0000000..d8c6aef --- /dev/null +++ b/composeApp/src/androidMain/res/values/strings.xml @@ -0,0 +1,3 @@ + + DecomposeApp + \ No newline at end of file diff --git a/composeApp/src/androidMain/res/values/styles.xml b/composeApp/src/androidMain/res/values/styles.xml new file mode 100644 index 0000000..c1f9831 --- /dev/null +++ b/composeApp/src/androidMain/res/values/styles.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/composeApp/src/commonMain/composeResources/drawable/CoffeeCup.png b/composeApp/src/commonMain/composeResources/drawable/CoffeeCup.png new file mode 100644 index 0000000..4a8d285 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/CoffeeCup.png differ diff --git a/composeApp/src/commonMain/composeResources/drawable/camera.png b/composeApp/src/commonMain/composeResources/drawable/camera.png new file mode 100644 index 0000000..ba6e143 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/camera.png differ diff --git a/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml b/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml new file mode 100644 index 0000000..d7bf795 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml @@ -0,0 +1,36 @@ + + + + + + + + diff --git a/composeApp/src/commonMain/composeResources/drawable/framea.png b/composeApp/src/commonMain/composeResources/drawable/framea.png new file mode 100644 index 0000000..a534293 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/framea.png differ diff --git a/composeApp/src/commonMain/composeResources/drawable/frameb.png b/composeApp/src/commonMain/composeResources/drawable/frameb.png new file mode 100644 index 0000000..58e6ceb Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/frameb.png differ diff --git a/composeApp/src/commonMain/composeResources/drawable/id-card.png b/composeApp/src/commonMain/composeResources/drawable/id-card.png new file mode 100644 index 0000000..eeac703 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/id-card.png differ diff --git a/composeApp/src/commonMain/composeResources/drawable/id.png b/composeApp/src/commonMain/composeResources/drawable/id.png new file mode 100644 index 0000000..6931491 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/id.png differ diff --git a/composeApp/src/commonMain/composeResources/drawable/imagee.png b/composeApp/src/commonMain/composeResources/drawable/imagee.png new file mode 100644 index 0000000..6b2e8c4 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/imagee.png differ diff --git a/composeApp/src/commonMain/composeResources/drawable/img.png b/composeApp/src/commonMain/composeResources/drawable/img.png new file mode 100644 index 0000000..519993e Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/img.png differ diff --git a/composeApp/src/commonMain/composeResources/drawable/photos.png b/composeApp/src/commonMain/composeResources/drawable/photos.png new file mode 100644 index 0000000..2bd0017 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/photos.png differ diff --git a/composeApp/src/commonMain/kotlin/App.kt b/composeApp/src/commonMain/kotlin/App.kt new file mode 100644 index 0000000..e1e0e2f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/App.kt @@ -0,0 +1,30 @@ +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import org.jetbrains.compose.ui.tooling.preview.Preview +import rootBottomStack.RootBottomComponent +import rootBottomStack.RootBottomScreen +import theme.ComposeExperimentalTheme + +@Composable +@Preview +fun App(component: RootBottomComponent, modifier: Modifier = Modifier) { + ComposeExperimentalTheme(content = { + Scaffold() { paddingFromPrent -> + Column( + Modifier + .padding(paddingFromPrent) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + + RootBottomScreen(component, modifier) + + } + } + }) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/Greeting.kt b/composeApp/src/commonMain/kotlin/Greeting.kt new file mode 100644 index 0000000..887d835 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/Greeting.kt @@ -0,0 +1,7 @@ +class Greeting { + private val platform = getPlatform() + + fun greet(): String { + return "Hello, ${platform.name}!" + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/Platform.kt b/composeApp/src/commonMain/kotlin/Platform.kt new file mode 100644 index 0000000..87ca3ff --- /dev/null +++ b/composeApp/src/commonMain/kotlin/Platform.kt @@ -0,0 +1,5 @@ +interface Platform { + val name: String +} + +expect fun getPlatform(): Platform \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/PlatformSpecific.kt b/composeApp/src/commonMain/kotlin/PlatformSpecific.kt new file mode 100644 index 0000000..4f940dd --- /dev/null +++ b/composeApp/src/commonMain/kotlin/PlatformSpecific.kt @@ -0,0 +1,8 @@ +import androidx.compose.ui.graphics.ImageBitmap + +expect open class PlatformSpecific{ + fun loadFiles(callback: (ImageBitmap?) -> Unit) + fun loadImages(callback: (ImageBitmap?) -> Unit) + fun launchDialer(phoneNumber: String) + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/about/AboutComponent.kt b/composeApp/src/commonMain/kotlin/about/AboutComponent.kt new file mode 100644 index 0000000..6884d58 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/about/AboutComponent.kt @@ -0,0 +1,11 @@ +package about + +import PlatformSpecific + + +interface AboutComponent { + val loadFiles: PlatformSpecific + + fun onUpdateGreetingText() + fun onBackClicked() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/about/AboutContent.kt b/composeApp/src/commonMain/kotlin/about/AboutContent.kt new file mode 100644 index 0000000..f47ae88 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/about/AboutContent.kt @@ -0,0 +1,303 @@ +package about + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import decomposeapp.composeapp.generated.resources.Res +import decomposeapp.composeapp.generated.resources.camera +import decomposeapp.composeapp.generated.resources.id_card +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource + +@OptIn(ExperimentalResourceApi::class, ExperimentalMaterial3Api::class) +@Composable +fun MessageContent( + component: AboutComponent, + modifier: Modifier = Modifier, +) { + val mutableBitmapState: MutableState = mutableStateOf(null) + val mutableFrontIdBitmapState: MutableState = mutableStateOf(null) + val mutableBackIdBitmapState: MutableState = mutableStateOf(null) + val stroke = Stroke( + width = 2f, + pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f) + ) + val color = MaterialTheme.colorScheme.onBackground + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { + TopAppBar(title = { Text(text = "About", + fontSize = 15.sp) }) + } + ) { innerPadding -> + LazyColumn(modifier = Modifier.padding(start = 16.dp, end = 16.dp)) { + item { + Spacer(modifier = Modifier.padding(innerPadding)) + } + item { + Text( + text = "Upload these documents", + fontSize = 12.sp, + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(top = 10.dp) + ) + Box( + Modifier + .fillMaxWidth() + .padding(top = 16.dp) + .wrapContentHeight() + .clickable { + component.loadFiles.loadImages { image -> + println("Loaded camera image + " + image) + mutableBitmapState.value = image + } + } + .drawBehind { + drawRoundRect(color = color, style = stroke) + } + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .fillMaxWidth(0.5f) + .padding(start = 5.dp) + ) { + Text( + text = "Take Passport Photo", + fontSize = 10.sp, + color = MaterialTheme.colorScheme.primary + ) + Text( + text = "Please take a photo for verification", + fontSize = 10.sp, + style = MaterialTheme.typography.bodySmall + ) + } + if (mutableBitmapState.value == null) { + Image( + painter = painterResource(Res.drawable.camera), + contentDescription = "Take photo", + contentScale = ContentScale.FillBounds, + modifier = Modifier + .size(60.dp) + .padding(end = 10.dp) + .clickable { + component.loadFiles.loadImages { image -> + println("Loaded camera image + " + image) + mutableBitmapState.value = image + } + } + ) + } else { + mutableBitmapState.value?.let { image -> + Image( + bitmap = image, + contentDescription = null, + modifier = Modifier + .size(150.dp) + .padding(1.dp), + alignment = Alignment.CenterEnd + ) + } + } + } + } + Box( + Modifier + .fillMaxWidth() + .padding(top = 16.dp) + .wrapContentHeight() + .clickable { + component.loadFiles.loadFiles { image -> + println("Loaded camera image + " + image) + mutableFrontIdBitmapState.value = image + } + } + .drawBehind { + drawRoundRect(color = color, style = stroke) + } + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .fillMaxWidth(0.5f) + .padding(start = 5.dp) + ) { + Text( + text = "Front Side of your", + fontSize = 10.sp, + color = MaterialTheme.colorScheme.primary + ) + Text( + text = "National ID/Passport", + fontSize = 10.sp, + color = MaterialTheme.colorScheme.primary + ) + Text( + text = "Upload front side", + fontSize = 10.sp + ) + Text( + text = "A front picture of your National ID/Passport", + fontSize = 10.sp, + style =MaterialTheme.typography.bodySmall + ) + + } + if (mutableFrontIdBitmapState.value == null) { + Image( + painter = painterResource(Res.drawable.id_card), + contentDescription = "Take photo", + contentScale = ContentScale.FillBounds, + modifier = Modifier + .padding(end = 10.dp) + .size(70.dp) + .clickable { + component.loadFiles.loadFiles { image -> + println("Loaded camera image + " + image) + mutableFrontIdBitmapState.value = image + } + } + ) + } else { + mutableFrontIdBitmapState.value.let { image -> + if (image != null) { + Image( + bitmap = image, + contentDescription = null, + modifier = Modifier + .size(150.dp) + .padding(1.dp), + alignment = Alignment.CenterEnd + ) + } + } + } + } + } + Box( + Modifier + .fillMaxWidth() + .padding(top = 16.dp) + .wrapContentHeight() + .clickable { + component.loadFiles.loadFiles { image -> + println("Loaded camera image + " + image) + mutableBackIdBitmapState.value = image + } + } + .drawBehind { + drawRoundRect(color = color, style = stroke) + } + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .fillMaxWidth(0.5f) + .padding(start = 5.dp) + ) { + Text( + text = "Back Side of your", + fontSize = 10.sp, + color = MaterialTheme.colorScheme.primary + ) + Text( + text = "National ID/Passport", + fontSize = 10.sp, + color = MaterialTheme.colorScheme.primary + ) + Text( + text = "Upload back side", + fontSize = 10.sp + ) + Text( + text = "A back picture of your National ID/Passport", + fontSize = 10.sp, + style =MaterialTheme.typography.bodySmall + ) + + } + if (mutableBackIdBitmapState.value == null) { + Image( + painter = painterResource(Res.drawable.id_card), + contentDescription = "Take photo", + contentScale = ContentScale.FillBounds, + modifier = Modifier + .padding(end = 10.dp) + .size(70.dp) + .clickable { + component.loadFiles.loadFiles { image -> + println("Loaded camera image + " + image) + mutableBackIdBitmapState.value = image + } + } + ) + } else { + mutableBackIdBitmapState.value.let { image -> + if (image != null) { + Image( + bitmap = image, + contentDescription = null, + modifier = Modifier + .size(150.dp) + .padding(1.dp), + alignment = Alignment.CenterEnd + ) + } + + } + + } + } + } + } + item { + Row(modifier = Modifier.padding(top = 20.dp, bottom = 20.dp)) { + + } + } + + } + } +} + + diff --git a/composeApp/src/commonMain/kotlin/about/DefaultAboutComponent.kt b/composeApp/src/commonMain/kotlin/about/DefaultAboutComponent.kt new file mode 100644 index 0000000..5541cb8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/about/DefaultAboutComponent.kt @@ -0,0 +1,21 @@ +package about + +import PlatformSpecific +import com.arkivanov.decompose.ComponentContext +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class DefaultAboutComponent( + componentContext: ComponentContext, + private val onShowWelcome: () -> Unit, + +) : AboutComponent, ComponentContext by componentContext, KoinComponent { + override val loadFiles: PlatformSpecific by inject() + override fun onUpdateGreetingText() { + TODO("Not yet implemented") + } + + override fun onBackClicked() { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/buy/BuyComponent.kt b/composeApp/src/commonMain/kotlin/buy/BuyComponent.kt new file mode 100644 index 0000000..682abf3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/buy/BuyComponent.kt @@ -0,0 +1,9 @@ +package buy + +interface BuyComponent { + + fun onUpdateGreetingText() + fun onBackClicked() + + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/buy/BuyContent.kt b/composeApp/src/commonMain/kotlin/buy/BuyContent.kt new file mode 100644 index 0000000..4454f82 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/buy/BuyContent.kt @@ -0,0 +1,215 @@ +package buy + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import decomposeapp.composeapp.generated.resources.CoffeeCup +import decomposeapp.composeapp.generated.resources.Res +import decomposeapp.composeapp.generated.resources.framea +import decomposeapp.composeapp.generated.resources.frameb +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalResourceApi::class) +@Composable +fun FeedsContent( + component: BuyComponent, + modifier: Modifier = Modifier +) { + Scaffold(topBar = { + TopAppBar(title = { + Text( + text = "Buy", + fontSize = 15.sp + ) + }) + }) { innerPadding -> + Column( + Modifier + .padding(innerPadding) + ) { + LazyColumn(modifier = Modifier.padding(start = 16.dp, end = 16.dp)) { + item { + Row( + modifier = Modifier + .padding(top = 10.dp) + .fillParentMaxHeight(0.3f), + horizontalArrangement = Arrangement.Center, + ) { + ElevatedCardExample() + } + Text( + text = "Cappucino", + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(top = 10.dp) + ) + Text( + text = "with Chocolate", + style = MaterialTheme.typography.titleMedium, + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row() { + Text(text = "4.8 (230)", + style = MaterialTheme.typography.titleSmall) + } + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + Image( + painter = painterResource(Res.drawable.framea), + contentDescription = "profile Picture", + contentScale = ContentScale.Fit, + modifier = Modifier.size(50.dp) + ) + Image( + painter = painterResource(Res.drawable.frameb), + contentDescription = "profile Picture", + contentScale = ContentScale.Fit, + modifier = Modifier.size(50.dp) + ) + } + } + Text( + text = "Description", + modifier = Modifier.padding(top = 10.dp) + ) + Text( + text = "A cappuccino is an approximately 150 ml (5 oz) beverage, with 25 ml of espresso coffee and 85ml of fresh milk the fo.. Read More", + fontSize = 12.sp, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(top = 10.dp) + ) + Text( + text = "Size", + fontSize = 12.sp, + style = MaterialTheme.typography.labelMedium, + modifier = Modifier.padding(top = 10.dp) + ) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + FilledTonalButtonExample( + onClick = {}, + label = "S" + ) + FilledTonalButtonExample( + onClick = {}, + label = "M" + ) + FilledTonalButtonExample( + onClick = {}, + label = "L" + ) + + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column() { + Text( + text = "Price" + ) + Text( + text = "$4.53" + ) + } + Column() { + Text( + text = "Price" + ) + Text( + text = "$10.53" + ) + } + Column() { + Text( + text = "Price" + ) + Text( + text = "$20.53" + ) + } + } + Row( + modifier = Modifier.fillMaxWidth() + .padding(top = 10.dp), + horizontalArrangement = Arrangement.Center + ) { + FilledTonalButtonExample( + onClick = {}, + label = " Buy Now " + ) + + } + } + } + item { + Spacer(modifier = Modifier.padding(bottom = 100.dp)) + } + } + } + } +} + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun ElevatedCardExample() { + ElevatedCard() { + Column() { + Box( + modifier = Modifier.fillMaxSize() + ) { + Image( + painter = painterResource(Res.drawable.CoffeeCup), + contentDescription = null, + contentScale = ContentScale.FillWidth, + modifier = Modifier.fillMaxSize() + ) + + } + } + } +} + +@Composable +fun FilledTonalButtonExample( + onClick: () -> Unit, + label: String +) { + FilledTonalButton(onClick = { onClick() }) { + Text(text = label) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/buy/DefautBuyComponent.kt b/composeApp/src/commonMain/kotlin/buy/DefautBuyComponent.kt new file mode 100644 index 0000000..a698420 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/buy/DefautBuyComponent.kt @@ -0,0 +1,18 @@ +package buy + +import com.arkivanov.decompose.ComponentContext + +class DefautBuyComponent( + componentContext: ComponentContext, + private val onShowWelcome: () -> Unit, +) : BuyComponent, ComponentContext by componentContext { + + + override fun onUpdateGreetingText() { + TODO("Not yet implemented") + } + + override fun onBackClicked() { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/AppModule.kt b/composeApp/src/commonMain/kotlin/di/AppModule.kt new file mode 100644 index 0000000..f973407 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/di/AppModule.kt @@ -0,0 +1,18 @@ +package di + +import PlatformSpecific +import org.koin.core.context.startKoin +import org.koin.dsl.KoinAppDeclaration +import org.koin.dsl.module + + +fun initKoin(enableNetworkLogs: Boolean = false, platform: PlatformSpecific?, appDeclaration: KoinAppDeclaration = {}) = startKoin { + appDeclaration() + modules( + networkModule(enableNetworkLogs,platform), + module { + factory { platform } + single { platform } + } + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/NetworkModule.kt b/composeApp/src/commonMain/kotlin/di/NetworkModule.kt new file mode 100644 index 0000000..f263154 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/di/NetworkModule.kt @@ -0,0 +1,11 @@ +package di +import org.koin.core.module.Module +import org.koin.dsl.module +import PlatformSpecific + +val networkModule: (enableLogging: Boolean, platform: PlatformSpecific?) -> Module get() = { enableLogging, platform -> + module { + single { enableLogging } + single { platform } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/home/DefaultHomeComponent.kt b/composeApp/src/commonMain/kotlin/home/DefaultHomeComponent.kt new file mode 100644 index 0000000..6e5d665 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/home/DefaultHomeComponent.kt @@ -0,0 +1,15 @@ +package home + +import com.arkivanov.decompose.ComponentContext + + +class DefaultHomeComponent( + private val componentContext: ComponentContext, + private val onFinished: () -> Unit, +) : HomeComponent, ComponentContext by componentContext { + override fun onUpdateGreetingText() { + } + override fun onBackClicked() { + onFinished() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/home/HomeComponent.kt b/composeApp/src/commonMain/kotlin/home/HomeComponent.kt new file mode 100644 index 0000000..b8138aa --- /dev/null +++ b/composeApp/src/commonMain/kotlin/home/HomeComponent.kt @@ -0,0 +1,9 @@ +package home + +interface HomeComponent { + + fun onUpdateGreetingText() + fun onBackClicked() + + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/home/HomeContent.kt b/composeApp/src/commonMain/kotlin/home/HomeContent.kt new file mode 100644 index 0000000..a1b94b6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/home/HomeContent.kt @@ -0,0 +1,300 @@ +package home + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Search +import androidx.compose.material.icons.outlined.Tune +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.ElevatedButton +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import decomposeapp.composeapp.generated.resources.CoffeeCup +import decomposeapp.composeapp.generated.resources.Res +import decomposeapp.composeapp.generated.resources.imagee +import decomposeapp.composeapp.generated.resources.img +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource +import theme.buttonColor + +@OptIn( + ExperimentalResourceApi::class, ExperimentalFoundationApi::class, + ExperimentalMaterial3Api::class +) +@Composable +fun WelcomeContent( + component: HomeComponent, + modifier: Modifier = Modifier +) { + val pagerState = rememberPagerState(pageCount = { + 10 + }) + Scaffold( + modifier = Modifier + .fillMaxSize(), + topBar = { + TopAppBar( + title = { + Row(modifier = Modifier.background(color = MaterialTheme.colorScheme.primary)) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, top = 10.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Column() { + Text( + text = "Location", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + Text( + text = "Dennis", + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + } + //open file picker and set profile picture + + + Image( + painter = painterResource(Res.drawable.img), + contentDescription = "profile Picture", + modifier = Modifier + .width(44.dp) + .height(44.dp) + ) + } + } + + }, + colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.primary) + ) + }, + + ) { innePadding -> + Column( + modifier = Modifier + .fillMaxWidth() + .padding() + ) { + Column(modifier = Modifier.padding(innePadding)) { + Box() { + Column( + modifier = Modifier + .background(color = MaterialTheme.colorScheme.primary) + .fillMaxHeight(0.3f) + ) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(top = 28.dp, start = 16.dp, end = 16.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier.padding( + top = 17.dp, + bottom = 17.dp, + start = 16.dp + ) + ) { + Row() { + Icon( + Icons.Outlined.Search, + contentDescription = "Search" + ) + Text( + modifier = Modifier.padding(start = 1.dp), + text = "Search Coffee", + style = MaterialTheme.typography.bodySmall + ) + } + } + Icon( + Icons.Outlined.Tune, + contentDescription = "filter", + modifier = Modifier + .size(40.dp) + .background( + color = MaterialTheme.colorScheme.tertiary, + shape = RoundedCornerShape(14.dp) + ), + tint = MaterialTheme.colorScheme.onSecondaryContainer + ) + } + } + } + Row( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.41f) + .padding(top = 110.dp, start = 16.dp, end = 16.dp), + horizontalArrangement = Arrangement.Center + ) { + Card( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(), + shape = RoundedCornerShape(16.dp), + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + + Image( + painter = painterResource(Res.drawable.imagee), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .matchParentSize() + ) + Column() { + Row( + modifier = Modifier + .padding(start = 16.dp) + ) { + ElevatedButtonExample() + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Buy one get one FREE", + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier + .fillMaxWidth(0.5f) + .padding(start = 16.dp), + ) + } + } + } + } + } + } + Column() { + LazyRow( + modifier = Modifier + .padding(top = 20.dp, start = 16.dp, end = 16.dp) + ) { + items(5) { + FilledTonalButton { } + Spacer(modifier = Modifier.padding(start = 5.dp)) + } + } + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Fixed(2), + verticalItemSpacing = 10.dp, + horizontalArrangement = Arrangement.spacedBy(24.dp), + content = { + items(10) { + ProductsCard() + } + }, + modifier = Modifier + .fillMaxSize() + .padding(top = 20.dp, start = 16.dp, end = 16.dp) + ) + } + } + } + } +} + +@Composable +fun FilledTonalButton(onClick: () -> Unit) { + FilledTonalButton(onClick = { onClick() }) { + Text("Cappuccino", + style = MaterialTheme.typography.labelLarge) + } +} + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun ProductsCard() { + ElevatedCard() { + Column() { + Box( + modifier = Modifier.fillMaxSize() + ) { + Image( + painter = painterResource(Res.drawable.CoffeeCup), + contentDescription = null, + contentScale = ContentScale.FillWidth, + modifier = Modifier.fillMaxSize() + ) + Column() { + Text( + text = "6.5", + modifier = Modifier.padding(start = 12.dp), + color = MaterialTheme.colorScheme.onSecondaryContainer, + style = MaterialTheme.typography.labelMedium + ) + } + } + Column(modifier = Modifier.padding(top = 12.dp, start = 12.dp)) { + Text(text = "Cappucino", + style = MaterialTheme.typography.bodyMedium) + Text(text = "with Chocolate", + style = MaterialTheme.typography.bodyMedium) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(text = "$ 4.53", + style = MaterialTheme.typography.labelLarge) + } + } + } + + } +} + +@Composable +fun ElevatedButtonExample() { + ElevatedButton(modifier = Modifier + .height(35.dp), + colors = ButtonDefaults.buttonColors(containerColor = buttonColor), + onClick = { }) { + Text("Promo") + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/notifications/DefaultNotificationComponent.kt b/composeApp/src/commonMain/kotlin/notifications/DefaultNotificationComponent.kt new file mode 100644 index 0000000..13d32ae --- /dev/null +++ b/composeApp/src/commonMain/kotlin/notifications/DefaultNotificationComponent.kt @@ -0,0 +1,20 @@ +package notifications + +import PlatformSpecific +import com.arkivanov.decompose.ComponentContext +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + + +class DefaultNotificationComponent( + componentContext: ComponentContext, + private val onShowWelcome: () -> Unit, +) : NotificationComponent, ComponentContext by componentContext, KoinComponent { + override val platformSpecific: PlatformSpecific by inject() + override fun onUpdateGreetingText() { + TODO("Not yet implemented") + } + override fun onBackClicked() { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/notifications/NotificationComponent.kt b/composeApp/src/commonMain/kotlin/notifications/NotificationComponent.kt new file mode 100644 index 0000000..bccd331 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/notifications/NotificationComponent.kt @@ -0,0 +1,12 @@ +package notifications + +import PlatformSpecific + + +interface NotificationComponent { + val platformSpecific: PlatformSpecific + fun onUpdateGreetingText() + fun onBackClicked() + + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/notifications/NotificationContent.kt b/composeApp/src/commonMain/kotlin/notifications/NotificationContent.kt new file mode 100644 index 0000000..2d7460c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/notifications/NotificationContent.kt @@ -0,0 +1,55 @@ +package notifications + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Call +import androidx.compose.material3.ElevatedButton +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun NotificationContent(component: NotificationComponent, modifier: Modifier = Modifier) { + Scaffold(topBar = { + TopAppBar(title = { Text(text = "Notify", fontSize = 15.sp) }) + }) { paddingValues -> + Column( + modifier = Modifier.padding(paddingValues) + .fillMaxSize() + ) { + Column(modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp)) { + ElevatedButton(modifier = Modifier + .fillMaxWidth(), onClick = { + component.platformSpecific.launchDialer("+2547897567") + + }) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + Icons.Default.Call, + contentDescription = "Call Icon", + tint = Color.Green, + modifier = Modifier.padding(1.dp) + ) + Text(text = "Call") + } + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/rootBottomStack/DefaultRootBottomComponent.kt b/composeApp/src/commonMain/kotlin/rootBottomStack/DefaultRootBottomComponent.kt new file mode 100644 index 0000000..de9cb58 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/rootBottomStack/DefaultRootBottomComponent.kt @@ -0,0 +1,148 @@ +package rootBottomStack + +import about.AboutComponent +import about.DefaultAboutComponent +import buy.BuyComponent +import buy.DefautBuyComponent +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.router.stack.ChildStack +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.active +import com.arkivanov.decompose.router.stack.bringToFront +import com.arkivanov.decompose.router.stack.childStack +import com.arkivanov.decompose.value.Value +import com.arkivanov.essenty.lifecycle.Lifecycle +import home.DefaultHomeComponent +import home.HomeComponent +import kotlinx.serialization.Serializable +import notifications.DefaultNotificationComponent +import notifications.NotificationComponent + + +class DefaultRootBottomComponent( + componentContext: ComponentContext + +) : RootBottomComponent, ComponentContext by componentContext { + private val navigationBottomStackNavigation = StackNavigation() + + private val _childStackBottom = + childStack( + source = navigationBottomStackNavigation, + serializer = ConfigBottom.serializer(), + initialConfiguration = ConfigBottom.Welcome, + handleBackButton = true, + childFactory = ::createChildBottom, + key = "authStack" + ) + + override val childStackBottom: Value> = + _childStackBottom + + + private fun createChildBottom( + config: ConfigBottom, + componentContext: ComponentContext + ): RootBottomComponent.ChildBottom = + when (config) { + + is ConfigBottom.Welcome -> RootBottomComponent.ChildBottom.WelcomeChild( + welcomeComponent(componentContext) + ) + + is ConfigBottom.Feeds -> RootBottomComponent.ChildBottom.FeedsChild( + feedsComponent(componentContext) + ) + + is ConfigBottom.Message -> RootBottomComponent.ChildBottom.MessageChild( + messageComponent(componentContext) + ) + + is ConfigBottom.Notification -> RootBottomComponent.ChildBottom.NotificationsChild( + notificationComponent(componentContext) + ) + } + + + private fun welcomeComponent(componentContext: ComponentContext): HomeComponent = + DefaultHomeComponent( + componentContext = componentContext, + onFinished = { + + } + + ) + + private fun feedsComponent(componentContext: ComponentContext): BuyComponent = + DefautBuyComponent( + componentContext = componentContext, + onShowWelcome = { + + } + + ) + + private fun messageComponent(componentContext: ComponentContext): AboutComponent = + DefaultAboutComponent( + componentContext = componentContext, + onShowWelcome = { + + } + + ) + + private fun notificationComponent(componentContext: ComponentContext): NotificationComponent = + DefaultNotificationComponent( + componentContext = componentContext, + onShowWelcome = { + + } + + ) + + override fun openHome() { + navigationBottomStackNavigation.bringToFront(ConfigBottom.Welcome) + } + + override fun openFeeds() { + navigationBottomStackNavigation.bringToFront(ConfigBottom.Feeds) + } + + override fun openMessage() { + navigationBottomStackNavigation.bringToFront(ConfigBottom.Message) + } + + override fun openNotifications() { + navigationBottomStackNavigation.bringToFront(ConfigBottom.Notification) + } + + @Serializable + private sealed class ConfigBottom { + @Serializable + data object Welcome : ConfigBottom() + + @Serializable + data object Feeds : ConfigBottom() + + @Serializable + data object Message : ConfigBottom() + + @Serializable + data object Notification : ConfigBottom() + + } + + init { + lifecycle.subscribe(object : Lifecycle.Callbacks { + override fun onResume() { + when (childStackBottom.active.configuration) { + is ConfigBottom.Message -> { + super.onResume() + } + + } + } + }) + + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/rootBottomStack/RootBottomComponent.kt b/composeApp/src/commonMain/kotlin/rootBottomStack/RootBottomComponent.kt new file mode 100644 index 0000000..a913b44 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/rootBottomStack/RootBottomComponent.kt @@ -0,0 +1,26 @@ +package rootBottomStack + +import com.arkivanov.decompose.router.stack.ChildStack +import com.arkivanov.decompose.value.Value +import buy.BuyComponent +import about.AboutComponent +import notifications.NotificationComponent +import home.HomeComponent + + +interface RootBottomComponent { + val childStackBottom: Value> + fun openHome() + fun openFeeds() + fun openMessage() + fun openNotifications() + + sealed class ChildBottom { + class WelcomeChild(val component: HomeComponent) : ChildBottom() + class FeedsChild(val component: BuyComponent) : ChildBottom() + class MessageChild(val component: AboutComponent) : ChildBottom() + class NotificationsChild(val component: NotificationComponent) : ChildBottom() + } + + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/rootBottomStack/RootBottomScreen.kt b/composeApp/src/commonMain/kotlin/rootBottomStack/RootBottomScreen.kt new file mode 100644 index 0000000..c186630 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/rootBottomStack/RootBottomScreen.kt @@ -0,0 +1,120 @@ +package rootBottomStack + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Badge +import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.Notifications +import androidx.compose.material.icons.outlined.ShoppingCart +import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children +import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.fade +import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.plus +import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.scale +import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stackAnimation +import buy.FeedsContent +import about.MessageContent +import notifications.NotificationContent +import home.WelcomeContent + +data class ScreensBottom(val name: String, val openScreen: () -> Unit, val isSelected: Boolean) + +@Composable +fun RootBottomScreen(component: RootBottomComponent, modifier: Modifier = Modifier) { + var selectedItem by remember { mutableIntStateOf(0) } + val screens by remember { + mutableStateOf( + listOf( + ScreensBottom("Home", component::openHome, false), + ScreensBottom("Buy", component::openFeeds, false), + ScreensBottom("About", component::openMessage, false), + ScreensBottom("Notify", component::openNotifications, false) + ) + ) + } + Scaffold( + bottomBar = { + BottomAppBar( + modifier = Modifier.fillMaxWidth(), + actions = { + screens.forEachIndexed { index, screensBottom -> + NavigationBarItem( + icon = { + when (screensBottom.name) { + "Home" -> Icon(Icons.Outlined.Home, contentDescription = null) + "Buy" -> Icon( + Icons.Outlined.ShoppingCart, + contentDescription = null + ) + + "About" -> Icon( + Icons.Outlined.Badge, + contentDescription = null + ) + + "Notify" -> Icon( + Icons.Outlined.Notifications, + contentDescription = null + ) + } + }, + label = { + Text( + text = screensBottom.name, + style = MaterialTheme.typography.labelLarge, + fontWeight = FontWeight.Light + ) + }, + selected = selectedItem == index, + onClick = { + selectedItem = index + screensBottom.openScreen() + }, + colors = NavigationBarItemDefaults.colors(selectedIconColor = MaterialTheme.colorScheme.primary) + ) + } + } + ) + + }, + content = { innerpadding -> + Column(modifier = Modifier.padding(innerpadding)) { + Children( + stack = component.childStackBottom, + modifier = modifier, + animation = stackAnimation(fade() + scale()), + ) { + when (val child = it.instance) { + is RootBottomComponent.ChildBottom.WelcomeChild -> WelcomeContent(component = child.component) + is RootBottomComponent.ChildBottom.FeedsChild -> FeedsContent(component = child.component) + is RootBottomComponent.ChildBottom.MessageChild -> MessageContent( + component = child.component, + modifier + ) + + is RootBottomComponent.ChildBottom.NotificationsChild -> NotificationContent( + component = child.component + ) + } + } + } + }) + + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/theme/Colors.kt b/composeApp/src/commonMain/kotlin/theme/Colors.kt new file mode 100644 index 0000000..72968aa --- /dev/null +++ b/composeApp/src/commonMain/kotlin/theme/Colors.kt @@ -0,0 +1,40 @@ + +package theme + +import androidx.compose.ui.graphics.Color + +val PrimaryColor = Color(0xff214E78) +val PrimaryLightColor = PrimaryColor.copy(alpha = 0.75f) + +val SecondaryColor = Color(0xff7C93BE) +val SecondaryLightColor = SecondaryColor.copy(alpha = 0.75f) + +val PrimaryTextColor = Color(0xffffffff) +val SecondaryTextColor = Color(0xff000000) +val customBackGround = Color(0xff262626) +val SurfaceDark = Color(0xFF161616) +val SurfaceLight = Color(0xffFFFFFFFF) + +val BackgroundLightColor = Color(0xffF1F0F5) + +val BackgroundDarkColor = Color(0xff010100) + +val ErrorColor = Color(0xFFFF8989) +val OnErrorColor = Color(0xFF000000) + +val iconColor = Color(0xFFC67C4E) + val buttonColor = Color(0xffED5151) + +const val SessionColor = 0xFfBA4949 +const val ShortBreakColor = 0xFf38858A +const val LongBreakColor = 0xFf397097 + +const val Red = 0xFFFF0000 +const val Orange = 0xFFFFA500 +const val Blue = 0xFF0000FF +const val Green = 0xFF00FF00 + +const val LightGreen = 0xFF90EE90 +const val Yellow = 0xFFFFFF00 +const val LightBlue = 0xFFADD8E6 +const val Pink = 0xFFFFC0CB diff --git a/composeApp/src/commonMain/kotlin/theme/Shapes.kt b/composeApp/src/commonMain/kotlin/theme/Shapes.kt new file mode 100644 index 0000000..802264f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/theme/Shapes.kt @@ -0,0 +1,12 @@ + +package theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Shapes +import androidx.compose.ui.unit.dp + +internal val Shapes = Shapes( + small = RoundedCornerShape(4.dp), + medium = RoundedCornerShape(8.dp), + large = RoundedCornerShape(12.dp), +) diff --git a/composeApp/src/commonMain/kotlin/theme/Theme.kt b/composeApp/src/commonMain/kotlin/theme/Theme.kt new file mode 100644 index 0000000..588ed24 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/theme/Theme.kt @@ -0,0 +1,57 @@ + +package theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +private val LightColors = lightColorScheme( + primary = customBackGround, + onPrimary = PrimaryTextColor, + secondary = SecondaryColor, + onSecondary = SecondaryTextColor, + tertiary = iconColor, + onTertiary = PrimaryTextColor, + background = BackgroundLightColor, + onBackground = Color.Black, + surface = SurfaceLight, + onSurface = Color.Black, + surfaceVariant = SurfaceLight, + onSurfaceVariant = Color.Black, + secondaryContainer = PrimaryColor, + onSecondaryContainer = Color.White, + error = ErrorColor, + onError = OnErrorColor, +) + +private val DarkColors = darkColorScheme( + primary = customBackGround, + onPrimary = PrimaryTextColor, + secondary = SecondaryLightColor, + onSecondary = SecondaryTextColor, + tertiary = iconColor, + onTertiary = PrimaryTextColor, + background = BackgroundDarkColor, + onBackground = Color.White, + surface = SurfaceDark, + onSurface = Color.White, + surfaceVariant = SurfaceDark, + onSurfaceVariant = Color.White, + secondaryContainer = PrimaryColor, + onSecondaryContainer = Color.White, + error = ErrorColor, + onError = OnErrorColor, +) + +@Composable +internal fun ComposeExperimentalTheme(useDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { + val autoColors = if (useDarkTheme)LightColors else LightColors + MaterialTheme( + colorScheme = autoColors, + shapes = Shapes, + content = content, + ) +} diff --git a/composeApp/src/iosMain/kotlin/MainViewController.kt b/composeApp/src/iosMain/kotlin/MainViewController.kt new file mode 100644 index 0000000..4f05df5 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/MainViewController.kt @@ -0,0 +1,39 @@ +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.InternalComposeApi +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSafeArea +import androidx.compose.ui.platform.PlatformInsets +import androidx.compose.ui.unit.dp +import com.arkivanov.decompose.DefaultComponentContext +import com.arkivanov.essenty.lifecycle.LifecycleRegistry +import com.moriatsushi.insetsx.WindowInsetsUIViewController +import di.initKoin +import platform.UIKit.UIViewController +import rootBottomStack.DefaultRootBottomComponent +import theme.ComposeExperimentalTheme + +@OptIn(ExperimentalComposeUiApi::class, InternalComposeApi::class) +@Suppress("unused", "FunctionName") +fun MainViewController( + lifecycle: LifecycleRegistry, + topSafeArea: Float, + bottomSafeArea: Float, +): UIViewController { + val defaultComponentCtx = DefaultComponentContext(lifecycle = lifecycle) + val root = DefaultRootBottomComponent( + componentContext = defaultComponentCtx + ) + initKoin(enableNetworkLogs = true, platform = PlatformSpecific()) + return WindowInsetsUIViewController { + val density = LocalDensity.current + val topSafeAreaDp = with(density) { topSafeArea.toDp() } + val bottomSafeAreaDp = with(density) { bottomSafeArea.toDp() } + val safeArea = PlatformInsets(top = topSafeAreaDp + 10.dp, bottom = bottomSafeAreaDp) + CompositionLocalProvider(LocalSafeArea provides safeArea) { + ComposeExperimentalTheme { + App(root) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/Platform.ios.kt b/composeApp/src/iosMain/kotlin/Platform.ios.kt new file mode 100644 index 0000000..5cef987 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/Platform.ios.kt @@ -0,0 +1,7 @@ +import platform.UIKit.UIDevice + +class IOSPlatform: Platform { + override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion +} + +actual fun getPlatform(): Platform = IOSPlatform() \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/PlatformSpecific.kt b/composeApp/src/iosMain/kotlin/PlatformSpecific.kt new file mode 100644 index 0000000..c1976e0 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/PlatformSpecific.kt @@ -0,0 +1,16 @@ +import androidx.compose.ui.graphics.ImageBitmap + +actual open class PlatformSpecific{ + actual fun loadFiles(callback: (ImageBitmap?) -> Unit) { + //convert an image to a Imagebitmap equivalent in swift + } + + actual fun loadImages(callback: (ImageBitmap?) -> Unit) { + } + + actual fun launchDialer(phoneNumber: String) { + + //swift launch dialer implementation + } + +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index b085ecd..25698c8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,24 +1,23 @@ -#Gradle -org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" - -#Kotlin kotlin.code.style=official -#MPP -kotlin.mpp.stability.nowarn=true -kotlin.mpp.enableCInteropCommonization=true -kotlin.mpp.androidSourceSetLayoutVersion=2 +#Gradle +org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" -#Compose -org.jetbrains.compose.experimental.uikit.enabled=true #Android +android.nonTransitiveRClass=true android.useAndroidX=true -android.targetSdk=34 -android.compileSdk=34 -android.minSdk=24 + +#MPP +kotlin.mpp.androidSourceSetLayoutVersion=2 +kotlin.mpp.enableCInteropCommonization=true + +#Development +development=true #Versions +#//To update kotlin.version=1.9.20 -agp.version=8.0.2 -compose.version=1.5.11 +agp.version=8.1.1 +compose.version=1.5.10 +sqldelight.version=2.0.0 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..cfc3ed2 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,130 @@ +[versions] +agp = "8.1.1" +android-compileSdk = "34" +android-minSdk = "24" +android-targetSdk = "34" +androidx-activityCompose = "1.8.2" +androidx-constraintlayout = "2.1.4" +androidx-core-ktx = "1.12.0" +androidx-espresso-core = "3.5.1" +androidx-material = "1.11.0" +androidx-test-junit = "1.1.5" +compose = "1.5.11" +compose-plugin = "1.6.0" +junit = "4.13.2" +kotlin = "1.9.21" +serialization = "1.6.0" +moko = "0.23.0" +moko-graphics = "0.9.0" +mviKotlin = "3.2.1" +logbackClassic = "1.2.11" +sqlDelight = "2.0.0" +koin = "3.5.0" +ktor = "2.3.7" +decompose = "2.2.2" +essenty = "1.3.0" +parcelizeDarwin = "0.2.3" +jetbrainsCompose = "1.5.10" +jetbrainsKotlinxCoroutines = "1.8.0" +jetbrainsBinaryCompatibilityValidator = "0.13.2" +androidxCore = "1.12.0" +androidxAppcompat = "1.6.1" +androidxLifecycle = "2.7.0" +androidxActivity = "1.8.2" +androidxFragment = "1.6.2" +androidxTestCore = "1.5.0" +extensionJetbrains = "2.0.0-compose-experimental-alpha-02" +russhwolf = "1.1.0" +gms = "4.4.0" +analytics = "21.3.0" +crashlytics = "18.3.2" + + +[libraries] +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } +androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-junit" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-espresso-core" } +androidx-material = { group = "com.google.android.material", name = "material", version.ref = "androidx-material" } +androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "androidx-constraintlayout" } +androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } +compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } +compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } + +analytics-firebase = {group = "com.google.firebase", name = "firebase-analytics-ktx", version.ref = "analytics"} +crashlytics-firebase = {group = "com.google.firebase", name = "firebase-crashlytics-ktx", version.ref = "crashlytics"} + +gms-google-services = {group = "com.google.gms", name = "google-services", version.ref = "gms"} + +russhwolf-settings-core = {group = "com.russhwolf", name = "multiplatform-settings", version.ref = "russhwolf" } +russhwolf-settings-serialization = {group = "com.russhwolf", name = "multiplatform-settings-serialization", version.ref = "russhwolf" } + + +#Koin +koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" } +koin-core = { group = "io.insert-koin", name = "koin-core", version.ref = "koin" } +koin-test = { group = "io.insert-koin", name = "koin-test", version.ref = "koin" } + + +#Decompose +mvikotlin = { group = "com.arkivanov.mvikotlin", name = "mvikotlin", version.ref = "mviKotlin" } +mviKotlin-mvikotlinMain = { group = "com.arkivanov.mvikotlin", name = "mvikotlin-main", version.ref = "mviKotlin" } +mviKotlin-mvikotlinExtensionsCoroutines = { group = "com.arkivanov.mvikotlin", name = "mvikotlin-extensions-coroutines", version.ref = "mviKotlin" } +decompose = { group = "com.arkivanov.decompose", name = "decompose", version.ref = "decompose" } +decompose-jetbrains = { group = "com.arkivanov.decompose", name = "extensions-compose-jetbrains", version.ref = "extensionJetbrains" } +essenty-lifecycle = { group = "com.arkivanov.essenty", name = "lifecycle", version.ref = "essenty" } +essenty-stateKeeper = { group = "com.arkivanov.essenty", name = "state-keeper", version.ref = "essenty" } +essenty-instanceKeeper = { group = "com.arkivanov.essenty", name = "instance-keeper", version.ref = "essenty" } +essenty-backHandler = { group = "com.arkivanov.essenty", name = "back-handler", version.ref = "essenty" } +parcelizeDarwin-gradlePlug = { group = "com.arkivanov.parcelize.darwin", name = "gradle-plugin", version.ref = "parcelizeDarwin" } +parcelizeDarwin-runtime = { group = "com.arkivanov.parcelize.darwin", name = "runtime", version.ref = "parcelizeDarwin" } +#Ktor +ktor-ClientCore = {group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } +ktor-SerializationKotlinxJson = {group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } +ktor-ClientContentNegotiation = {group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } +ktor-ClientLogging = {group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" } +ktor-ClientAndroid = {group = "io.ktor", name = "ktor-client-android", version.ref = "ktor" } +ktor-ClientDarwin = {group = "io.ktor", name = "ktor-client-darwin", version.ref = "ktor" } +#ktor-ClientJava = {group = "io.ktor", name = "ktor-client-java", version.ref = "ktor" } +#ktor-ClientJs = {group = "io.ktor", name = "ktor-client-js", version.ref = "ktor" } + +#Ktor logback +logbackClassic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logbackClassic" } + +#Sql +sqlDelight-gradlePlugin = {group = "app.cash.sqldelight", name = "gradle-plugin", version.ref = "sqlDelight" } +sqlDelight-androidDriver = {group = "app.cash.sqldelight", name = "android-driver", version.ref = "sqlDelight" } +sqlDelight-sqliteDriver = {group = "app.cash.sqldelight", name = "sqlite-driver", version.ref = "sqlDelight" } +sqlDelight-nativeDriver = {group = "app.cash.sqldelight", name = "native-driver", version.ref = "sqlDelight" } +sqlDelight-sqljsDriver = {group = "app.cash.sqldelight", name = "sqljs-driver", version.ref = "sqlDelight" } +sqlDelight-coroutinesExtensions = {group = "app.cash.sqldelight", name = "coroutines-extensions", version.ref = "sqlDelight" } +sqlDelight-primitiveAdapters = {group = "app.cash.sqldelight", name = "primitive-adapters", version.ref = "sqlDelight" } + + +androidx-core-coreKtx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" } +androidx-appcompat-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppcompat" } +androidx-lifecycle-lifecycleCommonJava8 = { group = "androidx.lifecycle", name = "lifecycle-common-java8", version.ref = "androidxLifecycle" } +androidx-activity-activityKtx = { group = "androidx.activity", name = "activity-ktx", version.ref = "androidxActivity" } +androidx-activity-activityCompose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" } +androidx-fragment-fragmentKtx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "androidxFragment" } +androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidxTestCore" } + + + +jetbrains-compose-composeGradlePlug = { group = "org.jetbrains.compose", name = "compose-gradle-plugin", version.ref = "jetbrainsCompose" } +jetbrains-kotlinx-binaryCompatibilityValidator = { group = "org.jetbrains.kotlinx", name = "binary-compatibility-validator", version.ref = "jetbrainsBinaryCompatibilityValidator" } +jetbrains-kotlinx-kotlinxCoroutinesCore = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "jetbrainsKotlinxCoroutines" } +jetbrains-kotlinx-kotlinxSerializationJson = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } +jetbrains-kotlinx-kotlinxCoroutinesSwing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "jetbrainsKotlinxCoroutines" } + + + + + +[plugins] +androidApplication = { id = "com.android.application", version.ref = "agp" } +androidLibrary = { id = "com.android.library", version.ref = "agp" } +jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135..033e24c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradlew b/gradlew index 1aa94a4..fcb6fca 100755 --- a/gradlew +++ b/gradlew @@ -83,8 +83,7 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -145,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +201,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/iosApp/Configuration/Config.xcconfig b/iosApp/Configuration/Config.xcconfig index f391597..2e659a0 100644 --- a/iosApp/Configuration/Config.xcconfig +++ b/iosApp/Configuration/Config.xcconfig @@ -1,3 +1,3 @@ TEAM_ID= -BUNDLE_ID=com.myapplication.MyApplication -APP_NAME=My application +BUNDLE_ID=org.example.project.DecomposeApp +APP_NAME=DecomposeApp \ No newline at end of file diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index c852042..48dc62d 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -17,14 +17,14 @@ 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; - 7555FF7B242A565900829871 /* My application.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "My application.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7555FF7B242A565900829871 /* DecomposeApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DecomposeApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - F85CB1118929364A9C6EFABC /* Frameworks */ = { + B92378962B6B1156000C7307 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -62,7 +62,7 @@ 7555FF7C242A565900829871 /* Products */ = { isa = PBXGroup; children = ( - 7555FF7B242A565900829871 /* My application.app */, + 7555FF7B242A565900829871 /* DecomposeApp.app */, ); name = Products; sourceTree = ""; @@ -94,18 +94,20 @@ isa = PBXNativeTarget; buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; buildPhases = ( - 05D91A912A5EF49C00F138EB /* Compile Kotlin */, + F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */, 7555FF77242A565900829871 /* Sources */, + B92378962B6B1156000C7307 /* Frameworks */, 7555FF79242A565900829871 /* Resources */, - F85CB1118929364A9C6EFABC /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = iosApp; + packageProductDependencies = ( + ); productName = iosApp; - productReference = 7555FF7B242A565900829871 /* My application.app */; + productReference = 7555FF7B242A565900829871 /* DecomposeApp.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -124,7 +126,7 @@ }; }; buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; - compatibilityVersion = "Xcode 9.3"; + compatibilityVersion = "Xcode 12.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -132,6 +134,8 @@ Base, ); mainGroup = 7555FF72242A565900829871; + packageReferences = ( + ); productRefGroup = 7555FF7C242A565900829871 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -154,7 +158,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 05D91A912A5EF49C00F138EB /* Compile Kotlin */ = { + F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -163,14 +167,14 @@ ); inputPaths = ( ); - name = "Compile Kotlin"; + name = "Compile Kotlin Framework"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n"; + shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -239,7 +243,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -295,7 +299,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -312,14 +316,13 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; - DEVELOPMENT_TEAM = "${TEAM_ID}"; + DEVELOPMENT_TEAM = 9Z5F72MRRD; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)\n", - "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); INFOPLIST_FILE = iosApp/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -327,9 +330,7 @@ OTHER_LDFLAGS = ( "$(inherited)", "-framework", - "shared\n$(inherited)", - "-framework", - "shared\n", + composeApp, ); PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}"; PRODUCT_NAME = "${APP_NAME}"; @@ -349,11 +350,10 @@ DEVELOPMENT_TEAM = "${TEAM_ID}"; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)\n", - "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); INFOPLIST_FILE = iosApp/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -361,9 +361,7 @@ OTHER_LDFLAGS = ( "$(inherited)", "-framework", - "shared\n$(inherited)", - "-framework", - "shared\n", + composeApp, ); PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}"; PRODUCT_NAME = "${APP_NAME}"; diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/iosApp/iosApp/Assets.xcassets/Contents.json b/iosApp/iosApp/Assets.xcassets/Contents.json index 73c0059..4aa7c53 100644 --- a/iosApp/iosApp/Assets.xcassets/Contents.json +++ b/iosApp/iosApp/Assets.xcassets/Contents.json @@ -3,4 +3,4 @@ "author" : "xcode", "version" : 1 } -} +} \ No newline at end of file diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 64e7056..4647c9f 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -1,19 +1,44 @@ import UIKit import SwiftUI -import shared +import ComposeApp struct ComposeView: UIViewControllerRepresentable { + + private let lifecycle: LifecycleRegistry + private let topSafeArea: Float + private let bottomSafeArea: Float + + init(lifecycle: LifecycleRegistry, topSafeArea: Float, bottomSafeArea: Float) { + self.lifecycle = lifecycle + self.topSafeArea = topSafeArea + self.bottomSafeArea = bottomSafeArea + } + func makeUIViewController(context: Context) -> UIViewController { - Main_iosKt.MainViewController() + MainViewControllerKt.MainViewController( + lifecycle: lifecycle, + topSafeArea: topSafeArea, + bottomSafeArea: bottomSafeArea + ) } - func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} } struct ContentView: View { + private let lifecycle: LifecycleRegistry + private let topSafeArea: Float + private let bottomSafeArea: Float + + init(lifecycle: LifecycleRegistry, topSafeArea: Float, bottomSafeArea: Float) { + self.lifecycle = lifecycle + self.topSafeArea = topSafeArea + self.bottomSafeArea = bottomSafeArea + } var body: some View { - ComposeView() - .ignoresSafeArea(.all, edges: .bottom) // Compose has own keyboard handler + ComposeView( lifecycle: lifecycle, + topSafeArea: topSafeArea, + bottomSafeArea: bottomSafeArea) + .ignoresSafeArea(.keyboard) // Compose has own keyboard handler } } diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift index b7bf2f4..746155c 100644 --- a/iosApp/iosApp/iOSApp.swift +++ b/iosApp/iosApp/iOSApp.swift @@ -1,10 +1,26 @@ import SwiftUI +import ComposeApp + @main struct iOSApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } + private var lifecycle = LifecycleRegistryKt.LifecycleRegistry() + + var body: some Scene { + WindowGroup { + GeometryReader { geo in + ContentView( + lifecycle:lifecycle, + topSafeArea: Float(geo.safeAreaInsets.top), + bottomSafeArea: Float(geo.safeAreaInsets.bottom) + ) + .ignoresSafeArea() + .onTapGesture { + // Hide keyboard on tap outside of TextField + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } + } + + } + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index a395d1f..968cc8d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,49 +1,25 @@ -rootProject.name = "MyApplication" - -include(":androidApp") -include(":shared") +rootProject.name = "DecomposeApp" +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") pluginManagement { repositories { + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + google() gradlePluginPortal() mavenCentral() - google() - maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") maven { url = uri("https://jitpack.io") } + mavenLocal() } - - plugins { - val kotlinVersion = extra["kotlin.version"] as String - val agpVersion = extra["agp.version"] as String - val composeVersion = extra["compose.version"] as String - - kotlin("jvm").version(kotlinVersion) - kotlin("multiplatform").version(kotlinVersion) - kotlin("android").version(kotlinVersion) - - id("com.android.application").version(agpVersion) - id("com.android.library").version(agpVersion) - - id("org.jetbrains.compose").version(composeVersion) - } -} - -plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version("0.7.0") } dependencyResolutionManagement { repositories { - mavenCentral() google() - gradlePluginPortal() - mavenLocal() + mavenCentral() maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") maven { url = uri("https://jitpack.io") } - } - versionCatalogs { - create("deps") { - from(files("deps.versions.toml")) - } + mavenLocal() } } + +include(":composeApp") \ No newline at end of file