├── .gitignore ├── README.md ├── androidApp ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── development │ └── res │ │ ├── keystore.jks.encrypted │ │ └── keystore.properties.encrypted │ ├── integration │ └── res │ │ ├── keystore.jks.encrypted │ │ └── keystore.properties.encrypted │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── br │ │ │ └── com │ │ │ └── progdeelite │ │ │ └── kmmprogdeelite │ │ │ └── android │ │ │ ├── localization │ │ │ └── LocaliseSdkWrapper.kt │ │ │ ├── modifiers │ │ │ └── Modifiers.kt │ │ │ ├── tracking │ │ │ └── adobe │ │ │ │ └── AdobeAnalytics.kt │ │ │ ├── ui │ │ │ ├── MainApplication.kt │ │ │ ├── activity │ │ │ │ └── MainActivity.kt │ │ │ ├── components │ │ │ │ ├── AccessibilityText.kt │ │ │ │ ├── BottomNavigationBar.kt │ │ │ │ ├── BottomSheet.kt │ │ │ │ ├── Buttons.kt │ │ │ │ ├── CardComponent.kt │ │ │ │ ├── ConnectivityStatus.kt │ │ │ │ ├── CurvedCanvas.kt │ │ │ │ ├── Dialogs.kt │ │ │ │ ├── DoubleSwitch.kt │ │ │ │ ├── DummyScreen.kt │ │ │ │ ├── FullScreenMessageDialog.kt │ │ │ │ ├── HorizontalViewPager.kt │ │ │ │ ├── LanguagePickerBottomSheet.kt │ │ │ │ ├── ListViewItem.kt │ │ │ │ ├── Spacing.kt │ │ │ │ ├── StateLessonsLearned.kt │ │ │ │ ├── TextFields.kt │ │ │ │ ├── Texts.kt │ │ │ │ └── WebView.kt │ │ │ ├── headers │ │ │ │ ├── ActionHeader.kt │ │ │ │ ├── HeaderOverlay.kt │ │ │ │ └── NavigationHeader.kt │ │ │ ├── navigation │ │ │ │ ├── Navigation.kt │ │ │ │ └── NavigationDeeplink.kt │ │ │ ├── screens │ │ │ │ ├── AnimationScreen.kt │ │ │ │ ├── BaseScreen.kt │ │ │ │ ├── ConfirmLoginScreen.kt │ │ │ │ ├── ConfirmRegisterScreen.kt │ │ │ │ ├── ConfirmScreen.kt │ │ │ │ ├── HomeScreen.kt │ │ │ │ ├── InsuranceScreen.kt │ │ │ │ ├── LoginScreen.kt │ │ │ │ ├── OnBoardingScreen.kt │ │ │ │ ├── ProfileScreen.kt │ │ │ │ ├── RegisterScreen.kt │ │ │ │ └── SupportScreen.kt │ │ │ ├── shapes │ │ │ │ └── Shapes.kt │ │ │ └── theme │ │ │ │ ├── FontTypes.kt │ │ │ │ ├── Spacing.kt │ │ │ │ ├── TextStyles.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ └── utils │ │ │ ├── CallToActionExt.kt │ │ │ ├── ConnectivityUtil.kt │ │ │ ├── DependencyInjectionForPreview.kt │ │ │ ├── ModifierUtil.kt │ │ │ └── UtilExt.kt │ └── res │ │ ├── drawable │ │ ├── app_gradient_bg.xml │ │ ├── fire.xml │ │ ├── flash.xml │ │ ├── home.xml │ │ ├── ic_warning.xml │ │ ├── info.xml │ │ ├── insurance.xml │ │ ├── lamp.xml │ │ ├── profile.xml │ │ ├── splash_transparent_image.xml │ │ ├── support.xml │ │ ├── switch_cam.xml │ │ └── wifi.xml │ │ ├── font │ │ ├── tt_norms_bold_webfont.ttf │ │ ├── tt_norms_medium_webfont.ttf │ │ └── tt_norms_regular_webfont.ttf │ │ ├── raw │ │ ├── keep.xml │ │ ├── loading_lottie_loading_animation.json │ │ └── splash_loading.json │ │ ├── values-v31 │ │ └── styles.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── production │ └── res │ ├── keystore.jks.encrypted │ └── keystore.properties.encrypted ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── java │ └── Dependencies.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── iosApp ├── iosApp.xcodeproj │ └── project.pbxproj └── iosApp │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ └── iOSApp.swift ├── proguard-rules.pro ├── settings.gradle.kts ├── shared ├── build.gradle.kts └── src │ ├── androidAndroidTestDebug │ └── kotlin │ │ └── br │ │ └── com │ │ └── progdeelite │ │ └── kmmprogdeelite │ │ └── database │ │ └── DatabaseTest.kt │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ └── br │ │ └── com │ │ └── progdeelite │ │ └── kmmprogdeelite │ │ ├── Platform.kt │ │ ├── database │ │ └── DatabaseDriverFactory.kt │ │ ├── localization │ │ └── Localization.kt │ │ ├── network │ │ ├── GetNetwork.kt │ │ └── OkHttpClientFactory.kt │ │ ├── resources │ │ ├── ColorResource.kt │ │ ├── DimenResource.kt │ │ ├── FontSizingResource.kt │ │ ├── ImageResource.kt │ │ ├── SpacingResource.kt │ │ └── components │ │ │ └── ResendSmsResources.kt │ │ ├── settings │ │ └── Settings.kt │ │ ├── utils │ │ ├── AndroidMainApp.kt │ │ ├── CommonLogger.kt │ │ └── UtilExtensions.kt │ │ └── viewmodels │ │ └── BaseSharedViewModel.kt │ ├── androidTest │ └── kotlin │ │ └── br │ │ └── com │ │ └── progdeelite │ │ └── kmmprogdeelite │ │ └── androidTest.kt │ ├── commonMain │ ├── kotlin │ │ └── br │ │ │ └── com │ │ │ └── progdeelite │ │ │ └── kmmprogdeelite │ │ │ ├── Greeting.kt │ │ │ ├── Platform.kt │ │ │ ├── database │ │ │ ├── Database.kt │ │ │ └── DatabaseDriverFactory.kt │ │ │ ├── di │ │ │ ├── DI.kt │ │ │ └── FakeDI.kt │ │ │ ├── localization │ │ │ ├── DialogTexts.kt │ │ │ ├── Language.kt │ │ │ ├── LanguagePickerTexts.kt │ │ │ ├── Localization.kt │ │ │ └── LokaliseSdk.kt │ │ │ ├── models │ │ │ └── Story.kt │ │ │ ├── navigation │ │ │ ├── BottomBarItem.kt │ │ │ ├── Graphs.kt │ │ │ └── Navigation.kt │ │ │ ├── network │ │ │ ├── ApiEndpoints.kt │ │ │ ├── ClientConfig.kt │ │ │ ├── Environment.kt │ │ │ ├── GetNetwork.kt │ │ │ ├── NetworkResult.kt │ │ │ ├── SafeApiCall.kt │ │ │ └── models │ │ │ │ ├── ApiError.kt │ │ │ │ └── EntryResponse.kt │ │ │ ├── providers │ │ │ └── DataSourceProvider.kt │ │ │ ├── repositories │ │ │ └── EntryRepository.kt │ │ │ ├── resources │ │ │ ├── ColorResource.kt │ │ │ ├── ColorResources.kt │ │ │ ├── ColorScheme.kt │ │ │ ├── DimenResource.kt │ │ │ ├── FontSizingResource.kt │ │ │ ├── FontSizingResources.kt │ │ │ ├── ImageResource.kt │ │ │ ├── ImageResources.kt │ │ │ ├── Resources.kt │ │ │ ├── SpacingResource.kt │ │ │ ├── SpacingResources.kt │ │ │ ├── StringResources.kt │ │ │ └── TextResource.kt │ │ │ ├── settings │ │ │ ├── Settings.kt │ │ │ ├── SettingsKeys.kt │ │ │ └── SettingsService.kt │ │ │ ├── tracking │ │ │ └── adobe │ │ │ │ └── AdobeAnalytics.kt │ │ │ ├── utils │ │ │ ├── CallToAction.kt │ │ │ ├── CommonLogger.kt │ │ │ ├── DeviceConnectivity.kt │ │ │ ├── Extentions.kt │ │ │ ├── LoadingState.kt │ │ │ └── Logger.kt │ │ │ ├── validations │ │ │ └── Validations.kt │ │ │ └── viewmodels │ │ │ ├── AuthViewModel.kt │ │ │ ├── BaseSharedViewModel.kt │ │ │ ├── EntryViewModel.kt │ │ │ ├── LanguageViewModel.kt │ │ │ ├── MainActivityViewModel.kt │ │ │ ├── OnBoardingViewModel.kt │ │ │ ├── SampleViewModel.kt │ │ │ └── ShimmerViewModel.kt │ └── sqldelight │ │ └── br │ │ └── com │ │ └── progdeelite │ │ └── kmmprogdeelite │ │ └── database │ │ └── AppDatabase.sq │ ├── commonTest │ └── kotlin │ │ └── br │ │ └── com │ │ └── progdeelite │ │ └── kmmprogdeelite │ │ ├── MockSampleTest.kt │ │ └── commonTest.kt │ ├── iosMain │ └── kotlin │ │ └── br │ │ └── com │ │ └── progdeelite │ │ └── kmmprogdeelite │ │ ├── Platform.kt │ │ ├── database │ │ └── DatabaseDriverFactory.kt │ │ ├── localization │ │ └── Localization.kt │ │ ├── network │ │ └── GetNetwork.kt │ │ ├── resources │ │ ├── ColorResource.kt │ │ ├── DimenResource.kt │ │ ├── FontSizingResource.kt │ │ ├── ImageResource.kt │ │ └── SpacingResource.kt │ │ ├── settings │ │ └── Settings.kt │ │ ├── utils │ │ ├── CommonLogger.kt │ │ └── IOSMainApp.kt │ │ └── viewmodels │ │ └── BaseSharedViewModel.kt │ └── iosTest │ └── kotlin │ └── br │ └── com │ └── progdeelite │ └── kmmprogdeelite │ └── iosTest.kt └── tools ├── convert_assets.sh ├── create_image_resources.sh ├── create_text_resources.sh ├── decrypt_secrets.sh ├── encrypt_secrets.sh └── read_keystore_secret.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | .DS_Store 5 | build 6 | /build 7 | */build 8 | captures 9 | /captures 10 | .externalNativeBuild 11 | .cxx 12 | local.properties 13 | /local.properties 14 | *keystore.properties 15 | *.jks 16 | *yarn.lock 17 | *google-services.json 18 | *GoogleServices-Info.plist 19 | xcuserdata 20 | xcuserdata/ -------------------------------------------------------------------------------- /androidApp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # https://developer.android.com/studio/build/shrink-code 3 | 4 | # Enable this while debugging 5 | #-keepnames class ** 6 | 7 | -dontwarn org.bouncycastle.jsse.BCSSLParameters 8 | -dontwarn org.bouncycastle.jsse.BCSSLSocket 9 | -dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider 10 | -dontwarn org.conscrypt.Conscrypt$Version 11 | -dontwarn org.conscrypt.Conscrypt 12 | -dontwarn org.conscrypt.ConscryptHostnameVerifier 13 | -dontwarn org.openjsse.javax.net.ssl.SSLParameters 14 | -dontwarn org.openjsse.javax.net.ssl.SSLSocket 15 | -dontwarn org.openjsse.net.ssl.OpenJSSE 16 | -dontwarn org.slf4j.impl.StaticLoggerBinder 17 | 18 | # https://developer.android.com/studio/build/shrink-code#usage 19 | # -printusage "~/temp/r8_usage.txt" 20 | # -printconfiguration "~/temp/r8_config.txt" 21 | 22 | 23 | # -keepattributes LineNumberTable,SourceFile 24 | # -renamesourcefileattribute SourceFile 25 | 26 | # -keep class com.seu_pacote_projeto.** { *; } -------------------------------------------------------------------------------- /androidApp/src/development/res/keystore.jks.encrypted: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treslines/kotlin_multiplatform_mobile/15d00b980111bd6283521fb477ac67711bc987f0/androidApp/src/development/res/keystore.jks.encrypted -------------------------------------------------------------------------------- /androidApp/src/development/res/keystore.properties.encrypted: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treslines/kotlin_multiplatform_mobile/15d00b980111bd6283521fb477ac67711bc987f0/androidApp/src/development/res/keystore.properties.encrypted -------------------------------------------------------------------------------- /androidApp/src/integration/res/keystore.jks.encrypted: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treslines/kotlin_multiplatform_mobile/15d00b980111bd6283521fb477ac67711bc987f0/androidApp/src/integration/res/keystore.jks.encrypted -------------------------------------------------------------------------------- /androidApp/src/integration/res/keystore.properties.encrypted: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treslines/kotlin_multiplatform_mobile/15d00b980111bd6283521fb477ac67711bc987f0/androidApp/src/integration/res/keystore.properties.encrypted -------------------------------------------------------------------------------- /androidApp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/localization/LocaliseSdkWrapper.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.localization 2 | 3 | import android.content.Context 4 | import br.com.progdeelite.kmmprogdeelite.android.BuildConfig 5 | import br.com.progdeelite.kmmprogdeelite.localization.Language 6 | import br.com.progdeelite.kmmprogdeelite.localization.LokaliseSdk 7 | import br.com.progdeelite.kmmprogdeelite.utils.logD 8 | import com.lokalise.sdk.Lokalise 9 | import com.lokalise.sdk.LokaliseCallback 10 | import com.lokalise.sdk.LokaliseResources 11 | import com.lokalise.sdk.LokaliseUpdateError 12 | import java.util.Locale 13 | 14 | class LocaliseSdkWrapper (private val appContext: Context): LokaliseSdk { 15 | 16 | private val logContext = "LocaliseSdkWrapper" 17 | init { 18 | // TODO: comentado pois não esta sendo usado no projeto, apenas para fins didáticos 19 | Lokalise.init(appContext, "CommonConfig.LOKALISE_TOKEN", "CommonConfig.LOKALISE_PROJEKT_ID") 20 | if (BuildConfig.DEBUG) { 21 | Lokalise.isPreRelease = true // Configuration in Lokalise dashboard to take all strings while developing 22 | } 23 | 24 | // ONLY TO DETECT FAILURE WHILE DEVELOPING 25 | Lokalise.addCallback(object: LokaliseCallback { 26 | override fun onUpdateFailed(error: LokaliseUpdateError) { 27 | logD(logContext, "callback onUpdateFailed: ${error.name}") 28 | } 29 | override fun onUpdateNotNeeded() { /* ignored */ } 30 | override fun onUpdated(oldBundleId: Long, newBundleId: Long) { /* ignored */ } 31 | }) 32 | 33 | Lokalise.updateTranslations() 34 | } 35 | 36 | override fun lokalise(stringRef: String): String? = LokaliseResources(appContext).getString(stringRef) 37 | 38 | override fun loadResources() { 39 | LokaliseResources(appContext) 40 | } 41 | 42 | override fun changeLanguage(language: Language) = Lokalise.setLocale(language.isoCode, language.region) 43 | 44 | override fun geLokaliseLanguage(): Language { 45 | val isoCode = Locale.getDefault().language 46 | val region = Locale.getDefault().country 47 | return Language.getLanguageByIsoCodeAndRegion(isoCode,region) 48 | } 49 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/modifiers/Modifiers.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.modifiers 2 | 3 | import androidx.compose.runtime.MutableState 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.runtime.remember 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.composed 8 | import androidx.compose.ui.focus.FocusState 9 | import androidx.compose.ui.focus.onFocusChanged 10 | import androidx.compose.ui.platform.debugInspectorInfo 11 | 12 | fun Modifier.onFocusChangedIgnoreInitialState(onFocusChanged: (FocusState) -> Unit): Modifier = 13 | composed( 14 | inspectorInfo = debugInspectorInfo { 15 | name = "onFocusChangedIgnoreInitialState" 16 | properties["onFocusChanged"] = onFocusChanged 17 | } 18 | ) { 19 | val focusState: MutableState = remember { mutableStateOf(null) } 20 | Modifier.onFocusChanged { 21 | if(focusState.value != it) { 22 | if(focusState.value != null){ 23 | onFocusChanged(it) 24 | } 25 | focusState.value = it 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/tracking/adobe/AdobeAnalytics.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.tracking.adobe 2 | 3 | import android.app.Application 4 | import br.com.progdeelite.kmmprogdeelite.tracking.adobe.AdobeAnalyticsSdk 5 | import br.com.progdeelite.kmmprogdeelite.utils.logD 6 | import br.com.progdeelite.kmmprogdeelite.utils.setLogLevelByBuildFlavor 7 | import com.adobe.marketing.mobile.* 8 | 9 | class AdobeAnalyticsSdkWrapper(app: Application) : AdobeAnalyticsSdk { 10 | 11 | private val logContext = "AdobeAnalyticsSdkWrapper" 12 | private val propertyMap: MutableMap = mutableMapOf() 13 | 14 | init { 15 | // https://aep-sdks.gitbook.io/docs/using-mobile-extensions/adobe-analytics#register-analytics-with-mobile-core 16 | MobileCore.setApplication(app) 17 | setLogLevelByBuildFlavor(BuildConfig.FLAVOR) { MobileCore.setLogLevel(LoggingMode.DEBUG) } 18 | MobileCore.configureWithAppID("CommonConfig.ADOBE_APP_ID") 19 | 20 | try { 21 | Analytics.registerExtension() 22 | Identity.registerExtension() 23 | Lifecycle.registerExtension() 24 | MobileCore.start(null) 25 | } catch (e: InvalidInitException) { 26 | logD(logContext,"Could not initialize Adobe MobileCore: ${e.message}") 27 | } 28 | } 29 | 30 | override fun trackAction(name: String, contextData: Map) { 31 | val combined = contextData.toMutableMap() 32 | combined.putAll(propertyMap) 33 | MobileCore.trackAction(name, combined) 34 | } 35 | 36 | override fun trackScreen(name: String, contextData: Map?) { 37 | val combined = contextData?.toMutableMap() ?: mutableMapOf() 38 | combined.putAll(propertyMap) 39 | MobileCore.trackState(name, combined) 40 | } 41 | 42 | override fun setProperty(name: String, value: String) { 43 | propertyMap[name] = value 44 | } 45 | 46 | override fun unsetProperty(name: String) { 47 | when{ 48 | propertyMap.containsKey(name) -> propertyMap.remove(name) 49 | else -> Unit 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import br.com.progdeelite.kmmprogdeelite.android.localization.LocaliseSdkWrapper 6 | import br.com.progdeelite.kmmprogdeelite.android.tracking.adobe.AdobeAnalyticsSdkWrapper 7 | import br.com.progdeelite.kmmprogdeelite.di.DI 8 | import br.com.progdeelite.kmmprogdeelite.network.Environment 9 | // import androidx.navigation.NavHostController // TODO: nav 10 | import br.com.progdeelite.kmmprogdeelite.utils.AndroidMainApp 11 | 12 | class MainApplication: Application() { 13 | 14 | override fun attachBaseContext(base: Context?) { 15 | super.attachBaseContext(base) 16 | base?.let { 17 | // inject dependencies into androidMain 18 | AndroidMainApp.applicationContext = it 19 | DI.Native.lokaliseSdk = LocaliseSdkWrapper(it) 20 | } 21 | setEnvironment() 22 | } 23 | 24 | override fun onCreate() { 25 | super.onCreate() 26 | DI.Native.adobeAnalyticsSdk = AdobeAnalyticsSdkWrapper(this) 27 | 28 | } 29 | 30 | // APENAS PARA FINS DIDÁTICOS ASSIM. PARA MAIS DETALHES VEJA: 31 | // - https://developer.android.com/studio/build/build-variants#kts 32 | // - https://developer.android.com/studio/build 33 | private fun setEnvironment() { 34 | // TODO: comentado pois não esta sendo usado no projeto, apenas para fins didáticos 35 | DI.Native.environment = Environment.getEnvironmentByBuildFlavor("CommonConfig.FLAVOR") 36 | } 37 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/components/DummyScreen.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.components 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.material.MaterialTheme 8 | import androidx.compose.material.Text 9 | import androidx.compose.material.TextField 10 | import androidx.compose.material.TextFieldDefaults 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import br.com.progdeelite.kmmprogdeelite.android.ui.theme.AndroidAppTheme 16 | import br.com.progdeelite.kmmprogdeelite.android.utils.DependencyInjectionForPreview 17 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 18 | 19 | @Composable 20 | fun DummyScreen( 21 | modifier: Modifier = Modifier.background(MaterialTheme.colors.background), 22 | name: String, 23 | onClick: () -> Unit 24 | ) { 25 | Column( 26 | modifier = modifier.fillMaxSize(), 27 | verticalArrangement = Arrangement.Center, 28 | horizontalAlignment = Alignment.CenterHorizontally 29 | ) { 30 | TextField(value = "", onValueChange = { {} }, 31 | colors = TextFieldDefaults.textFieldColors( 32 | backgroundColor = Resources.Theme.backgroundSecondary.getColor(), 33 | textColor = MaterialTheme.colors.error, 34 | ), 35 | placeholder = { 36 | Text("Exibir teclado") 37 | } 38 | ) 39 | Spacing.Big() 40 | PrimaryButton(text = name, onClick = onClick) 41 | } 42 | } 43 | 44 | @Preview 45 | @Composable 46 | fun ViewContentPreview() { 47 | DependencyInjectionForPreview() 48 | AndroidAppTheme { 49 | DummyScreen( 50 | name = "Cadastrar", 51 | onClick = {} 52 | ) 53 | } 54 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/components/Spacing.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.components 2 | 3 | import androidx.compose.foundation.layout.Spacer 4 | import androidx.compose.foundation.layout.height 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import br.com.progdeelite.kmmprogdeelite.resources.Resources.Spacing 8 | 9 | // 1) COMO CUSTOMIZAR SPACERS 10 | // 2) COMO USAR O SPACING COMPARTILHADO 11 | // 3) OUTRA FORMA ALTERNATIVA PARA VC DECIDIR QUAL USAR 12 | 13 | object Spacing { 14 | 15 | @Composable 16 | fun Tiny() { 17 | Spacer(modifier = Modifier.height(Spacing.tiny.dp)) 18 | } 19 | 20 | @Composable 21 | fun ExtraSmall() { 22 | Spacer(modifier = Modifier.height(Spacing.extraSmall.dp)) 23 | } 24 | 25 | @Composable 26 | fun Small() { 27 | Spacer(modifier = Modifier.height(Spacing.small.dp)) 28 | } 29 | 30 | @Composable 31 | fun Normal() { 32 | Spacer(modifier = Modifier.height(Spacing.normal.dp)) 33 | } 34 | 35 | @Composable 36 | fun Medium() { 37 | Spacer(modifier = Modifier.height(Spacing.medium.dp)) 38 | } 39 | 40 | @Composable 41 | fun Big() { 42 | Spacer(modifier = Modifier.height(Spacing.big.dp)) 43 | } 44 | 45 | @Composable 46 | fun ExtraBig() { 47 | Spacer(modifier = Modifier.height(Spacing.extraBig.dp)) 48 | } 49 | 50 | @Composable 51 | fun Large() { 52 | Spacer(modifier = Modifier.height(Spacing.large.dp)) 53 | } 54 | 55 | @Composable 56 | fun ExtraLarge() { 57 | Spacer(modifier = Modifier.height(Spacing.extraLarge.dp)) 58 | } 59 | 60 | @Composable 61 | fun Huge() { 62 | Spacer(modifier = Modifier.height(Spacing.huge.dp)) 63 | } 64 | 65 | @Composable 66 | fun ExtraHuge() { 67 | Spacer(modifier = Modifier.height(Spacing.extraHuge.dp)) 68 | } 69 | } 70 | 71 | 72 | //@Composable 73 | //fun SpacerNoSpace() { 74 | // Spacer(modifier = Modifier.height(MaterialTheme.spacing.noSpace)) 75 | //} 76 | 77 | //@Composable 78 | //fun SpacerTiny() { 79 | // Spacer(modifier = Modifier.height(MaterialTheme.spacing.tiny)) 80 | //} 81 | // 82 | //@Composable 83 | //fun SpacerExtraSmall() { 84 | // Spacer(modifier = Modifier.height(MaterialTheme.spacing.extraSmall)) 85 | //} 86 | // 87 | //@Composable 88 | //fun SpacerSmall() { 89 | // Spacer(modifier = Modifier.height(MaterialTheme.spacing.small)) 90 | //} 91 | // 92 | //@Composable 93 | //fun SpacerNormal() { 94 | // Spacer(modifier = Modifier.height(MaterialTheme.spacing.normal 95 | // )) 96 | //} 97 | // 98 | //@Composable 99 | //fun SpacerMedium() { 100 | // Spacer(modifier = Modifier.height(MaterialTheme.spacing.medium)) 101 | //} 102 | // 103 | //@Composable 104 | //fun SpacerBig() { 105 | // Spacer(modifier = Modifier.height(MaterialTheme.spacing.big)) 106 | //} 107 | // 108 | //@Composable 109 | //fun SpacerExtraBig() { 110 | // Spacer(modifier = Modifier.height(MaterialTheme.spacing.extraBig)) 111 | //} 112 | // 113 | //@Composable 114 | //fun SpacerLarge() { 115 | // Spacer(modifier = Modifier.height(MaterialTheme.spacing.large)) 116 | //} 117 | // 118 | //@Composable 119 | //fun SpacerExtraLarge() { 120 | // Spacer(modifier = Modifier.height(MaterialTheme.spacing.extraLarge)) 121 | //} 122 | // 123 | //@Composable 124 | //fun SpacerHuge() { 125 | // Spacer(modifier = Modifier.height(MaterialTheme.spacing.huge)) 126 | //} 127 | // 128 | //@Composable 129 | //fun SpacerExtraHuge() { 130 | // Spacer(modifier = Modifier.height(MaterialTheme.spacing.extraHuge)) 131 | //} -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/components/Texts.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.components 2 | 3 | import android.content.res.Configuration 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material.LocalTextStyle 8 | import androidx.compose.material.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.text.* 13 | import androidx.compose.ui.text.style.TextOverflow 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import androidx.compose.ui.unit.dp 16 | import br.com.progdeelite.kmmprogdeelite.android.ui.theme.AndroidAppTheme 17 | import br.com.progdeelite.kmmprogdeelite.android.utils.DependencyInjectionForPreview 18 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 19 | 20 | // 1) Adicionar cores novas no modulo de comum 21 | // 2) Como criar textos anotáveis (AnnotatedText - que permitem highlight) 22 | // 3) Como anotar(fazer o highlight de textos) e um compose 23 | 24 | @Composable 25 | fun SmsResendText( 26 | sendText: String = "Solicitar SMS novamente", 27 | resendText: String = "Próxima solicitação em {0}", // Esses textos vem das tradução, aqui apenas como exemplo! 28 | secondsText: String, // segundos restantes fornecido pelo componente pai 29 | style: TextStyle, 30 | disable: Boolean = false, // Para desabilita o campo, enquanto o count down esta andando 31 | onClick: () -> Unit = {} 32 | ) { 33 | val annotatedString = buildAnnotatedString { 34 | if (disable) { 35 | // Exemplo: Próxima solicitação em x 36 | withStyle(style = SpanStyle(Resources.Theme.labelDisabled.getColor())) { append(resendText.replace("{0}", "")) } 37 | withStyle(style = SpanStyle(Resources.Theme.labelDefault.getColor())) { append(secondsText) } 38 | } else { 39 | // Exemplo: Solicitar SMS novamente 40 | withStyle(style = SpanStyle(Resources.Theme.labelInteractive.getColor())) { append(sendText) } 41 | } 42 | } 43 | AnnotatedText( 44 | text = annotatedString, 45 | onClick = if (disable) { 46 | {} 47 | } else onClick, 48 | maxLines = 1, 49 | style = style 50 | ) 51 | } 52 | 53 | @Composable 54 | fun AnnotatedText( 55 | text: AnnotatedString, 56 | maxLines: Int, 57 | style: TextStyle = LocalTextStyle.current, 58 | onClick: () -> Unit = {} 59 | ) { 60 | Text( 61 | modifier = Modifier.clickable { onClick() }, 62 | text = text, 63 | style = style, 64 | maxLines = maxLines, // considerando textos grandes 65 | overflow = TextOverflow.Ellipsis, 66 | ) 67 | } 68 | 69 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, showSystemUi = true, showBackground = true) 70 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, showSystemUi = true, showBackground = true) 71 | @Composable 72 | fun TextsPreview() { 73 | DependencyInjectionForPreview() 74 | AndroidAppTheme { 75 | Column( 76 | modifier = Modifier.padding(20.dp), 77 | horizontalAlignment = Alignment.CenterHorizontally 78 | ) { 79 | SmsResendText( 80 | style = LocalTextStyle.current, 81 | secondsText = "10" 82 | ) 83 | Spacing.Big() 84 | SmsResendText( 85 | style = LocalTextStyle.current, 86 | secondsText = "10", 87 | disable = true 88 | ) 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/components/WebView.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.components 2 | 3 | import android.annotation.SuppressLint 4 | import android.webkit.WebView 5 | import android.webkit.WebViewClient 6 | import androidx.activity.compose.BackHandler 7 | import androidx.compose.foundation.layout.fillMaxHeight 8 | import androidx.compose.foundation.layout.fillMaxWidth 9 | import androidx.compose.foundation.layout.statusBarsPadding 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.viewinterop.AndroidView 13 | import androidx.lifecycle.viewmodel.compose.viewModel 14 | import com.google.accompanist.systemuicontroller.rememberSystemUiController 15 | 16 | @Composable 17 | fun WebViewScreen( 18 | webViewUrl: String, // passado como atributo na rota da tela anterior (tema pra outro video) 19 | ) { 20 | //val viewModel: WebViewModel = viewModel() 21 | 22 | val systemUiController = rememberSystemUiController() 23 | //systemUiController.setWebViewStatusBarColor() 24 | 25 | BackHandler(enabled = true) { 26 | //systemUiController.resetStatusBarColor() 27 | //viewModel.handleBackNavigation() 28 | } 29 | 30 | if (false/*viewModel.hasSeenWebViewConfirmationScreen()*/) { 31 | //systemUiController.resetStatusBarColor() 32 | //viewModel.onBackClicked() 33 | } else { 34 | //systemUiController.setWebViewStatusBarColor() 35 | WebViewContent(initialUrl = "", onUrlChanged = {}) 36 | } 37 | } 38 | 39 | // 1) COMO USAR UMA WEB VIEW EM COMPOSE 40 | // 2) COMO SABER PARA ONDE NAVEGAR DEPENDENDO DAS AçÕES DO USUÁRIO (HACK) 41 | // 3) COMO CONTORNAR PROBLEMAS COM A STATUS BAR (BÔNUS) 42 | 43 | @SuppressLint("SetJavaScriptEnabled") 44 | @Composable 45 | fun WebViewContent(initialUrl: String, onUrlChanged: (String?) -> Unit) { 46 | 47 | AndroidView( 48 | modifier = Modifier 49 | .fillMaxWidth() 50 | .fillMaxHeight() 51 | .statusBarsPadding(), 52 | factory = { context -> 53 | 54 | WebView(context).apply { 55 | // handle user interactions and scripts 56 | val webView = this 57 | webViewClient = object : WebViewClient() { 58 | override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) { 59 | onUrlChanged(url) 60 | super.doUpdateVisitedHistory(view, url, isReload) 61 | } 62 | } 63 | webView.settings.javaScriptEnabled = true // Needed for web view to work properly 64 | loadUrl(initialUrl) 65 | } 66 | }) 67 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/headers/HeaderOverlay.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.headers 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.height 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.graphics.Color 11 | import androidx.compose.ui.graphics.toArgb 12 | import androidx.compose.ui.tooling.preview.Preview 13 | import androidx.core.graphics.ColorUtils 14 | import br.com.progdeelite.kmmprogdeelite.android.ui.components.Spacing 15 | import br.com.progdeelite.kmmprogdeelite.android.ui.shapes.CurvedHeaderContent 16 | import br.com.progdeelite.kmmprogdeelite.android.ui.theme.AndroidAppTheme 17 | import br.com.progdeelite.kmmprogdeelite.android.utils.DependencyInjectionForPreview 18 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 19 | import com.google.accompanist.systemuicontroller.rememberSystemUiController 20 | 21 | @Composable 22 | fun HeaderOverlay( 23 | modifier: Modifier = Modifier, 24 | scrollBlendValue: Float, 25 | statusBarThreshold: Float, 26 | bgColorDefault: Color = Resources.Theme.bgOverlayHeaderDefault.getColor(), 27 | bgColorScroll: Color = Resources.Theme.bgOverlayHeaderScroll.getColor().copy(alpha = Resources.Dimen.header.fakeBlurAlpha), 28 | content: @Composable () -> Unit 29 | ) { 30 | val bgColor = Color(ColorUtils.blendARGB(bgColorDefault.toArgb(), bgColorScroll.toArgb(), scrollBlendValue)) 31 | 32 | val systemUiController = rememberSystemUiController() 33 | systemUiController.statusBarDarkContentEnabled = scrollBlendValue >= statusBarThreshold 34 | 35 | CurvedHeaderContent( 36 | modifier = Modifier.height(Resources.Dimen.header.height), 37 | backgroundColor = bgColor 38 | ) { 39 | Column( 40 | modifier = modifier 41 | .padding( 42 | start = Resources.Dimen.header.paddingStart, 43 | top = Resources.Dimen.header.paddingTop, 44 | end = Resources.Dimen.header.paddingEnd, 45 | bottom = Resources.Dimen.header.paddingBottom 46 | ) 47 | ) { 48 | Spacer(modifier = Modifier.weight(weight = 1f, fill = true)) 49 | content() 50 | } 51 | } 52 | } 53 | 54 | @Preview(showBackground = true) 55 | @Composable 56 | fun StickyHeaderPreview() { 57 | DependencyInjectionForPreview() 58 | 59 | AndroidAppTheme { 60 | Column { 61 | HeaderOverlay( 62 | scrollBlendValue = 1f, 63 | statusBarThreshold = 1f 64 | ) { 65 | Text("Overlay Header contetext") 66 | Text("Overlay Header contetext") 67 | Text("Overlay Header contetext") 68 | } 69 | Spacing.Normal() 70 | HeaderOverlay( 71 | scrollBlendValue = 0.5f, 72 | statusBarThreshold = 0.5f 73 | ) { 74 | Text("Overlay Header contetext") 75 | Text("Overlay Header contetext") 76 | Text("Overlay Header contetext") 77 | } 78 | Spacing.Normal() 79 | HeaderOverlay( 80 | scrollBlendValue = 0.1f, 81 | statusBarThreshold = 0.1f 82 | ) { 83 | Text("Overlay Header contetext") 84 | Text("Overlay Header contetext") 85 | Text("Overlay Header contetext") 86 | } 87 | Spacing.Normal() 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/navigation/Navigation.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.navigation 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material.Scaffold 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.graphics.Color 9 | import androidx.navigation.NavHostController 10 | import androidx.navigation.compose.NavHost 11 | import androidx.navigation.compose.composable 12 | import androidx.navigation.compose.rememberNavController 13 | import br.com.progdeelite.kmmprogdeelite.android.ui.components.BottomNavigationBar 14 | import br.com.progdeelite.kmmprogdeelite.android.ui.screens.* 15 | import br.com.progdeelite.kmmprogdeelite.navigation.Navigator 16 | import br.com.progdeelite.kmmprogdeelite.viewmodels.OnBoardingViewModel 17 | 18 | // 1) COMO CRIAR O ROOT-NAV-GRAPH 19 | // 2) COMO ADICIONAR ROTAS 20 | // 3) COMO INTEGRAR BOTTOM NAVIGATION BAR 21 | // 4) ADICIONAR A MAIN ACTIVITY 22 | @Composable 23 | fun RootNavigationGraph(navController: NavHostController = rememberNavController()) { 24 | NavHost( 25 | navController = navController, 26 | route = Navigator.initialGraph.root, 27 | startDestination = Navigator.initialGraph.splash 28 | ) { 29 | composable(route = Navigator.initialGraph.splash) { 30 | AnimationScreen(navController = navController) 31 | } 32 | composable(route = Navigator.homeGraph.root) { 33 | val mainNavController = rememberNavController() 34 | Scaffold( 35 | backgroundColor = Color.Transparent, // IMPORTANTE PARA PODER VER O EFEITO DO GRADIENTE 36 | bottomBar = { 37 | BottomNavigationBar(navController = mainNavController) 38 | } 39 | ) { 40 | Column(modifier = Modifier.padding(it)) { 41 | BottomNavGraph(navController = mainNavController) 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | @Composable 49 | fun BottomNavGraph(navController: NavHostController) { 50 | NavHost( 51 | navController = navController, 52 | route = Navigator.bottomNavGraph.root, 53 | startDestination = Navigator.bottomNavGraph.home 54 | ) { 55 | composable(route = Navigator.bottomNavGraph.home) { 56 | HomeScreen(navController = navController) 57 | } 58 | composable(route = Navigator.bottomNavGraph.insurance) { 59 | Confirm2faScreen({}, {}, {navController.navigate(Navigator.onboardingGraph.onboarding)}) 60 | } 61 | composable(route = Navigator.bottomNavGraph.support) { 62 | SupportScreen(navController = navController) 63 | } 64 | composable(route = Navigator.bottomNavGraph.profile) { 65 | ProfileScreen(navController = navController) 66 | } 67 | composable(route = Navigator.authGraph.login) { 68 | LoginScreen(onClose = {navController.navigate(Navigator.bottomNavGraph.root)}, 69 | onNext = {navController.navigate(Navigator.bottomNavGraph.profile)} 70 | ) 71 | } 72 | composable(route = Navigator.onboardingGraph.onboarding) { 73 | OnBoardingScreen(viewModel = OnBoardingViewModel()) 74 | } 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/screens/AnimationScreen.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.screens 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.getValue 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.navigation.NavHostController 12 | import br.com.progdeelite.kmmprogdeelite.android.R 13 | import br.com.progdeelite.kmmprogdeelite.navigation.Graphs 14 | import br.com.progdeelite.kmmprogdeelite.navigation.Navigator 15 | import br.com.progdeelite.kmmprogdeelite.tracking.adobe.AnalyticsService 16 | import br.com.progdeelite.kmmprogdeelite.tracking.adobe.ScreenInfo 17 | import com.airbnb.lottie.compose.* 18 | 19 | // 1) ADICIONAR DEPENDENCIAS 20 | // 2) CRIAR ROTAS DE NAVEGACÃO 21 | // 3) CRIAR TELA DE ANIMAçÃO - BAIXAR AQUI: https://lottiefiles.com/featured 22 | // 4) USAR ADOBE ANALYTICS PARA RASTREAR USUÁRIO 23 | 24 | // ASSISTA O VIDEO SPLASH SCREEN, VC VAI PRECISAR: https://youtu.be/qeurKOMugIU 25 | @Composable 26 | fun AnimationScreen(navController: NavHostController) { 27 | Column( 28 | modifier = Modifier.fillMaxSize(), 29 | verticalArrangement = Arrangement.Center, 30 | horizontalAlignment = Alignment.CenterHorizontally 31 | ) { 32 | Box { 33 | val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.splash_loading)) 34 | val splashAnimationState = animateLottieCompositionAsState( 35 | composition = composition, 36 | //iterations = LottieConstants.IterateForever // IMPORTANTE 37 | ) 38 | LottieAnimation( 39 | composition = composition, 40 | progress = { splashAnimationState.progress } 41 | ) 42 | navigateToHome(navController, splashAnimationState) 43 | } 44 | } 45 | } 46 | 47 | @Composable 48 | private fun navigateToHome(navController: NavHostController, splashAnimationState: LottieAnimationState) { 49 | if (splashAnimationState.isAtEnd && splashAnimationState.isPlaying) { 50 | AnalyticsService.instance.trackScreen(ScreenInfo.AnimationScreen) 51 | navController.popBackStack() 52 | navController.navigate(Graphs.HomeGraph.root) 53 | } 54 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/screens/InsuranceScreen.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.screens 2 | 3 | import androidx.compose.foundation.ScrollState 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.rememberScrollState 10 | import androidx.compose.foundation.shape.RoundedCornerShape 11 | import androidx.compose.material.Surface 12 | import androidx.compose.material.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.draw.clip 17 | import androidx.compose.ui.tooling.preview.Preview 18 | import androidx.navigation.NavHostController 19 | import br.com.progdeelite.kmmprogdeelite.android.ui.components.PrimaryButton 20 | import br.com.progdeelite.kmmprogdeelite.android.ui.components.Spacing 21 | import br.com.progdeelite.kmmprogdeelite.android.ui.headers.TopLevelHeader 22 | import br.com.progdeelite.kmmprogdeelite.android.ui.theme.AndroidAppTheme 23 | import br.com.progdeelite.kmmprogdeelite.android.ui.theme.TextStyles 24 | import br.com.progdeelite.kmmprogdeelite.android.utils.DependencyInjectionForPreview 25 | import br.com.progdeelite.kmmprogdeelite.navigation.Graphs 26 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 27 | 28 | @Composable 29 | fun InsuranceScreen( 30 | navController: NavHostController 31 | ) { 32 | InsuranceScreen( 33 | onInsuranceClick = { navController.navigate(Graphs.AuthLoginGraph.Login.startRoute)} 34 | ) 35 | } 36 | 37 | @Composable 38 | fun InsuranceScreen( 39 | onInsuranceClick: () -> Unit 40 | ) { 41 | val screenState = ScreenState(rememberScrollState()) 42 | 43 | BaseScreen( 44 | header = { 45 | TopLevelHeader( 46 | screenState = screenState, 47 | title = "Seguros" 48 | ) 49 | }, 50 | body = { 51 | InsuranceContent( 52 | scrollState = screenState.scrollState, 53 | onInsuranceClick = onInsuranceClick 54 | ) 55 | } 56 | ) 57 | } 58 | 59 | @Composable 60 | fun InsuranceContent( 61 | scrollState: ScrollState, 62 | onInsuranceClick: () -> Unit 63 | ) { 64 | BaseScreenContent( 65 | scrollState = scrollState, 66 | middleContent = { 67 | Surface( 68 | modifier = Modifier 69 | .fillMaxWidth() 70 | .padding(horizontal = Resources.Dimen.screen.padding) 71 | .clip(shape = RoundedCornerShape(size = Resources.Dimen.card.cornerRadius)) 72 | .background(color = Resources.Theme.surface.getColor()) 73 | .padding(all = Resources.Dimen.card.padding) 74 | ) { 75 | Column { 76 | Text( 77 | text = getLoremIpsumShort(), 78 | style = TextStyles.body1, 79 | color = Resources.Theme.defaultTextColor.getColor() 80 | ) 81 | } 82 | } 83 | }, 84 | bottomContent = { 85 | Column( 86 | modifier = Modifier 87 | .fillMaxSize() 88 | .padding(horizontal = Resources.Dimen.screen.padding), 89 | horizontalAlignment = Alignment.CenterHorizontally 90 | ) { 91 | Spacing.Big() 92 | PrimaryButton(text = "Contratar", onClick = onInsuranceClick) 93 | Spacing.Big() 94 | } 95 | } 96 | ) 97 | } 98 | 99 | 100 | @Preview(showBackground = true) 101 | @Composable 102 | fun InsuranceScreenPreview() { 103 | DependencyInjectionForPreview() 104 | AndroidAppTheme { 105 | InsuranceScreen {} 106 | } 107 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/screens/LoginScreen.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.screens 2 | 3 | import androidx.compose.foundation.ScrollState 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.foundation.rememberScrollState 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment.Companion.CenterHorizontally 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.tooling.preview.Preview 12 | import androidx.navigation.NavHostController 13 | import br.com.progdeelite.kmmprogdeelite.android.ui.components.PrimaryButton 14 | import br.com.progdeelite.kmmprogdeelite.android.ui.components.Spacing 15 | import br.com.progdeelite.kmmprogdeelite.android.ui.headers.NavigationHeader 16 | import br.com.progdeelite.kmmprogdeelite.android.ui.theme.AndroidAppTheme 17 | import br.com.progdeelite.kmmprogdeelite.android.ui.theme.TextStyles 18 | import br.com.progdeelite.kmmprogdeelite.android.utils.DependencyInjectionForPreview 19 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 20 | import br.com.progdeelite.kmmprogdeelite.viewmodels.LoginViewModel 21 | 22 | @Composable 23 | fun LoginScreen( 24 | navController: NavHostController? = null, 25 | viewModel: LoginViewModel? = null, 26 | onClose: () -> Unit = {}, 27 | onNext: () -> Unit = {} 28 | ) { 29 | val screenScrollState = ScreenState(rememberScrollState()) 30 | BaseScreen( 31 | header = { 32 | NavigationHeader( 33 | screenState = screenScrollState, 34 | title = "Fazer Login", 35 | onBack = null, 36 | onClose = onClose 37 | ) 38 | }, 39 | body = { 40 | LoginContent( 41 | scrollState = screenScrollState.scrollState, 42 | onNext = onNext 43 | ) 44 | } 45 | ) 46 | } 47 | 48 | 49 | @Composable 50 | fun LoginContent( 51 | scrollState: ScrollState, 52 | onNext: (() -> Unit)? = null 53 | ) { 54 | BaseScreenContent( 55 | scrollState = scrollState 56 | ) { 57 | Column( 58 | modifier = Modifier.padding(horizontal = Resources.Dimen.screen.padding) 59 | ) { 60 | Spacing.Big() 61 | Text( 62 | text = getLoremIpsumMedium(), 63 | style = TextStyles.body1, 64 | color = Resources.Theme.defaultTextColor.getColor() 65 | ) 66 | Spacing.Big() 67 | if (onNext != null) { 68 | PrimaryButton( 69 | modifier = Modifier.align(CenterHorizontally), 70 | text = "Login", 71 | onClick = onNext, 72 | ) 73 | } 74 | Spacing.Big() 75 | Text( 76 | text = getLoremIpsumLong(), 77 | style = TextStyles.body1, 78 | color = Resources.Theme.defaultTextColor.getColor() 79 | ) 80 | } 81 | } 82 | } 83 | 84 | @Preview 85 | @Composable 86 | fun LoginContentPreview() { 87 | DependencyInjectionForPreview() 88 | AndroidAppTheme { 89 | LoginScreen() 90 | } 91 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/screens/OnBoardingScreen.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.screens 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.foundation.rememberScrollState 6 | import androidx.compose.foundation.verticalScroll 7 | import androidx.compose.material.Surface 8 | import androidx.compose.material.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.res.painterResource 13 | import androidx.compose.ui.tooling.preview.Preview 14 | import androidx.compose.ui.unit.dp 15 | import br.com.progdeelite.kmmprogdeelite.android.R 16 | import br.com.progdeelite.kmmprogdeelite.android.ui.theme.AndroidAppTheme 17 | import br.com.progdeelite.kmmprogdeelite.android.utils.DependencyInjectionForPreview 18 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 19 | import br.com.progdeelite.kmmprogdeelite.resources.getPreviewImageResource 20 | import br.com.progdeelite.kmmprogdeelite.resources.getTextResource 21 | import br.com.progdeelite.kmmprogdeelite.viewmodels.LanguagePickerViewModel 22 | import br.com.progdeelite.kmmprogdeelite.viewmodels.OnBoardingImages 23 | import br.com.progdeelite.kmmprogdeelite.viewmodels.OnBoardingTexts 24 | import br.com.progdeelite.kmmprogdeelite.viewmodels.OnBoardingViewModel 25 | 26 | @Composable 27 | fun OnBoardingScreen( 28 | viewModel: OnBoardingViewModel, 29 | onDiscoverAction: () -> Unit = {}, 30 | onLoginAction: () -> Unit = {}, 31 | showLanguagePicker: () -> Unit = {} 32 | ) { 33 | Surface { 34 | Column( 35 | verticalArrangement = Arrangement.SpaceBetween, 36 | modifier = Modifier.fillMaxHeight(), 37 | ) { 38 | Column( 39 | modifier = Modifier 40 | .verticalScroll(state = rememberScrollState()) 41 | .weight(weight = 1f, fill = true) 42 | .fillMaxWidth() 43 | .padding(horizontal = 16.dp), 44 | verticalArrangement = Arrangement.Center, 45 | horizontalAlignment = Alignment.CenterHorizontally 46 | ) { 47 | Image( 48 | painter = painterResource(id = viewModel.images.topImage.id), 49 | contentDescription = "" 50 | ) 51 | // DIMENSÃO COMPARTILHADA 52 | Spacer(modifier = Modifier.height(Resources.Dimen.button.height)) 53 | Text(viewModel.texts.topImageText.localized) 54 | Image( 55 | painter = painterResource(id = viewModel.images.middleImage.id), 56 | contentDescription = "" 57 | ) 58 | Text( 59 | text = viewModel.texts.middleImageText.localized, 60 | fontSize = Resources.FontSizing.large.size // FONTE COMPARTILHADA 61 | ) 62 | Image( 63 | painter = painterResource(id = viewModel.images.bottomImage.id), 64 | contentDescription = "" 65 | ) 66 | Text(viewModel.texts.bottomImageText.localized) 67 | } 68 | } 69 | } 70 | } 71 | 72 | @Preview 73 | @Composable 74 | fun OnBoardingPreview() { 75 | DependencyInjectionForPreview() 76 | AndroidAppTheme { 77 | Column { 78 | OnBoardingScreen( 79 | viewModel = OnBoardingViewModel( 80 | images = OnBoardingImages( 81 | topImage = getPreviewImageResource(R.drawable.ic_warning), 82 | middleImage = getPreviewImageResource(R.drawable.ic_warning), 83 | bottomImage = getPreviewImageResource(R.drawable.ic_warning) 84 | ), 85 | texts = OnBoardingTexts( 86 | topImageText = getTextResource("Titulo: Hello Compose"), 87 | middleImageText = getTextResource("Titulo: Eu consigo me"), 88 | bottomImageText = getTextResource("Titulo: renderizar assim!") 89 | ), 90 | picker = LanguagePickerViewModel() 91 | ) 92 | ) 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/screens/RegisterScreen.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.screens 2 | 3 | import androidx.compose.foundation.ScrollState 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.rememberScrollState 10 | import androidx.compose.foundation.shape.RoundedCornerShape 11 | import androidx.compose.material.Surface 12 | import androidx.compose.material.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.draw.clip 17 | import androidx.compose.ui.tooling.preview.Preview 18 | import androidx.navigation.NavHostController 19 | import br.com.progdeelite.kmmprogdeelite.android.ui.components.PrimaryButton 20 | import br.com.progdeelite.kmmprogdeelite.android.ui.components.Spacing 21 | import br.com.progdeelite.kmmprogdeelite.android.ui.headers.TopLevelHeader 22 | import br.com.progdeelite.kmmprogdeelite.android.ui.theme.AndroidAppTheme 23 | import br.com.progdeelite.kmmprogdeelite.android.ui.theme.TextStyles 24 | import br.com.progdeelite.kmmprogdeelite.android.utils.DependencyInjectionForPreview 25 | import br.com.progdeelite.kmmprogdeelite.navigation.Graphs 26 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 27 | import br.com.progdeelite.kmmprogdeelite.viewmodels.RegisterViewModel 28 | 29 | @Composable 30 | fun RegisterScreen( 31 | navController: NavHostController, 32 | viewModel: RegisterViewModel 33 | ) { 34 | RegisterScreen( 35 | onRegisterClick = { navController.navigate(Graphs.AuthRegisterGraph.Register.startRoute)} 36 | ) 37 | } 38 | 39 | @Composable 40 | fun RegisterScreen( 41 | onRegisterClick: () -> Unit 42 | ) { 43 | val screenState = ScreenState(rememberScrollState()) 44 | 45 | BaseScreen( 46 | header = { 47 | TopLevelHeader( 48 | screenState = screenState, 49 | title = "Registar" 50 | ) 51 | }, 52 | body = { 53 | RegisterContent( 54 | scrollState = screenState.scrollState, 55 | onRegisterClick = onRegisterClick 56 | ) 57 | } 58 | ) 59 | } 60 | 61 | @Composable 62 | fun RegisterContent( 63 | scrollState: ScrollState, 64 | onRegisterClick: () -> Unit 65 | ) { 66 | BaseScreenContent( 67 | scrollState = scrollState, 68 | middleContent = { 69 | Surface( 70 | modifier = Modifier 71 | .fillMaxWidth() 72 | .padding(horizontal = Resources.Dimen.screen.padding) 73 | .clip(shape = RoundedCornerShape(size = Resources.Dimen.card.cornerRadius)) 74 | .background(color = Resources.Theme.surface.getColor()) 75 | .padding(all = Resources.Dimen.card.padding) 76 | ) { 77 | Column { 78 | Text( 79 | text = getLoremIpsumShort(), 80 | style = TextStyles.body1, 81 | color = Resources.Theme.defaultTextColor.getColor() 82 | ) 83 | } 84 | } 85 | }, 86 | bottomContent = { 87 | Column( 88 | modifier = Modifier 89 | .fillMaxSize() 90 | .padding(horizontal = Resources.Dimen.screen.padding), 91 | horizontalAlignment = Alignment.CenterHorizontally 92 | ) { 93 | Spacing.Big() 94 | PrimaryButton(text = "Registrar", onClick = onRegisterClick) 95 | Spacing.Big() 96 | } 97 | } 98 | ) 99 | } 100 | 101 | 102 | @Preview(showBackground = true) 103 | @Composable 104 | fun RegisterScreenPreview() { 105 | DependencyInjectionForPreview() 106 | AndroidAppTheme { 107 | RegisterScreen {} 108 | } 109 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/screens/SupportScreen.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.screens 2 | 3 | import androidx.compose.foundation.ScrollState 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.rememberScrollState 10 | import androidx.compose.foundation.shape.RoundedCornerShape 11 | import androidx.compose.material.Surface 12 | import androidx.compose.material.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.draw.clip 17 | import androidx.compose.ui.tooling.preview.Preview 18 | import androidx.navigation.NavHostController 19 | import br.com.progdeelite.kmmprogdeelite.android.ui.components.PrimaryButton 20 | import br.com.progdeelite.kmmprogdeelite.android.ui.components.Spacing 21 | import br.com.progdeelite.kmmprogdeelite.android.ui.headers.TopLevelHeader 22 | import br.com.progdeelite.kmmprogdeelite.android.ui.theme.AndroidAppTheme 23 | import br.com.progdeelite.kmmprogdeelite.android.ui.theme.TextStyles 24 | import br.com.progdeelite.kmmprogdeelite.android.utils.DependencyInjectionForPreview 25 | import br.com.progdeelite.kmmprogdeelite.navigation.Graphs 26 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 27 | 28 | @Composable 29 | fun SupportScreen( 30 | navController: NavHostController 31 | ) { 32 | SupportScreen( 33 | onSupportClick = { navController.navigate(Graphs.AuthLoginGraph.Login.startRoute)} 34 | ) 35 | } 36 | 37 | @Composable 38 | fun SupportScreen( 39 | onSupportClick: () -> Unit 40 | ) { 41 | val screenState = ScreenState(rememberScrollState()) 42 | 43 | BaseScreen( 44 | header = { 45 | TopLevelHeader( 46 | screenState = screenState, 47 | title = "Suporte" 48 | ) 49 | }, 50 | body = { 51 | SupportContent( 52 | scrollState = screenState.scrollState, 53 | onSupportClick = onSupportClick 54 | ) 55 | } 56 | ) 57 | } 58 | 59 | @Composable 60 | fun SupportContent( 61 | scrollState: ScrollState, 62 | onSupportClick: () -> Unit 63 | ) { 64 | BaseScreenContent( 65 | scrollState = scrollState, 66 | middleContent = { 67 | Surface( 68 | modifier = Modifier 69 | .fillMaxWidth() 70 | .padding(horizontal = Resources.Dimen.screen.padding) 71 | .clip(shape = RoundedCornerShape(size = Resources.Dimen.card.cornerRadius)) 72 | .background(color = Resources.Theme.surface.getColor()) 73 | .padding(all = Resources.Dimen.card.padding) 74 | ) { 75 | Column { 76 | Text( 77 | text = getLoremIpsumShort(), 78 | style = TextStyles.body1, 79 | color = Resources.Theme.defaultTextColor.getColor() 80 | ) 81 | } 82 | } 83 | }, 84 | bottomContent = { 85 | Column( 86 | modifier = Modifier 87 | .fillMaxSize() 88 | .padding(horizontal = Resources.Dimen.screen.padding), 89 | horizontalAlignment = Alignment.CenterHorizontally 90 | ) { 91 | Spacing.Big() 92 | PrimaryButton(text = "Support", onClick = onSupportClick) 93 | Spacing.Big() 94 | } 95 | } 96 | ) 97 | } 98 | 99 | 100 | @Preview(showBackground = true) 101 | @Composable 102 | fun SupportScreenPreview() { 103 | DependencyInjectionForPreview() 104 | AndroidAppTheme { 105 | SupportScreen {} 106 | } 107 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/theme/FontTypes.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.theme 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.ReadOnlyComposable 6 | import androidx.compose.runtime.compositionLocalOf 7 | import androidx.compose.ui.text.font.Font 8 | import androidx.compose.ui.text.font.FontStyle 9 | import androidx.compose.ui.text.font.FontWeight 10 | import br.com.progdeelite.kmmprogdeelite.android.R 11 | 12 | /** Centralizes app fonts, so we do not have to change each font manually in the whole app */ 13 | object FontTypes{ 14 | val ttNormsBold: Font = Font( 15 | resId = R.font.tt_norms_bold_webfont, 16 | weight = FontWeight.W700, 17 | style = FontStyle.Normal, 18 | ) 19 | val ttNormsMedium: Font = Font( 20 | resId = R.font.tt_norms_medium_webfont, 21 | weight = FontWeight.W700, 22 | style = FontStyle.Normal 23 | ) 24 | val ttNormsRegular: Font = Font( 25 | resId = R.font.tt_norms_regular_webfont, 26 | weight = FontWeight.W500, 27 | style = FontStyle.Normal 28 | ) 29 | } 30 | 31 | val LocalFonts = compositionLocalOf { FontTypes } 32 | 33 | /** Makes fonts available in app's theme like MaterialTheme.colors... or MaterialTheme.typography... and so on */ 34 | val MaterialTheme.fonts: FontTypes 35 | @Composable 36 | @ReadOnlyComposable 37 | get() = LocalFonts.current -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/theme/Spacing.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.theme 2 | 3 | 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.ReadOnlyComposable 7 | import androidx.compose.runtime.compositionLocalOf 8 | import androidx.compose.ui.unit.Dp 9 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 10 | 11 | // 1) Criar classe de spacing 12 | // 2) usar recurso de spacing 13 | // 3) disponibilizar no material theme 14 | 15 | /** Centralizes app spacings, so we do not have to change each spacing manually in the whole app */ 16 | data class Spacing( 17 | val noSpace: Dp = Resources.Spacing.noSpace.dp, 18 | val tiny: Dp = Resources.Spacing.tiny.dp, 19 | val extraSmall: Dp = Resources.Spacing.extraSmall.dp, 20 | val small: Dp = Resources.Spacing.small.dp, 21 | val extraTiny: Dp = Resources.Spacing.extraTiny.dp, 22 | val normal: Dp = Resources.Spacing.normal.dp, 23 | val medium: Dp = Resources.Spacing.medium.dp, 24 | val big: Dp = Resources.Spacing.big.dp, 25 | val extraBig: Dp = Resources.Spacing.extraBig.dp, 26 | val large: Dp = Resources.Spacing.large.dp, 27 | val extraLarge: Dp = Resources.Spacing.extraLarge.dp, 28 | val huge: Dp = Resources.Spacing.huge.dp, 29 | val extraHuge: Dp = Resources.Spacing.extraHuge.dp, 30 | ) 31 | 32 | val LocalSpacing = compositionLocalOf { Spacing() } 33 | 34 | /** Makes spacing available in app's theme like MaterialTheme.colors... or MaterialTheme.typography... and so on */ 35 | val MaterialTheme.spacing: Spacing 36 | @Composable 37 | @ReadOnlyComposable 38 | get() = LocalSpacing.current 39 | 40 | -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.foundation.shape.RoundedCornerShape 5 | import androidx.compose.material.MaterialTheme 6 | import androidx.compose.material.Shapes 7 | import androidx.compose.material.darkColors 8 | import androidx.compose.material.lightColors 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.graphics.Color 11 | import androidx.compose.ui.unit.dp 12 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 13 | import com.google.accompanist.systemuicontroller.rememberSystemUiController 14 | 15 | @Composable 16 | fun AndroidAppTheme( 17 | darkTheme: Boolean = isSystemInDarkTheme(), 18 | content: @Composable () -> Unit 19 | ) { 20 | 21 | val systemUiController = rememberSystemUiController() 22 | systemUiController.setStatusBarColor( 23 | color = Color.Transparent 24 | ) 25 | 26 | val colors = if (darkTheme) { 27 | darkColors( 28 | primary = Resources.Theme.primary.getColor(), 29 | primaryVariant = Resources.Theme.primaryVariant.getColor(), 30 | secondary = Resources.Theme.secondary.getColor() 31 | ) 32 | } else { 33 | lightColors( 34 | primary = Resources.Theme.primary.getColor(), 35 | primaryVariant = Resources.Theme.primaryVariant.getColor(), 36 | secondary = Resources.Theme.secondary.getColor() 37 | ) 38 | } 39 | 40 | val shapes = Shapes( 41 | small = RoundedCornerShape(4.dp), 42 | medium = RoundedCornerShape(4.dp), 43 | large = RoundedCornerShape(0.dp) 44 | ) 45 | 46 | MaterialTheme( 47 | colors = colors, 48 | typography = typography, // Usar custom typography aqui! (definida em Type.kt) 49 | shapes = shapes, 50 | content = content 51 | ) 52 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.ui.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // 1) importar fonts do seu projeto (resource folder font) 10 | // 2) Criar font types e disponibilizar no material theme (FontTypes) 11 | // 3) Criar estilos customizados (TextStyles) 12 | // 4) Usa-los na tipografia (Type) 13 | // 5) Criar MaterialTheme (Theme) 14 | 15 | // Set of Material typography styles to start with 16 | val Typography = Typography( 17 | body1 = TextStyle( 18 | fontFamily = FontFamily.Default, 19 | fontWeight = FontWeight.Normal, 20 | fontSize = 18.sp 21 | ), 22 | h1 = TextStyles.h1 23 | /* Other default text styles to override 24 | button = TextStyle( 25 | fontFamily = FontFamily.Default, 26 | fontWeight = FontWeight.W500, 27 | fontSize = 14.sp 28 | ), 29 | caption = TextStyle( 30 | fontFamily = FontFamily.Default, 31 | fontWeight = FontWeight.Normal, 32 | fontSize = 12.sp 33 | ) 34 | */ 35 | ) -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/utils/CallToActionExt.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.utils 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.widget.Toast 7 | import br.com.progdeelite.kmmprogdeelite.utils.CallToAction 8 | 9 | // 1) COMO CRIAR UMA CLASSE DE CALL TO ACTION COMPARTILHADA 10 | // 2) COMO ABRIR APENAS AS OPCÕES DE EMAIL INSTALADOS NO APP 11 | // 3) COMO SIMPLIFICAR UM TRY-CATCH EM KOTLIN 12 | 13 | fun openEmail(context: Context, emailContext: CallToAction.Email, errorMessage: String) { 14 | val intent = Intent(Intent.ACTION_SENDTO) 15 | intent.data = Uri.parse("mailto:") 16 | intent.putExtra(Intent.EXTRA_EMAIL, arrayOf(emailContext.address.localized)) 17 | intent.putExtra(Intent.EXTRA_SUBJECT, emailContext.subject.localized) 18 | // COMO SIMPLIFICAR ISSO AQUI EM KOTLIN? EXTRAIR MÉTODO? 19 | runCatching { 20 | context.startActivity(Intent.createChooser(intent, "Email")) 21 | }.onFailure { _: Throwable -> 22 | Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show() 23 | } 24 | } 25 | 26 | fun callSupport(context: Context, callContext: CallToAction.Call, errorMessage: String) { 27 | val phoneUri = Uri.parse("tel:${callContext.number.localized}") 28 | val phoneIntent = Intent(Intent.ACTION_DIAL, phoneUri) 29 | runCatching { 30 | context.startActivity(phoneIntent) 31 | }.onFailure { _: Throwable -> 32 | Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show() 33 | } 34 | } 35 | 36 | // PARA RE-USAR EM NOS MÉTODOS ACIMA 37 | private fun runCatching( 38 | context: Context, 39 | intentTitle:String? = null, 40 | intent:Intent, 41 | errorMessage:String 42 | ){ 43 | runCatching { 44 | when(intentTitle){ 45 | null -> context.startActivity(intent) 46 | else -> context.startActivity(Intent.createChooser(intent, intentTitle)) 47 | } 48 | }.onFailure { 49 | Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show() 50 | } 51 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/utils/ConnectivityUtil.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.utils 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.net.Network 6 | import android.net.NetworkCapabilities 7 | import android.net.NetworkRequest 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.State 10 | import androidx.compose.runtime.produceState 11 | import androidx.compose.ui.platform.LocalContext 12 | import br.com.progdeelite.kmmprogdeelite.utils.ConnectivityState 13 | import kotlinx.coroutines.channels.awaitClose 14 | import kotlinx.coroutines.flow.callbackFlow 15 | 16 | /** Network utility to get current state of internet connection */ 17 | val Context.currentConnectivityState: ConnectivityState 18 | get() { 19 | val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 20 | return getCurrentConnectivityState(connectivityManager) 21 | } 22 | 23 | @Suppress("DEPRECATION") 24 | private fun getCurrentConnectivityState(connectivityManager: ConnectivityManager): ConnectivityState { 25 | val connected = connectivityManager.allNetworks.any { network -> 26 | connectivityManager.getNetworkCapabilities(network)?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) ?: false 27 | } 28 | return if (connected) ConnectivityState.Online else ConnectivityState.Offline 29 | } 30 | 31 | /** 32 | * launches coroutine scoped to the Composition which holds the State. 33 | * It’ll be automatically get cancelled once it leaves the composition 34 | */ 35 | @Composable 36 | fun connectivityState(): State { 37 | val context = LocalContext.current 38 | 39 | return produceState(initialValue = context.currentConnectivityState) { 40 | context.observeConnectivityAsFlow().collect { value = it } 41 | } 42 | } 43 | 44 | /** Network Utility to observe availability or unavailability of Internet connection */ 45 | fun Context.observeConnectivityAsFlow() = callbackFlow { 46 | val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 47 | val callback = networkCallback { connectionState -> trySend(connectionState) } 48 | val networkRequest = NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build() 49 | connectivityManager.registerNetworkCallback(networkRequest, callback) 50 | 51 | // Set current state 52 | trySend(getCurrentConnectivityState(connectivityManager)) 53 | 54 | // Remove callback when not used 55 | awaitClose { 56 | connectivityManager.unregisterNetworkCallback(callback) 57 | } 58 | } 59 | 60 | private fun networkCallback(callback: (ConnectivityState) -> Unit): ConnectivityManager.NetworkCallback { 61 | return object : ConnectivityManager.NetworkCallback() { 62 | override fun onAvailable(network: Network) { 63 | callback(ConnectivityState.Online) 64 | } 65 | 66 | override fun onLost(network: Network) { 67 | callback(ConnectivityState.Offline) 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/utils/DependencyInjectionForPreview.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.utils 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.platform.LocalContext 5 | import br.com.progdeelite.kmmprogdeelite.di.DI 6 | import br.com.progdeelite.kmmprogdeelite.utils.AndroidMainApp 7 | 8 | @Composable 9 | fun DependencyInjectionForPreview() { 10 | AndroidMainApp.applicationContext = LocalContext.current.applicationContext 11 | DI.fake() 12 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/utils/ModifierUtil.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.utils 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.foundation.shape.RoundedCornerShape 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.draw.clip 10 | import androidx.compose.ui.graphics.Color 11 | import androidx.compose.ui.unit.dp 12 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 13 | 14 | fun Modifier.applyDefaultListViewItemModifier( 15 | onItemClick: () -> Unit, 16 | shape: RoundedCornerShape = RoundedCornerShape(0.dp), 17 | backgroundColor: Color = Resources.Theme.background.getColor() 18 | ): Modifier { 19 | return this 20 | .fillMaxWidth() 21 | .clip(shape) 22 | .clickable { onItemClick() } 23 | .background(backgroundColor) 24 | .padding(12.dp) 25 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/br/com/progdeelite/kmmprogdeelite/android/utils/UtilExt.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.android.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.res.Configuration 5 | import android.content.res.Resources 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.ui.unit.Dp 9 | import androidx.compose.ui.unit.dp 10 | import androidx.lifecycle.ViewModel 11 | import androidx.lifecycle.viewmodel.compose.viewModel 12 | import androidx.navigation.NavBackStackEntry 13 | import androidx.navigation.NavController 14 | 15 | // Use only if you are using compose verion <= 1.1.1, otherwise prefer 16 | // Modifier.navigationBarsPadding() >>> See MainActivity 17 | @SuppressLint("DiscouragedApi") 18 | fun getSysNavBarHeight(res: Resources, density: Float): Dp { 19 | 20 | val resName = if (res.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { 21 | "navigation_bar_height" 22 | } else { 23 | "navigation_bar_height_landscape" 24 | } 25 | 26 | val id: Int = res.getIdentifier(resName, "dimen", "android") 27 | 28 | return if (id > 0) { 29 | (res.getDimensionPixelSize(id) / density).dp 30 | } else { 31 | 0.dp // navigation bar does not exist 32 | } 33 | } 34 | 35 | // Extensão para obter um view apenas com o backstack da rota desejada 36 | @Composable 37 | inline fun NavController.scopedViewModel( 38 | currentBackStackEntry: NavBackStackEntry, 39 | route: String 40 | ): VM { 41 | val entry = remember(currentBackStackEntry) { this.getBackStackEntry(route) } 42 | return viewModel(entry) 43 | } -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/app_gradient_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/fire.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/flash.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/home.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/ic_warning.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/info.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/insurance.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/lamp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/profile.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/splash_transparent_image.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/support.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/switch_cam.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/wifi.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /androidApp/src/main/res/font/tt_norms_bold_webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treslines/kotlin_multiplatform_mobile/15d00b980111bd6283521fb477ac67711bc987f0/androidApp/src/main/res/font/tt_norms_bold_webfont.ttf -------------------------------------------------------------------------------- /androidApp/src/main/res/font/tt_norms_medium_webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treslines/kotlin_multiplatform_mobile/15d00b980111bd6283521fb477ac67711bc987f0/androidApp/src/main/res/font/tt_norms_medium_webfont.ttf -------------------------------------------------------------------------------- /androidApp/src/main/res/font/tt_norms_regular_webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treslines/kotlin_multiplatform_mobile/15d00b980111bd6283521fb477ac67711bc987f0/androidApp/src/main/res/font/tt_norms_regular_webfont.ttf -------------------------------------------------------------------------------- /androidApp/src/main/res/raw/keep.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /androidApp/src/main/res/values-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /androidApp/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | #FFE95D0F 11 | #FFF89F00 12 | 13 | 14 | #FF9CECFB 15 | #FF65C7F7 16 | -------------------------------------------------------------------------------- /androidApp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | KMM App 4 | Top Image Text 5 | Selecione Idioma 6 | 7 | Middle Image Text 8 | Bottom Image Text 9 | 10 | Este número não foi encontrado. 11 | Data de nascimento invalida. 12 | Código invalido. 13 | Formato incorreto. 14 | Código expirado. 15 | Campo vazio. 16 | 17 | Código SMS 18 | Solicitar SMS 19 | Próximo SMS em {0} 20 | Insira Código SMS 21 | 22 | Atualizar App 23 | Para continuar usando o app, você precisa atualiza-lo 24 | Atualizar 25 | Mais tarde 26 | 27 | Cadastro 28 | Tem certeza que deseja cancelar o cadastro? 29 | Cancelar 30 | Continuar 31 | 32 | You are online again 33 | You are offline 34 | 35 | Home 36 | Seguro 37 | Suporte 38 | Perfil 39 | 40 | -------------------------------------------------------------------------------- /androidApp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /androidApp/src/production/res/keystore.jks.encrypted: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treslines/kotlin_multiplatform_mobile/15d00b980111bd6283521fb477ac67711bc987f0/androidApp/src/production/res/keystore.jks.encrypted -------------------------------------------------------------------------------- /androidApp/src/production/res/keystore.properties.encrypted: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treslines/kotlin_multiplatform_mobile/15d00b980111bd6283521fb477ac67711bc987f0/androidApp/src/production/res/keystore.properties.encrypted -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | //trick: for the same plugin versions in all sub-modules 3 | id(Plugins.androidApplication).version(Versions.pluginAndroidApp).apply(false) 4 | id(Plugins.androidLibrary).version(Versions.pluginAndroidLib).apply(false) 5 | kotlin(Plugins.android).version(Versions.kotlin).apply(false) 6 | kotlin(Plugins.multiplatform).version(Versions.kotlin).apply(false) 7 | } 8 | 9 | buildscript { 10 | dependencies { 11 | classpath(Gradle.pluginSqlDelight) 12 | classpath(Jetbrains.serializationKotlin) 13 | classpath(BuildKonfig.plugin) 14 | } 15 | } 16 | 17 | tasks.register("clean", Delete::class) { 18 | delete(rootProject.buildDir) 19 | } 20 | 21 | allprojects { 22 | 23 | repositories { 24 | google() 25 | mavenCentral() 26 | maven { url = uri(Translation.lokaliseUri) } 27 | } 28 | 29 | // Hack: Determine current build flavor and add it to exported FLAVOR_PROPERTIES 30 | val tasks = gradle.startParameter.taskRequests.toString() 31 | if (tasks.contains("assemble")) { 32 | println("------------assemble------------") 33 | val pattern = java.util.regex.Pattern.compile("assemble(Development+|Integration+|Production+)") 34 | val matcher = pattern.matcher(tasks) 35 | 36 | if (matcher.find()) { 37 | val props = java.util.Properties() 38 | val flavor = matcher.group(1).toLowerCase() 39 | val propertiesFile = rootProject.file("androidApp/src/${flavor}/res/keystore.properties") 40 | 41 | props.setProperty("FLAVOR", flavor.toLowerCase()) 42 | props.load(java.io.FileInputStream(propertiesFile)) 43 | 44 | // export this flavor based property to all projects 45 | val FLAVOR_PROPERTIES by extra(props) 46 | println("Current selected build flavor: ${props["FLAVOR"]} -") 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | repositories { 5 | mavenCentral() 6 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Gradle 2 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" 3 | 4 | #Kotlin 5 | kotlin.code.style=official 6 | 7 | #Android 8 | android.useAndroidX=true 9 | android.nonTransitiveRClass=true 10 | 11 | #MPP 12 | kotlin.mpp.enableCInteropCommonization=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treslines/kotlin_multiplatform_mobile/15d00b980111bd6283521fb477ac67711bc987f0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Sep 25 12:30:59 CEST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /iosApp/iosApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import shared 3 | 4 | struct ContentView: View { 5 | let greet = Greeting().greeting() 6 | 7 | var body: some View { 8 | Text(greet) 9 | } 10 | } 11 | 12 | struct ContentView_Previews: PreviewProvider { 13 | static var previews: some View { 14 | ContentView() 15 | } 16 | } -------------------------------------------------------------------------------- /iosApp/iosApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIRequiredDeviceCapabilities 29 | 30 | armv7 31 | 32 | UISupportedInterfaceOrientations 33 | 34 | UIInterfaceOrientationPortrait 35 | UIInterfaceOrientationLandscapeLeft 36 | UIInterfaceOrientationLandscapeRight 37 | 38 | UISupportedInterfaceOrientations~ipad 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationPortraitUpsideDown 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | UILaunchScreen 46 | 47 | 48 | -------------------------------------------------------------------------------- /iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /iosApp/iosApp/iOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct iOSApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # https://developer.android.com/studio/build/shrink-code 3 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | gradlePluginPortal() 5 | mavenCentral() 6 | } 7 | } 8 | 9 | dependencyResolutionManagement { 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | 16 | rootProject.name = "KmmProgDeElite" 17 | include(":androidApp") 18 | include(":shared") -------------------------------------------------------------------------------- /shared/src/androidAndroidTestDebug/kotlin/br/com/progdeelite/kmmprogdeelite/database/DatabaseTest.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.database 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import br.com.progdeelite.kmmprogdeelite.models.Story 6 | import br.com.progdeelite.kmmprogdeelite.models.StoryMedia 7 | import br.com.progdeelite.kmmprogdeelite.utils.AndroidMainApp 8 | import org.junit.After 9 | import org.junit.Assert.* 10 | import org.junit.Before 11 | import org.junit.Test 12 | import org.junit.runner.RunWith 13 | 14 | @RunWith(AndroidJUnit4::class) 15 | class DatabaseTest { 16 | 17 | private lateinit var database: Database 18 | 19 | @Before 20 | fun prepareTest() { 21 | AndroidMainApp.applicationContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | database = Database(createSqlDriver()) 23 | database.clearDatabase() 24 | } 25 | 26 | @After 27 | fun cleanupTest() { 28 | database.clearDatabase() 29 | } 30 | 31 | @Test 32 | fun clearDatabase() { 33 | assertEquals(0, database.getAllStories().size) 34 | } 35 | 36 | @Test 37 | fun getAllStories() { 38 | val stories = listOf( 39 | createStory("1", "Story1"), 40 | createStory("2", "Story2"), 41 | ) 42 | database.insertStories(stories) 43 | val source = database.getAllStories() 44 | assertEquals(2, source.size) 45 | } 46 | 47 | @Test 48 | fun insertStories() { 49 | val stories = listOf( 50 | createStory("1", "Story1"), 51 | createStory("2", "Story2"), 52 | createStory("3", "Story3"), 53 | createStory("4", "Story4"), 54 | ) 55 | database.insertStories(stories) 56 | val source = database.getAllStories() 57 | assertEquals(4, source.size) 58 | } 59 | 60 | private fun createStory( 61 | id: String, 62 | name: String 63 | ): Story { 64 | return Story( 65 | id = id, 66 | name = name, 67 | storyMedia = StoryMedia( 68 | name = "Test", 69 | imgUrl = "none", 70 | mimeType = "image/jpg" 71 | ), 72 | slides = emptyList() 73 | ) 74 | } 75 | } -------------------------------------------------------------------------------- /shared/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/Platform.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | 6 | // TODO: nav 7 | //import androidx.navigation.NavHostController 8 | //import br.com.progdeelite.kmmprogdeelite.navigation.Navigation 9 | //import br.com.progdeelite.kmmprogdeelite.utils.AndroidApp 10 | 11 | class AndroidPlatform : Platform { 12 | override val name: String = "Android ${android.os.Build.VERSION.SDK_INT}" 13 | } 14 | 15 | actual fun getPlatform(): Platform = AndroidPlatform() 16 | 17 | // PLAYGROUND ONLY 18 | //actual abstract class BaseSharedViewModel: ViewModel()/*, Navigation TODO: nav */ { 19 | // 20 | // actual val scope = viewModelScope 21 | // 22 | // // private val navigator: NavHostController = AndroidApp.navHostController // TODO nav 23 | // 24 | // actual override fun onCleared() { 25 | // super.onCleared() 26 | // } 27 | // 28 | // TODO: nav 29 | // override fun navigateTo(destination: String) { 30 | // navigator.navigate(destination) 31 | // } 32 | // 33 | // override fun navigateToButRemoveBackStack(destination: String) { 34 | // navigator.popBackStack(destination, true) 35 | // } 36 | //} -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/database/DatabaseDriverFactory.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.database 2 | 3 | import br.com.progdeelite.kmmprogdeelite.utils.AndroidMainApp 4 | import com.squareup.sqldelight.android.AndroidSqliteDriver 5 | import com.squareup.sqldelight.db.SqlDriver 6 | 7 | actual fun createSqlDriver(): SqlDriver { 8 | return AndroidSqliteDriver(CommonDatabase.Schema, AndroidMainApp.applicationContext, "common.db") 9 | } -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/localization/Localization.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.localization 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.res.Resources.NotFoundException 5 | import br.com.progdeelite.kmmprogdeelite.utils.AndroidMainApp 6 | 7 | @SuppressLint("DiscouragedApi") 8 | actual fun getDefaultString(name: String): String { 9 | return with(AndroidMainApp.applicationContext) { 10 | try { 11 | getString(resources.getIdentifier(name, "string", packageName)) 12 | } catch (ex: NotFoundException){ 13 | name 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/network/GetNetwork.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.network 2 | 3 | import br.com.progdeelite.kmmprogdeelite.di.DI 4 | import io.ktor.client.* 5 | 6 | actual fun getAppEnvironment(): Environment = DI.Native.environment 7 | actual fun getHttpClient(clientConfig: ClientConfig): HttpClient = createOkHttpClient(clientConfig) -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/network/OkHttpClientFactory.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.network 2 | 3 | import br.com.progdeelite.kmmprogdeelite.network.models.ApiError 4 | import io.ktor.client.* 5 | import io.ktor.client.call.* 6 | import io.ktor.client.engine.okhttp.* 7 | import io.ktor.client.plugins.* 8 | import io.ktor.client.plugins.contentnegotiation.* 9 | import io.ktor.http.* 10 | import io.ktor.serialization.kotlinx.json.* 11 | import kotlinx.serialization.ExperimentalSerializationApi 12 | import kotlinx.serialization.json.Json 13 | 14 | fun createOkHttpClient(clientConfig: ClientConfig): HttpClient { 15 | return HttpClient(OkHttp) { 16 | installJsonSerializer() 17 | installRequestTimeouts() 18 | installDefaultUserAgentAndHeader(clientConfig) 19 | installResponseValidator() 20 | } 21 | } 22 | 23 | // 0) ASSISTA A AULA ANTERIOR - PRE-REQUISITO 24 | // 1) COMO CRIAR UM CUSTOM INTERCEPTOR 25 | // 2) COMO MAPEAR O RESULTADO DO BACKEND (ApiError / SafeApiCall) 26 | private fun HttpClientConfig.installResponseValidator() { 27 | HttpResponseValidator { 28 | validateResponse { response -> 29 | if(response.status != HttpStatusCode.OK){ 30 | try { 31 | val apiError = response.body() 32 | throw YourCompanyException(message = "YourCompanyException", apiError= apiError) 33 | } catch (e: Throwable) { 34 | throw Exception("${response.status}: ${response.body()}", e) 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | @OptIn(ExperimentalSerializationApi::class) 42 | fun HttpClientConfig.installJsonSerializer() { 43 | install(ContentNegotiation) { 44 | json(Json { 45 | prettyPrint = true 46 | isLenient = true 47 | ignoreUnknownKeys = true 48 | explicitNulls = false 49 | }) 50 | } 51 | } 52 | 53 | private fun HttpClientConfig.installRequestTimeouts() { 54 | install(HttpTimeout) { 55 | requestTimeoutMillis = 10_000 56 | socketTimeoutMillis = 10_000 57 | connectTimeoutMillis = 10_000 58 | } 59 | } 60 | 61 | private fun HttpClientConfig.installDefaultUserAgentAndHeader( 62 | clientConfig: ClientConfig 63 | ) { 64 | defaultRequest { 65 | userAgent(clientConfig.userAgent) 66 | } 67 | } -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/ColorResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | import android.content.res.Configuration 4 | import androidx.compose.ui.graphics.Color 5 | import br.com.progdeelite.kmmprogdeelite.utils.AndroidMainApp 6 | 7 | actual fun isSystemInDarkMode(): Boolean { 8 | val uiMode = AndroidMainApp.applicationContext.resources.configuration.uiMode 9 | return (uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES 10 | } 11 | 12 | actual class ColorResource actual constructor(light: Long, dark: Long) { 13 | private val colorDark = Color(dark) 14 | private val colorLight = Color(light) 15 | fun getColor() = if (isSystemInDarkMode()) colorDark else colorLight 16 | } -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/DimenResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | import androidx.compose.ui.unit.Dp 4 | import androidx.compose.ui.unit.dp 5 | 6 | actual class ButtonDimensResource actual constructor( 7 | private val roundedCornerUnit: Int, 8 | private val minWidthUnit: Int, 9 | private val heightUnit: Int, 10 | private val smallHeightUnit: Int 11 | ) { 12 | val roundedCorner: Dp by lazy { roundedCornerUnit.dp } 13 | val minWidth: Dp by lazy { minWidthUnit.dp } 14 | val height: Dp by lazy { heightUnit.dp } 15 | val smallHeight: Dp by lazy { smallHeightUnit.dp } 16 | } 17 | 18 | actual class SurfaceDimensResource actual constructor( 19 | private val roundedCornerUnit: Int 20 | ) { 21 | val roundedCorner: Dp by lazy { roundedCornerUnit.dp } 22 | } 23 | 24 | actual class HeaderDimensResource actual constructor( 25 | private val defaultHeight: Int, 26 | private val defaultContentHeight: Int, 27 | private val defaultPaddingStart: Int, 28 | private val defaultPaddingTop: Int, 29 | private val defaultPaddingEnd: Int, 30 | private val defaultPaddingBottom: Int, 31 | private val defaultFakeBlurAlpha: Float 32 | ) { 33 | val height: Dp by lazy { defaultHeight.dp } 34 | val contentHeight: Dp by lazy { defaultContentHeight.dp } 35 | val paddingStart: Dp by lazy { defaultPaddingStart.dp } 36 | val paddingTop: Dp by lazy { defaultPaddingTop.dp } 37 | val paddingEnd: Dp by lazy { defaultPaddingEnd.dp } 38 | val paddingBottom: Dp by lazy { defaultPaddingBottom.dp } 39 | val fakeBlurAlpha: Float by lazy { defaultFakeBlurAlpha } 40 | } 41 | 42 | actual class ScreenDimensResource actual constructor( 43 | private val defaultPadding: Int, 44 | private val defaultStatusBarThreshold: Float, 45 | private val defaultBlendLimit: Float, 46 | private val defaultCurveInset: Int 47 | ) { 48 | val padding: Dp by lazy { defaultPadding.dp } 49 | val statusBarThreshold: Float by lazy { defaultStatusBarThreshold } 50 | val blendLimit: Float by lazy { defaultBlendLimit } 51 | val curveInset: Dp by lazy { defaultCurveInset.dp } 52 | } 53 | 54 | actual class CardDimensResource actual constructor( 55 | private val defaultCornerRadius: Int, 56 | private val defaultPadding: Int, 57 | private val defaultHeight: Int, 58 | private val defaultWidth: Int 59 | ) { 60 | val cornerRadius: Dp by lazy { defaultCornerRadius.dp } 61 | val padding: Dp by lazy { defaultPadding.dp } 62 | val height: Dp by lazy { defaultHeight.dp } 63 | val width: Dp by lazy { defaultWidth.dp } 64 | } 65 | 66 | actual class ViewPagerResource actual constructor( 67 | private val defaultIndicatorComponentPadding: Int, 68 | private val defaultIndicatorPadding: Int, 69 | private val defaultIndicatorSize: Int 70 | ) { 71 | val indicatorComponentPadding: Dp by lazy { defaultIndicatorComponentPadding.dp } 72 | val indicatorPadding: Dp by lazy { defaultIndicatorPadding.dp } 73 | val indicatorSize: Dp by lazy { defaultIndicatorSize.dp } 74 | } 75 | 76 | actual class IconDimensResource actual constructor( 77 | private val defaultTiny: Int, 78 | private val defaultNormal: Int 79 | ) { 80 | val tiny: Dp by lazy { defaultTiny.dp } 81 | val normal: Dp by lazy { defaultNormal.dp } 82 | } 83 | 84 | actual class DefaultPaddingsResource actual constructor( 85 | private val defaultStart: Int, 86 | private val defaultEnd: Int, 87 | private val defaultTop: Int, 88 | private val defaultBottom: Int 89 | ) { 90 | val start: Dp by lazy { defaultStart.dp } 91 | val end: Dp by lazy { defaultEnd.dp } 92 | val top: Dp by lazy { defaultTop.dp } 93 | val bottom: Dp by lazy { defaultBottom.dp } 94 | } 95 | 96 | actual class TextFieldDimensResource actual constructor( 97 | private val minWidthUnit: Int, 98 | private val minHeightUnit: Int, 99 | private val roundCornerUnit: Int 100 | ) { 101 | val minWidth: Dp by lazy { minWidthUnit.dp } 102 | val minHeight: Dp by lazy { minHeightUnit.dp } 103 | val roundCorner: Dp by lazy { roundCornerUnit.dp } 104 | } -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/FontSizingResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | import androidx.compose.ui.unit.TextUnit 4 | import androidx.compose.ui.unit.sp 5 | 6 | actual class FontSizingResource actual constructor( 7 | private val fontSize: Int, 8 | private val fontLineHeight: Int 9 | ) { 10 | val size: TextUnit by lazy { fontSize.sp } 11 | val lineHeight: TextUnit by lazy { fontLineHeight.sp } 12 | } -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/ImageResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | import android.annotation.SuppressLint 4 | import br.com.progdeelite.kmmprogdeelite.utils.AndroidMainApp 5 | 6 | actual class ImageResource actual constructor(private val name: String) { 7 | 8 | private var _id: Int = -1 //only set by android preview 9 | val id: Int by lazy { getDrawableRes() } 10 | 11 | @SuppressLint("DiscouragedApi") 12 | private fun getDrawableRes(): Int { 13 | return if(_id == -1){ 14 | with(AndroidMainApp.applicationContext) { 15 | resources.getIdentifier(name, "drawable", packageName) 16 | } 17 | } else return _id 18 | } 19 | 20 | /** internal cause only used in shared code by getPreviewImageResource bellow */ 21 | internal fun setPreviewId(id: Int){ 22 | _id = id 23 | } 24 | } 25 | 26 | /** 27 | * call this function whenever you need to preview views on android 28 | * @param id any R.drawable.your_id to be able to preview screens on android while developing 29 | */ 30 | fun getPreviewImageResource(id: Int): ImageResource { 31 | val preview = ImageResource("") 32 | preview.setPreviewId(id) 33 | return preview 34 | } -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/SpacingResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | import android.util.DisplayMetrics 4 | import androidx.compose.ui.unit.Dp 5 | import androidx.compose.ui.unit.dp 6 | import br.com.progdeelite.kmmprogdeelite.utils.AndroidMainApp 7 | 8 | actual fun getWindowSize(): WindowSize { 9 | val dm : DisplayMetrics = AndroidMainApp.applicationContext.resources.displayMetrics 10 | val dpWidth = dm.widthPixels / dm.density 11 | return when{ 12 | dpWidth <= WindowSize.Small.size -> WindowSize.Small 13 | dpWidth > WindowSize.Small.size && dpWidth <= WindowSize.Medium.size -> WindowSize.Medium 14 | dpWidth > WindowSize.Medium.size -> WindowSize.Large 15 | else -> WindowSize.Small // should never happen 16 | } 17 | } 18 | 19 | actual class SpacingResource actual constructor(private val unit: Int) { 20 | val dp: Dp by lazy { dp() } 21 | private fun dp(): Dp { 22 | return unit.dp 23 | } 24 | } -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/components/ResendSmsResources.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources.components 2 | 3 | import br.com.progdeelite.kmmprogdeelite.resources.ImageResource 4 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 5 | import br.com.progdeelite.kmmprogdeelite.resources.TextResource 6 | 7 | data class ResendSmsResources( 8 | val title: TextResource, 9 | val sendSmsText: TextResource, 10 | val resendSmsText: TextResource, 11 | val placeholder: TextResource, 12 | val errorIcon: ImageResource 13 | 14 | ) { 15 | constructor() : this( 16 | title = Resources.Strings.sms_text_title, 17 | sendSmsText = Resources.Strings.sms_text_send, 18 | resendSmsText = Resources.Strings.sms_text_resend, 19 | placeholder = Resources.Strings.sms_textfield_hint, 20 | errorIcon = Resources.Image.info 21 | ) 22 | } -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/settings/Settings.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.settings 2 | 3 | import android.content.SharedPreferences 4 | import androidx.preference.PreferenceManager 5 | import br.com.progdeelite.kmmprogdeelite.utils.AndroidMainApp 6 | import com.russhwolf.settings.Settings 7 | import com.russhwolf.settings.SharedPreferencesSettings 8 | 9 | // Creates a shared prefs with name: {context.getPackageName() + "_preferences"} and MODE_PRIVATE 10 | val delegate: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(AndroidMainApp.applicationContext) 11 | 12 | actual fun getSettings(): Settings? { 13 | return SharedPreferencesSettings(delegate) 14 | } -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/utils/AndroidMainApp.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | 6 | // Parte 1 7 | // 1) https://plugins.jetbrains.com/plugin/8191-sqldelight 8 | // 2) definir dependencies no modulo de buildSrc 9 | // 3) referênciar dependencias nos build.gradles 10 | // 4) Criar classes de applications 11 | // 5) Criar modelos do banco em pacote models 12 | // 6) Configurar sqldelight no build.gradle 13 | // 7) Criar estrutura pacote database em common (seguir convenção sqldelight ) 14 | // 8) Criar Schema do banco de dados em database 15 | // 9) especificar drivers de plataforma e definir scheme database, alisar o elefante 16 | // 10) Criar classe de database em common em "sqldelight" no ROOT (muito importante) 17 | // 11) Usar database em DataSourceProvider 18 | 19 | // Parte 2 20 | // 12) Usar DataSourceProvider no view model 21 | // 13) Criar flow no view model para compose 22 | // 14) Usar flow na main activity em compose 23 | // 15) Criar teste para o banco de dados 24 | 25 | // mais resources: 26 | // https://bugonsoftware.substack.com/ 27 | // https://play.kotlinlang.org/hands-on/Networking%20and%20Data%20Storage%20with%20Kotlin%20Multiplatfrom%20Mobile/01_Introduction 28 | 29 | @SuppressLint("StaticFieldLeak") 30 | object AndroidMainApp { 31 | // since we are using the applicationContext and not 32 | // the context per se, nothing is going to leak here 33 | lateinit var applicationContext: Context 34 | } -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/utils/CommonLogger.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.utils 2 | 3 | import android.util.Log 4 | import br.com.progdeelite.kmmprogdeelite.di.DI 5 | import br.com.progdeelite.kmmprogdeelite.network.Environment 6 | 7 | actual interface CommonLogger { 8 | actual fun log(message:String, type: LogType){ 9 | when (DI.Native.environment) { 10 | Environment.INT -> { 11 | val integration = Environment.INT.name 12 | when(type){ 13 | LogType.DEBUG -> Log.d(integration, message) 14 | LogType.ERROR -> Log.e(integration, message) 15 | LogType.INFO -> Log.i(integration, message) 16 | LogType.WARNING -> Log.w(integration, message) 17 | } 18 | } 19 | Environment.DEV -> { 20 | val development = Environment.DEV.name 21 | when(type){ 22 | LogType.DEBUG -> Log.d(development, message) 23 | LogType.ERROR -> Log.e(development, message) 24 | LogType.INFO -> Log.i(development, message) 25 | LogType.WARNING -> Log.w(development, message) 26 | } 27 | } 28 | else -> Unit 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/utils/UtilExtensions.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.utils 2 | 3 | import kotlinx.serialization.decodeFromString 4 | import kotlinx.serialization.json.Json 5 | import kotlinx.serialization.json.JsonObject 6 | import java.net.URLEncoder 7 | import java.nio.charset.StandardCharsets 8 | import kotlinx.serialization.encodeToString as jsonEncoder 9 | import kotlinx.datetime.Clock 10 | import kotlinx.datetime.Instant 11 | import kotlinx.datetime.LocalDate 12 | import kotlinx.datetime.LocalDateTime 13 | import kotlinx.datetime.TimeZone 14 | import kotlinx.datetime.toLocalDateTime 15 | 16 | // 0) DEPENDÊNCIAS QUE PRECISAREMOS 17 | // 1) COMO SERIALIZAR E DESERIALIZAR OBJETOS 18 | // 2) COMO CRIAR UMA EXTENSÃO PARA USAR ONDE QUISER 19 | // 3) BÔNUS: CUIDADOS QUE DEVEMOS TER AO PASSAR ARGUMENTOS PARA ROTAS 20 | 21 | @kotlinx.serialization.Serializable 22 | data class ToBeEncoded(val name: String) 23 | 24 | fun ToBeEncoded.toJsonString(): String? = asJsonString() 25 | 26 | private inline fun T.asJsonString(): String? { 27 | return try { 28 | return Json.jsonEncoder(this) 29 | } catch (ex: Exception) { 30 | Logger.log("Json Encoding Exception: ${ex.message}", LogType.ERROR) 31 | null 32 | } 33 | } 34 | 35 | // funcão utilitária para quando os valores vem por algum motivo prefixados com aspas duplas 36 | fun JsonObject.string(name: String) = this[name].toString().replace("\"", "") 37 | 38 | val jsonBuilder = Json { 39 | this.ignoreUnknownKeys = true 40 | this.coerceInputValues = true 41 | } 42 | 43 | inline fun String.toObject(): T? { 44 | return try { 45 | return jsonBuilder.decodeFromString(this) 46 | } catch (ex: Exception) { 47 | Logger.log("Json Decoding Exception: ${ex.message}", LogType.ERROR) 48 | null 49 | } 50 | } 51 | 52 | // BÔNUS - NAVEGACão 53 | fun parseData(data: String): String { 54 | // you need this to ensure proper navigation when passing arguments to routes 55 | // if there are some special chars, urls etc. the navigation will crash 56 | return URLEncoder.encode(data, StandardCharsets.UTF_8.toString()) 57 | } 58 | 59 | // 0) DEPENDÊNCIAS QUE PRECISAREMOS 60 | // 1) COMO CRIAR EXTENSÃO QUE VERIFICA SE DATA ESTA EXPIRADA 61 | // 2) COMO OBTER O TEMPO (TIME) CORRENTE 62 | // 3) COMO OBTER A DATA ATUAL E COMO FORMATA-LA 63 | 64 | // returns current date time 65 | fun nowLocalDateTime(): LocalDateTime { 66 | val now: Instant = Clock.System.now() 67 | return now.toLocalDateTime(TimeZone.currentSystemDefault()) 68 | } 69 | 70 | // checks if today is bigger than this time 71 | fun LocalDate.isExpired() = this.now().compareTo(this) > 1 72 | 73 | // returns today's date 74 | fun LocalDate.now(): LocalDate { 75 | val now: Instant = Clock.System.now() 76 | return now.toLocalDateTime(TimeZone.currentSystemDefault()).date 77 | } 78 | 79 | // With leading zeros 80 | fun LocalDate.toDayMonthYear(): String { 81 | val dayWithLeadingZero = if (this.dayOfMonth.toString().length < 2) "0${this.dayOfMonth}" else this.dayOfMonth 82 | val monthWithLeadingZero = if (this.monthNumber.toString().length < 2) "0${this.monthNumber}" else this.monthNumber 83 | return "$dayWithLeadingZero.$monthWithLeadingZero.${this.year}" 84 | } -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/br/com/progdeelite/kmmprogdeelite/viewmodels/BaseSharedViewModel.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.viewmodels 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | 6 | actual abstract class BaseSharedViewModel: ViewModel() { 7 | 8 | actual val scope = viewModelScope 9 | 10 | actual override fun onCleared() { 11 | super.onCleared() 12 | } 13 | } -------------------------------------------------------------------------------- /shared/src/androidTest/kotlin/br/com/progdeelite/kmmprogdeelite/androidTest.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite 2 | 3 | import org.junit.Assert.assertTrue 4 | import org.junit.Test 5 | 6 | class AndroidGreetingTest { 7 | 8 | @Test 9 | fun testExample() { 10 | assertTrue("Check Android is mentioned", Greeting().greeting().contains("Android")) 11 | } 12 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/Greeting.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite 2 | 3 | class Greeting { 4 | private val platform: Platform = getPlatform() 5 | 6 | fun greeting(): String { 7 | return "Hello, ${platform.name}!" 8 | } 9 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/Platform.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite 2 | 3 | interface Platform { 4 | val name: String 5 | } 6 | 7 | expect fun getPlatform(): Platform 8 | 9 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/database/Database.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.database 2 | 3 | import br.com.progdeelite.kmmprogdeelite.models.Story 4 | import br.com.progdeelite.kmmprogdeelite.models.StoryMedia 5 | import com.squareup.sqldelight.db.SqlDriver 6 | 7 | // 1) COMO DEFINIR DEPENDÊNCIAS DE TESTE INSTRUMENTADO 8 | // 2) COMO ATUALIZAR O BUILD.GRADLE E RESOLVER POSSÍVEIS PROBLEMAS 9 | // 3) COMO CRIAR PACOTE DE TESTE INSTRUMENTADO 10 | // 4) COMO CRIAR TESTE INSTRUMENTADO PARA O BANCO DE DADOS (REQUER CONTEXTO) 11 | class Database(driver: SqlDriver, clearDatabase: Boolean = false) { 12 | private val database = CommonDatabase(driver) 13 | private val dbQuery = database.appDatabaseQueries 14 | 15 | init { 16 | if (clearDatabase) { 17 | clearDatabase() 18 | } 19 | } 20 | 21 | fun clearDatabase() { 22 | dbQuery.transaction { 23 | // nome que especificamos no schema 24 | dbQuery.removeAllStory() 25 | } 26 | } 27 | 28 | fun getAllStories(): List { 29 | // nome que especificamos no schema 30 | return dbQuery.selectAllStories(::mapStories).executeAsList() 31 | } 32 | 33 | fun insertStories(stories: List) { 34 | if (stories.isNotEmpty()) { 35 | dbQuery.transaction { 36 | stories.forEach { dbQuery.insertStory(id = it.id, body = it.name) } 37 | } 38 | } 39 | } 40 | 41 | // Aprender a mapear caso o banco tenha nomes diferentes 42 | private fun mapStories( 43 | id: String, 44 | body: String 45 | ): Story { 46 | return Story( 47 | id = id, 48 | name = body, 49 | StoryMedia( 50 | name = "test", 51 | imgUrl = "uri", 52 | mimeType = "image/png" 53 | ), 54 | slides = emptyList() 55 | ) 56 | } 57 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/database/DatabaseDriverFactory.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.database 2 | 3 | import com.squareup.sqldelight.db.SqlDriver 4 | 5 | expect fun createSqlDriver(): SqlDriver -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/di/DI.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.di 2 | 3 | import br.com.progdeelite.kmmprogdeelite.di.DI.Native.adobeAnalyticsSdk 4 | import br.com.progdeelite.kmmprogdeelite.di.DI.Native.environment 5 | import br.com.progdeelite.kmmprogdeelite.di.DI.Native.lokaliseSdk 6 | import br.com.progdeelite.kmmprogdeelite.localization.Localization 7 | import br.com.progdeelite.kmmprogdeelite.localization.LocalizationService 8 | import br.com.progdeelite.kmmprogdeelite.localization.LokaliseSdk 9 | import br.com.progdeelite.kmmprogdeelite.network.Environment 10 | import br.com.progdeelite.kmmprogdeelite.settings.AppSettings 11 | import br.com.progdeelite.kmmprogdeelite.settings.SettingsService 12 | import br.com.progdeelite.kmmprogdeelite.tracking.adobe.AdobeAnalyticsSdk 13 | import br.com.progdeelite.kmmprogdeelite.tracking.adobe.AnalyticsService 14 | import br.com.progdeelite.kmmprogdeelite.tracking.adobe.AnalyticsTracker 15 | import kotlin.native.concurrent.ThreadLocal 16 | 17 | // 1) Renomear AndroidApp para AndroidMainApp e criar IOSMainApp 18 | // 2) Mover dependencias comuns para DI 19 | // 3) Criar DependencyInjectionForPreview para android 20 | // 4) Defnir Fakes para Android 21 | // 5) Mostrar no OnboardingViewModel e OnboardingScreen 22 | 23 | 24 | /** 25 | * CUSTOM DEPENDENCY INJECTION - HOW TO USE IT: 26 | * 27 | * 1) Add dependency either to Native (direct in lookup from method inject) 28 | * 2) If Native, initialize it by assigning its value over MainApplication(Android)/UIApplicationDelegate(iOS) 29 | * 3) Add new lookup to inject method 30 | * 31 | * Usage (example): 32 | * val environments by inject() OR val environment: Environment by inject() 33 | */ 34 | object DI { 35 | 36 | @ThreadLocal 37 | object Native { 38 | // 1) 39 | lateinit var environment: Environment // 2) initialized in MainApplication/UIApplicationDelegate 40 | lateinit var lokaliseSdk: LokaliseSdk 41 | lateinit var adobeAnalyticsSdk: AdobeAnalyticsSdk 42 | } 43 | 44 | // PARA INJETAR NOS VIEW MODELS, REPOSITÓRIOS, APPS OU ONDE SEJA PRECISO 45 | inline fun inject(): Lazy { 46 | // 3) 47 | return when (T::class) { 48 | Environment::class -> lazy { environment as T } 49 | LokaliseSdk::class -> lazy { lokaliseSdk as T } 50 | AdobeAnalyticsSdk::class -> lazy { adobeAnalyticsSdk as T } 51 | else -> throw IllegalArgumentException("Dependency not found! Specify class \"${T::class.qualifiedName}\" in DI.inject()") 52 | } 53 | } 54 | 55 | // USADO PARA INJECÃO DE DEPENDÊNCIAS APENAS NO COMMON E 56 | // LIMITAR ACESSO DOS APPs ATRAVES DAS INTERFACES 57 | internal inline fun injectInternal(): Lazy { 58 | return when (T::class) { 59 | AnalyticsService::class -> lazy { AnalyticsTracker() as T } 60 | LocalizationService::class -> lazy { Localization() as T } 61 | SettingsService::class -> lazy { AppSettings() as T } 62 | else -> throw IllegalArgumentException("Dependency not found! Specify class \"${T::class.qualifiedName}\" in DI.injectInternal()") 63 | } 64 | } 65 | 66 | // for android preview [see DependencyInjectionForPreview] and testing later on 67 | fun fake() = fakeDI() 68 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/di/FakeDI.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.di 2 | 3 | import br.com.progdeelite.kmmprogdeelite.localization.Language 4 | import br.com.progdeelite.kmmprogdeelite.localization.LokaliseSdk 5 | import br.com.progdeelite.kmmprogdeelite.network.Environment 6 | import br.com.progdeelite.kmmprogdeelite.tracking.adobe.AdobeAnalyticsSdk 7 | 8 | internal fun fakeDI(){ 9 | DI.Native.environment = Environment.DEV 10 | DI.Native.lokaliseSdk = FakeLokalise() 11 | DI.Native.adobeAnalyticsSdk = FakeAdobeAnalytics() 12 | } 13 | 14 | class FakeLokalise: LokaliseSdk { 15 | override fun lokalise(stringRef: String): String = stringRef 16 | override fun loadResources() {} 17 | override fun changeLanguage(language: Language) {} 18 | override fun geLokaliseLanguage(): Language = Language.getDefaultLanguage() 19 | } 20 | 21 | class FakeAdobeAnalytics: AdobeAnalyticsSdk { 22 | override fun trackAction(name: String, contextData: Map) {} 23 | override fun trackScreen(name: String, contextData: Map?) {} 24 | override fun setProperty(name: String, value: String) {} 25 | override fun unsetProperty(name: String) {} 26 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/localization/DialogTexts.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.localization 2 | 3 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 4 | import br.com.progdeelite.kmmprogdeelite.resources.TextResource 5 | 6 | abstract class DialogTexts ( 7 | val title: TextResource, 8 | val description: TextResource, 9 | val primaryButtonText: TextResource, 10 | val secondaryButtonText: TextResource?, 11 | 12 | ){ 13 | object ForceUpdate: DialogTexts ( 14 | title = Resources.Strings.dialog_title_force_update, 15 | description = Resources.Strings.dialog_description_force_update, 16 | primaryButtonText = Resources.Strings.dialog_primary_button_text_force_update, 17 | secondaryButtonText = Resources.Strings.dialog_secondary_button_text_force_update 18 | ) 19 | 20 | object Cancel: DialogTexts ( 21 | title = Resources.Strings.dialog_title_cancel, 22 | description = Resources.Strings.dialog_description_cancel, 23 | primaryButtonText = Resources.Strings.dialog_primary_button_text_cancel, 24 | secondaryButtonText = Resources.Strings.dialog_secondary_button_cancel 25 | ) 26 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/localization/Language.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.localization 2 | 3 | enum class Language(val isoCode: String, val region: String, val text: String) { 4 | DE_CH("de", "CH", "Alemão - Suiça"), 5 | DE_AT("de", "AT", "Alemão - Austria"), 6 | IT_CH("it", "CH", "Italiano"), 7 | FR_CH("fr", "CH", "Français"); 8 | 9 | companion object { 10 | 11 | val fallback: Language = DE_CH 12 | 13 | fun getLanguageByIsoCodeAndRegion(isoCode: String, region: String): Language { 14 | return values().find { language -> 15 | language.isoCode.equals(isoCode, true) && 16 | language.region.equals(region, true) 17 | } ?: getDefaultLanguage() 18 | } 19 | 20 | /** @param isoCode low case country iso code. ex: "de", "it" etc. */ 21 | fun getDefaultLanguageByIsoCode(isoCode: String): Language { 22 | return when(isoCode){ 23 | DE_CH.isoCode -> DE_CH 24 | IT_CH.isoCode -> IT_CH 25 | FR_CH.isoCode -> FR_CH 26 | else -> fallback 27 | } 28 | } 29 | 30 | fun getLanguages(): List = values().toList() 31 | 32 | fun getDefaultLanguage(): Language = DE_CH 33 | } 34 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/localization/LanguagePickerTexts.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.localization 2 | 3 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 4 | import br.com.progdeelite.kmmprogdeelite.resources.TextResource 5 | 6 | data class LanguagePickerTexts ( 7 | val title: TextResource, 8 | val languageOptions: List 9 | ) { 10 | constructor() : this( 11 | title = Resources.Strings.app_language, 12 | languageOptions = Language.getLanguages() 13 | ) 14 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/localization/Localization.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.localization 2 | 3 | import br.com.progdeelite.kmmprogdeelite.di.DI 4 | import br.com.progdeelite.kmmprogdeelite.settings.SettingsService 5 | 6 | /** In case the localization service license expires, 7 | * we take the default value from available R.strings */ 8 | expect fun getDefaultString(name: String): String 9 | 10 | // 1) DIAGRAMA SERVICOS 11 | // 2) DEFINIR INTERFACE DE SERVICO 12 | // 3) IMPLEMENTAR SERVICO INTERNO 13 | // 4) ADICIONAR SERVICO A INJECÃO DE DEPENDENCIA 14 | interface LocalizationService { 15 | 16 | // acesso fácil e comum tanto para iOS como para Android 17 | companion object { 18 | val instance by DI.injectInternal() 19 | } 20 | 21 | fun lokalise(stringRef: String): String 22 | fun loadResources() 23 | fun setLanguage(language: Language) 24 | fun getCurrentLanguage(): Language 25 | } 26 | 27 | internal class Localization: LocalizationService { 28 | 29 | private val lokaliseSdk by DI.inject() // assista o video de localizacão 30 | private val settings by DI.injectInternal() // assista o video de settings 31 | 32 | override fun lokalise(stringRef: String): String { 33 | return lokaliseSdk.lokalise(stringRef) ?: getDefaultString(stringRef) // Lokalise license expired 34 | } 35 | 36 | override fun loadResources() = lokaliseSdk.loadResources() 37 | 38 | override fun setLanguage(language: Language) { 39 | lokaliseSdk.changeLanguage(language) 40 | settings.setLanguage(language) 41 | loadResources() 42 | } 43 | 44 | override fun getCurrentLanguage(): Language { 45 | // Read language from settings, if null return lokalise language or fallback 46 | return settings.getLanguage() ?: return lokaliseSdk.geLokaliseLanguage() 47 | } 48 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/localization/LokaliseSdk.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.localization 2 | 3 | /** common interface for lokalise.com */ 4 | interface LokaliseSdk { 5 | /** 6 | * @param stringRef string reference stored in the cloud from lokalise.com 7 | */ 8 | fun lokalise(stringRef: String): String? 9 | 10 | /** 11 | * Call this method whenever the changeLanguage method is called. 12 | */ 13 | fun loadResources() 14 | 15 | /** 16 | * Call this method whenever you whant to switch languages 17 | */ 18 | fun changeLanguage(language: Language) 19 | 20 | 21 | fun geLokaliseLanguage(): Language 22 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/models/Story.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.models 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class Story( 8 | @SerialName("id") 9 | val id: String, 10 | @SerialName("name") 11 | val name: String, 12 | @SerialName("media") 13 | val storyMedia: StoryMedia, 14 | @SerialName("slides") 15 | val slides: List 16 | ) 17 | 18 | @Serializable 19 | data class StoryMedia( 20 | @SerialName("name") 21 | val name: String, 22 | @SerialName("imgurl") 23 | val imgUrl: String, 24 | @SerialName("mimetype") 25 | val mimeType: String 26 | ) 27 | 28 | @Serializable 29 | data class Slide( 30 | @SerialName("id") 31 | val id: String, 32 | ) 33 | 34 | 35 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/navigation/BottomBarItem.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.navigation 2 | 3 | import br.com.progdeelite.kmmprogdeelite.resources.ImageResource 4 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 5 | import br.com.progdeelite.kmmprogdeelite.resources.TextResource 6 | import br.com.progdeelite.kmmprogdeelite.tracking.adobe.AnalyticsAction 7 | import br.com.progdeelite.kmmprogdeelite.tracking.adobe.ScreenInfo 8 | 9 | // 1) ESPECIFIQUE OS RECURSOS 10 | // - ImageResource 11 | // - StringResource 12 | // 2) CRIE E DEFINA OS VECTORES DE IMAGENS E TEXTOS EM STRINGS.XML 13 | // - strings.xml & drawable 14 | // 3) CRIE CLASSE BOTTOM BAR ITEM (POR ISSO PUBLIQUEI O VIDEO ANTERIOR) 15 | // - Navigation.kt (rotas) - ASSISTA O VIDEO, VC VAI PRECISAR: https://youtu.be/qeurKOMugIU 16 | // 4) DEFINA A BottomNavigationBar 17 | // - Theme (cores dos itens selecionados e nao selecionados) 18 | // - TextStyles (definir estilo da barra de navegação) 19 | // 5) JÁ SEGUE O CANAL PARA NAO PERDER A CONTINUAçÃO - VC VAI PRECISAR 100% 20 | 21 | abstract class BottomBarItem( 22 | val route: String, 23 | val title: TextResource, 24 | val icon: ImageResource, 25 | val action: AnalyticsAction, 26 | val screenInfo: ScreenInfo 27 | ) { 28 | 29 | object Home : BottomBarItem( 30 | route = Graphs.HomeGraph.root, 31 | title = Resources.Strings.nav_bar_home, 32 | icon = Resources.Image.home, 33 | action = AnalyticsAction.NavHomeAction, 34 | screenInfo = ScreenInfo.HomeScreen 35 | ) 36 | 37 | object Insurance : BottomBarItem( 38 | route = Graphs.InsuranceGraph.root, 39 | title = Resources.Strings.nav_bar_insurance, 40 | icon = Resources.Image.insurance, 41 | action = AnalyticsAction.NavInsuranceAction, 42 | screenInfo = ScreenInfo.InsuranceScreen 43 | ) 44 | 45 | object Support : BottomBarItem( 46 | route = Graphs.SupportGraph.root, 47 | title = Resources.Strings.nav_bar_support, 48 | icon = Resources.Image.support, 49 | action = AnalyticsAction.NavSupportAction, 50 | screenInfo = ScreenInfo.SupportScreen 51 | ) 52 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/navigation/Navigation.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.navigation 2 | 3 | /** 4 | * Shared routes to be used by iOS and Android 5 | */ 6 | object Navigator { 7 | val initialGraph = InitialGraph 8 | val authGraph = AuthGraph 9 | val authLoginGraph = AuthLoginGraph 10 | val homeGraph = HomeGraph 11 | val startGraph = StartGraph 12 | val insuranceGraph = InsuranceGraph 13 | val onboardingGraph = OnboardingGraph 14 | val supportGraph = SupportGraph 15 | val profileGraph = ProfileGraph 16 | val bottomNavGraph = BottomNavGraph 17 | } 18 | 19 | object InitialGraph { 20 | const val root = "root_graph" 21 | const val splash = "splash_screen" 22 | } 23 | 24 | object AuthGraph { 25 | const val root = "auth_graph" 26 | const val login = "login" 27 | const val signUp = "sign_up" 28 | const val forgotPassword = "forgot_password" 29 | } 30 | 31 | object AuthLoginGraph { 32 | const val root = "auth_login_graph" 33 | const val mobileNumber = "mobile" 34 | const val confirmSms = "sms" 35 | } 36 | 37 | object OnboardingGraph { 38 | const val root = "onboarding_graph" 39 | const val onboarding = "onboarding" 40 | } 41 | 42 | object InsuranceGraph { 43 | const val root = "insurance_graph" 44 | const val insurance = "insurance" 45 | } 46 | 47 | object ProfileGraph { 48 | const val root = "profile_graph" 49 | const val profile = "profile" 50 | const val settings = "settings" 51 | } 52 | 53 | object SupportGraph { 54 | const val root = "support_graph" 55 | const val support = "support" 56 | const val callCenter = "call_center" 57 | } 58 | 59 | object HomeGraph { 60 | const val root = "home_graph" 61 | const val home = "home" 62 | } 63 | 64 | object StartGraph { 65 | const val root = "start_graph" 66 | const val start = "start" 67 | } 68 | 69 | // ERRO COMUM: ROTAS REPETIDAS EM GRAFICOS DIFERENTES 70 | // ESSES ID's TEM QUE SER UNICOS 71 | object BottomNavGraph { 72 | const val root = "nav_graph" 73 | const val home = "nav_home" 74 | const val insurance = "nav_insurance" 75 | const val profile = "nav_profile" 76 | const val support = "nav_support" 77 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/network/ApiEndpoints.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.network 2 | 3 | import br.com.progdeelite.kmmprogdeelite.network.models.Entry 4 | import br.com.progdeelite.kmmprogdeelite.network.models.EntryResponse 5 | import io.ktor.client.* 6 | import io.ktor.client.call.* 7 | import io.ktor.client.request.* 8 | import kotlinx.coroutines.flow.Flow 9 | import kotlinx.coroutines.flow.flowOf 10 | 11 | // SEU PONTO DE PARTIDA - SEGUE O CANALA PORQUE VC VAI PRECISAR CEDO OU TARDE! 12 | interface Endpoints { 13 | interface Entries { 14 | suspend fun getEntries(): Flow>> 15 | } 16 | interface EndpointA 17 | interface EndpointB 18 | interface EndpointC 19 | } 20 | 21 | class ApiEndpoints(private val httpClient: HttpClient, private val env: Environment): Endpoints.Entries { 22 | 23 | override suspend fun getEntries(): Flow>> { 24 | val networkResult = safeApiCall { 25 | httpClient.get(urlString = "${env.hostTest}/random").body().entries.toList() 26 | } 27 | return flowOf(networkResult) 28 | } 29 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/network/ClientConfig.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.network 2 | 3 | // 1) CRIAR CLIENT CONFIG 4 | // 2) COMO CRIAR O CLIENT HTTP 5 | // 3) COMO CRIAR O OK_HTTP CLIENT FACTORY 6 | 7 | /** 8 | * Client configuration to be applied whenever a HttpClient is created 9 | */ 10 | data class ClientConfig( 11 | /** To be used as environment switcher */ 12 | val environment: Environment, 13 | 14 | /** To be used while sending requests "MOBILE", "WEB" or any other identifier you define */ 15 | var userAgent: String 16 | ) -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/network/Environment.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.network 2 | 3 | // 1) CRIAR DEPENDENCIAS, CRIAR/ATUALIZAR MANIFEST 4 | // 2) ATUALIZAR BUILD GRADLES 5 | // 3) CRIAR ENVIRONMENT 6 | // 4) INJETAR ENVIRONMENT IN COMMON (AndroidApp) 7 | // 5) MOSTRAR BUILD VARIANTS E TESTE NA MAIN ACTIVITY 8 | 9 | /** 10 | * Environment variable used while creating different http clients 11 | * @param host the host name of the desired environment 12 | * @param certificatePinningHashes hashes to be pinned if any (Only prepared for later) 13 | */ 14 | enum class Environment( 15 | val host: String, 16 | val certificatePinningHashes: List = emptyList(), 17 | val hostTest: String = "" 18 | ) { 19 | 20 | // +--------------------------+ 21 | // | AVAILABLE ENVIRONMENTS | 22 | // +--------------------------+ 23 | PROD( 24 | host = "https://api.yourcompany.com", 25 | certificatePinningHashes = listOf( 26 | "sha256/rE/SEU_HASH_DE_PINNING", 27 | "sha256/rE/SEU_HASH_DE_PINNING", 28 | "sha256/rE/SEU_HASH_DE_PINNING", 29 | ) 30 | ), 31 | 32 | INT( 33 | host = "https://api.integration.yourcompany.com", 34 | certificatePinningHashes = listOf( 35 | "sha256/rE/SEU_HASH_DE_PINNING", 36 | "sha256/rE/SEU_HASH_DE_PINNING", 37 | "sha256/rE/SEU_HASH_DE_PINNING", 38 | ), 39 | hostTest = "https://api.publicapis.org" 40 | ), 41 | 42 | DEV( 43 | host = "https://api.development.yourcompany.com", 44 | certificatePinningHashes = listOf( 45 | "sha256/rE/SEU_HASH_DE_PINNING", 46 | "sha256/rE/SEU_HASH_DE_PINNING", 47 | "sha256/rE/SEU_HASH_DE_PINNING", 48 | ), 49 | hostTest = "https://api.publicapis.org" // DEFINICÃO DA API DE TESTE PÚBLICA 50 | ); 51 | 52 | companion object{ 53 | fun getEnvironmentByBuildFlavor(buildFlavor: String): Environment { 54 | return when(buildFlavor){ 55 | "production" -> PROD 56 | "development" -> DEV 57 | "integration" -> INT 58 | else -> PROD 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/network/GetNetwork.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.network 2 | 3 | import io.ktor.client.* 4 | 5 | expect fun getAppEnvironment(): Environment 6 | expect fun getHttpClient(clientConfig: ClientConfig): HttpClient -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/network/NetworkResult.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.network 2 | 3 | // ESTADO DA ARTE - SEGUE O CANAL, VC VAI PRECISAR DISSO! 4 | sealed class NetworkResult( 5 | val data: T? = null, 6 | val errorCode: String? = null, 7 | val errorMessage: String? = null, 8 | val exception: Throwable? = null 9 | ) { 10 | class Success(data: T) : NetworkResult(data) 11 | class Error(code: String, errorMessage: String?) : NetworkResult( 12 | errorCode = code, 13 | errorMessage = errorMessage 14 | ) 15 | class Exception(exception: Throwable?) : NetworkResult( 16 | exception = exception 17 | ) 18 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/network/SafeApiCall.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.network 2 | 3 | import br.com.progdeelite.kmmprogdeelite.network.models.ApiError 4 | 5 | class YourCompanyException( 6 | message: String? = null, 7 | val apiError: ApiError? = null 8 | ): Exception(message) 9 | 10 | suspend fun safeApiCall(apiCall: suspend () -> T): NetworkResult { 11 | return try { 12 | NetworkResult.Success(data = apiCall.invoke()) 13 | } 14 | catch (e: YourCompanyException){ 15 | return mapYourCompanyException(e) 16 | } 17 | catch (e: Exception) { 18 | NetworkResult.Exception(e) 19 | } 20 | } 21 | 22 | fun mapYourCompanyException(e: YourCompanyException): NetworkResult { 23 | return NetworkResult.Error( 24 | code = e.apiError?.code ?: "0", 25 | errorMessage = e.apiError?.message ?: "Erro desconhecido!" 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/network/models/ApiError.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.network.models 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | // VOCE JUNTO COM SEU BACKEND DEFINEM COMO O ERRO SERÁ RETORNADO 7 | // AQUI UM EXEMPLO COMUM DE UM RETORNO DE UMA API DE UM BACKEND 8 | @Serializable 9 | data class ApiError( 10 | @SerialName("code") 11 | val code: String, 12 | @SerialName("message") 13 | val message: String 14 | ) 15 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/network/models/EntryResponse.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.network.models 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | // 1) DEFINIR MODELOS DE CONTEUDO (EntryResponse) 7 | // 2) DEFINIR MODELOS DE ERRO (ApiError) 8 | // 3) CRIAR OS TIPOS DE RESULTADOS DA API (NetworkResult) 9 | // 4) TRATAR/MAPEAR RESULTADOS DA API (SafeApiCall) 10 | // 5) ESPECIFICAR ENDEREçO BASE DO ENDPOINT DENTRO DA VARIAVEL DE AMBIENTE (Environment) 11 | // 6) ESPECIFICAR API ENDPOINTS (ApiEndpoints) 12 | 13 | 14 | 15 | // COMO CRIEI ESSES OBJETOS? 16 | // pesquisa: json to kotlin data class online generator 17 | // https://json2kt.com/ 18 | @Serializable 19 | data class EntryResponse( 20 | @SerialName("count") var count: Int? = null, 21 | @SerialName("entries") var entries: ArrayList = arrayListOf() 22 | ) 23 | 24 | @Serializable 25 | data class Entry( 26 | @SerialName("API") var api: String? = null, 27 | @SerialName("Description") var description: String? = null, 28 | @SerialName("Auth") var auth: String? = null, 29 | @SerialName("HTTPS") var https: Boolean? = null, 30 | @SerialName("Cors") var cors: String? = null, 31 | @SerialName("Link") var link: String? = null, 32 | @SerialName("Category") var category: String? = null 33 | ) 34 | 35 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/providers/DataSourceProvider.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.providers 2 | 3 | import br.com.progdeelite.kmmprogdeelite.database.Database 4 | import br.com.progdeelite.kmmprogdeelite.database.createSqlDriver 5 | 6 | class DataSourceProvider { 7 | private val database = Database(createSqlDriver()) 8 | 9 | fun getLocalCommonDatabase() = database 10 | 11 | // Outros provedores de dados que voce poderia ter aqui mais pra frente 12 | // fun getXyzRepository() 13 | // fun getXyzUseCase() 14 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/repositories/EntryRepository.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.repositories 2 | 3 | import br.com.progdeelite.kmmprogdeelite.network.Endpoints 4 | import br.com.progdeelite.kmmprogdeelite.network.NetworkResult 5 | import br.com.progdeelite.kmmprogdeelite.network.models.Entry 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | // 1) COMO CRIAR O REPOSITORY 9 | // 2) COMO CRIAR O VIEWMODEL PARA EMITIR UM FLOW 10 | // 3) COMO USAR O VIEWMODEL NA PRÁTICA DENTRO DA VIEW 11 | 12 | interface EntrySourceType { 13 | suspend fun fetchEntries(): Flow>> 14 | } 15 | 16 | class EntryRepository(private val entryEndpoint: Endpoints.Entries): EntrySourceType { 17 | override suspend fun fetchEntries(): Flow>> { 18 | return entryEndpoint.getEntries() 19 | } 20 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/ColorResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | // 1) Adicionar dependencia 4 | // 2) Definir expecte/actuals 5 | // 3) implementar em android e iOS 6 | // 4) implementar ColorResources 7 | // 5) disponibilizar em Resources 8 | 9 | /** 10 | * Indicates whether the app is using dark mode or not 11 | */ 12 | expect fun isSystemInDarkMode(): Boolean 13 | 14 | /** 15 | * iOS can handle only RGB-values to create its color instances. That's why we offered the abstract class [IosColor]. 16 | * It is intended to be used by iOS only. Android on the other hand must resolve its reference first. For that reason 17 | * we offer a color resolver method for android only in its actual implementation. 18 | */ 19 | expect class ColorResource(light: Long, dark: Long) -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/ColorScheme.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | // 1) ColorScheme - Agrupar cores e atribuir nomes 4 | // 2) Refatorar ColorResource para receber dois parametros dark e light 5 | // 3) Atualizar as implementações de ColorResource 6 | // 4) Atualizar Arquivo ColorResources (Themas) 7 | // 5) Atualizar Resource 8 | // 6) Atualuzar arquivo de Thema do android 9 | 10 | object ColorScheme { 11 | const val gray60a: Long = 0xFF707070 12 | const val grayLaminate: Long = 0xFFCECECE 13 | 14 | const val black: Long = 0xFF131313 15 | const val black5a: Long = 0xFFEFEFEF 16 | const val black60a: Long = 0x99131313 17 | const val gold: Long = 0xFFFFD700 18 | 19 | 20 | const val redDark: Long = 0xFFDC4657 21 | 22 | const val greenExtraDark: Long = 0xff001e00 23 | const val greenDark: Long = 0xFF00D80A 24 | const val greenLight: Long = 0xFF76DA96 25 | const val orangeDark: Long = 0xFFE95D0F 26 | const val orangeLight: Long = 0xFFF09C6D 27 | 28 | const val white: Long = 0xFFFFFFFF 29 | const val white40a: Long = 0x66FFFFFF 30 | const val white0a: Long = 0x00FFFFFF 31 | 32 | const val blue: Long = 0xFF414141 33 | const val blueSky: Long = 0xFF1894E1 34 | const val blueLight: Long = 0xFF9CECFB 35 | const val blueCloudy: Long = 0xFFC0D6DF 36 | const val blue60a: Long = 0xFF65C7F7 37 | 38 | const val transparent: Long = 0x00000000 39 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/DimenResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | import kotlin.native.concurrent.ThreadLocal 4 | 5 | // 1) DEFINICÃO DA REGUA DAS CONTANTES 6 | // 2) IMPLEMENTAçÃO ANDROID E IOS 7 | // 3) INCORPORAçÃO NO RECURSO CENTRAL DE RESOURCES COMPARTILHADO 8 | // 4) USO PRÁTICO NAS PLATAFORMAS 9 | 10 | @ThreadLocal 11 | object LayoutDimens { 12 | private val dimensions by lazy { ComponentDimens.Dimens() } 13 | internal fun getDimens(): ComponentDimens = dimensions 14 | } 15 | 16 | abstract class ComponentDimens( 17 | val button: ButtonDimensResource, 18 | val surface: SurfaceDimensResource, 19 | val textInputField: TextFieldDimensResource, 20 | val defaultPadding: DefaultPaddingsResource, 21 | val header: HeaderDimensResource, 22 | val screen: ScreenDimensResource, 23 | val card: CardDimensResource, 24 | val icon: IconDimensResource, 25 | val viewPager: ViewPagerResource 26 | // ... OUTROS COMPONENTES ADICIONE AQUI .... 27 | ) { 28 | internal class Dimens : ComponentDimens( 29 | button = ButtonDimensResource(20, 0, 44, 34), 30 | surface = SurfaceDimensResource(20), 31 | textInputField = TextFieldDimensResource(40, 54, 40), 32 | defaultPadding = DefaultPaddingsResource(12, 12, 12, 12), 33 | header = HeaderDimensResource(100, 45, 24, 0, 24, 12, 0.95f), 34 | screen = ScreenDimensResource(24, 0.1f, 100f, 20), 35 | card = CardDimensResource(15, 24, 56, 56), 36 | icon = IconDimensResource(24, 48), 37 | viewPager = ViewPagerResource(10, 10, 20) 38 | ) 39 | } 40 | 41 | expect class HeaderDimensResource( 42 | defaultHeight: Int, 43 | defaultContentHeight: Int, 44 | defaultPaddingStart: Int, 45 | defaultPaddingTop: Int, 46 | defaultPaddingEnd: Int, 47 | defaultPaddingBottom: Int, 48 | defaultFakeBlurAlpha: Float 49 | ) 50 | 51 | expect class ScreenDimensResource(defaultPadding: Int, defaultStatusBarThreshold: Float, defaultBlendLimit: Float, defaultCurveInset: Int) 52 | expect class CardDimensResource( 53 | defaultCornerRadius: Int, 54 | defaultPadding: Int, 55 | defaultHeight: Int, 56 | defaultWidth: Int 57 | ) 58 | 59 | expect class ViewPagerResource( 60 | defaultIndicatorComponentPadding: Int, 61 | defaultIndicatorPadding: Int, 62 | defaultIndicatorSize: Int, 63 | ) 64 | 65 | expect class IconDimensResource(defaultTiny: Int, defaultNormal: Int) 66 | 67 | expect class DefaultPaddingsResource(defaultStart: Int, defaultEnd: Int, defaultTop: Int, defaultBottom: Int) 68 | expect class SurfaceDimensResource(roundedCornerUnit: Int) 69 | expect class ButtonDimensResource(roundedCornerUnit: Int, minWidthUnit: Int, heightUnit: Int, smallHeightUnit: Int) 70 | expect class TextFieldDimensResource(minWidthUnit: Int, minHeightUnit: Int, roundCornerUnit: Int) -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/FontSizingResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | 4 | // 1) COMO COMPARTILHAR TAMANHO DE TEXTO 5 | // 2) COMO IMPLEMENTAR EM ANDROID E iOS (Expect/actual) 6 | // 3) DISPONIBILIZAR EM RESOURCES SHARED 7 | 8 | expect class FontSizingResource(fontSize: Int, fontLineHeight: Int) -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/FontSizingResources.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | import kotlin.native.concurrent.ThreadLocal 4 | 5 | @ThreadLocal 6 | object FontSizingResources { 7 | private val small by lazy { FontSizing.Small() } 8 | private val medium by lazy { FontSizing.Medium() } 9 | private val large by lazy { FontSizing.Large() } 10 | 11 | internal fun getFontSizing(): FontSizing { 12 | return when (getWindowSize()) { 13 | WindowSize.Small -> small 14 | WindowSize.Medium -> medium 15 | WindowSize.Large -> large 16 | } 17 | } 18 | } 19 | 20 | abstract class FontSizing( 21 | val tiny: FontSizingResource, 22 | val small: FontSizingResource, 23 | val normal: FontSizingResource, 24 | val medium: FontSizingResource, 25 | val big: FontSizingResource, 26 | val large: FontSizingResource, 27 | val huge: FontSizingResource, 28 | ) { 29 | internal class Small : FontSizing( 30 | tiny = FontSizingResource(8, 14), 31 | small = FontSizingResource(13, 18), 32 | normal = FontSizingResource(15, 19), 33 | medium = FontSizingResource(17, 23), 34 | big = FontSizingResource(19, 24), 35 | large = FontSizingResource(24, 28), 36 | huge = FontSizingResource(27, 30), 37 | ) 38 | 39 | internal class Medium : FontSizing( 40 | tiny = FontSizingResource(10, 16), 41 | small = FontSizingResource(13, 18), 42 | normal = FontSizingResource(15, 19), 43 | medium = FontSizingResource(17, 23), 44 | big = FontSizingResource(20, 25), 45 | large = FontSizingResource(26, 30), 46 | huge = FontSizingResource(30, 34), 47 | ) 48 | 49 | internal class Large : FontSizing( 50 | tiny = FontSizingResource(12, 18), 51 | small = FontSizingResource(14, 19), 52 | normal = FontSizingResource(16, 20), 53 | medium = FontSizingResource(19, 25), 54 | big = FontSizingResource(22, 27), 55 | large = FontSizingResource(30, 34), 56 | huge = FontSizingResource(34, 38), 57 | ) 58 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/ImageResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | /** 4 | * iOS only needs the image string reference to resolve its images. 5 | * Android on the other hand must resolve its reference from R.drawable first. 6 | * For that reason, we offer a drawable resolver method for android only in its actual implementation. 7 | */ 8 | expect class ImageResource(name: String) -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/ImageResources.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | /** Generated object! Do not change it manually! Use the script located at \"../tools/create_image_resources.sh\" instead. */ 4 | object ImageResources { 5 | val switch = ImageResource("switch_cam") 6 | val lamp = ImageResource("lamp") 7 | val fire = ImageResource("fire") 8 | val info = ImageResource("info") 9 | 10 | val flash = ImageResource("flash") 11 | val wifi = ImageResource("wifi") 12 | 13 | // VIDEO IMAGENS E ASSETS COMPARTILHADOS (ASSISTA, VC VAI PRECISAR 100%): https://youtu.be/ukXx8mD9Lmo 14 | val home = ImageResource("home") 15 | val insurance = ImageResource("insurance") 16 | val support = ImageResource("support") 17 | val profile = ImageResource("profile") 18 | 19 | } 20 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/Resources.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | /** Shared resources for android and iOS */ 4 | object Resources { 5 | val Image = ImageResources 6 | val Strings = StringResources 7 | val Color = ColorResources 8 | 9 | // IMPORTANTE ENTENDER: como isso aqui é um objeto singleton (equivalente a um objeto estático em java) 10 | // ele so seria instanciado uma única vez. Como o thema pode mudar dinámicamente, é importante 11 | // sempre fazer uma "chamada" para o Color.getTheme() para garantir o theming correto! 12 | // Uso: Iremos acessar e atribuir da seguinte maneira nas views: Resources.Theme.primaryColor.getColor() 13 | val Theme: Themes 14 | get() = Color.getTheme() 15 | 16 | // COMO USAR EX: modifier = modifier.height(Spacing.extraLarge.dp) 17 | val Spacing = SpacingResources.getSpacing() 18 | 19 | val FontSizing = FontSizingResources.getFontSizing() 20 | 21 | // DIMENSÕES DE COMPONENTES (BUTTONS, TEXTFIELDS... ETC) 22 | val Dimen = LayoutDimens.getDimens() 23 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/SpacingResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | // 1) ESPECIFICAR TAMANHOS DE TELAS 4 | // 2) OBTER TAMANHO DA TELA E RECURSO DE TAMANHO (ANDROID/IOS) 5 | // 3) IMPLEMENTAR EM ANDROID E IOS (IOS FOI BEM TRICKY) 6 | // 4) CRIAR SPACING RESOURCES 7 | // 5) USAR EM RESOURCES 8 | 9 | /** 10 | * Indicates which port view (window size) the app is running on 11 | */ 12 | expect fun getWindowSize(): WindowSize 13 | 14 | /** 15 | * Returns Dp for android and points for iOS 16 | */ 17 | expect class SpacingResource(unit: Int) 18 | 19 | enum class WindowSize(val size: Int) { 20 | // Those break points are defined by your UI/UX team 21 | Small(320), Medium(375), Large(430) 22 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/SpacingResources.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | import kotlin.native.concurrent.ThreadLocal 4 | 5 | @ThreadLocal 6 | object SpacingResources { 7 | private val small by lazy { Small() } 8 | private val medium by lazy { Medium() } 9 | private val large by lazy { Large() } 10 | 11 | internal fun getSpacing(): Spacing { 12 | return when (getWindowSize()) { 13 | WindowSize.Small -> small 14 | WindowSize.Medium -> medium 15 | WindowSize.Large -> large 16 | } 17 | } 18 | } 19 | 20 | abstract class Spacing( 21 | val noSpace: SpacingResource, 22 | val extraTiny: SpacingResource, 23 | val tiny: SpacingResource, 24 | val extraSmall: SpacingResource, 25 | val small: SpacingResource, 26 | val normal: SpacingResource, 27 | val medium: SpacingResource, 28 | val big: SpacingResource, 29 | val extraBig: SpacingResource, 30 | val large: SpacingResource, 31 | val extraLarge: SpacingResource, 32 | val huge: SpacingResource, 33 | val extraHuge: SpacingResource, 34 | ) 35 | 36 | private class Small : Spacing( 37 | noSpace = SpacingResource(0), 38 | extraTiny = SpacingResource(2), 39 | tiny = SpacingResource(4), 40 | extraSmall = SpacingResource(8), 41 | small = SpacingResource(10), 42 | normal = SpacingResource(12), 43 | medium = SpacingResource(16), 44 | big = SpacingResource(20), 45 | extraBig = SpacingResource(22), 46 | large = SpacingResource(24), 47 | extraLarge = SpacingResource(36), 48 | huge = SpacingResource(40), 49 | extraHuge = SpacingResource(48), 50 | ) 51 | 52 | private class Medium : Spacing( 53 | noSpace = SpacingResource(0), 54 | extraTiny = SpacingResource(2), 55 | tiny = SpacingResource(4), 56 | extraSmall = SpacingResource(8), 57 | small = SpacingResource(10), 58 | normal = SpacingResource(12), 59 | medium = SpacingResource(16), 60 | big = SpacingResource(24), 61 | extraBig = SpacingResource(28), 62 | large = SpacingResource(32), 63 | extraLarge = SpacingResource(40), 64 | huge = SpacingResource(48), 65 | extraHuge = SpacingResource(72), 66 | ) 67 | 68 | private class Large : Spacing( 69 | noSpace = SpacingResource(0), 70 | extraTiny = SpacingResource(4), 71 | tiny = SpacingResource(6), 72 | extraSmall = SpacingResource(12), 73 | small = SpacingResource(16), 74 | normal = SpacingResource(20), 75 | medium = SpacingResource(24), 76 | big = SpacingResource(28), 77 | extraBig = SpacingResource(34), 78 | large = SpacingResource(36), 79 | extraLarge = SpacingResource(48), 80 | huge = SpacingResource(56), 81 | extraHuge = SpacingResource(96), 82 | ) -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/StringResources.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | /** Generated object! Do not change it manually! Use the script located at \"../tools/generate_text_resources.sh\" instead.*/ 4 | object StringResources { 5 | val app_name = TextResource("app_name") 6 | 7 | val app_language = TextResource("app_language") 8 | 9 | val top_image_text = TextResource("top_image_text") 10 | val middle_image_text = TextResource("middle_image_text") 11 | val bottom_image_text = TextResource("bottom_image_text") 12 | 13 | val textfield_number_not_found = TextResource("textfield_number_not_found") 14 | val textfield_invalid_birthdate = TextResource("textfield_invalid_birthdate") 15 | val textfield_invalid_code = TextResource("textfield_invalid_code") 16 | val textfield_invalid_phone_number_format = TextResource("textfield_invalid_phone_number_format") 17 | val textfield_code_expired = TextResource("textfield_code_expired") 18 | val textfield_empty = TextResource("textfield_empty") 19 | 20 | val sms_text_title = TextResource("sms_text_title") 21 | val sms_text_send = TextResource("sms_text_send") 22 | val sms_text_resend = TextResource("sms_text_resend") 23 | val sms_textfield_hint = TextResource("sms_textfield_hint") 24 | 25 | val dialog_title_force_update = TextResource("dialog_title_force_update") 26 | val dialog_description_force_update = TextResource("dialog_description_force_update") 27 | val dialog_primary_button_text_force_update = TextResource("dialog_primary_button_text_force_update") 28 | val dialog_secondary_button_text_force_update = TextResource("dialog_secondary_button_text_force_update") 29 | 30 | val dialog_title_cancel = TextResource("dialog_title_cancel") 31 | val dialog_description_cancel = TextResource("dialog_description_cancel") 32 | val dialog_primary_button_text_cancel = TextResource("dialog_primary_button_text_cancel") 33 | val dialog_secondary_button_cancel = TextResource("dialog_secondary_button_cancel") 34 | 35 | val connectivity_ct_device_online = TextResource("connectivity_device_online") 36 | val connectivity_ct_device_offline = TextResource("connectivity_device_offline") 37 | 38 | // VIDEO TEXTOS COMPARTILHADOS (ASSISTA, VC VAI PRECISAR 100%): https://youtu.be/MjitUKJFu80 39 | val nav_bar_home = TextResource("nav_bar_home") 40 | val nav_bar_insurance = TextResource("nav_bar_insurance") 41 | val nav_bar_support = TextResource("nav_bar_support") 42 | val nav_bar_profile = TextResource("nav_bar_profile") 43 | } 44 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/TextResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | import br.com.progdeelite.kmmprogdeelite.di.DI.injectInternal 4 | import br.com.progdeelite.kmmprogdeelite.localization.LocalizationService 5 | import kotlinx.coroutines.flow.MutableStateFlow 6 | import kotlinx.coroutines.flow.StateFlow 7 | 8 | // 1) Definir dependencias 9 | // 2) especificar interface de localização e implementar nas plataformas 10 | // 3) injetar dependências através de DI 11 | // 4) Definir interface do servico de localizazão e usar localização 12 | // 5) especificar dependências através de DI 13 | // 6) Criar classe TextResource para compartilhar textos 14 | // 7) Definir strings em Resources, StringsResources e strings.xml (fallback) 15 | // 8) Criar os textos de onboarding em OnBoardingTexts 16 | // 9) Mostrar uso na prática no OnboardingViewModel (OnBoardingTexts) 17 | // 10) Ver uso na prática dentro da OnboardingScreen 18 | 19 | /** 20 | * Android and iOS localizes its text references over the Lokalise-SDK. 21 | * @param name resource name declared in the lokalise cloud 22 | */ 23 | class TextResource(val name: String) { 24 | 25 | private var _text: String = "" 26 | private val service by injectInternal() 27 | 28 | private fun localized(): String { 29 | return _text.ifBlank { service.lokalise(name) } 30 | } 31 | 32 | val localized: String 33 | get() = this.localized() 34 | 35 | internal fun setText(text: String) { 36 | _text = text 37 | } 38 | } 39 | 40 | fun getTextResource(text: String): TextResource { 41 | val resource = TextResource("") 42 | resource.setText(text) 43 | return resource 44 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/settings/Settings.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.settings 2 | 3 | import com.russhwolf.settings.Settings 4 | 5 | expect fun getSettings(): Settings? -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/settings/SettingsKeys.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.settings 2 | 3 | // 1) Adicionar dependencias 4 | // 2) Criar/Pensar nas keys de migração e novas 5 | // 3) Criar Settings em ambas as plataformas 6 | // 4) Criar Serviço de settings para os apps 7 | // 5) Adicionar no mecanismo de dependency injection 8 | 9 | object SettingsKeys { 10 | // ************************* PAY ATTENTION ***************************** 11 | // DO NOT CHANGE THIS PROP NAME. IT MUST BE COMPATIBLE WITH OLD VERSION 12 | // ********************************************************************* 13 | const val PREF_KEY_LANGUAGE = "pref_key_app_language" 14 | const val PREF_KEY_LANGUAGE_REGION = "pref_key_app_region" 15 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/settings/SettingsService.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.settings 2 | 3 | import br.com.progdeelite.kmmprogdeelite.di.DI 4 | import br.com.progdeelite.kmmprogdeelite.localization.Language 5 | 6 | // 1) DIAGRAMA SERVICOS 7 | // 2) DEFINIR INTERFACE DE SERVICO 8 | // 3) IMPLEMENTAR SERVICO INTERNO 9 | // 4) ADICIONAR SERVICO A INJECÃO DE DEPENDENCIA 10 | interface SettingsService { 11 | 12 | // acesso fácil e comum tanto para iOS como para Android 13 | companion object { 14 | val instance by DI.injectInternal() 15 | } 16 | 17 | fun getLanguage(): Language? 18 | fun setLanguage(language: Language) 19 | 20 | fun getRegion(): String? 21 | fun setRegion(region: String) 22 | } 23 | 24 | internal class AppSettings: SettingsService { 25 | 26 | private val settings = getSettings() 27 | 28 | override fun getLanguage(): Language? { 29 | val langIsoCode = settings?.getStringOrNull(SettingsKeys.PREF_KEY_LANGUAGE) 30 | 31 | if (langIsoCode.isNullOrBlank()) { 32 | return null 33 | } 34 | 35 | return when (langIsoCode) { 36 | Language.DE_CH.isoCode -> migrateLegacyLanguage(langIsoCode) 37 | Language.IT_CH.isoCode -> migrateLegacyLanguage(langIsoCode) 38 | Language.FR_CH.isoCode -> migrateLegacyLanguage(langIsoCode) 39 | else -> Language.valueOf(langIsoCode) 40 | } 41 | } 42 | 43 | // MECANISMO PARA MIGRAR APP ANTIGAS E POSSIBILITAR FUTURAS MIGRACÕES 44 | private fun migrateLegacyLanguage(isoCode: String): Language { 45 | val region = settings?.getStringOrNull(SettingsKeys.PREF_KEY_LANGUAGE_REGION) 46 | 47 | if (region.isNullOrBlank()) { 48 | return Language.getDefaultLanguageByIsoCode(isoCode) 49 | } 50 | 51 | return Language.getLanguageByIsoCodeAndRegion(isoCode, region) 52 | } 53 | 54 | override fun setLanguage(language: Language) { 55 | settings?.putString(SettingsKeys.PREF_KEY_LANGUAGE, language.name) 56 | } 57 | 58 | override fun getRegion(): String? { 59 | return settings?.getStringOrNull(SettingsKeys.PREF_KEY_LANGUAGE_REGION) 60 | } 61 | override fun setRegion(region: String) { 62 | settings?.putString(SettingsKeys.PREF_KEY_LANGUAGE_REGION, region) 63 | } 64 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/tracking/adobe/AdobeAnalytics.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.tracking.adobe 2 | 3 | import br.com.progdeelite.kmmprogdeelite.di.DI 4 | 5 | // 1) DEFINIR DEPENDENCIAS 6 | // 2) DEFINIR ACÕES, INFOS DE TELAS, PROPERTIES 7 | // 3) DEFINIR INTERFACE, IMPLEMENTAR EM ANDROID 8 | // 4) INJETAR A DEPENDENCIA EM DI ATRAVES DA MAIN APPLICATION 9 | // 5) IMPLEMENTAR SERVICO COMUM PARA IOS e ANDROID 10 | 11 | enum class AnalyticsAction(val actionName: String, val actionContext: String?) { 12 | 13 | DiscoverAction("discover", "Explorar App"), 14 | LanguageAction("language_switch", null), 15 | LoginAction("login", "Entrar no App"), 16 | SupportEmailAction("support_email", "Suporte via E-Mail"), 17 | SupportCallAction("support_call", "Supporte via telefone"), 18 | NavHomeAction("nav_home", "Navegou para Tela Inicial"), 19 | NavInsuranceAction("nav_insurance", "Navegou para Tela de Seguros"), 20 | NavSupportAction("nav_help", "Navegou para Tela de Suporte"), 21 | TooManySmsAction("sms_exceeded", "Número de SMS por dia excedido!"), 22 | NavProfileAction("nav_profile", "Navegou para Tela de Perfil"); 23 | 24 | companion object { 25 | fun getContext(action: AnalyticsAction) = if (action.actionContext == null) mapOf() else mapOf("context" to action.actionContext) 26 | } 27 | } 28 | 29 | enum class ScreenInfo(val screenName: String) { 30 | HomeScreen("home"), 31 | InsuranceScreen("insurance"), 32 | SupportScreen("help"), 33 | ProfileScreen("profile"), 34 | AnimationScreen("intro"), 35 | OnboardingScreen("onboarding") 36 | } 37 | 38 | enum class AnalyticsProperty(val propertyName: String) { 39 | AppLanguage("AppLanguage") 40 | } 41 | 42 | interface AdobeAnalyticsSdk { 43 | fun trackAction(name: String, contextData: Map) 44 | fun trackScreen(name: String, contextData: Map? = null) 45 | fun setProperty(name: String, value: String) 46 | fun unsetProperty(name: String) 47 | } 48 | 49 | interface AnalyticsService { 50 | 51 | companion object { 52 | val instance by DI.injectInternal() 53 | } 54 | 55 | fun trackScreen(screen: ScreenInfo, contextData: Map? = null) 56 | fun trackAction(action: AnalyticsAction, contextData: Map? = null) 57 | fun setProperty(property: AnalyticsProperty, value: String) 58 | fun unsetProperty(property: AnalyticsProperty) 59 | } 60 | 61 | 62 | internal class AnalyticsTracker : AnalyticsService { 63 | 64 | private val adobeAnalytics by DI.inject() 65 | 66 | override fun trackAction(action: AnalyticsAction, contextData: Map?) { 67 | val combined = AnalyticsAction.getContext(action).toMutableMap() 68 | if (contextData != null) { 69 | combined.putAll(contextData) 70 | } 71 | adobeAnalytics.trackAction(action.actionName, combined) 72 | } 73 | 74 | override fun trackScreen(screen: ScreenInfo, contextData: Map?) { 75 | adobeAnalytics.trackScreen(screen.screenName, contextData) 76 | } 77 | 78 | override fun setProperty(property: AnalyticsProperty, value: String) { 79 | adobeAnalytics.setProperty(property.propertyName, value) 80 | } 81 | 82 | override fun unsetProperty(property: AnalyticsProperty) { 83 | adobeAnalytics.unsetProperty(property.propertyName) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/utils/CallToAction.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.utils 2 | 3 | import br.com.progdeelite.kmmprogdeelite.resources.TextResource 4 | 5 | abstract class CallToAction { 6 | class Email( 7 | val address: TextResource, 8 | val subject: TextResource 9 | ): CallToAction() 10 | 11 | class Call( 12 | val number: TextResource 13 | ): CallToAction() 14 | } 15 | 16 | // 1) LICÕES APRENDIDAS - COMO COMPARTILHAR RECURSOS/ 17 | // 2) QUAIS CLASSES EVITAR (QUE O DEV KOTLIN VAI CAIR NA ARMADILHA) 18 | // 3) NÃO USE DEFAULT ARGUMENTS EM CLASSES COMPARTILHADAS/WORKAROUND 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | // NÃO FACA ASSIM! USE CLASSE ABSTRATA - IOS NÃO SABE LIDAR (AINDA) COM SEALED CLASSES 31 | sealed class CallToAction2 { 32 | class Email( 33 | val address: TextResource, 34 | val subject: TextResource 35 | ): CallToAction2() 36 | 37 | class Call( 38 | val number: TextResource 39 | ): CallToAction2() 40 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/utils/CommonLogger.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.utils 2 | 3 | // 1) COMO CRIAR UM LOGGER CUSTOMIZADO EM KMM 4 | // 2) COMO IMPLEMENTAR EXPECT/ACTUAL EM INTERFACE 5 | // 3) COMO DISPONIBILIZAR A CLASSE IMPL 6 | // 4) COMO USAR NA PRÁTICA DENTRO DO VIEW MODEL 7 | expect interface CommonLogger { 8 | open fun log(message:String, type: LogType) 9 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/utils/DeviceConnectivity.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.utils 2 | 3 | import br.com.progdeelite.kmmprogdeelite.resources.* 4 | 5 | abstract class ConnectivityState { 6 | object Online : ConnectivityState() 7 | object Offline : ConnectivityState() 8 | } 9 | 10 | interface ConnectivityProperty { 11 | val icon: ImageResource 12 | val iconColor: ColorResource 13 | val text: TextResource 14 | val textColor: ColorResource 15 | val textSize: FontSizingResource 16 | val backgroundColor: ColorResource 17 | val slideOutDurationInSeconds: Int 18 | } 19 | 20 | class OfflineProperty : ConnectivityProperty { 21 | override val icon: ImageResource by lazy { Resources.Image.flash } 22 | override val iconColor: ColorResource by lazy { Resources.Theme.iconConnectivityOffline } 23 | override val text: TextResource by lazy { Resources.Strings.connectivity_ct_device_offline } 24 | override val textSize: FontSizingResource by lazy { Resources.FontSizing.small } 25 | override val textColor: ColorResource by lazy { Resources.Theme.textConnectivityOffline } 26 | override val backgroundColor: ColorResource by lazy { Resources.Theme.bgConnectivityOffline } 27 | override val slideOutDurationInSeconds: Int by lazy { 0 } 28 | } 29 | 30 | class OnlineProperty : ConnectivityProperty { 31 | override val icon: ImageResource by lazy { Resources.Image.wifi } 32 | override val iconColor: ColorResource by lazy { Resources.Theme.iconConnectivityOnline } 33 | override val text: TextResource by lazy { Resources.Strings.connectivity_ct_device_online } 34 | override val textSize: FontSizingResource by lazy { Resources.FontSizing.small } 35 | override val textColor: ColorResource by lazy { Resources.Theme.textConnectivityOnline } 36 | override val backgroundColor: ColorResource by lazy { Resources.Theme.bgConnectivityOnline } 37 | override val slideOutDurationInSeconds: Int by lazy { 2 } 38 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/utils/Extentions.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.utils 2 | 3 | fun setLogLevelByBuildFlavor(buildFlavor: String, logLevel: () -> Unit) { 4 | when (buildFlavor) { 5 | "production" -> Unit 6 | "development" -> logLevel() 7 | "integration" -> logLevel() 8 | else -> Unit 9 | } 10 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/utils/LoadingState.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.utils 2 | 3 | enum class LoadingButtonState{ 4 | DISABLED, 5 | ACTIVE, 6 | LOADING, 7 | FINISHED 8 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/utils/Logger.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.utils 2 | 3 | enum class LogType { 4 | DEBUG, ERROR, INFO, WARNING 5 | } 6 | 7 | 8 | object Logger: CommonLogger 9 | 10 | 11 | fun logD(context: String, msg: String) { 12 | Logger.log("$context: $msg", LogType.DEBUG) 13 | } 14 | 15 | fun logE(context: String, msg: String) { 16 | Logger.log("$context: $msg", LogType.ERROR) 17 | } 18 | 19 | fun logI(context: String, msg: String) { 20 | Logger.log("$context: $msg", LogType.INFO) 21 | } 22 | 23 | fun logW(context: String, msg: String) { 24 | Logger.log("$context: $msg", LogType.WARNING) 25 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/validations/Validations.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.validations 2 | 3 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 4 | import br.com.progdeelite.kmmprogdeelite.resources.TextResource 5 | import kotlinx.coroutines.flow.MutableStateFlow 6 | import kotlinx.coroutines.flow.StateFlow 7 | 8 | // 1) Como criar tipos para validar campos de texto 9 | // 2) Como criar os erros que serão observados 10 | // 3) Como criar as regras de validação 11 | // 4) Como criar o validador para usar e observar os estados de erro nos componentes 12 | 13 | class TextFieldValidator( 14 | private val type: TextFieldType = TextFieldType.Default 15 | ) { 16 | private val _error = MutableStateFlow(TextFieldErrorType.None) 17 | val error: StateFlow = _error 18 | 19 | fun updateErrorState(input: String) { 20 | setErrorState(validate(input)) 21 | } 22 | 23 | private fun setErrorState(errorType: TextFieldErrorType) { 24 | _error.tryEmit(errorType) 25 | } 26 | 27 | private fun validate(input: String): TextFieldErrorType { 28 | return when (type) { 29 | TextFieldType.PhoneNumber -> TextFieldValidations.phoneNumber(input) 30 | TextFieldType.Otp -> TextFieldValidations.otpCode(input) 31 | else -> TextFieldErrorType.None 32 | } 33 | } 34 | 35 | fun isValid(input: String): Boolean { 36 | return validate(input) == TextFieldErrorType.None 37 | } 38 | } 39 | 40 | // 3) 41 | object TextFieldValidations { 42 | fun empty(input: String) = if (input.isEmpty()) TextFieldErrorType.EmptyField else TextFieldErrorType.None 43 | fun phoneNumber(input: String) = if (input.length in 3..18) TextFieldErrorType.None else TextFieldErrorType.PhoneNumberInvalidFormat 44 | fun birthday(input: String) = if (input.length != 10) TextFieldErrorType.InvalidBirthdate else TextFieldErrorType.None 45 | fun otpCode(input: String) = if (input.length != 6) TextFieldErrorType.InvalidCode else TextFieldErrorType.None 46 | 47 | fun onValidInputs(errorTypes: List, success: () -> Unit = {}, failure: () -> Unit = {}) { 48 | if (errorTypes.all { it == TextFieldErrorType.None }) success() else failure() 49 | } 50 | } 51 | 52 | // 2) 53 | abstract class TextFieldErrorType(val text: TextResource) { 54 | 55 | object None : TextFieldErrorType( 56 | text = TextResource("") 57 | ) 58 | 59 | object EmptyField : TextFieldErrorType( 60 | text = Resources.Strings.textfield_empty 61 | ) 62 | 63 | object PhoneNumberNotFound : TextFieldErrorType( 64 | text = Resources.Strings.textfield_number_not_found 65 | ) 66 | 67 | object PhoneNumberInvalidFormat : TextFieldErrorType( 68 | text = Resources.Strings.textfield_invalid_phone_number_format 69 | ) 70 | 71 | object InvalidBirthdate : TextFieldErrorType( 72 | text = Resources.Strings.textfield_invalid_birthdate 73 | ) 74 | 75 | object InvalidCode : TextFieldErrorType( 76 | text = Resources.Strings.textfield_invalid_code 77 | ) 78 | 79 | object CodeExpired : TextFieldErrorType( 80 | text = Resources.Strings.textfield_code_expired 81 | ) 82 | } 83 | 84 | // 1) 85 | enum class TextFieldType { 86 | Default, PhoneNumber, Otp 87 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/viewmodels/AuthViewModel.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.viewmodels 2 | 3 | import kotlinx.coroutines.flow.MutableStateFlow 4 | import kotlinx.coroutines.flow.StateFlow 5 | import kotlinx.coroutines.launch 6 | 7 | // 1) CRIAR VIEW MODELS DE LOGIN E REGISTRO 8 | // 2) CRIAR EXTENSÃO PARA OBTER SCOPED VIEW MODELS 9 | // 3) AJUSTAR BottomBarItem E Graphs 10 | // 4) REFATORAR BottomBarConfigList 11 | 12 | abstract class AuthViewModel : BaseSharedViewModel() { 13 | var startScreen: String? = null 14 | 15 | private val _errorMessage = MutableStateFlow("") 16 | val errorMessage: StateFlow 17 | get() { 18 | return _errorMessage 19 | } 20 | 21 | abstract fun onConfirmOtp(confirmCode: String) 22 | 23 | protected fun emitErrorMsg(msg: String){ 24 | scope.launch { 25 | _errorMessage.emit(msg) 26 | } 27 | } 28 | } 29 | 30 | class RegisterViewModel : AuthViewModel() { 31 | fun register(mobile: String, name: String, birthdate: String, email: String) { 32 | // seu_servico.signUp(mobile, name, birthdate, email) 33 | } 34 | 35 | override fun onConfirmOtp(confirmCode: String) { 36 | when{ 37 | confirmCode.isNotEmpty() -> {} //seu_servico.confirmSignUp(mobile, confirmCode) 38 | confirmCode.isEmpty() -> emitErrorMsg("Insira códido SMS") 39 | } 40 | } 41 | } 42 | 43 | class LoginViewModel : AuthViewModel() { 44 | fun login(mobile: String) = "" // seu_servico.signIn(mobile) 45 | 46 | override fun onConfirmOtp(confirmCode: String) { 47 | when{ 48 | confirmCode.isNotEmpty() -> {} // seu_servico.confirmSignIn(confirmCode) 49 | confirmCode.isEmpty() -> emitErrorMsg("Insira códido SMS") 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/viewmodels/BaseSharedViewModel.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.viewmodels 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | 5 | expect abstract class BaseSharedViewModel() { 6 | val scope: CoroutineScope 7 | protected fun onCleared() 8 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/viewmodels/EntryViewModel.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.viewmodels 2 | 3 | import br.com.progdeelite.kmmprogdeelite.di.DI.inject 4 | import br.com.progdeelite.kmmprogdeelite.network.* 5 | import br.com.progdeelite.kmmprogdeelite.network.models.Entry 6 | import br.com.progdeelite.kmmprogdeelite.repositories.EntryRepository 7 | import br.com.progdeelite.kmmprogdeelite.utils.* 8 | import kotlinx.coroutines.flow.MutableStateFlow 9 | import kotlinx.coroutines.flow.StateFlow 10 | import kotlinx.coroutines.launch 11 | 12 | class EntryViewModel: BaseSharedViewModel() { 13 | 14 | private lateinit var storyRepository: EntryRepository 15 | private val logContext = "EntryViewModel" 16 | private val environment by inject() 17 | 18 | private val _entries = MutableStateFlow?>(null) 19 | val entries: StateFlow?> 20 | get() { 21 | return _entries 22 | } 23 | private val _error = MutableStateFlow(null) 24 | val error: StateFlow 25 | get() { 26 | return _error 27 | } 28 | 29 | init { 30 | initEndPointService() 31 | } 32 | 33 | private fun initEndPointService() { 34 | val httpClient = getHttpClient( 35 | ClientConfig( 36 | environment = getAppEnvironment(), // VEJA AULA DE VARIAVEIS DE AMBIENTE 37 | userAgent = "Android" 38 | ) 39 | ) 40 | storyRepository = EntryRepository(ApiEndpoints(httpClient, getAppEnvironment())) 41 | } 42 | 43 | fun fetchEntries() { 44 | logD(logContext, "Meu ambiente é: ${environment.name}") 45 | scope.launch { 46 | storyRepository.fetchEntries().collect { result -> 47 | when(result){ 48 | is NetworkResult.Success -> _entries.emit(result.data).also { 49 | logI(logContext, "Entries fetched successfully") 50 | } 51 | is NetworkResult.Error -> _error.emit(result.errorMessage).also{ 52 | logE(logContext,"Entries Error ${result.errorMessage}") 53 | } 54 | is NetworkResult.Exception -> _error.emit(result.exception?.message).also{ 55 | logE(logContext,"Entries Exception ${result.exception?.message}") 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/viewmodels/LanguageViewModel.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.viewmodels 2 | 3 | import br.com.progdeelite.kmmprogdeelite.localization.Language 4 | import br.com.progdeelite.kmmprogdeelite.localization.LanguagePickerTexts 5 | import br.com.progdeelite.kmmprogdeelite.localization.LocalizationService 6 | import br.com.progdeelite.kmmprogdeelite.tracking.adobe.AnalyticsAction 7 | import br.com.progdeelite.kmmprogdeelite.tracking.adobe.AnalyticsProperty 8 | import br.com.progdeelite.kmmprogdeelite.tracking.adobe.AnalyticsService 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.flow.StateFlow 11 | import kotlinx.coroutines.launch 12 | 13 | open class LanguagePickerViewModel ( 14 | val translations: LanguagePickerTexts 15 | ) : BaseSharedViewModel() { 16 | 17 | // Way to define default params so that it works on iOS too. 18 | constructor() : this( 19 | translations = LanguagePickerTexts() 20 | ) 21 | 22 | private val localisation: LocalizationService = LocalizationService.instance 23 | private val analytics: AnalyticsService = AnalyticsService.instance 24 | 25 | private var currentSelectedLang = Language.fallback 26 | 27 | private val _language = MutableStateFlow(currentSelectedLang) 28 | open val language: StateFlow = _language 29 | 30 | init { 31 | restoreAppDefaultLanguageFromSettings() 32 | } 33 | 34 | open fun setLanguage(language: Language) { 35 | if (currentSelectedLang != language) { 36 | analytics.setProperty(AnalyticsProperty.AppLanguage, language.isoCode) 37 | currentSelectedLang = language 38 | localisation.setLanguage(language) 39 | scope.launch { 40 | _language.emit(language) 41 | } 42 | } 43 | } 44 | 45 | private fun restoreAppDefaultLanguageFromSettings() = setLanguage(getCurrentLanguage()) 46 | 47 | fun getCurrentLanguage(): Language { 48 | return localisation.getCurrentLanguage() 49 | } 50 | 51 | fun trackLanguageAction() { 52 | analytics.trackAction(AnalyticsAction.LanguageAction) 53 | } 54 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/viewmodels/MainActivityViewModel.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.viewmodels 2 | 3 | import kotlinx.coroutines.flow.MutableStateFlow 4 | import kotlinx.coroutines.flow.StateFlow 5 | import kotlinx.coroutines.launch 6 | 7 | // 1) COMO ENDERECAR ISSUE DE SEGURANçA APOS AUDITORIA 8 | // 2) COMO PREVINIR QUE USUÁRO FACA PRINTSCREEN DO SEU APP (SEGURO/BANCARIO) 9 | // 3) COMO EVITAR WORKAROUND ATRAVES DO MULTIWINDOW MODE 10 | 11 | class MainActivityViewModel : BaseSharedViewModel() { 12 | 13 | // Endereça auditoria de segurança: 14 | // hide recent thumbnails plus multi-screen mode 15 | private val _hideThumbnail = MutableStateFlow(false) 16 | val hideThumbnail: StateFlow 17 | get() = _hideThumbnail 18 | 19 | fun toggleHideThumbnailState(hide: Boolean) { 20 | scope.launch { 21 | _hideThumbnail.emit(hide) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/viewmodels/OnBoardingViewModel.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.viewmodels 2 | 3 | import br.com.progdeelite.kmmprogdeelite.di.DI.inject 4 | import br.com.progdeelite.kmmprogdeelite.network.Environment 5 | import br.com.progdeelite.kmmprogdeelite.resources.ImageResource 6 | import br.com.progdeelite.kmmprogdeelite.resources.Resources 7 | import br.com.progdeelite.kmmprogdeelite.resources.TextResource 8 | import br.com.progdeelite.kmmprogdeelite.utils.logD 9 | 10 | // 1) Implementar iOS e Android 11 | // 2) definir classe compartilhada de Resources 12 | // 3) Criar viewModel 13 | // 4) Exemplificar o preview 14 | 15 | class OnBoardingViewModel( 16 | val images: OnBoardingImages, // Pitfall Nr1. kmm para iOS não sabe lidar com default parameters (ainda) 17 | val texts: OnBoardingTexts, 18 | val picker: LanguagePickerViewModel 19 | ) : BaseSharedViewModel() { 20 | 21 | private val logContext = "OnBoardingViewModel" 22 | private val environment by inject() 23 | 24 | init{ 25 | logD(logContext,"Meu ambiente corrente é: ${environment.name}") 26 | } 27 | 28 | constructor() : this( 29 | images = OnBoardingImages(), // iOS: tivemos que mover as inicializações padrão para o construtor 30 | texts = OnBoardingTexts(), 31 | picker = LanguagePickerViewModel() 32 | ) 33 | } 34 | 35 | // Para fins didáticos aqui, mas poderia estar em um arquivo separado 36 | data class OnBoardingImages( 37 | val topImage: ImageResource, 38 | val middleImage: ImageResource, 39 | val bottomImage: ImageResource 40 | ) { 41 | constructor() : this( 42 | topImage = Resources.Image.fire, 43 | middleImage = Resources.Image.lamp, 44 | bottomImage = Resources.Image.switch 45 | ) 46 | } 47 | 48 | // Para fins didáticos aqui, mas poderia estar em um arquivo separado 49 | data class OnBoardingTexts( 50 | val topImageText: TextResource, 51 | val middleImageText: TextResource, 52 | val bottomImageText: TextResource 53 | ) { 54 | constructor() : this( 55 | topImageText = Resources.Strings.top_image_text, 56 | middleImageText = Resources.Strings.middle_image_text, 57 | bottomImageText = Resources.Strings.bottom_image_text 58 | ) 59 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/viewmodels/SampleViewModel.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.viewmodels 2 | 3 | import br.com.progdeelite.kmmprogdeelite.models.Story 4 | import br.com.progdeelite.kmmprogdeelite.models.StoryMedia 5 | import br.com.progdeelite.kmmprogdeelite.providers.DataSourceProvider 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | import kotlinx.coroutines.flow.StateFlow 8 | import kotlinx.coroutines.launch 9 | 10 | /** Reference common view model implementation as a staring point */ 11 | class SampleViewModel : BaseSharedViewModel() { 12 | // Your business logic and states goes here... 13 | 14 | private val _stories = MutableStateFlow>(emptyList()) 15 | val stories: StateFlow> 16 | get() { 17 | return _stories 18 | } 19 | 20 | private val _error = MutableStateFlow(false) 21 | val error: StateFlow 22 | get() { 23 | return _error 24 | } 25 | 26 | private val dataSourceProvider = DataSourceProvider() 27 | 28 | fun loadStories() { 29 | scope.launch { 30 | kotlin.runCatching { 31 | val db = dataSourceProvider.getLocalCommonDatabase() 32 | db.clearDatabase() 33 | db.insertStories(mockStories()) 34 | db.getAllStories() 35 | }.onSuccess { 36 | _stories.emit(it) 37 | }.onFailure { 38 | _error.emit(true) 39 | } 40 | } 41 | } 42 | 43 | fun clearStories(){ 44 | scope.launch { 45 | kotlin.runCatching { 46 | val db = dataSourceProvider.getLocalCommonDatabase() 47 | db.clearDatabase() 48 | db.getAllStories() 49 | }.onSuccess { 50 | _stories.emit(it) 51 | }.onFailure { 52 | _error.emit(true) 53 | } 54 | } 55 | } 56 | 57 | private fun mockStories(): List { 58 | return listOf( 59 | createStory("1", "Story1"), 60 | createStory("2", "Story2"), 61 | createStory("2", "Story2") 62 | ) 63 | } 64 | 65 | private fun createStory(id: String, name: String): Story { 66 | return Story( 67 | id = id, 68 | name = name, 69 | StoryMedia( 70 | name = "test", 71 | imgUrl = "uri", 72 | mimeType = "image/png" 73 | ), 74 | slides = emptyList() 75 | ) 76 | } 77 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/br/com/progdeelite/kmmprogdeelite/viewmodels/ShimmerViewModel.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.viewmodels 2 | 3 | import br.com.progdeelite.kmmprogdeelite.utils.logI 4 | import kotlinx.coroutines.flow.MutableStateFlow 5 | import kotlinx.coroutines.flow.StateFlow 6 | import kotlinx.coroutines.launch 7 | 8 | // 1) ADICIONAR DEPENDÊNCIAS & ADICIONAR AO BUILD.GRADLE 9 | // 2) CRIAR DIALOG DE TELA CHEIA EXEMPLAR 10 | // 3) CRIAR VIEW MODEL PARA ATIVAR O EFEITO DE SOMBRA (SHIMMER) 11 | // 4) USAR NA PRÁTICA DENTRO DA MAIN ACTIVITY 12 | class ShimmerViewModel: BaseSharedViewModel() { 13 | 14 | private val _loading = MutableStateFlow(false) 15 | private val logContext = "ShimmerViewModel" 16 | 17 | val loading: StateFlow 18 | get() = _loading 19 | 20 | fun toggleLoadingState() { 21 | logI(logContext, "Loading State Triggered!") 22 | scope.launch { 23 | _loading.emit(_loading.value.not()) 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /shared/src/commonMain/sqldelight/br/com/progdeelite/kmmprogdeelite/database/AppDatabase.sq: -------------------------------------------------------------------------------- 1 | CREATE TABLE Story( 2 | id TEXT NOT NULL, 3 | body TEXT NOT NULL --,(virgula) erro banal mas que te pode roubar muito tempo 4 | ); 5 | 6 | insertStory: 7 | INSERT INTO Story (id, body) VALUES(?,?); 8 | 9 | removeAllStory: 10 | DELETE FROM Story; 11 | 12 | selectStoryById: 13 | SELECT * FROM Story WHERE id = ?; 14 | 15 | selectAllStories: 16 | SELECT * FROM Story; -------------------------------------------------------------------------------- /shared/src/commonTest/kotlin/br/com/progdeelite/kmmprogdeelite/MockSampleTest.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite 2 | 3 | import io.mockk.every 4 | import io.mockk.mockk 5 | import io.mockk.verify 6 | import kotlin.test.Test 7 | import kotlin.test.assertEquals 8 | 9 | // 1) Adicionar dependencias de teste 10 | // 2) adicionar dependencias de mockk 11 | // 3) fazer o sync do build gradle 12 | // 4) Criar exemplo de teste e detalhar métodos 13 | 14 | class MockSampleTest { 15 | 16 | class Clock { 17 | fun getCurrentTime() : String = "12:00" 18 | } 19 | 20 | @Test 21 | fun sampleMockTest() { 22 | val clock = mockk() 23 | every { clock.getCurrentTime() } returns "13:15" 24 | 25 | val time = clock.getCurrentTime() 26 | 27 | verify { clock.getCurrentTime() } 28 | assertEquals("13:15" , time) 29 | } 30 | } -------------------------------------------------------------------------------- /shared/src/commonTest/kotlin/br/com/progdeelite/kmmprogdeelite/commonTest.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertTrue 5 | 6 | class CommonGreetingTest { 7 | 8 | @Test 9 | fun testExample() { 10 | assertTrue(Greeting().greeting().contains("Hello"), "Check 'Hello' is mentioned") 11 | } 12 | } -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/br/com/progdeelite/kmmprogdeelite/Platform.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite 2 | 3 | class IOSPlatform: Platform { 4 | override val name: String = "Alô iOS Freaks!" 5 | } 6 | 7 | actual fun getPlatform(): Platform = IOSPlatform() -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/br/com/progdeelite/kmmprogdeelite/database/DatabaseDriverFactory.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.database 2 | 3 | import com.squareup.sqldelight.db.SqlDriver 4 | import com.squareup.sqldelight.drivers.native.NativeSqliteDriver 5 | 6 | actual fun createSqlDriver(): SqlDriver { 7 | return NativeSqliteDriver(CommonDatabase.Schema, "common.db") 8 | } -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/br/com/progdeelite/kmmprogdeelite/localization/Localization.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.localization 2 | 3 | /** iOS only needs the string reference to resolve its text resource. Just fine that way */ 4 | actual fun getDefaultString(name: String): String = name -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/br/com/progdeelite/kmmprogdeelite/network/GetNetwork.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.network 2 | 3 | import br.com.progdeelite.kmmprogdeelite.di.DI 4 | import io.ktor.client.* 5 | import io.ktor.client.engine.darwin.* 6 | 7 | actual fun getAppEnvironment(): Environment = DI.Native.environment 8 | 9 | actual fun getHttpClient(clientConfig: ClientConfig): HttpClient { 10 | return HttpClient(Darwin) { 11 | engine { 12 | configureRequest { 13 | //setAllowsCellularAccess(true) 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/ColorResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | // IMPORTANT: DESCOMENTA QUANTO TIVER NO MAC 4 | 5 | //import platform.UIKit.UIColor 6 | //import platform.UIKit.UITraitCollection 7 | //import platform.UIKit.UIUserInterfaceStyle 8 | //import platform.UIKit.currentTraitCollection 9 | 10 | actual fun isSystemInDarkMode(): Boolean { 11 | // val mode = UITraitCollection.currentTraitCollection.userInterfaceStyle() 12 | // return mode == UIUserInterfaceStyle.UIUserInterfaceStyleDark 13 | return false 14 | } 15 | 16 | /** 17 | * iOS works only with RGB-values while android needs an HEX-Value. That's way we need [IosColor]. 18 | * We pass value over to [IosColor] which takes an HEX-Value and extracts its RBG components making it available for iOS. 19 | */ 20 | actual class ColorResource actual constructor(light: Long, dark: Long) { 21 | // private val colorLight by lazy { 22 | // val rgb = IosColor(light) 23 | // UIColor(red = rgb.r, blue = rgb.b, green = rgb.g, alpha = rgb.a) 24 | // } 25 | // 26 | // private val colorDark by lazy { 27 | // val rgb = IosColor(dark) 28 | // UIColor(red = rgb.r, blue = rgb.b, green = rgb.g, alpha = rgb.a) 29 | // } 30 | // 31 | // val uiColor: UIColor 32 | // get() = if (isSystemInDarkMode()) colorDark else colorLight 33 | // 34 | // /** 35 | // * iOS works only with RGB-values while android needs an HEX-Value. That's why we need to extend from [IosColor]. 36 | // * We pass value over to IosColor which takes an HEX-Value and extracts its RBG components making it available for iOS. 37 | // */ 38 | // private class IosColor(colorHexValue: Long) { 39 | // var r = 0.0 40 | // var g = 0.0 41 | // var b = 0.0 42 | // var a = 0.0 43 | // 44 | // init { 45 | // a = ((colorHexValue shr 24) and 0xFF).toDouble() / 255 46 | // r = ((colorHexValue shr 16) and 0xFF).toDouble() / 255 47 | // g = ((colorHexValue shr 8) and 0xFF).toDouble() / 255 48 | // b = ((colorHexValue shr 0) and 0xFF).toDouble() / 255 49 | // } 50 | // } 51 | } -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/FontSizingResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | actual class FontSizingResource actual constructor( 4 | private val fontSize: Int, 5 | private val fontLineHeight: Int 6 | ) { 7 | val size: Double by lazy { fontSize.toDouble() } 8 | val lineHeight: Double by lazy { fontLineHeight.toDouble() / 100.0 } 9 | } -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/ImageResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | /** iOS only needs the string reference to resolve its images. Just fine that way */ 4 | actual class ImageResource actual constructor(val name: String) -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/br/com/progdeelite/kmmprogdeelite/resources/SpacingResource.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.resources 2 | 3 | // IMPORTANTE: DESCOMENTA QUANDO TIVER NO MAC DESENVOLVENDO 4 | 5 | actual fun getWindowSize(): WindowSize { 6 | // if (UIScreen.mainScreen.scale < 3.0) return WindowSize.Small 7 | // val width = UIScreen.mainScreen.bounds.useContents { // FOI SUPER TRICKY ISSO AQUI! 8 | // this.size.width 9 | // } 10 | // return if(width < 400.0) WindowSize.Medium else WindowSize.Large 11 | return WindowSize.Medium 12 | } 13 | 14 | actual class SpacingResource actual constructor(private val unit: Int) { 15 | // private val scale = UIScreen.mainScreen.scale 16 | // 17 | // val pt: Double by lazy { pt() } 18 | // private fun pt(): Double { 19 | // return unit.toDouble() * scale 20 | // } 21 | } -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/br/com/progdeelite/kmmprogdeelite/settings/Settings.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.settings 2 | 3 | // IMPORTANTE: NO MAC, DESCOMENTA ISSO AQUI E REMOVE *) 4 | 5 | //import com.russhwolf.settings.NSUserDefaultsSettings 6 | import com.russhwolf.settings.Settings 7 | //import platform.Foundation.NSUserDefaults 8 | 9 | // val delegate: NSUserDefaults = NSUserDefaults.standardUserDefaults() 10 | //actual fun getSettings(): Settings = NSUserDefaultsSettings(delegate) 11 | 12 | 13 | // *) AQUI APENAS PARA PODER COMPILAR NO WINDOWS 14 | actual fun getSettings(): Settings? = null // remove isso aqui quando descomentar o de cima! -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/br/com/progdeelite/kmmprogdeelite/utils/CommonLogger.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.utils 2 | 3 | //import platform.Foundation.NSLog 4 | //import br.com.progdeelite.kmmprogdeelite.di.DI 5 | //import br.com.progdeelite.kmmprogdeelite.network.Environment 6 | 7 | actual interface CommonLogger { 8 | 9 | actual fun log(message: String, type: LogType) { 10 | // when (DI.Native.environment) { 11 | // Environment.INT -> { 12 | // when(type){ 13 | // LogType.DEBUG -> NSLog("Debug: $message - ${Environment.INT.name}") 14 | // LogType.ERROR -> NSLog("Error: $message - ${Environment.INT.name}") 15 | // LogType.INFO -> NSLog("Info: $message - ${Environment.INT.name}") 16 | // LogType.WARNING -> NSLog("Warning: $message - ${Environment.INT.name}") 17 | // } 18 | // } 19 | // Environment.DEV -> { 20 | // when(type){ 21 | // LogType.DEBUG -> NSLog("Debug: $message - ${Environment.DEV.name}") 22 | // LogType.ERROR -> NSLog("Error: $message - ${Environment.DEV.name}") 23 | // LogType.INFO -> NSLog("Info: $message - ${Environment.DEV.name}") 24 | // LogType.WARNING -> NSLog("Warning: $message - ${Environment.DEV.name}") 25 | // } 26 | // } 27 | // else -> Unit 28 | // } 29 | } 30 | } -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/br/com/progdeelite/kmmprogdeelite/utils/IOSMainApp.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.utils 2 | 3 | @ThreadLocal 4 | object IOSMainApp { 5 | // iOS things as soon as needed 6 | } -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/br/com/progdeelite/kmmprogdeelite/viewmodels/BaseSharedViewModel.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite.viewmodels 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.MainScope 5 | import kotlinx.coroutines.cancel 6 | 7 | actual abstract class BaseSharedViewModel { 8 | 9 | actual val scope: CoroutineScope = MainScope() 10 | 11 | protected actual open fun onCleared() { 12 | // outras coisa que deseje limpar 13 | } 14 | 15 | fun clear() { 16 | onCleared() 17 | scope.cancel() 18 | } 19 | } -------------------------------------------------------------------------------- /shared/src/iosTest/kotlin/br/com/progdeelite/kmmprogdeelite/iosTest.kt: -------------------------------------------------------------------------------- 1 | package br.com.progdeelite.kmmprogdeelite 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertTrue 5 | 6 | class IosGreetingTest { 7 | 8 | @Test 9 | fun testExample() { 10 | assertTrue(Greeting().greeting().contains("iOS"), "Check iOS is mentioned") 11 | } 12 | } -------------------------------------------------------------------------------- /tools/convert_assets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 1) download assets from Figma/Zepplin into the Downloads folder (iOS: .pdf / Android: .svg) 4 | # 2) run script from tools folder: ./convert_assets 5 | # 3) final files are found in folders: /Downloads/Assets/[iOS|android] (iOS and android respectively) 6 | 7 | source=~/Downloads/Assets 8 | 9 | # apply naming conventions and save new file in output 10 | # param1 ($1): file to process 11 | # param2 ($2): output folder 12 | function rename_asset { 13 | 14 | # cut -d'/' -f6- ==> remove leading $source folders from path 15 | # tr "/-. " "_" ==> replace all "/", "-", "." and " " with "_" in path 16 | # sed 's/\(.*\)_/\1./' ==> replace last "_" with "." (proper file extension) 17 | # tr "[:upper:]" "[:lower:]" ==> to lower case 18 | # xargs -I@ cp $f ${target}/@. ==> copy file into $target folder with new name 19 | 20 | echo "$1" | cut -d'/' -f6- | tr -s "\\/\\-\\. " "_" | sed 's/\(.*\)_/\1./' | tr "[:upper:]" "[:lower:]" | xargs -I@ cp "$1" "$2"/@; 21 | } 22 | 23 | 24 | # Android 25 | target=${source}/android # ==> define output folder for android 26 | mkdir -p ${target} # ==> create android output folder if not exists already 27 | 28 | # process all .svg files including whitespaces in name 29 | find ${source} -name "*.svg" ! -path "${target}/*" -print0 | while read -r -d '' file 30 | do 31 | rename_asset "${file}" "${target}" 32 | done 33 | echo "android finished: see ${target}" 34 | 35 | 36 | # iOS 37 | target=${source}/ios # ==> define output folder for android 38 | mkdir -p ${target} # ==> create android output folder if not exists already 39 | 40 | # process all .pdf files including whitespaces in name 41 | find ${source} -name "*.pdf" ! -path "${target}/*" -print0 | while read -r -d '' file 42 | do 43 | rename_asset "${file}" "${target}" 44 | done 45 | echo "ios finished: see ${target}" -------------------------------------------------------------------------------- /tools/create_image_resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sourceDir=~/Downloads/Assets/android 4 | yourCompanyDir=br/com/progdeelite/kmmprogdeelite/ # replace with your own 5 | resourcePackageDir=br.com.progdeelite.kmmprogdeelite # replace with your own 6 | resourceDir=${yourCompanyDir}resources/ 7 | # relativeDestinationDir=../shared/src/commonMain/kotlin/${resourceDir} // on mac / linux 8 | relativeDestinationDir=./shared/src/commonMain/kotlin/${resourceDir} # on windows 9 | relativeDestinationFile=${relativeDestinationDir}ImageResources.kt 10 | 11 | # +------------------------------------------- HOW TO USE IT ----------------------------------------------+ 12 | # 13 | # 1) run script convert_assets.sh first 14 | # 2) create an initial empty object called ImageResources.kt in relativeDestinationDir 15 | # 3) make sure "~Downloads/Assets/android" exits after running "convert_assets.sh" 16 | # 4) run script from tools folder: ./create_image_resources.sh 17 | # 5) generated object will be create at "$relativeDestinationDir" 18 | # 19 | # +--------------------------------------------------------------------------------------------------------+ 20 | 21 | 22 | # template for the generated object (CONTENT) 23 | # <<- ignores leading tabs in the output 24 | template=$(cat <<-EOF 25 | package ${resourcePackageDir}.resources 26 | 27 | /** Generated object! Do not change it manually! Use the script located at \"../tools/create_image_resources.sh\" instead. */ 28 | object ImageResources { 29 | CONTENT 30 | 31 | } 32 | EOF 33 | ) 34 | 35 | # creates an object to be used by in ImageResources 36 | function create_images_object() { 37 | 38 | defineSourceDirErrorMsg="Source dir ($sourceDir) does not exist. Run script ./convert_assets.sh first." 39 | defineSourceFileErrorMsg="Source dir ($sourceDir) is empty. Run script ./convert_assets.sh first." 40 | 41 | # Check source dir exists and is not empty 42 | [ -d "$sourceDir" ] || { echo "$defineSourceDirErrorMsg" && exit 1; } 43 | [ -z "$(find "$sourceDir" -prune -empty)" ] || { echo "$defineSourceFileErrorMsg" && exit 1; } 44 | 45 | # create destination if not exists 46 | mkdir -p "$relativeDestinationDir" 47 | 48 | # Parse image names 49 | content="" 50 | for file in "$sourceDir"/*.*; do 51 | # get filename without extension 52 | filename=$(basename "$file") 53 | filename="${filename%.*}" 54 | 55 | content="$content\n val $filename = ImageResource(\"$filename\")" 56 | done 57 | 58 | # replace the CONTENT of the template 59 | echo -e "${template/CONTENT/$content}" > "$relativeDestinationFile" 60 | 61 | echo "File created successfully at: $relativeDestinationDir" 62 | } 63 | 64 | create_images_object -------------------------------------------------------------------------------- /tools/create_text_resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sourceDir=~/Downloads/TranslationStrings/values 4 | sourceFile=${sourceDir}/strings.xml 5 | yourCompanyDir=br/com/progdeelite/kmmprogdeelite/ # replace with your own 6 | resourcePackageDir=br.com.progdeelite.kmmprogdeelite # replace with your own 7 | resourceDir=${yourCompanyDir}resources/ 8 | #relativeDestinationDir=../shared/src/commonMain/kotlin/${resourceDir} # on mac / linux 9 | relativeDestinationDir=./shared/src/commonMain/kotlin/${resourceDir} # on windows 10 | relativeDestinationFile=${relativeDestinationDir}/StringResources.kt 11 | 12 | # +------------------------------------------- HOW TO USE IT ----------------------------------------------+ 13 | # 14 | # 1) https://app.lokalise.com > Tab Download > Select "Android Resources (.xml)" > hit "Build and Download" 15 | # 2) unzip "TranslationStrings.zip" in your Download folder 16 | # 3) make sure strings.xml from Lokalise is located at "Downloads/TranslationStrings/values/strings.xml" 17 | # 4) run the script under tools folder by calling: ./create_text_object.sh 18 | # 5) generated object will be create at "$relativeDestinationDir" 19 | # 20 | # +--------------------------------------------------------------------------------------------------------+ 21 | 22 | # template for the generated object (HERE DOCUMENT) 23 | # <<- ignores leading tabs in the output 24 | template=$(cat <<-EOF 25 | package ${resourcePackageDir}.resources 26 | 27 | /** Generated object! Do not change it manually! Use the script located at \"../tools/generate_text_resources.sh\" instead.*/ 28 | object StringResources { 29 | CONTENT 30 | 31 | } 32 | EOF 33 | ) 34 | 35 | # creates an object to be used in StringResources 36 | function create_text_object() { 37 | 38 | defineSourceDirErrorMsg="Define source file dir and file first." 39 | defineSourceFileErrorMsg="does not exist." 40 | 41 | # Check source dir exists and is not empty 42 | [ -d "$sourceDir" ] || { echo "$defineSourceDirErrorMsg" && exit 1; } 43 | [ -z "$(find "$sourceDir" -prune -empty)" ] || { echo "$defineSourceFileErrorMsg" && exit 1; } 44 | 45 | # create destination if not exists 46 | mkdir -p "$relativeDestinationDir" 47 | 48 | # Parse strings.xml values 49 | content="" 50 | while read -r line; 51 | do 52 | result=$(grep -o '=".*">' <<< "$line" | sed 's/=//g' | sed 's/>//g' | sed 's/"//g') 53 | if test -n "$result" 54 | then 55 | content="$content\n val $result = TextResource(\"$result\")" 56 | fi 57 | done < $sourceFile 58 | 59 | # replace the CONTENT of the template 60 | echo -e "${template/CONTENT/$content}" > "$relativeDestinationFile" 61 | 62 | echo "File created successfully at: $relativeDestinationDir" 63 | } 64 | 65 | create_text_object -------------------------------------------------------------------------------- /tools/decrypt_secrets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Decrypt Google Services File 4 | # openssl enc -d -aes256 -md sha256 -pbkdf2 -iter 100000 -k $SECRET_FILES_PASSWORD -in androidApp/google-services.json.encrypted -out androidApp/google-services.json 5 | # openssl enc -d -aes256 -md sha256 -pbkdf2 -iter 100000 -k $SECRET_FILES_PASSWORD -in iosApp/your_project_name\ DEV/GoogleService-Info.plist.encrypted -out iosApp/your_project_name\ DEV/GoogleService-Info.plist 6 | # openssl enc -d -aes256 -md sha256 -pbkdf2 -iter 100000 -k $SECRET_FILES_PASSWORD -in iosApp/your_project_name\ INT/GoogleService-Info.plist.encrypted -out iosApp/your_project_name\ INT/GoogleService-Info.plist 7 | # openssl enc -d -aes256 -md sha256 -pbkdf2 -iter 100000 -k $SECRET_FILES_PASSWORD -in iosApp/your_project_name\ PRD/GoogleService-Info.plist.encrypted -out iosApp/your_project_name\ PRD/GoogleService-Info.plist 8 | 9 | # Decrypt KeyStore Properties 10 | openssl enc -d -aes256 -md sha256 -pbkdf2 -iter 100000 -k $SECRET_FILES_PASSWORD -in androidApp/src/development/res/keystore.properties.encrypted -out androidApp/src/development/res/keystore.properties 11 | openssl enc -d -aes256 -md sha256 -pbkdf2 -iter 100000 -k $SECRET_FILES_PASSWORD -in androidApp/src/integration/res/keystore.properties.encrypted -out androidApp/src/integration/res/keystore.properties 12 | openssl enc -d -aes256 -md sha256 -pbkdf2 -iter 100000 -k $SECRET_FILES_PASSWORD -in androidApp/src/production/res/keystore.properties.encrypted -out androidApp/src/production/res/keystore.properties 13 | 14 | # Decrypt Playstore keystore (jks) 15 | openssl enc -d -aes256 -md sha256 -pbkdf2 -iter 100000 -k $SECRET_FILES_PASSWORD -in androidApp/src/development/res/keystore.jks.encrypted -out androidApp/src/development/res/keystore.jks 16 | openssl enc -d -aes256 -md sha256 -pbkdf2 -iter 100000 -k $SECRET_FILES_PASSWORD -in androidApp/src/integration/res/keystore.jks.encrypted -out androidApp/src/integration/res/keystore.jks 17 | openssl enc -d -aes256 -md sha256 -pbkdf2 -iter 100000 -k $SECRET_FILES_PASSWORD -in androidApp/src/production/res/keystore.jks.encrypted -out androidApp/src/production/res/keystore.jks -------------------------------------------------------------------------------- /tools/read_keystore_secret.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | source ~/.bash_profile 3 | set "$KEYSTORE_JKS_PASSWORD" 4 | echo "$KEYSTORE_JKS_PASSWORD" --------------------------------------------------------------------------------