Skip to content

Commit 9060a41

Browse files
authored
Merge pull request #35 from snabble/couponsUi
Coupons UI
2 parents 39b1437 + e25765a commit 9060a41

33 files changed

+1143
-118
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# Changelog
22
All notable changes to this project will be documented in this file.
33

4+
## [0.65.0]
5+
6+
### Added
7+
- Added support for displaying coupons
8+
- Added CouponFragment to display a single coupon
9+
- Added CouponDetailActivity to display a single coupon in an Activity
10+
- Added CouponOverviewView to display a collection of coupons depending on the project
11+
412
## [0.64.1]
513

614
### Fixed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ allprojects {
3131
}
3232

3333
project.ext {
34-
sdkVersion='0.64.1'
34+
sdkVersion='0.65.0'
3535
versionCode=1
3636

3737
compileSdkVersion=31

core/src/main/java/io/snabble/sdk/Project.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ package io.snabble.sdk
33
import com.google.gson.JsonElement
44
import com.google.gson.JsonObject
55
import com.google.gson.reflect.TypeToken
6-
import io.snabble.sdk.Snabble.instance
76

87
import io.snabble.sdk.googlepay.GooglePayHelper
98
import io.snabble.sdk.encodedcodes.EncodedCodesOptions
109
import io.snabble.sdk.codes.templates.CodeTemplate
1110
import io.snabble.sdk.codes.templates.PriceOverrideTemplate
1211
import io.snabble.sdk.auth.SnabbleAuthorizationInterceptor
1312
import io.snabble.sdk.checkout.Checkout
13+
import io.snabble.sdk.coupons.Coupon
14+
import io.snabble.sdk.coupons.CouponSource
15+
import io.snabble.sdk.coupons.Coupons
1416
import io.snabble.sdk.utils.*
1517
import okhttp3.OkHttpClient
1618
import okhttp3.Request

core/src/main/java/io/snabble/sdk/ShoppingCart.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import io.snabble.sdk.checkout.Violation;
2828
import io.snabble.sdk.codes.ScannedCode;
2929
import io.snabble.sdk.codes.templates.CodeTemplate;
30+
import io.snabble.sdk.coupons.Coupon;
31+
import io.snabble.sdk.coupons.CouponType;
3032
import io.snabble.sdk.utils.Dispatch;
3133
import io.snabble.sdk.utils.GsonHolder;
3234

@@ -110,7 +112,7 @@ void initWithProject(Project project) {
110112
item.cart = this;
111113
}
112114
}
113-
115+
114116
if (uuid == null) {
115117
generateNewUUID();
116118
}
@@ -178,7 +180,6 @@ void insert(Item item, int index, boolean update) {
178180
}
179181
}
180182

181-
182183
items.add(index, item);
183184

184185
clearBackup();
@@ -218,7 +219,6 @@ public Iterator<Item> iterator() {
218219
return items.iterator();
219220
}
220221

221-
222222
public Item getExistingMergeableProduct(Product product) {
223223
if (product == null) {
224224
return null;
@@ -385,7 +385,7 @@ public void invalidateOnlinePrices() {
385385
}
386386

387387
public void updatePrices(boolean debounce) {
388-
if(debounce) {
388+
if (debounce) {
389389
updater.dispatchUpdate();
390390
} else {
391391
updater.update(true);
@@ -416,8 +416,6 @@ public void generateNewUUID() {
416416
notifyProductsUpdate(this);
417417
}
418418

419-
420-
421419
public String getUUID() {
422420
return uuid;
423421
}
@@ -626,7 +624,7 @@ private Item(ShoppingCart cart, Product product, ScannedCode scannedCode) {
626624
} else {
627625
for (Product.Code code : product.getScannableCodes()) {
628626
if (code.template != null && code.template.equals(scannedCode.getTemplateName())
629-
&& code.lookupCode != null && code.lookupCode.equals(scannedCode.getLookupCode())) {
627+
&& code.lookupCode != null && code.lookupCode.equals(scannedCode.getLookupCode())) {
630628
this.quantity = code.specifiedQuantity;
631629

632630
if (!code.isPrimary && code.specifiedQuantity > 1) {
@@ -695,7 +693,7 @@ public int getQuantity(boolean ignoreLineItem) {
695693
if (lineItem != null && !ignoreLineItem) {
696694
if (lineItem.getWeight() != null) {
697695
return lineItem.getWeight();
698-
} else if (lineItem.getUnits() != null){
696+
} else if (lineItem.getUnits() != null) {
699697
return lineItem.getUnits();
700698
} else {
701699
return lineItem.getAmount();
@@ -1202,13 +1200,13 @@ void resolveViolations(List<Violation> violations) {
12021200
Item item = items.get(i);
12031201
items.remove(item);
12041202
boolean found = false;
1205-
for(ViolationNotification notification: violationNotifications) {
1206-
if(notification.getRefersTo().equals(violation.getRefersTo())) {
1203+
for (ViolationNotification notification : violationNotifications) {
1204+
if (notification.getRefersTo().equals(violation.getRefersTo())) {
12071205
found = true;
12081206
break;
12091207
}
12101208
}
1211-
if(!found) {
1209+
if (!found) {
12121210
violationNotifications.add(new ViolationNotification(
12131211
item.coupon.getName(),
12141212
violation.getRefersTo(),
@@ -1224,6 +1222,7 @@ void resolveViolations(List<Violation> violations) {
12241222

12251223
/**
12261224
* Remove the handled ViolationNotifications.
1225+
*
12271226
* @param violations the handled ViolationNotifications.
12281227
*/
12291228
public void removeViolationNotification(List<ViolationNotification> violations) {
@@ -1328,7 +1327,7 @@ public void onCheckoutLimitReached(ShoppingCart list) {}
13281327
public void onOnlinePaymentLimitReached(ShoppingCart list) {}
13291328

13301329
@Override
1331-
public void onViolationDetected(List<ViolationNotification> violations) {}
1330+
public void onViolationDetected(@NonNull List<ViolationNotification> violations) {}
13321331
}
13331332

13341333
private void notifyItemAdded(final ShoppingCart list, final Item item) {

core/src/main/java/io/snabble/sdk/ShoppingCartUpdater.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.snabble.sdk.checkout.PaymentMethodInfo;
2424
import io.snabble.sdk.checkout.SignedCheckoutInfo;
2525
import io.snabble.sdk.codes.ScannedCode;
26+
import io.snabble.sdk.coupons.Coupon;
2627
import io.snabble.sdk.utils.Dispatch;
2728
import io.snabble.sdk.utils.GsonHolder;
2829
import io.snabble.sdk.utils.Logger;

core/src/main/java/io/snabble/sdk/Snabble.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,15 +550,15 @@ object Snabble {
550550

551551
private fun getUrl(jsonObject: JsonObject, urlName: String): String? {
552552
return try {
553-
absoluteUrl(jsonObject["links"].asJsonObject[urlName].asJsonObject["href"].asString)
553+
jsonObject["links"]?.asJsonObject?.get(urlName)?.asJsonObject?.get("href")?.asString?.let(::absoluteUrl)
554554
} catch (e: Exception) {
555555
null
556556
}
557557
}
558558

559559
private fun parseBrands(jsonObject: JsonObject) {
560560
val jsonBrands = GsonHolder.get().fromJson(jsonObject["brands"], Array<Brand>::class.java)
561-
brands = jsonBrands.map { it.id to it }.toMap()
561+
brands = jsonBrands.associateBy { it.id }
562562
}
563563

564564
private fun parseProjects(jsonObject: JsonObject) {

core/src/main/java/io/snabble/sdk/checkout/CheckoutApi.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import com.google.gson.reflect.TypeToken
77
import io.snabble.sdk.ShoppingCart.BackendCart
88
import io.snabble.sdk.payment.PaymentCredentials
99
import io.snabble.sdk.Product
10-
import io.snabble.sdk.Coupon
10+
import io.snabble.sdk.coupons.Coupon
1111
import io.snabble.sdk.FulfillmentState
1212
import io.snabble.sdk.PaymentMethod
1313
import java.lang.Exception

core/src/main/java/io/snabble/sdk/checkout/PersistentState.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.snabble.sdk.checkout
22

3-
import io.snabble.sdk.Coupon
3+
import io.snabble.sdk.coupons.Coupon
44
import io.snabble.sdk.PaymentMethod
55
import io.snabble.sdk.Product
66
import io.snabble.sdk.utils.Dispatch
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package io.snabble.sdk.coupons
2+
3+
import android.graphics.Color
4+
import android.os.Parcelable
5+
import android.util.DisplayMetrics
6+
import androidx.annotation.ColorInt
7+
import com.google.gson.annotations.SerializedName
8+
import io.snabble.sdk.Snabble
9+
import kotlinx.parcelize.IgnoredOnParcel
10+
import kotlinx.parcelize.Parcelize
11+
import java.time.ZonedDateTime
12+
13+
@Parcelize
14+
data class Coupon (
15+
val id: String,
16+
val name: String,
17+
val description: String?,
18+
val promotionDescription: String?,
19+
val type: CouponType,
20+
val codes: List<CouponCode>?,
21+
val code: String?,
22+
@SerializedName("validFrom")
23+
private val _validFrom: String?,
24+
@SerializedName("validUntil")
25+
private val _validUntil: String?,
26+
val image: CouponImage?,
27+
val disclaimer: String?,
28+
val colors: Map<String, String>?,
29+
) : Parcelable {
30+
val isValid: Boolean
31+
get() = when(type) {
32+
CouponType.DIGITAL -> image != null
33+
CouponType.MANUAL -> name != null
34+
CouponType.PRINTED -> true
35+
}
36+
37+
@IgnoredOnParcel
38+
val validFrom: ZonedDateTime?
39+
get() = _validFrom?.let { ZonedDateTime.parse(_validFrom)}
40+
41+
@IgnoredOnParcel
42+
val validUntil: ZonedDateTime?
43+
get() = _validUntil?.let { ZonedDateTime.parse(_validUntil)}
44+
45+
@IgnoredOnParcel
46+
val backgroundColor
47+
get() = parseColor(colors?.get("background"), Color.WHITE)
48+
49+
@IgnoredOnParcel
50+
val textColor
51+
get() = parseColor(colors?.get("foreground"), Color.BLACK)
52+
53+
fun parseColor(color: String?, @ColorInt default: Int) =
54+
color?.let {
55+
Color.parseColor(when {
56+
"^[0-9a-fA-F]{6}(?:[0-9a-fA-F]{2})?$".toRegex().matches(color) -> {
57+
// add missing prefix
58+
"#$color"
59+
}
60+
"^#?[0-9a-fA-F]{3}$".toRegex().matches(color) -> {
61+
// convert 3 digit color to 6 digits
62+
color.removePrefix("#").toCharArray()
63+
.joinToString(separator = "", prefix = "#") { "$it$it" }
64+
}
65+
else -> {
66+
color
67+
}
68+
})
69+
} ?: default
70+
}
71+
72+
@Parcelize
73+
data class CouponCode (
74+
val code: String,
75+
val template: String,
76+
) : Parcelable
77+
78+
@Parcelize
79+
data class CouponImage (
80+
val name: String?,
81+
val formats: List<CouponImageFormats>,
82+
) : Parcelable {
83+
val bestResolutionUrl: String
84+
get() {
85+
val res = Snabble.application.resources
86+
87+
val mdpiRange = 0..DisplayMetrics.DENSITY_MEDIUM
88+
val hdpiRange = DisplayMetrics.DENSITY_MEDIUM..DisplayMetrics.DENSITY_HIGH
89+
val xhdpiRange = DisplayMetrics.DENSITY_HIGH..DisplayMetrics.DENSITY_XHIGH
90+
val xxhdpiRange = DisplayMetrics.DENSITY_XHIGH..DisplayMetrics.DENSITY_XXHIGH
91+
val xxxhdpiRange = DisplayMetrics.DENSITY_XXXHIGH..Int.MAX_VALUE
92+
93+
val preferredDpi = when (res.displayMetrics.densityDpi) {
94+
in mdpiRange -> "mdpi"
95+
in hdpiRange -> "hdpi"
96+
in xhdpiRange -> "xhdpi"
97+
in xxhdpiRange -> "xxhdpi"
98+
in xxxhdpiRange -> "xxxhdpi"
99+
else -> null
100+
}
101+
102+
val image = formats
103+
.filter { it.contentType == "image/webp" }
104+
.firstOrNull { it.size == preferredDpi }
105+
?: this.formats.last()
106+
107+
return image.url
108+
}
109+
}
110+
111+
@Parcelize
112+
data class CouponImageFormats (
113+
val contentType: String,
114+
val width: Int?,
115+
val height: Int?,
116+
val size: String,
117+
val url: String,
118+
) : Parcelable
119+
120+
enum class CouponType {
121+
@SerializedName("manual") MANUAL,
122+
@SerializedName("printed") PRINTED,
123+
@SerializedName("digital") DIGITAL,
124+
}

core/src/main/java/io/snabble/sdk/Coupons.kt renamed to core/src/main/java/io/snabble/sdk/coupons/Coupons.kt

Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,18 @@
1-
package io.snabble.sdk
1+
package io.snabble.sdk.coupons
22

33
import android.os.Looper
4-
import android.os.Parcelable
54
import androidx.annotation.Keep
65
import androidx.lifecycle.LiveData
76
import com.google.gson.GsonBuilder
8-
import com.google.gson.annotations.SerializedName
9-
import kotlinx.parcelize.Parcelize
7+
import io.snabble.sdk.MutableAccessibleLiveData
8+
import io.snabble.sdk.Project
9+
import io.snabble.sdk.Snabble
1010
import okhttp3.Call
1111
import okhttp3.Callback
1212
import okhttp3.Request
1313
import okhttp3.Response
1414
import java.io.IOException
1515

16-
@Parcelize
17-
data class Coupon (
18-
val id: String,
19-
val name: String?,
20-
val description: String?,
21-
val promotionDescription: String?,
22-
val type: CouponType,
23-
val codes: List<CouponCode>?,
24-
val code: String?,
25-
val validFrom: String?,
26-
val validUntil: String?,
27-
val image: CouponImage?,
28-
val disclaimer: String?,
29-
val colors: Map<String, String>?,
30-
) : Parcelable {
31-
val isValid: Boolean
32-
get() = when(type) {
33-
CouponType.DIGITAL -> image != null
34-
CouponType.MANUAL -> name != null
35-
CouponType.PRINTED -> true
36-
}
37-
}
38-
39-
@Parcelize
40-
data class CouponCode (
41-
val code: String,
42-
val template: String,
43-
) : Parcelable
44-
45-
@Parcelize
46-
data class CouponImage (
47-
val name: String,
48-
val formats: List<CouponImageFormats>,
49-
) : Parcelable
50-
51-
@Parcelize
52-
data class CouponImageFormats (
53-
val contentType: String,
54-
val width: Int?,
55-
val height: Int?,
56-
val size: String,
57-
val url: String,
58-
) : Parcelable
59-
60-
enum class CouponType {
61-
@SerializedName("manual") MANUAL,
62-
@SerializedName("printed") PRINTED,
63-
@SerializedName("digital") DIGITAL,
64-
}
65-
6616
enum class CouponSource {
6717
Bundled,
6818
Online,
@@ -108,8 +58,9 @@ class Coupons (
10858

10959
override fun onResponse(call: Call, response: Response) {
11060
if (response.isSuccessful) {
61+
val response = response.body?.string()
11162
val localizedResponse = GsonBuilder().create()
112-
.fromJson(response.body?.string(), CouponResponse::class.java)
63+
.fromJson(response, CouponResponse::class.java)
11364
postValue(localizedResponse.coupons.filter { coupon ->
11465
coupon.isValid
11566
})

0 commit comments

Comments
 (0)