├── .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 |
3 |
4 |
5 |
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"
--------------------------------------------------------------------------------