Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a677fb9
add new property to the project containing the app theme
Fabtron Aug 27, 2024
e2ea58e
WIP: Replace MaterialButton with CustomView e.g. SnabblePrimaryButton
Fabtron Aug 27, 2024
8bd358b
WIP: make compose fun public
Fabtron Aug 28, 2024
7c2097c
WIP: only set background color
Fabtron Aug 28, 2024
a0a528a
WIP: fix typo
Fabtron Aug 28, 2024
895c774
WIP: add dependency for live data as flow extension
Fabtron Aug 28, 2024
448c011
WIP: remove observe forever and create extension functions for theming
Fabtron Aug 28, 2024
61aed6d
WIP: adjust primary button default style and add second impl for seco…
Fabtron Aug 28, 2024
d9f48b0
add TextView with default primary color
Fabtron Aug 29, 2024
d26964d
remove obsolete loading of the theme and change parsing to get direct…
Fabtron Aug 30, 2024
c703a6f
use json element instead of the object
Fabtron Aug 30, 2024
d0fb844
switch to secondary button
Fabtron Aug 30, 2024
2ea2069
split up theme into light and dark mode colors
Fabtron Sep 2, 2024
a8d0d95
rename
Fabtron Sep 4, 2024
73f940e
rewrite extension functions to provide the color depending on the ui …
Fabtron Sep 4, 2024
3aa6b72
WIP: set text colors according to the remote theme
Fabtron Sep 4, 2024
1a27cc1
refactor to make it more readable and renaming
Fabtron Sep 4, 2024
5ce21ae
set button color for alert dialog
Fabtron Sep 5, 2024
767bef7
add option to show keyboard via the TextFieldManager
Fabtron Sep 5, 2024
9b25725
apply remote theme to compose theme
Fabtron Sep 5, 2024
e803ee7
migrate searchbar to compose to apply remote theme
Fabtron Sep 5, 2024
c5e5fd8
roll back to use material button
Fabtron Sep 5, 2024
3015d35
adjust alert dialog color
Fabtron Sep 5, 2024
26c4ca3
move theme into own package and revert some formatting's
Fabtron Sep 5, 2024
928c124
revert obsolete changes
Fabtron Sep 5, 2024
f767f57
revert some formatting's
Fabtron Sep 5, 2024
8fbc082
remove obsolete blank line
Fabtron Sep 5, 2024
48e00e3
rename function to match it's usage
Fabtron Sep 5, 2024
9b85318
formatting and change import
Fabtron Sep 5, 2024
c6e8a9f
revert changes
Fabtron Sep 5, 2024
badcfd6
revert changes
Fabtron Sep 5, 2024
01790ca
revert changes
Fabtron Sep 5, 2024
96a3ee2
revert changes
Fabtron Sep 5, 2024
adda30e
revert changes
Fabtron Sep 5, 2024
02a8ac5
revert changes
Fabtron Sep 5, 2024
84a4fd3
switch to secondary button
Fabtron Sep 5, 2024
01208cd
switch to secondary button
Fabtron Sep 5, 2024
9a637ba
switch back to button
Fabtron Sep 6, 2024
838193e
use whole import
Fabtron Sep 6, 2024
0ab4f8a
Merge branch 'refs/heads/main' into apps-1773-remote-theming
Fabtron Sep 6, 2024
36d1e34
update doc and CHANGELOG.md
Fabtron Sep 6, 2024
89cb279
formatting
Fabtron Sep 6, 2024
b83060c
apply small changes
Fabtron Sep 6, 2024
9848e19
use secondary button
Fabtron Sep 6, 2024
6c967e9
use secondary button
Fabtron Sep 6, 2024
049512a
use secondary button
Fabtron Sep 6, 2024
d1716d6
use secondary button
Fabtron Sep 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file.
### Fixed

## [0.75.7]
### Added
* Add views to handle and apply the remote theme automatically
### Fixed
* Handle npe when loading the shopping cart data and restore from it

Expand Down
47 changes: 38 additions & 9 deletions core/src/main/java/io/snabble/sdk/Project.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.snabble.sdk

import com.google.gson.Gson
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import io.snabble.sdk.auth.SnabbleAuthorizationInterceptor
import io.snabble.sdk.checkout.Checkout
Expand All @@ -13,6 +16,9 @@ import io.snabble.sdk.coupons.Coupons
import io.snabble.sdk.encodedcodes.EncodedCodesOptions
import io.snabble.sdk.events.Events
import io.snabble.sdk.googlepay.GooglePayHelper
import io.snabble.sdk.remoteTheme.AppTheme
import io.snabble.sdk.remoteTheme.DarkModeColors
import io.snabble.sdk.remoteTheme.LightModeColors
import io.snabble.sdk.shoppingcart.ShoppingCart
import io.snabble.sdk.shoppingcart.ShoppingCartStorage
import io.snabble.sdk.utils.GsonHolder
Expand All @@ -39,7 +45,10 @@ import java.util.concurrent.CopyOnWriteArrayList
* A project contains configuration information and backend api urls needed for a
* retailer.
*/
class Project internal constructor(jsonObject: JsonObject) {
class Project internal constructor(
private val gson: Gson = GsonHolder.get(),
jsonObject: JsonObject
) {

/**
* The unique identifier of the Project
Expand Down Expand Up @@ -164,7 +173,8 @@ class Project internal constructor(jsonObject: JsonObject) {

// refresh encoded codes options for encoded codes that contain customer cards
if (encodedCodesJsonObject != null) {
encodedCodesOptions = EncodedCodesOptions.fromJsonObject(this, encodedCodesJsonObject)
encodedCodesOptions =
EncodedCodesOptions.fromJsonObject(this, encodedCodesJsonObject)
}
}

Expand Down Expand Up @@ -340,6 +350,8 @@ class Project internal constructor(jsonObject: JsonObject) {
lateinit var assets: Assets
private set

var appTheme: AppTheme? = null

init {
parse(jsonObject)
}
Expand All @@ -356,11 +368,22 @@ class Project internal constructor(jsonObject: JsonObject) {
brand = Snabble.brands[brandId]
}

val customizationConfig: JsonElement? = jsonObject["appCustomizationConfig"]
try {
val lightModeColors: LightModeColors? = gson.fromJson(customizationConfig, LightModeColors::class.java)
val darkModeColors: DarkModeColors? = gson.fromJson(customizationConfig, DarkModeColors::class.java)
appTheme = AppTheme(lightModeColors, darkModeColors)
Logger.d("AppTheme for $id loaded: $appTheme")
} catch (e: JsonSyntaxException) {
Logger.e(e.message)
}

val urls = mutableMapOf<String, String>()
val links = jsonObject["links"].asJsonObject
links.entrySet().forEach {
urls[it.key] = Snabble.absoluteUrl(it.value.asJsonObject["href"].asString)
}

this.urls = urls

tokensUrl = "${urls["tokens"]}?role=retailerApp"
Expand Down Expand Up @@ -415,7 +438,7 @@ class Project internal constructor(jsonObject: JsonObject) {

paymentMethodDescriptors = jsonObject["paymentMethodDescriptors"]?.let {
val typeToken = object : TypeToken<List<PaymentMethodDescriptor?>?>() {}.type
val paymentMethodDescriptors = GsonHolder.get().fromJson<List<PaymentMethodDescriptor>>(it, typeToken)
val paymentMethodDescriptors = gson.fromJson<List<PaymentMethodDescriptor>>(it, typeToken)
paymentMethodDescriptors.filter { desc ->
PaymentMethod.fromString(desc.id) != null
}
Expand All @@ -428,12 +451,13 @@ class Project internal constructor(jsonObject: JsonObject) {
}

if (jsonObject.has("company")) {
company = GsonHolder.get().fromJson(jsonObject["company"], Company::class.java)
company = gson.fromJson(jsonObject["company"], Company::class.java)
}

val codeTemplates = jsonObject["codeTemplates"]?.asJsonObject?.entrySet()?.map { (name, pattern) ->
CodeTemplate(name, pattern.asString)
}?.toMutableList() ?: mutableListOf()
val codeTemplates =
jsonObject["codeTemplates"]?.asJsonObject?.entrySet()?.map { (name, pattern) ->
CodeTemplate(name, pattern.asString)
}?.toMutableList() ?: mutableListOf()

val hasDefaultTemplate = codeTemplates.any { it.name == "default" }
if (!hasDefaultTemplate) {
Expand Down Expand Up @@ -494,7 +518,7 @@ class Project internal constructor(jsonObject: JsonObject) {
if (jsonObject.has("coupons")) {
val couponsJsonObject = jsonObject["coupons"]
val couponsType = object : TypeToken<List<Coupon?>?>() {}.type
couponList = GsonHolder.get().fromJson(couponsJsonObject, couponsType)
couponList = gson.fromJson(couponsJsonObject, couponsType)
}
} catch (e: Exception) {
Logger.e("Could not parse coupons")
Expand All @@ -514,7 +538,12 @@ class Project internal constructor(jsonObject: JsonObject) {

checkout = Checkout(this, shoppingCartFlow.value)

productDatabase = ProductDatabase(this, shoppingCartFlow.value, "$id.sqlite3", Snabble.config.generateSearchIndex)
productDatabase = ProductDatabase(
this,
shoppingCartFlow.value,
"$id.sqlite3",
Snabble.config.generateSearchIndex
)

events = Events(this, shoppingCartFlow.value)

Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/io/snabble/sdk/Snabble.kt
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ object Snabble {
// if it does not exist, add it
if (!updated) {
try {
val project = Project(jsonProject)
val project = Project(jsonObject = jsonProject)
newProjects.add(project)
} catch (e: IllegalArgumentException) {
Logger.d(e.message)
Expand Down
22 changes: 22 additions & 0 deletions core/src/main/java/io/snabble/sdk/remoteTheme/AppTheme.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.snabble.sdk.remoteTheme

import com.google.gson.annotations.SerializedName

data class AppTheme(
val lightModeColors: LightModeColors? = null,
val darkModeColors: DarkModeColors? = null,
)

data class LightModeColors(
@SerializedName("colorPrimary_light") val primaryColor: String,
@SerializedName("colorOnPrimary_light") val onPrimaryColor: String,
@SerializedName("colorSecondary_light") val secondaryColor: String,
@SerializedName("colorOnSecondary_light") val onSecondaryColor: String
)

data class DarkModeColors(
@SerializedName("colorPrimary_dark") val primaryColor: String,
@SerializedName("colorOnPrimary_dark") val onPrimaryColor: String,
@SerializedName("colorSecondary_dark") val secondaryColor: String,
@SerializedName("colorOnSecondary_dark") val onSecondaryColor: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@
tools:text="Next"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="32dp" />
</LinearLayout>
</LinearLayout>
2 changes: 1 addition & 1 deletion ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ dependencies {
implementation(libs.androidx.cardview)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.gridlayout)
implementation(libs.androidx.lifecycleLiveData)
implementation(libs.android.material)
implementation(libs.androidx.recyclerview)
implementation(libs.androidx.startupRuntime)
Expand All @@ -102,7 +103,6 @@ dependencies {
implementation(libs.rekisoftLazyWorker)
implementation(libs.relex.circleindicator)
implementation(libs.snabble.phoneAuth.countryCodePicker)

implementation(libs.bundles.camera)
implementation(libs.bundles.navigation)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ internal class TextFieldManager(
focusManager.clearFocus()
keyboardController?.hide()
}

fun showKeyboard(){
keyboardController?.show()
}

fun moveFocusToNext() {
focusManager.moveFocus(FocusDirection.Next)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import io.snabble.sdk.ui.SnabbleUI
import io.snabble.sdk.ui.payment.creditcard.datatrans.ui.DatatransFragment
import io.snabble.sdk.ui.payment.creditcard.fiserv.FiservInputView
import io.snabble.sdk.ui.payment.externalbilling.ExternalBillingFragment.Companion.ARG_PROJECT_ID
import io.snabble.sdk.ui.remotetheme.getPrimaryColorForProject
import io.snabble.sdk.ui.utils.KeyguardUtils
import io.snabble.sdk.ui.utils.UIUtils
import io.snabble.sdk.utils.Logger
Expand Down Expand Up @@ -42,7 +43,9 @@ object PaymentInputViewHelper {
args.putSerializable(DatatransFragment.ARG_PAYMENT_TYPE, paymentMethod)
SnabbleUI.executeAction(context, SnabbleUI.Event.SHOW_DATATRANS_INPUT, args)
}

usePayone -> Payone.registerCard(activity, project, paymentMethod, Snabble.formPrefillData)

useFiserv -> {
args.putString(FiservInputView.ARG_PROJECT_ID, projectId)
args.putSerializable(FiservInputView.ARG_PAYMENT_TYPE, paymentMethod.name)
Expand All @@ -66,11 +69,19 @@ object PaymentInputViewHelper {
}
}
} else {
AlertDialog.Builder(context)
val alertDialog = AlertDialog.Builder(context)
.setMessage(R.string.Snabble_Keyguard_requireScreenLock)
.setPositiveButton(R.string.Snabble_ok, null)
.setCancelable(false)
.show()
.create()

val primaryColor: Int = context.getPrimaryColorForProject(Snabble.instance.checkedInProject.value)

alertDialog.setOnShowListener {
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(primaryColor)
}

alertDialog.show()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.snabble.sdk.ui.remotetheme

import android.content.Context
import android.graphics.Color
import io.snabble.sdk.Project
import io.snabble.sdk.ui.R
import io.snabble.sdk.utils.getColorByAttribute

fun Context.getPrimaryColorForProject(project: Project?): Int {
val lightColor = project?.appTheme?.lightModeColors?.primaryColor?.asColor()
val darkColor = project?.appTheme?.darkModeColors?.primaryColor?.asColor()
return when {
isDarkMode() -> darkColor ?: lightColor ?: getColorByAttribute(R.attr.colorPrimary)
else -> lightColor ?: getColorByAttribute(R.attr.colorPrimary)
}
}

fun Context.getOnPrimaryColorForProject(project: Project?): Int {
val lightColor = project?.appTheme?.lightModeColors?.onPrimaryColor?.asColor()
val darkColor = project?.appTheme?.darkModeColors?.onPrimaryColor?.asColor()
return when {
isDarkMode() -> darkColor ?: lightColor ?: getColorByAttribute(R.attr.colorOnPrimary)
else -> lightColor ?: getColorByAttribute(R.attr.colorOnPrimary)
}
}

fun Context.getSecondaryColorForProject(project: Project?): Int {
val lightColor = project?.appTheme?.lightModeColors?.secondaryColor?.asColor()
val darkColor = project?.appTheme?.darkModeColors?.secondaryColor?.asColor()
return when {
isDarkMode() -> darkColor ?: lightColor ?: getColorByAttribute(R.attr.colorSecondary)
else -> lightColor ?: getColorByAttribute(R.attr.colorSecondary)
}
}

fun Context.getOnSecondaryColorForProject(project: Project?): Int {
val lightColor = project?.appTheme?.lightModeColors?.onSecondaryColor?.asColor()
val darkColor = project?.appTheme?.darkModeColors?.onSecondaryColor?.asColor()
return when {
isDarkMode() -> darkColor ?: lightColor ?: getColorByAttribute(R.attr.colorOnSecondary)
else -> lightColor ?: getColorByAttribute(R.attr.colorOnSecondary)
}
}

fun String.asColor() = Color.parseColor(this)

fun Context.isDarkMode(): Boolean {
val currentNightMode =
resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK
return currentNightMode == android.content.res.Configuration.UI_MODE_NIGHT_YES
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.snabble.sdk.ui.remotetheme

import android.content.Context
import android.util.AttributeSet
import com.google.android.material.button.MaterialButton
import io.snabble.sdk.Snabble
import io.snabble.sdk.ui.R

/**
* A default Materialbutton which automatically sets the remote theme colors of the
* current checked in project.
*/
class SnabblePrimaryButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.materialButtonStyle,
) : MaterialButton(context, attrs, defStyleAttr) {

init {
setProjectAppTheme()
}

private fun setProjectAppTheme() {
val project = Snabble.checkedInProject.value
setBackgroundColor(context.getPrimaryColorForProject(project))
setTextColor(context.getOnPrimaryColorForProject(project))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.snabble.sdk.ui.remotetheme

import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import io.snabble.sdk.Snabble

/**
* A default AppCompatTextView which automatically sets the primary color from the remote theme
* of the current checked in project as text color.
*/
class SnabblePrimaryTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = android.R.attr.textViewStyle
) : AppCompatTextView(context, attrs, defStyleAttr) {


init {
setProjectAppTheme()
}

private fun setProjectAppTheme() {
val project = Snabble.checkedInProject.value
setTextColor(context.getPrimaryColorForProject(project))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.snabble.sdk.ui.remotetheme

import android.content.Context
import android.util.AttributeSet
import com.google.android.material.button.MaterialButton
import io.snabble.sdk.Snabble
import io.snabble.sdk.ui.R

/**
* A default Materialbutton which automatically sets the primary color from the remote theme
* of the current checked in project as text color.
*
* To use it as secondary button it is required to apply the Widget.Material3.Button.TextButton style.
*/
class SnabbleSecondaryButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.materialButtonStyle,
) : MaterialButton(context, attrs, defStyleAttr) {

init {
setProjectAppTheme()
}

private fun setProjectAppTheme() {
val project = Snabble.checkedInProject.value
setTextColor(context.getPrimaryColorForProject(project))
}
}
Loading