diff --git a/.gitignore b/.gitignore index f6a3ad6d..57bb88e2 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ app-iOS/app-iOS.xcworkspace/* *.plist .kotlin/ -venv/ \ No newline at end of file +venv/ +daraja/swiftpackage \ No newline at end of file diff --git a/app-desktop/.gitignore b/app-desktop/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/app-desktop/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/app-desktop/build.gradle.kts b/app-desktop/build.gradle.kts deleted file mode 100644 index a81edb8c..00000000 --- a/app-desktop/build.gradle.kts +++ /dev/null @@ -1,22 +0,0 @@ -plugins { - alias(libs.plugins.jvm) - alias(libs.plugins.compose) - alias(libs.plugins.compose.compiler) -} - -repositories { - mavenCentral() - maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") - google() -} - -dependencies { - implementation(project(":daraja")) - implementation(compose.desktop.currentOs) -} - -compose.desktop { - application { - mainClass = "MainKt" - } -} diff --git a/app-desktop/src/main/java/com/vickikbt/app_desktop/Main.kt b/app-desktop/src/main/java/com/vickikbt/app_desktop/Main.kt deleted file mode 100644 index 97c42fc1..00000000 --- a/app-desktop/src/main/java/com/vickikbt/app_desktop/Main.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2022 Daraja Multiplatform - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.vickikbt.app_desktop - -import androidx.compose.ui.window.application -import ui.screens.main.MainScreen - -fun main() { - return application { - MainScreen(applicationScope = this) - } -} diff --git a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/home/HomeScreen.kt b/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/home/HomeScreen.kt deleted file mode 100644 index 09f39fcd..00000000 --- a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/home/HomeScreen.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2022 Daraja Multiplatform - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.vickikbt.app_desktop.ui.screens.home - -import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.Button -import androidx.compose.material.MaterialTheme -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text -import androidx.compose.material.TextFieldDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.vickbt.darajakmp.Daraja -import com.vickbt.darajakmp.utils.onFailure -import com.vickbt.darajakmp.utils.onSuccess -import com.vickikbt.app_android.ui.theme.DarajaKmpTheme - -@Composable -fun HomeScreen() { - val tillNumber by remember { mutableStateOf("174379") } - var amount by remember { mutableStateOf(1) } - var phoneNumber by remember { mutableStateOf("0714091304") } - - val daraja by remember { - mutableStateOf( - Daraja.Builder() - .setConsumerKey("zg1m1CbMGx8E2BqVThHIJHFMWSnVJ4XA") - .setConsumerSecret("z4CAY2TUw6rprEvy") - .setPassKey("bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919") - .isSandbox() - .build(), - ) - } - - Box( - modifier = - Modifier - .fillMaxSize() - .padding(vertical = 16.dp, horizontal = 8.dp), - ) { - Text( - modifier = - Modifier - .align(Alignment.TopCenter) - .padding(horizontal = 2.dp), - text = "Daraja Multiplatform Desktop", - fontWeight = FontWeight.ExtraBold, - fontSize = 32.sp, - textAlign = TextAlign.Center, - ) - - Column( - modifier = Modifier.align(Alignment.Center), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = - Arrangement.spacedBy( - space = 42.dp, - alignment = Alignment.CenterVertically, - ), - ) { - OutlinedTextField( - modifier = Modifier.fillMaxWidth(.8f), - value = amount.toString(), - onValueChange = { amount = it.toInt() }, - singleLine = true, - maxLines = 1, - textStyle = - TextStyle( - fontSize = 20.sp, - color = MaterialTheme.colors.onBackground, - ), - label = { Text(text = "Amount") }, - colors = TextFieldDefaults.outlinedTextFieldColors(focusedBorderColor = MaterialTheme.colors.primary), - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), - ) - - OutlinedTextField( - modifier = Modifier.fillMaxWidth(.8f), - value = phoneNumber, - onValueChange = { phoneNumber = it }, - singleLine = true, - maxLines = 1, - textStyle = - TextStyle( - fontSize = 20.sp, - color = MaterialTheme.colors.onBackground, - ), - label = { Text(text = "Phone Number") }, - colors = TextFieldDefaults.outlinedTextFieldColors(focusedBorderColor = MaterialTheme.colors.primary), - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Phone), - ) - } - - Button( - modifier = Modifier.align(Alignment.BottomCenter), - onClick = { initiateMpesaStk(daraja, tillNumber, amount, phoneNumber) }, - ) { - Text(text = "Make Payment", fontSize = 20.sp) - } - } -} - -fun initiateMpesaStk( - daraja: Daraja, - tillNumber: String, - amount: Int, - phoneNumber: String, -) { - daraja.mpesaExpress( - businessShortCode = tillNumber, - amount = amount, - phoneNumber = phoneNumber, - transactionDesc = "Mpesa payment", - callbackUrl = "https://mydomain.com/path", - accountReference = "Daraja KMP Android", - ).onSuccess { - println(message = "On success block called: $it") - }.onFailure { - println(message = "On failure block called: $it") - } -} - -@Preview -@Composable -fun DefaultPreview() { - DarajaKmpTheme { - HomeScreen() - } -} diff --git a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/main/MainScreen.kt b/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/main/MainScreen.kt deleted file mode 100644 index 43ce3fee..00000000 --- a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/screens/main/MainScreen.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2022 Daraja Multiplatform - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ui.screens.main - -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.window.ApplicationScope -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.WindowPosition -import androidx.compose.ui.window.rememberWindowState -import com.vickikbt.app_android.ui.theme.DarajaKmpTheme -import com.vickikbt.app_desktop.ui.screens.home.HomeScreen - -@Composable -fun MainScreen(applicationScope: ApplicationScope) { - Window( - onCloseRequest = { applicationScope.exitApplication() }, - title = "Daraja Multiplatform Desktop", - state = - rememberWindowState( - position = WindowPosition.Aligned(Alignment.Center), - width = Dp.Unspecified, - height = Dp.Unspecified, - ), - ) { - DarajaKmpTheme(darkTheme = true) { - Surface(color = MaterialTheme.colors.surface) { - HomeScreen() - } - } - } -} diff --git a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Color.kt b/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Color.kt deleted file mode 100644 index 8cc531e5..00000000 --- a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Color.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2022 Daraja Multiplatform - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.vickikbt.app_android.ui.theme - -import androidx.compose.ui.graphics.Color - -val Purple80 = Color(0xFFD0BCFF) -val PurpleGrey80 = Color(0xFFCCC2DC) -val Pink80 = Color(0xFFEFB8C8) - -val Purple40 = Color(0xFF6650a4) -val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) diff --git a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Theme.kt b/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Theme.kt deleted file mode 100644 index 6a610f30..00000000 --- a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Theme.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2022 Daraja Multiplatform - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.vickikbt.app_android.ui.theme - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material.MaterialTheme -import androidx.compose.material.darkColors -import androidx.compose.material.lightColors -import androidx.compose.runtime.Composable - -private val DarkColorScheme = - darkColors( - primary = Purple80, - secondary = PurpleGrey80, - ) - -private val LightColorScheme = - lightColors( - primary = Purple40, - secondary = PurpleGrey40, - /* Other default colors to override - background = Color(0xFFFFFBFE), - surface = Color(0xFFFFFBFE), - onPrimary = Color.White, - onSecondary = Color.White, - onTertiary = Color.White, - onBackground = Color(0xFF1C1B1F), - onSurface = Color(0xFF1C1B1F), - */ - ) - -@Composable -fun DarajaKmpTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit, -) { - val colorScheme = - when { - darkTheme -> DarkColorScheme - else -> LightColorScheme - } - - MaterialTheme( - colors = colorScheme, - typography = Typography, - content = content, - ) -} diff --git a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Type.kt b/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Type.kt deleted file mode 100644 index 8bb67f83..00000000 --- a/app-desktop/src/main/java/com/vickikbt/app_desktop/ui/theme/Type.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2022 Daraja Multiplatform - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.vickikbt.app_android.ui.theme - -import androidx.compose.material.Typography -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp - -// Set of Material typography styles to start with -val Typography = - Typography( - h4 = - TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp, - ), - /* Other default text styles to override - titleLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 22.sp, - lineHeight = 28.sp, - letterSpacing = 0.sp - ), - labelSmall = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Medium, - fontSize = 11.sp, - lineHeight = 16.sp, - letterSpacing = 0.5.sp - ) - */ - ) diff --git a/app-iOS/Podfile b/app-iOS/Podfile deleted file mode 100644 index 54e587ba..00000000 --- a/app-iOS/Podfile +++ /dev/null @@ -1,4 +0,0 @@ -target 'app-iOS' do - use_frameworks! - platform :ios, '14.1' -end \ No newline at end of file diff --git a/app-iOS/app-iOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/app-iOS/app-iOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/app-iOS/app-iOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/app-iOS/app-iOS/ContentView.swift b/app-iOS/app-iOS/ContentView.swift deleted file mode 100644 index bff2fcad..00000000 --- a/app-iOS/app-iOS/ContentView.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// ContentView.swift -// app-iOS -// -// Created by Victor Kabata on 15/08/2023. -// - -import DarajaMultiplatform -import SwiftUI - -struct ContentView: View { - - @State private var amount: Int32 = 1 - @FocusState private var isAmountTextFieldFocused: Bool - - @State private var phoneNumber: String = "" - @FocusState private var isPhoneTextFieldFocused: Bool - - var daraja=Daraja( - consumerKey: "MAYA4V9yTveRBa3yP1syMfkgGzDNqSxO", - consumerSecret: "WU6A0ojDuSJUSP77", - passKey:"bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919", - environment: DarajaEnvironment.sandboxEnvironment) - - var body: some View { - VStack { - - // Amount textfield - TextField("Amount", text: .init(get: {"\(amount)"}, set: { - if let newValue = Int32($0) { - amount = newValue - } - })) - .padding() - .accentColor(.green) - .background(.white) - .keyboardType(.numberPad) - .overlay( - RoundedRectangle(cornerRadius: 10) - .stroke(isAmountTextFieldFocused ? Color.green: Color.gray.opacity(0.5), lineWidth: 1) - ) - .cornerRadius(10) - .shadow(color: Color.gray.opacity(0.4), radius: 4, x: 0, y: 2) - .padding() - .focused($isAmountTextFieldFocused) - - // Phone Number textfield - TextField("Phone Number", text: $phoneNumber) - .padding() - .accentColor(.green) - .background(.white) - .keyboardType(.phonePad) - .overlay( - RoundedRectangle(cornerRadius: 10) - .stroke(isPhoneTextFieldFocused ? Color.green:Color.gray.opacity(0.5), lineWidth: 1) - ) - .cornerRadius(10) - .shadow(color: Color.gray.opacity(0.4), radius: 4, x: 0, y: 2) - .padding() - .focused($isPhoneTextFieldFocused) - - // Pay Button - Button(action: { - initiateMpesaPayment(daraja: daraja, businessShortCode: "174379", amount: amount, phoneNumber: phoneNumber, transactionDesc: "M-Pesa payment", callbackUrl: "https://mydomain.com/path", accountReference: "174379") - }) { - Image(systemName: "plus") - .padding(20) - .background(Color.green) - .foregroundColor(.white) - .clipShape(Circle()) - } - }.padding(.horizontal,24) - - } -} - -func initiateMpesaPayment(daraja:Daraja, - businessShortCode: String, - amount: Int32, - phoneNumber: String, - transactionDesc: String, - callbackUrl: String, - accountReference: String){ - - let response=daraja.initiateMpesaExpressPayment(businessShortCode: businessShortCode, amount: amount, phoneNumber: phoneNumber,transactionType: DarajaTransactionType.customerpaybillonline, transactionDesc: "M-Pesa payment", callbackUrl: "https://mydomain.com/path", accountReference: "Daraja KMP iOS") - - response.onSuccess(action: {data in - print(data.self) - }) - .onFailure(action: {error in - print(error) - }) -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/build.gradle.kts b/build.gradle.kts index fdd253b2..2ca54341 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,6 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.multiplatform) apply false alias(libs.plugins.jvm) apply false - alias(libs.plugins.nativeCocoapod) apply false alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.ktLint) diff --git a/daraja/build.gradle.kts b/daraja/build.gradle.kts index 4d4b0c34..14a40982 100644 --- a/daraja/build.gradle.kts +++ b/daraja/build.gradle.kts @@ -25,7 +25,6 @@ fun isNonStable(version: String): Boolean { } plugins { - alias(libs.plugins.nativeCocoapod) alias(libs.plugins.multiplatform) alias(libs.plugins.android.library) alias(libs.plugins.kotlinX.serialization) @@ -43,23 +42,19 @@ kotlin { kotlin.applyDefaultHierarchyTemplate() androidTarget { - publishLibraryVariants("release") + publishLibraryVariants("debug", "release") compilerOptions { jvmTarget.set(JvmTarget.JVM_1_8) } } - iosX64() - iosArm64() - iosSimulatorArm64() - - cocoapods { - summary = "Daraja API Swift Wrapper built using Kotlin Multiplatform" - homepage = "https://github.com/VictorKabata/DarajaMultiplatform.git" - version = "1.0" - ios.deploymentTarget = "14.1" - framework { + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64(), + ).forEach { + it.binaries.framework { baseName = "DarajaMultiplatform" isStatic = true } @@ -232,7 +227,7 @@ multiplatformSwiftPackage { packageName("DarajaMultiplatform") swiftToolsVersion("5.3") targetPlatforms { - iOS { v("14.1") } + iOS { v("13") } } } diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt index d717af47..6c3af625 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/Daraja.kt @@ -18,8 +18,6 @@ package com.vickbt.darajakmp import com.vickbt.darajakmp.network.DarajaApiService import com.vickbt.darajakmp.network.DarajaHttpClientFactory -import com.vickbt.darajakmp.network.models.AccountBalanceRequest -import com.vickbt.darajakmp.network.models.AccountBalanceResponse import com.vickbt.darajakmp.network.models.C2BRegistrationRequest import com.vickbt.darajakmp.network.models.C2BRequest import com.vickbt.darajakmp.network.models.C2BResponse @@ -35,7 +33,6 @@ import com.vickbt.darajakmp.network.models.QueryMpesaExpressRequest import com.vickbt.darajakmp.network.models.QueryMpesaExpressResponse import com.vickbt.darajakmp.utils.C2BResponseType import com.vickbt.darajakmp.utils.DarajaEnvironment -import com.vickbt.darajakmp.utils.DarajaIdentifierType import com.vickbt.darajakmp.utils.DarajaResult import com.vickbt.darajakmp.utils.DarajaTransactionCode import com.vickbt.darajakmp.utils.DarajaTransactionType @@ -44,7 +41,6 @@ import com.vickbt.darajakmp.utils.getDarajaPassword import com.vickbt.darajakmp.utils.getDarajaPhoneNumber import com.vickbt.darajakmp.utils.getDarajaTimestamp import io.ktor.client.HttpClient -import io.ktor.util.encodeBase64 import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.runBlocking @@ -354,46 +350,4 @@ class Daraja( darajaApiService.c2b(c2bRequest = c2bRequest) } - - /**Request the account balance of a short code. This can be used for both B2C, buy goods and pay bill accounts. - * - * @param [initiator] This is the credential/username used to authenticate the transaction request - * @param [initiatorPassword] This is the credential/password used to authenticate the account balance request - * @param [commandId] A unique command is passed to the M-PESA system. Max length is 64. - * @param [partyA] The shortcode of the organization querying for the account balance. - * @param [identifierType] Type of organization querying for the account balance. - * @param [remarks] Comments that are sent along with the transaction - * @param [queueTimeOutURL] The end-point that receives a timeout message. - * @param [resultURL] It indicates the destination URL which Daraja should send the result message to. - * - * @return [AccountBalanceResponse] - * */ - internal fun accountBalance( - initiator: String, - initiatorPassword: String, - commandId: String = "AccountBalance", - partyA: Int, - identifierType: DarajaIdentifierType, - remarks: String = "Account balance request", - queueTimeOutURL: String, - resultURL: String, - ): DarajaResult = - runBlocking(Dispatchers.IO) { - val key = initiator + initiatorPassword - val securityCredential = key.encodeBase64() - - val accountBalanceRequest = - AccountBalanceRequest( - initiator = initiator, - securityCredential = securityCredential, - commandId = commandId, - partyA = partyA, - identifierType = if (identifierType == DarajaIdentifierType.TILL_NUMBER) 2 else 4, - remarks = remarks, - queueTimeOutURL = queueTimeOutURL, - resultURL = resultURL, - ) - - darajaApiService.accountBalance(accountBalanceRequest = accountBalanceRequest) - } } diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt index a24dc7a0..70388cf5 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/network/DarajaApiService.kt @@ -16,8 +16,6 @@ package com.vickbt.darajakmp.network -import com.vickbt.darajakmp.network.models.AccountBalanceRequest -import com.vickbt.darajakmp.network.models.AccountBalanceResponse import com.vickbt.darajakmp.network.models.C2BRegistrationRequest import com.vickbt.darajakmp.network.models.C2BRequest import com.vickbt.darajakmp.network.models.C2BResponse @@ -129,7 +127,7 @@ internal class DarajaApiService( }.body() } - suspend fun c2bRegistration(c2bRegistrationRequest: C2BRegistrationRequest): DarajaResult = + internal suspend fun c2bRegistration(c2bRegistrationRequest: C2BRegistrationRequest): DarajaResult = darajaSafeApiCall { val accessToken = inMemoryCache.get(1) { @@ -154,17 +152,4 @@ internal class DarajaApiService( setBody(c2bRequest) }.body() } - - internal suspend fun accountBalance(accountBalanceRequest: AccountBalanceRequest): DarajaResult = - darajaSafeApiCall { - val accessToken = - inMemoryCache.get(1) { - fetchAccessToken().getOrThrow() - } - - return@darajaSafeApiCall httpClient.post(urlString = DarajaEndpoints.ACCOUNT_BALANCE) { - headers { append(HttpHeaders.Authorization, "Bearer ${accessToken.accessToken}") } - setBody(accountBalanceRequest) - }.body() - } } diff --git a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt index 65a98450..1445ff71 100644 --- a/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt +++ b/daraja/src/commonMain/kotlin/com/vickbt/darajakmp/utils/DarajaConfigs.kt @@ -48,13 +48,13 @@ enum class C2BResponseType { /** * @param [BG] Pay Merchant (Buy Goods). * - * @param [WA]: Withdraw Cash at Agent Till. + * @param [WA] Withdraw Cash at Agent Till. * - * @param [PB]: Paybill or Business number. + * @param [PB] Paybill or Business number. * - * @param [SM]: Send Money(Mobile number) + * @param [SM] Send Money(Mobile number) * - * @param [SB]: Sent to Business. Business number CPI in MSISDN format.*/ + * @param [SB] Sent to Business. Business number CPI in MSISDN format.*/ enum class DarajaTransactionCode { BG, WA, diff --git a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/UtilsTest.kt b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/UtilsTest.kt index 5122dc61..a550e9f6 100644 --- a/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/UtilsTest.kt +++ b/daraja/src/commonTest/kotlin/com/vickbt/darajakmp/utils/UtilsTest.kt @@ -122,10 +122,13 @@ class UtilsTest { @Test fun `phone number with spaces are formatted correctly`() = runTest { - val phoneNumbers = listOf("0 714091 30 3", " + 2 547 140 913 03", "2 5 4714 091 30 3 ") + val phoneNumbers = + listOf("0 714091 30 3", " + 2 547 140 913 03", "2 5 4714 091 30 3 ") val expectedPhoneNumbers = listOf("254714091303", "254714091303", "254714091303") - assertThat(phoneNumbers.map { it.getDarajaPhoneNumber() }).isEqualTo(expectedPhoneNumbers) + assertThat(phoneNumbers.map { it.getDarajaPhoneNumber() }).isEqualTo( + expectedPhoneNumbers, + ) } @Test @@ -145,4 +148,13 @@ class UtilsTest { assertThat(timeUnit.asFormattedWithZero()).isEqualTo(expectedTimeUnit) } + + @Test + fun `capitalize returns strings in title case`() = + runTest { + val wordList = listOf("heLlo", "WorlD", "DARAJA", "kmp", "DaRaJa", "2") + val expectedWordList = listOf("Hello", "World", "Daraja", "Kmp", "Daraja", "2") + + assertThat(wordList.map { it.capitalize() }).isEqualTo(expectedWordList) + } } diff --git a/docs/kotlin.md b/docs/kotlin.md index 8574b6c1..18f6b9ce 100644 --- a/docs/kotlin.md +++ b/docs/kotlin.md @@ -1,14 +1,16 @@ -# Kotlin SDK +# __Kotlin SDK__ ## Getting Started -To get started with Daraja Multiplatform SDK, you will need to [create a Daraja API account](https://developer.safaricom.co.ke/) on the Daraja API portal and [set up a new test application](https://developer.safaricom.co.ke/MyApps). +To get started with Daraja Multiplatform SDK, you will need +to [create a Daraja API account](https://developer.safaricom.co.ke/) on the Daraja API portal +and [set up a new test application](https://developer.safaricom.co.ke/MyApps). Once you have access to the created test app, retrieve the ___Consumer Key___, ___Consumer Secret___ and ___Passkey___. ## Installation -- In Android projects, add the following dependency in the app/feature module gradle file: +- In an Android projects, add the following dependency in the app/feature module gradle file: ```Kotlin hl_lines="2" dependencies { @@ -31,9 +33,14 @@ kotlin { ## Setting Up - Add the Consumer Key, Consumer Secret and Passkey to your project's environment secrets or local.properties file. -> To protect your sensitive API keys, it's recommended to store your Consumer Key, Consumer Secret, and Passkey in your project's environment secrets or a local properties file (outside of version control). This ensures they are not accidentally exposed in public repositories. -- Instantiate a `Daraja` object, providing the necessary environment variables. This Daraja instance serves as the entry point for various M-Pesa operations. Core functionalities include obtaining an access token required for subsequent API calls and initiating M-Pesa Express STK push requests. +> To protect your sensitive API keys, it's recommended to store your Consumer Key, Consumer Secret, and Passkey in your +> project's environment secrets or a local properties file (outside of version control). This ensures they are not +> accidentally exposed in public repositories. + +- Instantiate a `Daraja` object, providing the necessary environment variables. This Daraja instance serves as the entry + point for various M-Pesa operations. Core functionalities include obtaining an access token required for subsequent + API calls and initiating M-Pesa Express STK push requests. ```Kotlin val daraja: Daraja = Daraja.Builder() @@ -43,7 +50,10 @@ val daraja: Daraja = Daraja.Builder() .isProduction() // Optional. Will default to sandbox_mode = true .build() ``` -> Daraja Multiplatform includes built-in network logging, which is active by default in sandbox mode but disabled in production mode. To inspect network requests and responses, view logs in Android Studio's Logcat under the __Daraja Multiplatform__ tag. + +> Daraja Multiplatform includes built-in network logging, which is active by default in sandbox mode but disabled in +> production mode. To inspect network requests and responses, view logs in Android Studio's Logcat under the __Daraja +Multiplatform__ tag. ## Usage @@ -66,19 +76,19 @@ accessTokenResult.onSuccess { accessToken -> - To initiate M-Pesa Express(Lipa na M-Pesa Online) STK request, invoke the `mpesaExpress` function: ```Kotlin -val darajaPaymentResponse: DarajaResult = daraja.mpesaExpress( - businessShortCode = "174379", - amount = 1, - phoneNumber = "07xxxxxxxx", // or +2547xxxxxxxx or 2547xxxxxxxx - transactionDesc = "M-Pesa payment", - callbackUrl = "your_callback_url", - accountReference = "CompanyName" - ) - -darajaPaymentResponse.onSuccess { paymentResponse -> +val paymentResponse: DarajaResult = daraja.mpesaExpress( + businessShortCode = "174379", + amount = 1, + phoneNumber = "07xxxxxxxx", // or +2547xxxxxxxx or 2547xxxxxxxx + transactionDesc = "M-Pesa payment", + callbackUrl = "your_callback_url", + accountReference = "CompanyName" +) + +paymentResponse.onSuccess { paymentResponse -> println(paymentResponse) }.onFailure { error -> - println(error) + println(error) } ``` @@ -87,16 +97,37 @@ darajaPaymentResponse.onSuccess { paymentResponse -> - To check the status of M-Pesa Express(Lipa na M-Pesa Online) STK request, invoke the `mpesaExpressQuery` function: ```Kotlin -val darajaMpesaExpressQuery: DarajaResult = daraja.mpesaExpressQuery( - businessShortCode = "174379", - timeStamp = "20160216165627", - checkOutRequestID = "ws_CO_260520211133524545" - ) +val mpesaExpressQuery: DarajaResult = daraja.mpesaExpressQuery( + businessShortCode = "174379", + timeStamp = "20160216165627", + checkOutRequestID = "ws_CO_260520211133524545" +) -darajaMpesaExpressQuery.onSuccess { mpesaExpressQuery -> +mpesaExpressQuery.onSuccess { mpesaExpressQuery -> println(mpesaExpressQuery) }.onFailure { error -> - println(error) + println(error) +} +``` + +### Generate Dynamic QR Code + +- To generate a dynamic QR code that can be used to trigger payment, invoke the `generateQR` function: + +```kotlin +val qrCode: DarajaResult = daraja.generateDynamicQr( + merchantName = "Shop 1", + referenceNumber = UUID().uuidString, + amount = 10, + transactionCode = DarajaTransactionCode.sm, + cpi = "373132", + size = 300 +) + +qrCode.onSuccess { qr -> + println(qr) +}.onFailure { error -> + println(error) } ``` diff --git a/docs/swift.md b/docs/swift.md index cf3d7be2..aa277012 100644 --- a/docs/swift.md +++ b/docs/swift.md @@ -1 +1,122 @@ -# Swift \ No newline at end of file +# __Swift SDK__ + +## Getting Started + +To get started with Daraja Multiplatform SDK, you will need +to [create a Daraja API account](https://developer.safaricom.co.ke/) on the Daraja API portal +and [set up a new test application](https://developer.safaricom.co.ke/MyApps). + +Once you have access to the created test app, retrieve the ___Consumer Key___, ___Consumer Secret___ and ___Passkey___. + +## Installation + +- In Xcode add the DarajaMultiplatform package, navigate to File -> Add package dependecies. Enter the package GitHub + url below, select the package and choose the target(s) to add it to and click "Add Package". + +```curl +https://github.com/VictorKabata/DarajaSwiftPackage.git +``` + +> To protect your sensitive API keys, it's recommended to store your Consumer Key, Consumer Secret, and Passkey in your +> project's environment secrets (outside of version control). This ensures they are not accidentally exposed in public +> repositories. + +- Instantiate a `Daraja` object, providing the necessary environment variables. This Daraja instance serves as the entry + point for various M-Pesa operations. Core functionalities include obtaining an access token required for subsequent + API calls and initiating M-Pesa Express STK push requests. + +```swift +let daraja = Daraja( + consumerKey: "your_consumer_key", + consumerSecret: "your_consumer_secret", + passKey: "your_pass_key", + environment: DarajaEnvironment.sandboxEnvironment) +``` + +- The environment can be either _DarajaEnvironment.sandboxEnvironment_ or _DarajaEnvironment.productionEnvironment_. + +> Daraja Multiplatform includes built-in network logging, which is active by default in sandbox mode but disabled in +> production mode. To inspect network requests and responses, view logs in Android Studio's Logcat under the __Daraja +Multiplatform__ tag. + +## Usage + +- Start by importing the package to your Swift code: + +```swift +import DarajaMultiplatform +``` + +### Request Access Token + +To request an access token from Daraja API, invoke the `authorization` function: + +```swift +daraja.authorization() +.onSuccess(action: { accessToken in + print(accessToken) +}) +.onFailure(action: { error in + print(error) +}) + +``` + +### Initiate M-Pesa Express STK Request + +- To initiate M-Pesa Express(Lipa na M-Pesa Online) STK request, invoke the `mpesaExpress` function: + +```swift +daraja.mpesaExpress( + businessShortCode: "174379", + amount: 1, + phoneNumber: "07xxxxxxxx", // or +2547xxxxxxxx or 2547xxxxxxxx + transactionType: .customerBuyGoodsOnline, + transactionDesc: "your_callback_url", + callbackUrl: "your_callback_url", + accountReference: "CompanyName" +).onSuccess(action: { paymentResponse in + print(paymentResponse) +}) +.onFailure(action: { error in + print(error) +}) + +``` + +### Query M-Pesa Express STK Status + +- To check the status of M-Pesa Express(Lipa na M-Pesa Online) STK request, invoke the `mpesaExpressQuery` function: + +```swift +daraja.mpesaExpressQuery( + businessShortCode: "174379", + timestamp: "20160216165627", + checkoutRequestID: "ws_CO_260520211133524545" +).onSuccess(action: { transaction in + print(transaction) +}) +.onFailure(action: { error in + print(error) +}) +``` + +### Generate Dynamic QR Code + +- To generate a dynamic QR code that can be used to trigger payment, invoke the `generateQR` function: + +```swift +daraja.generateDynamicQr( + merchantName: "Shop 1", + referenceNumber: UUID().uuidString, + amount: 10, + transactionCode: DarajaTransactionCode.sm, + cpi: "373132", + size: 300 +).onSuccess(action: { qrCode in + print(qrCode) +}) +.onFailure(action: { error in + print(error) +}) +``` \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 1b8236cd..e8128c77 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,4 +16,6 @@ kotlin.mpp.androidSourceSetLayoutVersion1.nowarn=true org.gradle.caching=true android.defaults.buildfeatures.buildconfig=true -android.nonFinalResIds=false \ No newline at end of file +android.nonFinalResIds=false + +kotlin.apple.xcodeCompatibility.nowarn=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dfa1a965..5f690430 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ detekt = "1.23.6" spotless = "6.2.2" dokka = "1.9.20" kover = "0.8.3" -mulitplatformSwiftPackage = "2.0.3" +mulitplatformSwiftPackage = "2.2.2" gradleVersionUpdate = "0.51.0" # Kotlin Multiplatform Version @@ -33,7 +33,7 @@ jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlinX-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } -multiplatformSwiftPackage = { id = "com.chromaticnoise.multiplatform-swiftpackage", version.ref = "mulitplatformSwiftPackage" } +multiplatformSwiftPackage = { id = "io.github.luca992.multiplatform-swiftpackage", version.ref = "mulitplatformSwiftPackage" } gradleVersionUpdate = { id = "com.github.ben-manes.versions", version.ref = "gradleVersionUpdate" } compose = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } diff --git a/mkdocs.yml b/mkdocs.yml index d3aa1c01..46997bb0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,7 +11,7 @@ remote_branch: gh-pages nav: - Overview: index.md - Kotlin SDK: kotlin.md -# - Swift SDK: swift.md + - Swift SDK: swift.md theme: name: material diff --git a/samples/android/app/build.gradle.kts b/samples/android/app/build.gradle.kts index da0710ac..08cf1ff6 100644 --- a/samples/android/app/build.gradle.kts +++ b/samples/android/app/build.gradle.kts @@ -1,8 +1,7 @@ -import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties - plugins { alias(libs.plugins.android.application) alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.kotlinx.serialization) } android { @@ -75,6 +74,8 @@ dependencies { implementation("io.github.victorkabata:daraja-multiplatform:0.9.8") + implementation(libs.kotlinx.serialization) + testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) diff --git a/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/components/CollapsableCard.kt b/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/components/CollapsableCard.kt index 0008843e..f8eb0b17 100644 --- a/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/components/CollapsableCard.kt +++ b/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/components/CollapsableCard.kt @@ -1,28 +1,41 @@ +@file:OptIn(ExperimentalFoundationApi::class) + package com.vickbt.daraja.android.ui.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight import androidx.compose.material3.Card +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRow import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -33,8 +46,19 @@ import androidx.compose.ui.unit.sp fun CollapsableCard( modifier: Modifier = Modifier, cardTitle: String, - cardContent: @Composable () -> Unit + tabItems: List = listOf("Request", "Response"), + requestContent: @Composable () -> Unit = {}, + responseContent: @Composable () -> Unit = {} ) { + var selectedTabIndex by remember { mutableIntStateOf(0) } + val pagerState = rememberPagerState { tabItems.size } + + LaunchedEffect(selectedTabIndex) { + pagerState.animateScrollToPage(selectedTabIndex) + } + LaunchedEffect(pagerState.currentPage, pagerState.isScrollInProgress) { + selectedTabIndex = pagerState.currentPage + } var isExpanded by remember { mutableStateOf(false) } val rotationDegree by animateFloatAsState( @@ -44,18 +68,23 @@ fun CollapsableCard( Card( modifier = modifier + .clickable(onClick = { isExpanded = !isExpanded }, + indication = null, + interactionSource = remember { MutableInteractionSource() }) .fillMaxWidth() - .padding(8.dp) - .clickable { isExpanded = !isExpanded }, - shape = RoundedCornerShape(8.dp) + .padding(vertical = 8.dp), + shape = RoundedCornerShape(8.dp), ) { Column( - modifier = Modifier.padding(16.dp), + modifier = Modifier.padding(vertical = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { + //region Title and Drop Down Icon Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { @@ -73,10 +102,52 @@ fun CollapsableCard( contentDescription = null ) } + //endregion + //region AnimatedVisibility(visible = isExpanded) { - cardContent() + Column(modifier = Modifier) { + TabRow( + modifier = Modifier.fillMaxWidth(), + selectedTabIndex = selectedTabIndex, + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.onSurface, + divider = { + HorizontalDivider( + thickness = 1.dp, + color = MaterialTheme.colorScheme.onSurface + ) + } + ) { + tabItems.forEachIndexed { index, title -> + Tab( + selected = index == selectedTabIndex, + onClick = { selectedTabIndex = index }, + text = { + Text( + text = title, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + ) + } + } + + HorizontalPager( + modifier = Modifier.padding(16.dp), + state = pagerState + ) { page -> + when (page) { + 0 -> requestContent() + 1 -> responseContent() + } + } + } } + //endregion } } @@ -84,10 +155,7 @@ fun CollapsableCard( } @Composable -@Preview +@Preview(showBackground = true) fun CollapsableCardPreview() { - CollapsableCard(cardTitle = "Title") { - Text(text = "Content") - } - + CollapsableCard(cardTitle = "Title", requestContent = {}, responseContent = {}) } \ No newline at end of file diff --git a/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/components/ResultComponent.kt b/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/components/ResultComponent.kt new file mode 100644 index 00000000..cabc094b --- /dev/null +++ b/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/components/ResultComponent.kt @@ -0,0 +1,59 @@ +package com.vickbt.daraja.android.ui.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.AddCircle +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun ResultComponent(modifier: Modifier = Modifier, result: String, onClickCopy: (String) -> Unit) { + Card( + modifier = Modifier, + shape = RoundedCornerShape(2.dp), + colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surface.copy(alpha = .6f)) + ) { + Column(modifier = modifier) { + Box(modifier = Modifier.fillMaxWidth()) { + IconButton( + modifier = Modifier + .size(48.dp) + .align(Alignment.TopEnd), + onClick = { onClickCopy(result) }) { + Icon( + imageVector = Icons.Rounded.AddCircle, + contentDescription = "Copy", + tint = MaterialTheme.colorScheme.onSurface + ) + } + } + + Text( + modifier = Modifier.padding(8.dp), + text = result, + fontWeight = FontWeight.Normal, + fontSize = 18.sp, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Start, + color = MaterialTheme.colorScheme.onSurface + ) + } + } +} \ No newline at end of file diff --git a/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/screen/MainScreen.kt b/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/screen/MainScreen.kt index 1ac36089..f28653ba 100644 --- a/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/screen/MainScreen.kt +++ b/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/screen/MainScreen.kt @@ -1,32 +1,80 @@ package com.vickbt.daraja.android.ui.screen -import androidx.compose.foundation.layout.Column +import android.util.Log import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.vickbt.daraja.android.ui.components.CollapsableCard +import com.vickbt.daraja.android.ui.components.ResultComponent +import com.vickbt.daraja.android.utils.toJson import com.vickbt.darajakmp.Daraja import org.koin.compose.koinInject @Composable fun MainScreen(modifier: Modifier = Modifier, daraja: Daraja = koinInject()) { - Column(modifier = modifier.padding(horizontal = 12.dp)) { - CollapsableCard(cardTitle = "M-Pesa Express") { - MpesaExpressScreen(modifier = Modifier, daraja = daraja) + LazyColumn(modifier = modifier.padding(horizontal = 12.dp)) { + item { + var requestContent by remember { mutableStateOf("") } + Log.e("VicKbt", "Result: ${requestContent.toJson()}") + CollapsableCard( + cardTitle = "M-Pesa Express", + requestContent = { + MpesaExpressScreen( + modifier = Modifier, + daraja = daraja, + onResult = { requestContent = it.toJson() }) + }, + responseContent = { + ResultComponent( + modifier = Modifier, + result = requestContent.toJson(), + onClickCopy = {} + ) + } + ) } - CollapsableCard(cardTitle = "Dynamic QR") { - QrCodeScreen(modifier = Modifier, daraja = daraja) + item { + var requestContent by remember { mutableStateOf("") } + CollapsableCard( + cardTitle = "Dynamic QR", + requestContent = { + QrCodeScreen( + modifier = Modifier, + daraja = daraja, + onResult = { requestContent = it.toJson() }) + }, + responseContent = { + ResultComponent( + modifier = Modifier, + result = requestContent.toJson(), + onClickCopy = {} + ) + } + ) } - CollapsableCard(cardTitle = "C2B Registration") { - C2BScreen(modifier = Modifier, daraja = daraja) + item { + CollapsableCard( + cardTitle = "C2B Registration", + requestContent = { C2BScreen(modifier = Modifier, daraja = daraja) }, + responseContent = {} + ) } - CollapsableCard(cardTitle = "Initiate C2B") { - C2BInitiateScreen(modifier = Modifier, daraja = daraja) + item { + CollapsableCard( + cardTitle = "Initiate C2B", + requestContent = { C2BInitiateScreen(modifier = Modifier, daraja = daraja) }, + responseContent = {} + ) } } } diff --git a/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/screen/MpesaExpressScreen.kt b/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/screen/MpesaExpressScreen.kt index 03471a07..08b32dde 100644 --- a/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/screen/MpesaExpressScreen.kt +++ b/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/screen/MpesaExpressScreen.kt @@ -38,13 +38,14 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.vickbt.darajakmp.Daraja +import com.vickbt.darajakmp.network.models.MpesaExpressResponse import com.vickbt.darajakmp.utils.DarajaTransactionType import com.vickbt.darajakmp.utils.onFailure import com.vickbt.darajakmp.utils.onSuccess import org.koin.compose.koinInject @Composable -fun MpesaExpressScreen(modifier: Modifier = Modifier, daraja: Daraja) { +fun MpesaExpressScreen(modifier: Modifier = Modifier, daraja: Daraja, onResult: (MpesaExpressResponse) -> Unit = {}) { val context = LocalContext.current @@ -115,7 +116,7 @@ fun MpesaExpressScreen(modifier: Modifier = Modifier, daraja: Daraja) { callbackUrl = "https://mydomain.com", accountReference = "CompanyX" ).onSuccess { - Toast.makeText(context, "Success: $it", Toast.LENGTH_SHORT).show() + onResult(it) }.onFailure { Toast.makeText(context, "Error: ${it.errorMessage}", Toast.LENGTH_SHORT).show() } diff --git a/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/screen/QrCodeScreen.kt b/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/screen/QrCodeScreen.kt index 09660fe1..f535e680 100644 --- a/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/screen/QrCodeScreen.kt +++ b/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/screen/QrCodeScreen.kt @@ -37,13 +37,14 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.vickbt.darajakmp.Daraja +import com.vickbt.darajakmp.network.models.DynamicQrResponse import com.vickbt.darajakmp.utils.DarajaTransactionCode import com.vickbt.darajakmp.utils.onFailure import com.vickbt.darajakmp.utils.onSuccess import java.util.UUID @Composable -fun QrCodeScreen(modifier: Modifier, daraja: Daraja) { +fun QrCodeScreen(modifier: Modifier, daraja: Daraja, onResult: (DynamicQrResponse) -> Unit = {}) { val context = LocalContext.current @@ -130,7 +131,7 @@ fun QrCodeScreen(modifier: Modifier, daraja: Daraja) { transactionCode = DarajaTransactionCode.PB, size = 300 ).onSuccess { - Toast.makeText(context, "Success: $it", Toast.LENGTH_SHORT).show() + onResult(it) isLoading = false }.onFailure { Toast.makeText(context, "Error: ${it.errorMessage}", Toast.LENGTH_SHORT) diff --git a/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/theme/Theme.kt b/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/theme/Theme.kt index 654e7371..2a67f709 100644 --- a/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/theme/Theme.kt +++ b/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/theme/Theme.kt @@ -35,7 +35,5 @@ fun DarajaAndroidTheme( else -> LightColorScheme } - MaterialTheme( - colorScheme = colorScheme, typography = Typography, content = content - ) + MaterialTheme(colorScheme = colorScheme, content = content) } \ No newline at end of file diff --git a/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/theme/Type.kt b/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/theme/Type.kt deleted file mode 100644 index 3b1e1e63..00000000 --- a/samples/android/app/src/main/java/com/vickbt/daraja/android/ui/theme/Type.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.vickbt.daraja.android.ui.theme - -import androidx.compose.material3.Typography -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp - -// Set of Material typography styles to start with -val Typography = Typography( - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp - ) - /* Other default text styles to override - titleLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 22.sp, - lineHeight = 28.sp, - letterSpacing = 0.sp - ), - labelSmall = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Medium, - fontSize = 11.sp, - lineHeight = 16.sp, - letterSpacing = 0.5.sp - ) - */ -) \ No newline at end of file diff --git a/samples/android/app/src/main/java/com/vickbt/daraja/android/utils/Extensions.kt b/samples/android/app/src/main/java/com/vickbt/daraja/android/utils/Extensions.kt new file mode 100644 index 00000000..3ec745dd --- /dev/null +++ b/samples/android/app/src/main/java/com/vickbt/daraja/android/utils/Extensions.kt @@ -0,0 +1,12 @@ +package com.vickbt.daraja.android.utils + +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +inline fun T.toJson(): String { + val json = Json { + prettyPrint = true + } + + return json.encodeToString(this).replaceFirst("\"","") +} \ No newline at end of file diff --git a/samples/android/gradle/libs.versions.toml b/samples/android/gradle/libs.versions.toml index eddd3b74..b65591af 100644 --- a/samples/android/gradle/libs.versions.toml +++ b/samples/android/gradle/libs.versions.toml @@ -2,6 +2,8 @@ agp = "8.5.2" kotlin = "1.9.0" coreKtx = "1.13.1" +serialization="1.9.0" +serializationJson ="1.7.3" junit = "4.13.2" junitVersion = "1.2.1" espressoCore = "3.6.1" @@ -25,6 +27,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +kotlinx-serialization={module="org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref= "serializationJson" } koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koinBom" } koin-android = { module = "io.insert-koin:koin-androidx-compose" } @@ -32,4 +35,5 @@ koin-android = { module = "io.insert-koin:koin-androidx-compose" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlinx-serialization={id="org.jetbrains.kotlin.plugin.serialization", version.ref="serialization"} diff --git a/app-iOS/app-iOS.xcodeproj/project.pbxproj b/samples/ios/Daraja iOS.xcodeproj/project.pbxproj similarity index 53% rename from app-iOS/app-iOS.xcodeproj/project.pbxproj rename to samples/ios/Daraja iOS.xcodeproj/project.pbxproj index 267b2056..260f48b8 100644 --- a/app-iOS/app-iOS.xcodeproj/project.pbxproj +++ b/samples/ios/Daraja iOS.xcodeproj/project.pbxproj @@ -3,136 +3,117 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ - 1C89232D2AA7C1EF00700D4F /* DarajaMultiplatform in Frameworks */ = {isa = PBXBuildFile; productRef = 1C89232C2AA7C1EF00700D4F /* DarajaMultiplatform */; }; - 1CDEAB762A8BC12A00EBC42E /* app_iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CDEAB752A8BC12A00EBC42E /* app_iOSApp.swift */; }; - 1CDEAB782A8BC12A00EBC42E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CDEAB772A8BC12A00EBC42E /* ContentView.swift */; }; - 1CDEAB7A2A8BC12C00EBC42E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1CDEAB792A8BC12C00EBC42E /* Assets.xcassets */; }; - 1CDEAB7D2A8BC12C00EBC42E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1CDEAB7C2A8BC12C00EBC42E /* Preview Assets.xcassets */; }; - 2F22CD940E774E4F0F3FFB25 /* Pods_app_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B2DEC6E2423A8E7DC28CEC6 /* Pods_app_iOS.framework */; }; + 1CC3931E2CDCE7FE0092A633 /* DarajaMultiplatform in Frameworks */ = {isa = PBXBuildFile; productRef = 1CC3931D2CDCE7FE0092A633 /* DarajaMultiplatform */; }; + 1CC393212CDCF2DD0092A633 /* DarajaMultiplatform in Frameworks */ = {isa = PBXBuildFile; productRef = 1CC393202CDCF2DD0092A633 /* DarajaMultiplatform */; }; + 1CC393242CDCF7C40092A633 /* DarajaMultiplatform in Frameworks */ = {isa = PBXBuildFile; productRef = 1CC393232CDCF7C40092A633 /* DarajaMultiplatform */; }; + 1CC4C39E2C7204EC00D66DB1 /* Daraja_iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC4C39D2C7204EC00D66DB1 /* Daraja_iOSApp.swift */; }; + 1CC4C3A02C7204EC00D66DB1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC4C39F2C7204EC00D66DB1 /* ContentView.swift */; }; + 1CC4C3A22C7204EE00D66DB1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1CC4C3A12C7204EE00D66DB1 /* Assets.xcassets */; }; + 1CC4C3A52C7204EE00D66DB1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1CC4C3A42C7204EE00D66DB1 /* Preview Assets.xcassets */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 1CDEAB722A8BC12A00EBC42E /* app-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "app-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 1CDEAB752A8BC12A00EBC42E /* app_iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = app_iOSApp.swift; sourceTree = ""; }; - 1CDEAB772A8BC12A00EBC42E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 1CDEAB792A8BC12C00EBC42E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 1CDEAB7C2A8BC12C00EBC42E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 6B2DEC6E2423A8E7DC28CEC6 /* Pods_app_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_app_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 93C28A294DB1FFBA3DF573C6 /* Pods-app-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-iOS.release.xcconfig"; path = "Target Support Files/Pods-app-iOS/Pods-app-iOS.release.xcconfig"; sourceTree = ""; }; - 965FE27CCADC2E0C3CD864CC /* Pods-app-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-iOS.debug.xcconfig"; path = "Target Support Files/Pods-app-iOS/Pods-app-iOS.debug.xcconfig"; sourceTree = ""; }; + 1CC4C39A2C7204EC00D66DB1 /* Daraja iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Daraja iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1CC4C39D2C7204EC00D66DB1 /* Daraja_iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Daraja_iOSApp.swift; sourceTree = ""; }; + 1CC4C39F2C7204EC00D66DB1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 1CC4C3A12C7204EE00D66DB1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 1CC4C3A42C7204EE00D66DB1 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 1CDEAB6F2A8BC12A00EBC42E /* Frameworks */ = { + 1CC4C3972C7204EC00D66DB1 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 1C89232D2AA7C1EF00700D4F /* DarajaMultiplatform in Frameworks */, - 2F22CD940E774E4F0F3FFB25 /* Pods_app_iOS.framework in Frameworks */, + 1CC393212CDCF2DD0092A633 /* DarajaMultiplatform in Frameworks */, + 1CC3931E2CDCE7FE0092A633 /* DarajaMultiplatform in Frameworks */, + 1CC393242CDCF7C40092A633 /* DarajaMultiplatform in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 18EE2D64E07D047770CDEB83 /* Pods */ = { + 1CC4C3912C7204EC00D66DB1 = { isa = PBXGroup; children = ( - 965FE27CCADC2E0C3CD864CC /* Pods-app-iOS.debug.xcconfig */, - 93C28A294DB1FFBA3DF573C6 /* Pods-app-iOS.release.xcconfig */, + 1CC4C39C2C7204EC00D66DB1 /* Daraja iOS */, + 1CC4C39B2C7204EC00D66DB1 /* Products */, ); - path = Pods; sourceTree = ""; }; - 1CDEAB692A8BC12A00EBC42E = { + 1CC4C39B2C7204EC00D66DB1 /* Products */ = { isa = PBXGroup; children = ( - 1CDEAB742A8BC12A00EBC42E /* app-iOS */, - 1CDEAB732A8BC12A00EBC42E /* Products */, - 18EE2D64E07D047770CDEB83 /* Pods */, - CAA5BE13CB8DBB302143230F /* Frameworks */, - ); - sourceTree = ""; - }; - 1CDEAB732A8BC12A00EBC42E /* Products */ = { - isa = PBXGroup; - children = ( - 1CDEAB722A8BC12A00EBC42E /* app-iOS.app */, + 1CC4C39A2C7204EC00D66DB1 /* Daraja iOS.app */, ); name = Products; sourceTree = ""; }; - 1CDEAB742A8BC12A00EBC42E /* app-iOS */ = { + 1CC4C39C2C7204EC00D66DB1 /* Daraja iOS */ = { isa = PBXGroup; children = ( - 1CDEAB752A8BC12A00EBC42E /* app_iOSApp.swift */, - 1CDEAB772A8BC12A00EBC42E /* ContentView.swift */, - 1CDEAB792A8BC12C00EBC42E /* Assets.xcassets */, - 1CDEAB7B2A8BC12C00EBC42E /* Preview Content */, + 1CC4C39D2C7204EC00D66DB1 /* Daraja_iOSApp.swift */, + 1CC4C39F2C7204EC00D66DB1 /* ContentView.swift */, + 1CC4C3A12C7204EE00D66DB1 /* Assets.xcassets */, + 1CC4C3A32C7204EE00D66DB1 /* Preview Content */, ); - path = "app-iOS"; + path = "Daraja iOS"; sourceTree = ""; }; - 1CDEAB7B2A8BC12C00EBC42E /* Preview Content */ = { + 1CC4C3A32C7204EE00D66DB1 /* Preview Content */ = { isa = PBXGroup; children = ( - 1CDEAB7C2A8BC12C00EBC42E /* Preview Assets.xcassets */, + 1CC4C3A42C7204EE00D66DB1 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; - CAA5BE13CB8DBB302143230F /* Frameworks */ = { - isa = PBXGroup; - children = ( - 6B2DEC6E2423A8E7DC28CEC6 /* Pods_app_iOS.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 1CDEAB712A8BC12A00EBC42E /* app-iOS */ = { + 1CC4C3992C7204EC00D66DB1 /* Daraja iOS */ = { isa = PBXNativeTarget; - buildConfigurationList = 1CDEAB802A8BC12C00EBC42E /* Build configuration list for PBXNativeTarget "app-iOS" */; + buildConfigurationList = 1CC4C3A82C7204EE00D66DB1 /* Build configuration list for PBXNativeTarget "Daraja iOS" */; buildPhases = ( - 753367D962DB398356D0B725 /* [CP] Check Pods Manifest.lock */, - 1CDEAB6E2A8BC12A00EBC42E /* Sources */, - 1CDEAB6F2A8BC12A00EBC42E /* Frameworks */, - 1CDEAB702A8BC12A00EBC42E /* Resources */, + 1CC4C3962C7204EC00D66DB1 /* Sources */, + 1CC4C3972C7204EC00D66DB1 /* Frameworks */, + 1CC4C3982C7204EC00D66DB1 /* Resources */, ); buildRules = ( ); dependencies = ( ); - name = "app-iOS"; + name = "Daraja iOS"; packageProductDependencies = ( - 1C89232C2AA7C1EF00700D4F /* DarajaMultiplatform */, + 1CC3931D2CDCE7FE0092A633 /* DarajaMultiplatform */, + 1CC393202CDCF2DD0092A633 /* DarajaMultiplatform */, + 1CC393232CDCF7C40092A633 /* DarajaMultiplatform */, ); - productName = "app-iOS"; - productReference = 1CDEAB722A8BC12A00EBC42E /* app-iOS.app */; + productName = "Daraja iOS"; + productReference = 1CC4C39A2C7204EC00D66DB1 /* Daraja iOS.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - 1CDEAB6A2A8BC12A00EBC42E /* Project object */ = { + 1CC4C3922C7204EC00D66DB1 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1430; - LastUpgradeCheck = 1430; + LastSwiftUpdateCheck = 1540; + LastUpgradeCheck = 1540; TargetAttributes = { - 1CDEAB712A8BC12A00EBC42E = { - CreatedOnToolsVersion = 14.3.1; + 1CC4C3992C7204EC00D66DB1 = { + CreatedOnToolsVersion = 15.4; }; }; }; - buildConfigurationList = 1CDEAB6D2A8BC12A00EBC42E /* Build configuration list for PBXProject "app-iOS" */; + buildConfigurationList = 1CC4C3952C7204EC00D66DB1 /* Build configuration list for PBXProject "Daraja iOS" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; @@ -140,73 +121,49 @@ en, Base, ); - mainGroup = 1CDEAB692A8BC12A00EBC42E; + mainGroup = 1CC4C3912C7204EC00D66DB1; packageReferences = ( - 1C89232B2AA7C1EF00700D4F /* XCRemoteSwiftPackageReference "DarajaSwiftPackage" */, + 1CC393222CDCF7C40092A633 /* XCLocalSwiftPackageReference "../../daraja/swiftpackage" */, ); - productRefGroup = 1CDEAB732A8BC12A00EBC42E /* Products */; + productRefGroup = 1CC4C39B2C7204EC00D66DB1 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - 1CDEAB712A8BC12A00EBC42E /* app-iOS */, + 1CC4C3992C7204EC00D66DB1 /* Daraja iOS */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 1CDEAB702A8BC12A00EBC42E /* Resources */ = { + 1CC4C3982C7204EC00D66DB1 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1CDEAB7D2A8BC12C00EBC42E /* Preview Assets.xcassets in Resources */, - 1CDEAB7A2A8BC12C00EBC42E /* Assets.xcassets in Resources */, + 1CC4C3A52C7204EE00D66DB1 /* Preview Assets.xcassets in Resources */, + 1CC4C3A22C7204EE00D66DB1 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 753367D962DB398356D0B725 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-app-iOS-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ - 1CDEAB6E2A8BC12A00EBC42E /* Sources */ = { + 1CC4C3962C7204EC00D66DB1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1CDEAB782A8BC12A00EBC42E /* ContentView.swift in Sources */, - 1CDEAB762A8BC12A00EBC42E /* app_iOSApp.swift in Sources */, + 1CC4C3A02C7204EC00D66DB1 /* ContentView.swift in Sources */, + 1CC4C39E2C7204EC00D66DB1 /* Daraja_iOSApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ - 1CDEAB7E2A8BC12C00EBC42E /* Debug */ = { + 1CC4C3A62C7204EE00D66DB1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -239,7 +196,8 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; @@ -253,20 +211,22 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; - 1CDEAB7F2A8BC12C00EBC42E /* Release */ = { + 1CC4C3A72C7204EE00D66DB1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -299,7 +259,8 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -307,28 +268,26 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; - 1CDEAB812A8BC12C00EBC42E /* Debug */ = { + 1CC4C3A92C7204EE00D66DB1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 965FE27CCADC2E0C3CD864CC /* Pods-app-iOS.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"app-iOS/Preview Content\""; + DEVELOPMENT_ASSET_PATHS = "\"Daraja iOS/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleDisplayName = "Daraja Multiplatform iOS"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -339,7 +298,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "VicKbt.app-iOS"; + PRODUCT_BUNDLE_IDENTIFIER = "VicKbt.Daraja-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -347,18 +306,16 @@ }; name = Debug; }; - 1CDEAB822A8BC12C00EBC42E /* Release */ = { + 1CC4C3AA2C7204EE00D66DB1 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 93C28A294DB1FFBA3DF573C6 /* Pods-app-iOS.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"app-iOS/Preview Content\""; + DEVELOPMENT_ASSET_PATHS = "\"Daraja iOS/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleDisplayName = "Daraja Multiplatform iOS"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -369,7 +326,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "VicKbt.app-iOS"; + PRODUCT_BUNDLE_IDENTIFIER = "VicKbt.Daraja-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -380,44 +337,47 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 1CDEAB6D2A8BC12A00EBC42E /* Build configuration list for PBXProject "app-iOS" */ = { + 1CC4C3952C7204EC00D66DB1 /* Build configuration list for PBXProject "Daraja iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( - 1CDEAB7E2A8BC12C00EBC42E /* Debug */, - 1CDEAB7F2A8BC12C00EBC42E /* Release */, + 1CC4C3A62C7204EE00D66DB1 /* Debug */, + 1CC4C3A72C7204EE00D66DB1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 1CDEAB802A8BC12C00EBC42E /* Build configuration list for PBXNativeTarget "app-iOS" */ = { + 1CC4C3A82C7204EE00D66DB1 /* Build configuration list for PBXNativeTarget "Daraja iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( - 1CDEAB812A8BC12C00EBC42E /* Debug */, - 1CDEAB822A8BC12C00EBC42E /* Release */, + 1CC4C3A92C7204EE00D66DB1 /* Debug */, + 1CC4C3AA2C7204EE00D66DB1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ -/* Begin XCRemoteSwiftPackageReference section */ - 1C89232B2AA7C1EF00700D4F /* XCRemoteSwiftPackageReference "DarajaSwiftPackage" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/VictorKabata/DarajaSwiftPackage.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.0.1; - }; +/* Begin XCLocalSwiftPackageReference section */ + 1CC393222CDCF7C40092A633 /* XCLocalSwiftPackageReference "../../daraja/swiftpackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../../daraja/swiftpackage; }; -/* End XCRemoteSwiftPackageReference section */ +/* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 1C89232C2AA7C1EF00700D4F /* DarajaMultiplatform */ = { + 1CC3931D2CDCE7FE0092A633 /* DarajaMultiplatform */ = { + isa = XCSwiftPackageProductDependency; + productName = DarajaMultiplatform; + }; + 1CC393202CDCF2DD0092A633 /* DarajaMultiplatform */ = { + isa = XCSwiftPackageProductDependency; + productName = DarajaMultiplatform; + }; + 1CC393232CDCF7C40092A633 /* DarajaMultiplatform */ = { isa = XCSwiftPackageProductDependency; - package = 1C89232B2AA7C1EF00700D4F /* XCRemoteSwiftPackageReference "DarajaSwiftPackage" */; productName = DarajaMultiplatform; }; /* End XCSwiftPackageProductDependency section */ }; - rootObject = 1CDEAB6A2A8BC12A00EBC42E /* Project object */; + rootObject = 1CC4C3922C7204EC00D66DB1 /* Project object */; } diff --git a/app-iOS/app-iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/samples/ios/Daraja iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from app-iOS/app-iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to samples/ios/Daraja iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/app-iOS/app-iOS/Assets.xcassets/AccentColor.colorset/Contents.json b/samples/ios/Daraja iOS/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from app-iOS/app-iOS/Assets.xcassets/AccentColor.colorset/Contents.json rename to samples/ios/Daraja iOS/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/app-iOS/app-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/samples/ios/Daraja iOS/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from app-iOS/app-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json rename to samples/ios/Daraja iOS/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/app-iOS/app-iOS/Assets.xcassets/Contents.json b/samples/ios/Daraja iOS/Assets.xcassets/Contents.json similarity index 100% rename from app-iOS/app-iOS/Assets.xcassets/Contents.json rename to samples/ios/Daraja iOS/Assets.xcassets/Contents.json diff --git a/samples/ios/Daraja iOS/ContentView.swift b/samples/ios/Daraja iOS/ContentView.swift new file mode 100644 index 00000000..cb82b82f --- /dev/null +++ b/samples/ios/Daraja iOS/ContentView.swift @@ -0,0 +1,73 @@ +// +// ContentView.swift +// Daraja iOS +// +// Created by Victor Kabata on 18/08/2024. +// + +import SwiftUI +import DarajaMultiplatform + +struct ContentView: View { + var body: some View { + +// let daraja=Daraja(consumerKey: "ewP4be7L00bA8RylBT0z9tEhlgkMlLJiLV0gJB374BeGnyJ7", consumerSecret: "o2fNGnTmYfHAfl65HAaK9jLh2a703wkTgiz1dqGCO9Vi3yBCOBL5Rpuu13kIgrQm", passKey: "bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919", environment: DarajaEnvironment.sandboxEnvironment) + + List { + DisclosureGroup("M-Pesa Express") { + @State var amount: Int32 = 1 + @FocusState var isAmountTextFieldFocused: Bool + + @State var phoneNumber: String = "" + @FocusState var isPhoneTextFieldFocused: Bool + + VStack { + // Amount textfield + TextField("Amount", text: .init(get: { "\(amount)" }, set: { + if let newValue = Int32($0) { + amount = newValue + } + })) + .padding() + .accentColor(.green) + .background(.white) + .keyboardType(.numberPad) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(isAmountTextFieldFocused ? Color.green : Color.gray.opacity(0.5), lineWidth: 1) + ) + .cornerRadius(10) + .shadow(color: Color.gray.opacity(0.4), radius: 4, x: 0, y: 2) + .focused($isAmountTextFieldFocused) + + // Phone Number textfield + TextField("Phone Number", text: $phoneNumber) + .padding() + .accentColor(.green) + .background(.white) + .keyboardType(.phonePad) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(isPhoneTextFieldFocused ? Color.green : Color.gray.opacity(0.5), lineWidth: 1) + ) + .cornerRadius(10) + .shadow(color: Color.gray.opacity(0.4), radius: 4, x: 0, y: 2) + .focused($isPhoneTextFieldFocused) + + // Pay Button + Button(action: {}) { + Image(systemName: "plus") + .padding(20) + .background(Color.green) + .foregroundColor(.white) + .clipShape(Circle()) + } + } + } + } + } +} + +#Preview { + ContentView() +} diff --git a/app-iOS/app-iOS/app_iOSApp.swift b/samples/ios/Daraja iOS/Daraja_iOSApp.swift similarity index 52% rename from app-iOS/app-iOS/app_iOSApp.swift rename to samples/ios/Daraja iOS/Daraja_iOSApp.swift index bbeedd73..dcf488a4 100644 --- a/app-iOS/app-iOS/app_iOSApp.swift +++ b/samples/ios/Daraja iOS/Daraja_iOSApp.swift @@ -1,14 +1,14 @@ // -// app_iOSApp.swift -// app-iOS +// Daraja_iOSApp.swift +// Daraja iOS // -// Created by Victor Kabata on 15/08/2023. +// Created by Victor Kabata on 18/08/2024. // import SwiftUI @main -struct app_iOSApp: App { +struct Daraja_iOSApp: App { var body: some Scene { WindowGroup { ContentView() diff --git a/samples/ios/Daraja iOS/MpesaExpressView.swift b/samples/ios/Daraja iOS/MpesaExpressView.swift new file mode 100644 index 00000000..5822e057 --- /dev/null +++ b/samples/ios/Daraja iOS/MpesaExpressView.swift @@ -0,0 +1,8 @@ +// +// MpesaExpressView.swift +// Daraja iOS +// +// Created by Victor Kabata on 18/08/2024. +// + +import Foundation diff --git a/app-iOS/app-iOS/Preview Content/Preview Assets.xcassets/Contents.json b/samples/ios/Daraja iOS/Preview Content/Preview Assets.xcassets/Contents.json similarity index 100% rename from app-iOS/app-iOS/Preview Content/Preview Assets.xcassets/Contents.json rename to samples/ios/Daraja iOS/Preview Content/Preview Assets.xcassets/Contents.json