Panagiotis Triantafyllou

get coupons request, single coupon activity

Showing 25 changed files with 1619 additions and 481 deletions
...@@ -18,11 +18,6 @@ Debug=true ...@@ -18,11 +18,6 @@ Debug=true
18 # DEH Development: https://engage-uat.dei.gr 18 # DEH Development: https://engage-uat.dei.gr
19 BaseURL=https://engage-uat.dei.gr 19 BaseURL=https://engage-uat.dei.gr
20 20
21 -# For Verify Ticket request
22 -VerifyURL=/partners/dei/verify
23 -
24 -#WebActionHandler=app_package_name.WarplyWebActionHandler
25 -
26 # Replace the color with one you want the progress bar to have depending on you app theme-coloring 21 # Replace the color with one you want the progress bar to have depending on you app theme-coloring
27 # If not defined the colorPrimary will used 22 # If not defined the colorPrimary will used
28 #ProgressColor=red 23 #ProgressColor=red
...@@ -46,13 +41,10 @@ SendPackages=false ...@@ -46,13 +41,10 @@ SendPackages=false
46 # The app language 41 # The app language
47 Language=el 42 Language=el
48 43
49 -# The merchant id for some requests
50 -MerchantId=20113
51 -
52 # The login type must be one of the below: 44 # The login type must be one of the below:
53 # email, msisdn, username 45 # email, msisdn, username
54 -LoginType=username 46 +LoginType=email
55 47
56 # The deeplink url scheme for react native campaigns: 48 # The deeplink url scheme for react native campaigns:
57 -# Example visit.greece.gr
58 -DL_URL_SCHEME=demo
...\ No newline at end of file ...\ No newline at end of file
49 +# Example demo.app.gr
50 +DL_URL_SCHEME=demo.app.gr
...\ No newline at end of file ...\ No newline at end of file
......
1 #Fri Jul 26 17:08:44 EEST 2024 1 #Fri Jul 26 17:08:44 EEST 2024
2 distributionBase=GRADLE_USER_HOME 2 distributionBase=GRADLE_USER_HOME
3 distributionPath=wrapper/dists 3 distributionPath=wrapper/dists
4 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip 4 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
5 zipStoreBase=GRADLE_USER_HOME 5 zipStoreBase=GRADLE_USER_HOME
6 zipStorePath=wrapper/dists 6 zipStorePath=wrapper/dists
......
1 +# Warply Android SDK — AI Agent Skill File
2 +
3 +> **Purpose**: This file provides comprehensive context about the Warply Android SDK codebase for AI coding agents (Claude Code, Gemini CLI, Codex, OpenCode, Cline, etc.). It describes architecture, patterns, conventions, and critical rules to follow when working with this project.
4 +
5 +---
6 +
7 +## 1. Project Overview
8 +
9 +- **What**: Warply Android SDK — a loyalty, marketing, and engagement SDK for Android apps. It provides campaigns, coupons, couponsets, push notifications, beacons, location tracking, analytics, and user management.
10 +- **Type**: Android Library (AAR) published to Maven via Gradle.
11 +- **Package namespace**: `ly.warp.sdk`
12 +- **License**: BSD-2-Clause (Warply Ltd.) — all source files must retain the license header.
13 +- **Repository**: Git-based (`warply_android_sdk_maven_plugin`)
14 +
15 +---
16 +
17 +## 2. Project Structure
18 +
19 +```
20 +warply_android_sdk_maven_plugin/ # Root project
21 +├── build.gradle # Root build config (AGP 8.7.3, plugins)
22 +├── settings.gradle # Includes: app, warply_android_sdk
23 +├── gradle.properties # Gradle/Maven properties
24 +├── scripts/
25 +│ ├── publish-module.gradle # Module publishing script
26 +│ └── publish-root.gradle # Root publishing script
27 +
28 +├── app/ # Demo/host application
29 +│ ├── build.gradle
30 +│ ├── src/main/
31 +│ │ ├── AndroidManifest.xml
32 +│ │ ├── assets/warply.properties # SDK configuration file
33 +│ │ └── java/warp/ly/android_sdk/
34 +│ │ ├── WarplyAndroidSDKApplication.java
35 +│ │ └── activities/
36 +│ │ ├── BaseActivity.java
37 +│ │ └── SplashActivity.java
38 +
39 +├── warply_android_sdk/ # SDK Library Module (the main codebase)
40 +│ ├── build.gradle # Library build config, dependencies, publishing
41 +│ ├── proguard-rules.pro
42 +│ └── src/main/
43 +│ ├── AndroidManifest.xml # SDK manifest (permissions, services, receivers)
44 +│ ├── java/ly/warp/sdk/
45 +│ │ ├── Warply.java # ★ SDK Singleton entry point
46 +│ │ ├── activities/ # UI Activities
47 +│ │ ├── fragments/ # UI Fragments
48 +│ │ ├── db/ # SQLite database layer
49 +│ │ ├── dexter/ # Runtime permissions library (embedded)
50 +│ │ ├── io/
51 +│ │ │ ├── adapters/ # RecyclerView/ViewPager adapters
52 +│ │ │ ├── callbacks/ # Async callback interfaces
53 +│ │ │ ├── models/ # Data models (Campaign, Coupon, etc.)
54 +│ │ │ ├── request/ # Custom Volley request types
55 +│ │ │ └── volley/ # Custom Volley fork + Retrofit layer
56 +│ │ ├── receivers/ # Broadcast receivers
57 +│ │ ├── services/ # Background services (push, location, etc.)
58 +│ │ ├── utils/ # Utility classes
59 +│ │ │ ├── constants/ # WarpConstants, GCMConstants
60 +│ │ │ └── managers/ # Business logic managers
61 +│ │ └── views/ # Custom views and decorations
62 +│ └── res/ # Resources (layouts, drawables, fonts, etc.)
63 +```
64 +
65 +---
66 +
67 +## 3. Build Configuration
68 +
69 +| Setting | Value |
70 +|---------|-------|
71 +| **Android Gradle Plugin** | 8.7.3 |
72 +| **compileSdkVersion** | 35 |
73 +| **buildToolsVersion** | 35.0.0 |
74 +| **minSdkVersion** | 31 |
75 +| **targetSdkVersion** | 35 |
76 +| **Namespace** | `ly.warp.sdk` |
77 +| **Java version** | Java 8+ (no Kotlin) |
78 +| **Publishing** | Maven (single variant: release with sources) |
79 +
80 +### Key Dependencies
81 +
82 +| Category | Library | Version/Notes |
83 +|----------|---------|---------------|
84 +| UI | `androidx.appcompat:appcompat` | 1.7.1 |
85 +| UI | `com.google.android.material:material` | 1.13.0 |
86 +| Security | `androidx.security:security-crypto` | 1.1.0 |
87 +| Networking | `com.squareup.retrofit2:retrofit` | 3.0.0 |
88 +| Networking | `com.squareup.retrofit2:converter-gson` | 3.0.0 |
89 +| Networking | `com.squareup.okhttp3:logging-interceptor` | 5.1.0 |
90 +| Networking | Custom Volley fork (embedded in source) | — |
91 +| Reactive | `io.reactivex.rxjava3:rxjava` | 3.1.8 |
92 +| Reactive | `io.reactivex.rxjava3:rxandroid` | 3.0.2 |
93 +| Async | `com.google.guava:guava` | 33.0.0-android |
94 +| Events | `org.greenrobot:eventbus` | 3.3.1 |
95 +| Images | `com.github.bumptech.glide:glide` | 4.16.0 |
96 +| Firebase | `com.google.firebase:firebase-bom` | 34.2.0 |
97 +| Firebase | `com.google.firebase:firebase-messaging` | (from BOM) |
98 +| GMS | `com.google.android.gms:play-services-base` | 18.7.2 |
99 +| GMS | `com.google.android.gms:play-services-location` | 21.3.0 |
100 +| Huawei | `com.huawei.agconnect:agconnect-core` | 1.9.3.301 |
101 +| Huawei | `com.huawei.hms:push` | 6.10.0.300 |
102 +| Beacons | `org.altbeacon:android-beacon-library` | 2.19.3 |
103 +| DB | `androidx.sqlite:sqlite` | 2.5.2 |
104 +| Work | `androidx.work:work-runtime` | 2.10.3 |
105 +| Lifecycle | `androidx.lifecycle:lifecycle-extensions` | 2.2.0 |
106 +
107 +### SDK Configuration
108 +
109 +The SDK is configured at runtime via a `warply.properties` file placed in the host app's `assets/` directory. Key properties include:
110 +
111 +- `Uuid` — Application UUID (identifies the app to the Warply backend)
112 +- `BaseURL` — Backend server base URL
113 +- `Language` — Default language
114 +- `Debug` — Enable/disable debug logging
115 +- `MerchantId` — Merchant identifier
116 +- `LoginType` — Authentication type
117 +- `PushColor`, `PushIcon`, `PushSound` — Push notification customization
118 +- `ProgressColor`, `ProgressDrawable` — UI loading customization
119 +
120 +**Configuration is read via `WarplyProperty.java`** which loads properties from the assets file.
121 +
122 +---
123 +
124 +## 4. Architecture & Core Patterns
125 +
126 +### 4.1 SDK Entry Point — `Warply.java` (Singleton Enum)
127 +
128 +```java
129 +public enum Warply {
130 + INSTANCE;
131 + // ...
132 +}
133 +```
134 +
135 +- **Initialization**: `Warply.getInitializer(context)` or `Warply.getInitializer(context, callback)`
136 +- Uses `WarplyInitializer` for setup, which calls `initInternal()` to configure the request queue, debug mode, server preferences, etc.
137 +- **Registration**: `Warply.registerWarply()` — registers the device with the Warply backend
138 +- **Context**: `Warply.getWarplyContext()` — returns the application context
139 +- **Request Queue**: Internal Volley `RequestQueue` for legacy API calls
140 +- **Microapp Pattern**: Data is posted/retrieved per "microapp" (e.g., `device_info`, `application_data`, `inapp_analytics`, `offers`, etc.)
141 +
142 +**Key Pattern — Public static → private internal delegation:**
143 +```java
144 +public static void someMethod() {
145 + INSTANCE.isInitializedOrThrow();
146 + INSTANCE.someMethodInternal();
147 +}
148 +private void someMethodInternal() { /* actual logic */ }
149 +```
150 +
151 +### 4.2 API Manager — `WarplyManager.java`
152 +
153 +The primary class for all backend API calls. Uses **static methods** exclusively.
154 +
155 +**Key API Methods:**
156 +| Method | Returns | Description |
157 +|--------|---------|-------------|
158 +| `login(email, receiver)` | `JSONObject` | Authenticate user |
159 +| `logout(receiver)` | `JSONObject` | Logout and clear tokens |
160 +| `getUser(receiver)` | `User` | Fetch user profile |
161 +| `getCampaigns(receiver)` | `ArrayList<BannerItem>` | Fetch campaigns + articles (parallel) |
162 +| `getCouponsets(receiver)` | `LinkedHashMap<String, ArrayList<Couponset>>` | Fetch couponsets + merchants (parallel, categorized) |
163 +| `getUserCouponsWithCouponsets(receiver)` | `CouponList` | Fetch user's redeemed coupons |
164 +| `getSingleCampaign(sessionUuid)` | void | Trigger campaign session |
165 +
166 +**Async Pattern — Guava ListenableFuture for parallel requests:**
167 +```java
168 +ListenableFuture<ArrayList<Campaign>> futureCampaigns = getCampaignsRetro(service);
169 +ListenableFuture<ArrayList<Content>> futureArticles = getArticlesRetro(service, categories);
170 +
171 +ListenableFuture<List<Object>> allResultsFuture = Futures.allAsList(futureCampaigns, futureArticles);
172 +ListenableFuture<ArrayList<BannerItem>> mergedResult = Futures.transformAsync(allResultsFuture, results -> {
173 + // merge logic
174 +}, executorService);
175 +
176 +Futures.addCallback(mergedResult, new FutureCallback<>() {
177 + public void onSuccess(result) { new Handler(Looper.getMainLooper()).post(() -> receiver.onSuccess(result)); }
178 + public void onFailure(throwable) { new Handler(Looper.getMainLooper()).post(() -> receiver.onFailure(2)); }
179 +}, executorService);
180 +```
181 +
182 +**Token Refresh Pattern:**
183 +- On HTTP 401 → call `refreshToken()` → retry original request
184 +- Max 3 retries (`MAX_RETRIES = 3`)
185 +- On refresh failure with 401 → clear auth tokens and fail
186 +
187 +### 4.3 In-Memory Cache — `WarplyManagerHelper.java`
188 +
189 +Static fields caching the latest fetched data:
190 +- `mCouponRedeemedList` — user's active coupons
191 +- `mCampaignListAll` — all campaigns
192 +- `mBannerListAll` — banner items (campaigns + articles merged)
193 +- `mCouponsetCategorizedMap` — couponsets organized by category (`LinkedHashMap<String, ArrayList<Couponset>>`)
194 +
195 +**Important**: These are set by `WarplyManager` after API calls and read by UI components.
196 +
197 +### 4.4 Callback Interface
198 +
199 +```java
200 +public interface CallbackReceiver<T> {
201 + void onSuccess(T result);
202 + void onFailure(int errorCode);
203 +}
204 +```
205 +
206 +All async operations use this pattern. Error codes are defined in `WarpConstants`:
207 +- `1` = Success
208 +- `2` = Generic error
209 +- `401` = Unauthorized (triggers token refresh)
210 +- `-1` = No internet
211 +- `-4` = Not registered
212 +
213 +---
214 +
215 +## 5. Networking Layer
216 +
217 +### 5.1 Retrofit (Modern API calls)
218 +
219 +**`ApiClient.java`** — Singleton Retrofit instance:
220 +- Base URL from `WarplyProperty.getBaseUrl()`
221 +- OkHttp client with 30s connect/write/read timeouts
222 +- GsonConverterFactory for serialization
223 +
224 +**`ApiService.java`** — Retrofit interface defining all endpoints:
225 +| Endpoint | Path | Auth |
226 +|----------|------|------|
227 +| Login | `POST /partners/dei/app_login` | No Bearer |
228 +| Logout | `POST /oauth/{appUuid}/logout` | Bearer token |
229 +| Logout (JWT) | `POST /user/v5/{appUuid}/logout` | Bearer token |
230 +| Get User | `POST /oauth/{appUuid}/context` | Bearer token |
231 +| Get Coupons | `POST /oauth/{appUuid}/context` | Bearer token |
232 +| Get Campaigns | `POST /api/mobile/v2/{appUuid}/context/` | Signature only |
233 +| Get Articles | `POST /api/mobile/v2/{appUuid}/context/` | Signature only |
234 +| Get Campaigns (Personalized) | `POST /oauth/{appUuid}/context` | Bearer token |
235 +| Get Couponsets | `POST /api/mobile/v2/{appUuid}/context/` | Bearer token |
236 +| Get Merchants | `POST /api/mobile/v2/{appUuid}/context/` | Signature only |
237 +
238 +**Required Headers on all requests:**
239 +```
240 +Content-Type: application/json
241 +loyalty-date: <timestamp yyyy-MM-dd hh:mm:ss>
242 +loyalty-bundle-id: android:<package_name>
243 +unique-device-id: <device_unique_id>
244 +channel: mobile
245 +loyalty-web-id: <web_id>
246 +loyalty-signature: SHA256(api_key + timestamp)
247 +Authorization: Bearer <access_token> (when authenticated)
248 +```
249 +
250 +**Signature Generation** (`WarpUtils.produceSignature`):
251 +```java
252 +SHA-256(apiKey + timestamp) hex string
253 +```
254 +
255 +### 5.2 Custom Volley Fork (Legacy)
256 +
257 +The project contains a **full embedded copy of Volley** under `io/volley/` with custom modifications. This is used for:
258 +- Legacy microapp data posting (batched requests from DB queue)
259 +- Context retrieval
260 +- Device registration
261 +
262 +**⚠️ Do NOT replace or upgrade this Volley fork** — it contains custom request types (`WarplyJsonObjectRequest`, `WarplyJsonArrayRequest`) and batching logic specific to the SDK.
263 +
264 +---
265 +
266 +## 6. Data Layer
267 +
268 +### 6.1 SQLite Database — `WarplyDBHelper.java`
269 +
270 +**Singleton pattern**: `WarplyDBHelper.getInstance(context)`
271 +
272 +| Table | Purpose | Key Columns |
273 +|-------|---------|-------------|
274 +| `requests` | Queued microapp data requests | microapp, entity, force_post, date_added |
275 +| `push_requests` | Queued push notification events | microapp, entity, force_post, date_added |
276 +| `push_ack_requests` | Queued push acknowledgments | microapp, entity, force_post, date_added |
277 +| `client` | OAuth client credentials | client_id (encrypted), client_secret (encrypted) |
278 +| `auth` | OAuth tokens | access_token (encrypted), refresh_token (encrypted) |
279 +| `tags` | User tags | tag, last_add_date |
280 +
281 +**⚠️ CRITICAL**: Auth tokens and client credentials are stored with **field-level encryption** using `CryptoUtils.encrypt()`/`CryptoUtils.decrypt()`. Always use these methods when reading/writing to the `client` and `auth` tables.
282 +
283 +### 6.2 Encrypted SharedPreferences — `WarpUtils.java`
284 +
285 +Preferences are stored using `EncryptedSharedPreferences` with:
286 +- `MasterKey` (AES256_GCM scheme)
287 +- Key encryption: AES256_SIV
288 +- Value encryption: AES256_GCM
289 +- Fallback to plain `SharedPreferences` on error
290 +
291 +Key stored values: web_id, api_key, device_token, device_info, app_data, locale, dark_mode, JWT enabled flag, webview params.
292 +
293 +---
294 +
295 +## 7. Data Models
296 +
297 +All models are in `ly.warp.sdk.io.models` and follow these conventions:
298 +
299 +### 7.1 Model Pattern
300 +
301 +```java
302 +public class ModelName implements Parcelable, Serializable {
303 + private static final long serialVersionUID = ...;
304 +
305 + // JSON key constants
306 + private static final String FIELD_NAME = "json_key";
307 +
308 + // Member variables
309 + private String fieldName;
310 +
311 + // Null-safe JSON helper
312 + private static String optNullableString(JSONObject json, String key) {
313 + return json.isNull(key) ? null : json.optString(key);
314 + }
315 +
316 + // Constructors: default, JSONObject, String (JSON), Parcel
317 + public ModelName() { /* defaults */ }
318 + public ModelName(JSONObject json) { /* parse from JSON */ }
319 + public ModelName(String json) throws JSONException { this(new JSONObject(json)); }
320 + public ModelName(Parcel source) { /* read from parcel */ }
321 +
322 + // Serialization
323 + public JSONObject toJSONObject() { /* to JSON */ }
324 + public String toString() { return toJSONObject().toString(); }
325 + public String toHumanReadableString() { return toJSONObject().toString(2); }
326 +
327 + // Parcelable
328 + public void writeToParcel(Parcel dest, int flags) { /* write */ }
329 + public static final Creator<ModelName> CREATOR = ...;
330 +
331 + // Getters and Setters
332 +}
333 +```
334 +
335 +### 7.2 Key Models
336 +
337 +| Model | Key Fields | Notes |
338 +|-------|-----------|-------|
339 +| **Campaign** | sessionUUID, title, subtitle, logoUrl, indexUrl, offerCategory, sorting, type, extraFields, actions, startDate, endDate | Represents loyalty campaigns |
340 +| **Couponset** | uuid, name, description, imgPreview, merchantUuid, offerCategory, promoted, points, discount, availability, extraFields | Represents coupon offers |
341 +| **Coupon** | barcode, coupon, couponsetUuid, merchantUuid, status, expiration, discount, couponsetDetails, merchantDetails, redeemDetails | User's acquired coupons |
342 +| **Merchant** | uuid, name, address, logo, imgPreview, latitude, longitude, category, extraFields, merchantMetadata | Merchant/store info |
343 +| **User** | (parsed from JSON result) | User profile data |
344 +| **BannerItem** | Contains either Campaign or Content | Unified carousel item |
345 +| **Content** | (articles/content items) | CMS content with imgPreview, extraFields |
346 +| **CouponList** | Extends ArrayList\<Coupon\> | Typed list of coupons |
347 +
348 +**⚠️ JSON Handling**: Models use `org.json.JSONObject`/`JSONArray`**NOT Gson**. Do not change this pattern. The `optNullableString()` helper must be used for nullable String fields.
349 +
350 +---
351 +
352 +## 8. UI Layer
353 +
354 +### 8.1 Activities
355 +
356 +| Activity | Purpose |
357 +|----------|---------|
358 +| `HomeActivity` | Main SDK screen — banner carousel + categorized couponset sections |
359 +| `ProfileActivity` | User profile screen |
360 +| `SingleCouponActivity` | Single coupon detail view |
361 +| `SingleCouponsetActivity` | Single couponset/offer detail view |
362 +| `WarpViewActivity` | WebView for campaign content (loads campaign URLs) |
363 +| `BaseFragmentActivity` | Fragment host activity |
364 +| `WarpBaseActivity` | Base activity class |
365 +| `ApplicationSessionActivity` | Session management |
366 +
367 +**UI Patterns:**
368 +- Activities extend `android.app.Activity` (NOT `AppCompatActivity`) — except where noted
369 +- Use `View.OnClickListener` interface implementation
370 +- Navigation via `Intent` with `Parcelable` extras
371 +- Loading states with `RelativeLayout` overlays (`mPbLoading`)
372 +- Edge-to-edge display: `WarpUtils.applyEdgeToEdge(activity, topView, bottomView)`
373 +
374 +### 8.2 Banner Carousel (HomeActivity)
375 +
376 +- `ViewPager2` with `BannerAdapter`
377 +- Pagination dots (custom `LinearLayout` with `ImageView` dots)
378 +- Data source: `WarplyManagerHelper.getBannerList()` (merged campaigns + articles)
379 +- Image preloading with Glide (`DiskCacheStrategy.DATA`)
380 +- Click handlers: campaigns → `WarpViewActivity`, articles → `SingleCouponsetActivity` (via UUID lookup)
381 +
382 +### 8.3 Couponset Sections (HomeActivity)
383 +
384 +- Dynamically inflated sections from `item_couponset_section.xml`
385 +- Each section: title + "See all" + horizontal `RecyclerView`
386 +- `CouponsetAdapter` with `OnCouponsetClickListener`
387 +- Max 5 items per section (`MAX_ITEMS_PER_SECTION = 5`)
388 +- Data source: `LinkedHashMap<String, ArrayList<Couponset>>` — "Top offers" first, then by category
389 +
390 +### 8.4 Campaign URL Construction
391 +
392 +```java
393 +// WarplyManagerHelper.constructCampaignUrl(campaign)
394 +// Sets webview params as JSON in SharedPreferences:
395 +{
396 + "web_id": "...",
397 + "app_uuid": "...",
398 + "api_key": "...",
399 + "session_uuid": "...",
400 + "access_token": "...",
401 + "refresh_token": "...",
402 + "client_id": "...",
403 + "client_secret": "...",
404 + "lan": "...",
405 + "dark": "true/false"
406 +}
407 +```
408 +
409 +### 8.5 Fonts
410 +
411 +Custom fonts in `res/font/`:
412 +- `ping_lcg_bold.otf`
413 +- `ping_lcg_regular.otf`
414 +- `ping_lcg_light.otf`
415 +
416 +Applied via: `WarpUtils.renderCustomFont(context, R.font.ping_lcg_bold, textView1, textView2, ...)`
417 +
418 +---
419 +
420 +## 9. Services & Background Work
421 +
422 +| Service | Purpose |
423 +|---------|---------|
424 +| `FCMBaseMessagingService` | Firebase Cloud Messaging (currently commented out in manifest) |
425 +| `HMSBaseMessagingService` | Huawei Push (currently commented out in manifest) |
426 +| `UpdateUserLocationService` | Location updates (JobService) |
427 +| `EventRefreshDeviceTokenService` | Device token refresh (JobService) |
428 +| `WarplyBeaconsRangingService` | iBeacon ranging |
429 +| `PushEventsWorkerService` | Push event processing (WorkManager) |
430 +| `PushEventsClickedWorkerService` | Push click tracking (WorkManager) |
431 +
432 +| Receiver | Purpose |
433 +|----------|---------|
434 +| `ConnectivityChangedReceiver` | Network state changes |
435 +| `LocationChangedReceiver` | Location updates |
436 +
437 +---
438 +
439 +## 10. Code Style & Conventions
440 +
441 +### 10.1 Section Comments
442 +```java
443 +// ===========================================================
444 +// Constants
445 +// ===========================================================
446 +
447 +// ===========================================================
448 +// Fields
449 +// ===========================================================
450 +
451 +// ===========================================================
452 +// Methods for/from SuperClass/Interfaces
453 +// ===========================================================
454 +
455 +// ===========================================================
456 +// Methods
457 +// ===========================================================
458 +
459 +// ===========================================================
460 +// Getter & Setter
461 +// ===========================================================
462 +
463 +// ===========================================================
464 +// Inner and Anonymous Classes
465 +// ===========================================================
466 +```
467 +
468 +### 10.2 License Header
469 +Every source file must start with the BSD-2-Clause license block:
470 +```java
471 +/*
472 + * Copyright 2010-2013 Warply Ltd. All rights reserved.
473 + * ...BSD-2-Clause text...
474 + */
475 +```
476 +
477 +### 10.3 Naming Conventions
478 +- **Packages**: `ly.warp.sdk.*`
479 +- **Member variables**: `mPrefixedName` for UI fields (e.g., `mPbLoading`, `mBannerViewPager`)
480 +- **Constants**: `UPPER_SNAKE_CASE` (e.g., `MAX_ITEMS_PER_SECTION`, `HEADER_SIGNATURE`)
481 +- **JSON keys**: `lower_snake_case` as `private static final String` constants
482 +- **Callbacks**: Anonymous inner classes or final field references (e.g., `mCampaignsCallback`)
483 +
484 +### 10.4 Logging
485 +```java
486 +WarpUtils.log("message"); // Debug log (only when WarpConstants.DEBUG is true)
487 +WarpUtils.verbose("message"); // Verbose log
488 +WarpUtils.warn("message", exception); // Warning log
489 +```
490 +
491 +### 10.5 General Rules
492 +- **Pure Java** — no Kotlin in the SDK module
493 +- **No data binding** — traditional `findViewById()` approach
494 +- **No dependency injection** — manual singleton/static patterns
495 +- **Callbacks over coroutines** — all async uses `CallbackReceiver<T>`
496 +- **Author attribution**: `Created by Panagiotis Triantafyllou on <date>.`
497 +
498 +---
499 +
500 +## 11. Critical Rules for AI Agents
501 +
502 +### ⚠️ DO NOT:
503 +1. **Do NOT use Gson for model serialization** — all models use `org.json.JSONObject`/`JSONArray`. The `converter-gson` dependency is only for Retrofit response parsing.
504 +2. **Do NOT modify the embedded Volley fork** (`io/volley/`) — it contains customized request handling specific to the SDK's microapp architecture.
505 +3. **Do NOT store tokens in plain text** — always use `CryptoUtils.encrypt()` when writing to the `client` or `auth` DB tables, and `CryptoUtils.decrypt()` when reading.
506 +4. **Do NOT change Activities to AppCompatActivity** unless explicitly required — the SDK uses `android.app.Activity` base class.
507 +5. **Do NOT introduce Kotlin** into the SDK module — it is a pure Java codebase.
508 +6. **Do NOT hard-code server URLs, API keys, or UUIDs** — these come from `warply.properties` via `WarplyProperty`.
509 +7. **Do NOT remove or modify the BSD license headers** from source files.
510 +8. **Do NOT expose sensitive data** (tokens, keys, credentials) in logs — all logging is gated behind `WarpConstants.DEBUG`.
511 +
512 +### ✅ DO:
513 +1. **Follow the Parcelable + Serializable pattern** for all new data models.
514 +2. **Use `optNullableString()` helper** for nullable String JSON fields in models.
515 +3. **Use `CallbackReceiver<T>` pattern** for all new async operations.
516 +4. **Use Guava `ListenableFuture`** with `Futures.allAsList()` for parallel API calls in `WarplyManager`.
517 +5. **Post results to main thread** via `new Handler(Looper.getMainLooper()).post(...)` in Futures callbacks.
518 +6. **Implement token refresh retry** (max 3 attempts) for authenticated endpoints.
519 +7. **Use `WarpUtils.renderCustomFont()`** for applying custom fonts to TextViews.
520 +8. **Use `WarpUtils.applyEdgeToEdge()`** for edge-to-edge display in new Activities.
521 +9. **Cache API results** in `WarplyManagerHelper` static fields for UI consumption.
522 +10. **Update `WarpConstants.SDK_VERSION`** and `build.gradle` `PUBLISH_VERSION` when making version changes.
523 +
524 +---
525 +
526 +## 12. Common Tasks Quick Reference
527 +
528 +### Adding a new API endpoint:
529 +1. Add Retrofit method to `ApiService.java`
530 +2. Add static method in `WarplyManager.java` with `ListenableFuture` pattern
531 +3. Create/update model in `io/models/`
532 +4. Add cache field in `WarplyManagerHelper.java` if needed
533 +5. Call from Activity/Fragment with `CallbackReceiver<T>`
534 +
535 +### Adding a new Activity:
536 +1. Create in `ly.warp.sdk.activities`
537 +2. Extend `android.app.Activity`
538 +3. Register in `warply_android_sdk/src/main/AndroidManifest.xml` with `android:exported="false"` and `android:theme="@style/SDKAppTheme"`
539 +4. Use `WarpUtils.applyEdgeToEdge()` in `onCreate()`
540 +5. Follow `mPrefixed` naming for view fields
541 +
542 +### Adding a new data model:
543 +1. Create in `ly.warp.sdk.io.models`
544 +2. Implement `Parcelable, Serializable`
545 +3. Define JSON key constants as `private static final String`
546 +4. Add `optNullableString()` helper
547 +5. Implement constructors: default, `JSONObject`, `String`, `Parcel`
548 +6. Implement `toJSONObject()`, `toString()`, `toHumanReadableString()`
549 +7. Implement `writeToParcel()` and `CREATOR`
550 +8. Add getters/setters
551 +
552 +### Debugging:
553 +- Set `Debug=true` in `warply.properties` to enable `WarpUtils.log()` output
554 +- Enable OkHttp logging interceptor in `ApiClient.getClient()` (uncomment the interceptor lines)
555 +- Check `WARP_DEBUG` tag in Logcat
556 +
557 +---
558 +
559 +## 13. Manifest Permissions
560 +
561 +The SDK requests these permissions:
562 +```xml
563 +<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
564 +<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
565 +<uses-permission android:name="android.permission.READ_PHONE_STATE" />
566 +<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
567 +<uses-permission android:name="android.permission.BLUETOOTH" />
568 +<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
569 +<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
570 +<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
571 +<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
572 +```
573 +
574 +Runtime permissions are handled via the embedded **Dexter** library (`ly.warp.sdk.dexter`).
575 +
576 +---
577 +
578 +## 14. Version Management
579 +
580 +- **SDK Version constant**: `WarpConstants.SDK_VERSION` (in `WarpConstants.java`)
581 +- **Publish version**: `PUBLISH_VERSION` (in `warply_android_sdk/build.gradle`)
582 +- **Group ID**: `ly.warp`
583 +- **Artifact ID**: `warply-android-sdk`
584 +- Both must be updated in sync when releasing new versions.
...@@ -5,7 +5,7 @@ android.buildFeatures.buildConfig = true ...@@ -5,7 +5,7 @@ android.buildFeatures.buildConfig = true
5 5
6 ext { 6 ext {
7 PUBLISH_GROUP_ID = 'ly.warp' 7 PUBLISH_GROUP_ID = 'ly.warp'
8 - PUBLISH_VERSION = '4.5.5.4deh3' 8 + PUBLISH_VERSION = '4.5.5.6deh4'
9 PUBLISH_ARTIFACT_ID = 'warply-android-sdk' 9 PUBLISH_ARTIFACT_ID = 'warply-android-sdk'
10 } 10 }
11 11
......
...@@ -45,6 +45,12 @@ ...@@ -45,6 +45,12 @@
45 android:theme="@style/SDKAppTheme" /> 45 android:theme="@style/SDKAppTheme" />
46 46
47 <activity 47 <activity
48 + android:name=".activities.SingleCouponsetActivity"
49 + android:exported="false"
50 + android:screenOrientation="portrait"
51 + android:theme="@style/SDKAppTheme" />
52 +
53 + <activity
48 android:name=".activities.ProfileActivity" 54 android:name=".activities.ProfileActivity"
49 android:exported="false" 55 android:exported="false"
50 android:screenOrientation="portrait" 56 android:screenOrientation="portrait"
......
...@@ -1343,8 +1343,6 @@ public enum Warply { ...@@ -1343,8 +1343,6 @@ public enum Warply {
1343 else { 1343 else {
1344 if (warplyPath.equals("handle_image")) 1344 if (warplyPath.equals("handle_image"))
1345 sb = new StringBuilder(WarplyProperty.getBaseUrl(mContext) + WarpConstants.BASE_URL_API); 1345 sb = new StringBuilder(WarplyProperty.getBaseUrl(mContext) + WarpConstants.BASE_URL_API);
1346 - else if (warplyPath.equals("verify"))
1347 - sb = new StringBuilder(WarplyProperty.getBaseUrl(mContext) + WarplyProperty.getVerifyUrl(mContext));
1348 else if (warplyPath.equals("cosuser")) 1346 else if (warplyPath.equals("cosuser"))
1349 sb = new StringBuilder(WarplyProperty.getBaseUrl(mContext) + "/partners/oauth/" + WarplyProperty.getAppUuid(mContext) + "/token"); 1347 sb = new StringBuilder(WarplyProperty.getBaseUrl(mContext) + "/partners/oauth/" + WarplyProperty.getAppUuid(mContext) + "/token");
1350 else 1348 else
......
...@@ -3,6 +3,7 @@ package ly.warp.sdk.activities; ...@@ -3,6 +3,7 @@ package ly.warp.sdk.activities;
3 import android.app.Activity; 3 import android.app.Activity;
4 import android.content.Intent; 4 import android.content.Intent;
5 import android.os.Bundle; 5 import android.os.Bundle;
6 +import android.os.Parcelable;
6 import android.util.TypedValue; 7 import android.util.TypedValue;
7 import android.view.LayoutInflater; 8 import android.view.LayoutInflater;
8 import android.view.View; 9 import android.view.View;
...@@ -93,9 +94,9 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -93,9 +94,9 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
93 94
94 @Override 95 @Override
95 public void onCouponsetClick(Couponset couponset, int position) { 96 public void onCouponsetClick(Couponset couponset, int position) {
96 -// Intent myIntent = new Intent(HomeActivity.this, SingleCouponActivity.class); 97 + Intent myIntent = new Intent(HomeActivity.this, SingleCouponsetActivity.class);
97 -// myIntent.putExtra(SingleCouponActivity.EXTRA_OFFER_ITEM, couponset); 98 + myIntent.putExtra(SingleCouponsetActivity.EXTRA_OFFER_ITEM, (Parcelable) couponset);
98 -// startActivity(myIntent); 99 + startActivity(myIntent);
99 } 100 }
100 101
101 // =========================================================== 102 // ===========================================================
...@@ -114,6 +115,69 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -114,6 +115,69 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
114 mIvProfile.setOnClickListener(this); 115 mIvProfile.setOnClickListener(this);
115 } 116 }
116 117
118 + private void setupBannerCarousel() {
119 + mBannerViewPager = findViewById(R.id.banner_viewpager);
120 + mDotsContainer = findViewById(R.id.dots_container);
121 +
122 + mBannerAdapter = new BannerAdapter(this, WarplyManagerHelper.getBannerList());
123 + mBannerAdapter.setOnBannerCampaignClickListener(campaign -> {
124 + startActivity(WarpViewActivity.createIntentFromURL(this, WarplyManagerHelper.constructCampaignUrl(campaign)));
125 + });
126 + mBannerAdapter.setOnBannerContentClickListener(article -> {
127 + if (article != null && article.getExtraFields() != null) {
128 + String couponsetUuid = article.getExtraFields().optString("url_link", null);
129 + if (couponsetUuid != null && !couponsetUuid.isEmpty()) {
130 + Couponset matchedCouponset = findCouponsetByUuid(couponsetUuid);
131 + if (matchedCouponset != null) {
132 + Intent myIntent = new Intent(HomeActivity.this, SingleCouponsetActivity.class);
133 + myIntent.putExtra(SingleCouponsetActivity.EXTRA_OFFER_ITEM, (Parcelable) matchedCouponset);
134 + startActivity(myIntent);
135 + }
136 + }
137 + }
138 + });
139 +
140 + // Set the number of pages to preload for adjacent items
141 + mBannerViewPager.setOffscreenPageLimit(5);
142 + mBannerViewPager.setAdapter(mBannerAdapter);
143 +
144 + setupPaginationDots(WarplyManagerHelper.getBannerList().size());
145 +
146 + mBannerViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
147 + @Override
148 + public void onPageSelected(int position) {
149 + updateDots(position);
150 + }
151 + });
152 + }
153 +
154 + private void setupPaginationDots(int count) {
155 + mDots = new ArrayList<>();
156 + mDotsContainer.removeAllViews();
157 +
158 + for (int i = 0; i < count; i++) {
159 + ImageView dot = new ImageView(this);
160 +
161 + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
162 + LinearLayout.LayoutParams.WRAP_CONTENT,
163 + LinearLayout.LayoutParams.WRAP_CONTENT
164 + );
165 + params.setMargins(8, 0, 8, 0);
166 + dot.setLayoutParams(params);
167 +
168 + dot.setImageResource(i == 0 ? R.drawable.dot_active : R.drawable.dot_inactive);
169 +
170 + mDotsContainer.addView(dot);
171 + mDots.add(dot);
172 + }
173 + }
174 +
175 + private void updateDots(int position) {
176 + for (int i = 0; i < mDots.size(); i++) {
177 + mDots.get(i).setImageResource(i == position ? R.drawable.dot_active : R.drawable.dot_inactive);
178 + }
179 + }
180 +
117 private void setupCouponsetSections(LinkedHashMap<String, ArrayList<Couponset>> categorizedMap) { 181 private void setupCouponsetSections(LinkedHashMap<String, ArrayList<Couponset>> categorizedMap) {
118 mSectionsContainer.removeAllViews(); 182 mSectionsContainer.removeAllViews();
119 183
...@@ -163,67 +227,20 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -163,67 +227,20 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
163 mSectionsLoading.setVisibility(View.GONE); 227 mSectionsLoading.setVisibility(View.GONE);
164 } 228 }
165 229
166 - // =========================================================== 230 + private Couponset findCouponsetByUuid(String uuid) {
167 - // Banner Methods 231 + LinkedHashMap<String, ArrayList<Couponset>> categorizedMap = WarplyManagerHelper.getCouponsetCategorizedMap();
168 - // =========================================================== 232 + if (categorizedMap != null) {
169 - 233 + for (ArrayList<Couponset> couponsets : categorizedMap.values()) {
170 - private void setupBannerCarousel() { 234 + for (Couponset couponset : couponsets) {
171 - mBannerViewPager = findViewById(R.id.banner_viewpager); 235 + if (uuid.equals(couponset.getUuid())) {
172 - mDotsContainer = findViewById(R.id.dots_container); 236 + return couponset;
173 -
174 - mBannerAdapter = new BannerAdapter(this, WarplyManagerHelper.getBannerList());
175 - mBannerAdapter.setOnBannerCampaignClickListener(campaign -> {
176 - startActivity(WarpViewActivity.createIntentFromURL(this, WarplyManagerHelper.constructCampaignUrl(campaign)));
177 - });
178 - mBannerAdapter.setOnBannerContentClickListener(article -> {
179 - //TODO: click article
180 - });
181 -
182 - // Set the number of pages to preload for adjacent items
183 - mBannerViewPager.setOffscreenPageLimit(5);
184 - mBannerViewPager.setAdapter(mBannerAdapter);
185 -
186 - setupPaginationDots(WarplyManagerHelper.getBannerList().size());
187 -
188 - mBannerViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
189 - @Override
190 - public void onPageSelected(int position) {
191 - updateDots(position);
192 - }
193 - });
194 } 237 }
195 -
196 - private void setupPaginationDots(int count) {
197 - mDots = new ArrayList<>();
198 - mDotsContainer.removeAllViews();
199 -
200 - for (int i = 0; i < count; i++) {
201 - ImageView dot = new ImageView(this);
202 -
203 - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
204 - LinearLayout.LayoutParams.WRAP_CONTENT,
205 - LinearLayout.LayoutParams.WRAP_CONTENT
206 - );
207 - params.setMargins(8, 0, 8, 0);
208 - dot.setLayoutParams(params);
209 -
210 - dot.setImageResource(i == 0 ? R.drawable.dot_active : R.drawable.dot_inactive);
211 -
212 - mDotsContainer.addView(dot);
213 - mDots.add(dot);
214 } 238 }
215 } 239 }
216 -
217 - private void updateDots(int position) {
218 - for (int i = 0; i < mDots.size(); i++) {
219 - mDots.get(i).setImageResource(i == position ? R.drawable.dot_active : R.drawable.dot_inactive);
220 } 240 }
241 + return null;
221 } 242 }
222 243
223 - // ===========================================================
224 - // Callbacks
225 - // ===========================================================
226 -
227 private final CallbackReceiver<ArrayList<BannerItem>> mCampaignsCallback = new CallbackReceiver<ArrayList<BannerItem>>() { 244 private final CallbackReceiver<ArrayList<BannerItem>> mCampaignsCallback = new CallbackReceiver<ArrayList<BannerItem>>() {
228 @Override 245 @Override
229 public void onSuccess(ArrayList<BannerItem> result) { 246 public void onSuccess(ArrayList<BannerItem> result) {
......
...@@ -3,28 +3,28 @@ package ly.warp.sdk.activities; ...@@ -3,28 +3,28 @@ package ly.warp.sdk.activities;
3 import android.app.Activity; 3 import android.app.Activity;
4 import android.content.Intent; 4 import android.content.Intent;
5 import android.graphics.Color; 5 import android.graphics.Color;
6 -import android.os.Build;
7 import android.os.Bundle; 6 import android.os.Bundle;
7 +import android.os.Parcelable;
8 import android.util.TypedValue; 8 import android.util.TypedValue;
9 import android.view.View; 9 import android.view.View;
10 -import android.view.WindowInsetsController;
11 import android.widget.ImageView; 10 import android.widget.ImageView;
11 +import android.widget.RelativeLayout;
12 import android.widget.TextView; 12 import android.widget.TextView;
13 13
14 import androidx.recyclerview.widget.LinearLayoutManager; 14 import androidx.recyclerview.widget.LinearLayoutManager;
15 import androidx.recyclerview.widget.RecyclerView; 15 import androidx.recyclerview.widget.RecyclerView;
16 16
17 -import java.util.List; 17 +import java.util.ArrayList;
18 18
19 import ly.warp.sdk.R; 19 import ly.warp.sdk.R;
20 import ly.warp.sdk.io.adapters.CouponAdapter; 20 import ly.warp.sdk.io.adapters.CouponAdapter;
21 import ly.warp.sdk.io.adapters.OfferAdapter; 21 import ly.warp.sdk.io.adapters.OfferAdapter;
22 -import ly.warp.sdk.io.models.CouponItem; 22 +import ly.warp.sdk.io.callbacks.CallbackReceiver;
23 -import ly.warp.sdk.io.models.DummyDataProvider; 23 +import ly.warp.sdk.io.models.Coupon;
24 -import ly.warp.sdk.io.models.OfferCategory;
25 import ly.warp.sdk.io.models.OfferItem; 24 import ly.warp.sdk.io.models.OfferItem;
26 import ly.warp.sdk.utils.WarpUtils; 25 import ly.warp.sdk.utils.WarpUtils;
27 -import ly.warp.sdk.views.HorizontalSpaceItemDecoration; 26 +import ly.warp.sdk.utils.WarplyManagerHelper;
27 +import ly.warp.sdk.utils.managers.WarplyManager;
28 import ly.warp.sdk.views.VerticalSpaceItemDecoration; 28 import ly.warp.sdk.views.VerticalSpaceItemDecoration;
29 29
30 public class ProfileActivity extends Activity implements View.OnClickListener, OfferAdapter.OnOfferClickListener, CouponAdapter.OnCouponClickListener { 30 public class ProfileActivity extends Activity implements View.OnClickListener, OfferAdapter.OnOfferClickListener, CouponAdapter.OnCouponClickListener {
...@@ -37,6 +37,7 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O ...@@ -37,6 +37,7 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O
37 // Fields 37 // Fields
38 // =========================================================== 38 // ===========================================================
39 39
40 + private RelativeLayout mPbLoading;
40 private TextView mTvHeaderTitle; 41 private TextView mTvHeaderTitle;
41 private ImageView mIvBack; 42 private ImageView mIvBack;
42 43
...@@ -51,7 +52,6 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O ...@@ -51,7 +52,6 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O
51 private RecyclerView mRvCoupons; 52 private RecyclerView mRvCoupons;
52 private CouponAdapter mCouponsAdapter; 53 private CouponAdapter mCouponsAdapter;
53 private TextView mBtnFilterActive, mBtnFilterFavorites, mBtnFilterRedeemed; 54 private TextView mBtnFilterActive, mBtnFilterFavorites, mBtnFilterRedeemed;
54 - private List<CouponItem> mCouponItems;
55 55
56 // =========================================================== 56 // ===========================================================
57 // Methods for/from SuperClass/Interfaces 57 // Methods for/from SuperClass/Interfaces
...@@ -68,11 +68,8 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O ...@@ -68,11 +68,8 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O
68 findViewById(R.id.header_layout), 68 findViewById(R.id.header_layout),
69 null); 69 null);
70 70
71 - // Setup profile suggestions section 71 + mPbLoading.setVisibility(View.VISIBLE);
72 - setupProfileSuggestionsSection(); 72 + WarplyManager.getCoupons(mCouponsCallback);
73 -
74 - // Setup my coupons section
75 - setupMyCouponsSection();
76 } 73 }
77 74
78 @Override 75 @Override
...@@ -86,19 +83,19 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O ...@@ -86,19 +83,19 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O
86 if (id == R.id.iv_back) { 83 if (id == R.id.iv_back) {
87 onBackPressed(); 84 onBackPressed();
88 } else if (id == R.id.btn_filter_active) { 85 } else if (id == R.id.btn_filter_active) {
89 - filterCoupons(CouponItem.STATUS_ACTIVE); 86 +// filterCoupons(CouponItem.STATUS_ACTIVE);
90 } else if (id == R.id.btn_filter_favorites) { 87 } else if (id == R.id.btn_filter_favorites) {
91 - filterCoupons(CouponItem.STATUS_FAVORITE); 88 +// filterCoupons(CouponItem.STATUS_FAVORITE);
92 } else if (id == R.id.btn_filter_redeemed) { 89 } else if (id == R.id.btn_filter_redeemed) {
93 - filterCoupons(CouponItem.STATUS_REDEEMED); 90 +// filterCoupons(CouponItem.STATUS_REDEEMED);
94 } 91 }
95 } 92 }
96 93
97 @Override 94 @Override
98 public void onOfferClick(OfferItem offerItem, int position) { 95 public void onOfferClick(OfferItem offerItem, int position) {
99 - Intent myIntent = new Intent(ProfileActivity.this, SingleCouponActivity.class); 96 +// Intent myIntent = new Intent(ProfileActivity.this, SingleCouponActivity.class);
100 - myIntent.putExtra(SingleCouponActivity.EXTRA_OFFER_ITEM, offerItem); 97 +// myIntent.putExtra(SingleCouponActivity.EXTRA_OFFER_ITEM, offerItem);
101 - startActivity(myIntent); 98 +// startActivity(myIntent);
102 } 99 }
103 100
104 @Override 101 @Override
...@@ -107,14 +104,14 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O ...@@ -107,14 +104,14 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O
107 } 104 }
108 105
109 @Override 106 @Override
110 - public void onCouponClick(CouponItem couponItem, int position) { 107 + public void onCouponClick(Coupon couponItem, int position) {
111 Intent myIntent = new Intent(ProfileActivity.this, SingleCouponActivity.class); 108 Intent myIntent = new Intent(ProfileActivity.this, SingleCouponActivity.class);
112 - myIntent.putExtra(SingleCouponActivity.EXTRA_OFFER_ITEM, couponItem); 109 + myIntent.putExtra(SingleCouponActivity.EXTRA_OFFER_ITEM, (Parcelable) couponItem);
113 startActivity(myIntent); 110 startActivity(myIntent);
114 } 111 }
115 112
116 @Override 113 @Override
117 - public void onFavoriteClick(CouponItem couponItem, int position) { 114 + public void onFavoriteClick(Coupon couponItem, int position) {
118 // Handle favorite click if needed 115 // Handle favorite click if needed
119 } 116 }
120 117
...@@ -126,6 +123,9 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O ...@@ -126,6 +123,9 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O
126 mIvBack = findViewById(R.id.iv_back); 123 mIvBack = findViewById(R.id.iv_back);
127 mIvBack.setOnClickListener(this); 124 mIvBack.setOnClickListener(this);
128 125
126 + mPbLoading = findViewById(R.id.pb_loading);
127 + mPbLoading.setOnTouchListener((v, event) -> true);
128 +
129 mTvHeaderTitle = findViewById(R.id.tv_header_title); 129 mTvHeaderTitle = findViewById(R.id.tv_header_title);
130 130
131 // Initialize Profile Suggestions section 131 // Initialize Profile Suggestions section
...@@ -151,63 +151,22 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O ...@@ -151,63 +151,22 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O
151 mBtnFilterActive, mBtnFilterFavorites, mBtnFilterRedeemed); 151 mBtnFilterActive, mBtnFilterFavorites, mBtnFilterRedeemed);
152 } 152 }
153 153
154 - /**
155 - * Set up the Profile Suggestions section with dummy data
156 - */
157 - private void setupProfileSuggestionsSection() {
158 - // Get Profile Suggestions data
159 - OfferCategory profileSuggestionsCategory = DummyDataProvider.getProfileSuggestions();
160 -
161 - // Set category title with item count
162 - String categoryTitle = profileSuggestionsCategory.getName() + " (" + profileSuggestionsCategory.getItems().size() + ")";
163 - mTvCategoryProfileSuggestions.setText(categoryTitle);
164 -
165 - // Set up RecyclerView
166 - LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
167 - mRvProfileSuggestions.setLayoutManager(layoutManager);
168 -
169 - // Add spacing between items
170 - int spacingInPixels = (int) TypedValue.applyDimension(
171 - TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics());
172 - mRvProfileSuggestions.addItemDecoration(new HorizontalSpaceItemDecoration(spacingInPixels));
173 -
174 - // Create and set adapter
175 - mProfileSuggestionsAdapter = new OfferAdapter(this, profileSuggestionsCategory.getItems());
176 - mProfileSuggestionsAdapter.setOnOfferClickListener(this);
177 - mRvProfileSuggestions.setAdapter(mProfileSuggestionsAdapter);
178 - }
179 -
180 - /**
181 - * Set up the My Coupons section with dummy data and filters
182 - */
183 private void setupMyCouponsSection() { 154 private void setupMyCouponsSection() {
184 - // Get coupons data
185 - mCouponItems = DummyDataProvider.getCoupons();
186 -
187 - // Set up RecyclerView
188 LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); 155 LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
189 mRvCoupons.setLayoutManager(layoutManager); 156 mRvCoupons.setLayoutManager(layoutManager);
190 mRvCoupons.setHasFixedSize(true); 157 mRvCoupons.setHasFixedSize(true);
191 158
192 - // Create and set adapter 159 + mCouponsAdapter = new CouponAdapter(this, WarplyManagerHelper.getCoupons());
193 - mCouponsAdapter = new CouponAdapter(this, mCouponItems);
194 mCouponsAdapter.setOnCouponClickListener(this); 160 mCouponsAdapter.setOnCouponClickListener(this);
195 mRvCoupons.setAdapter(mCouponsAdapter); 161 mRvCoupons.setAdapter(mCouponsAdapter);
196 162
197 - // Add 16dp spacing between coupon items
198 int verticalSpacingInPixels = (int) TypedValue.applyDimension( 163 int verticalSpacingInPixels = (int) TypedValue.applyDimension(
199 TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()); 164 TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics());
200 mRvCoupons.addItemDecoration(new VerticalSpaceItemDecoration(verticalSpacingInPixels)); 165 mRvCoupons.addItemDecoration(new VerticalSpaceItemDecoration(verticalSpacingInPixels));
201 166
202 - // Filter by active coupons by default 167 +// filterCoupons(CouponItem.STATUS_ACTIVE);
203 - filterCoupons(CouponItem.STATUS_ACTIVE);
204 } 168 }
205 169
206 - /**
207 - * Filter coupons by status
208 - *
209 - * @param status The status to filter by
210 - */
211 private void filterCoupons(String status) { 170 private void filterCoupons(String status) {
212 // Reset all filter button styles 171 // Reset all filter button styles
213 mBtnFilterActive.setBackgroundResource(R.drawable.shape_transparent_black_border); 172 mBtnFilterActive.setBackgroundResource(R.drawable.shape_transparent_black_border);
...@@ -220,22 +179,35 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O ...@@ -220,22 +179,35 @@ public class ProfileActivity extends Activity implements View.OnClickListener, O
220 mBtnFilterRedeemed.setTextColor(getResources().getColor(R.color.custom_black2)); 179 mBtnFilterRedeemed.setTextColor(getResources().getColor(R.color.custom_black2));
221 180
222 // Set selected filter button style 181 // Set selected filter button style
223 - if (CouponItem.STATUS_ACTIVE.equals(status)) { 182 +// if (CouponItem.STATUS_ACTIVE.equals(status)) {
224 - mBtnFilterActive.setBackgroundResource(R.drawable.shape_rectangle_rounded_black); 183 +// mBtnFilterActive.setBackgroundResource(R.drawable.shape_rectangle_rounded_black);
225 - mBtnFilterActive.setTextColor(Color.WHITE); 184 +// mBtnFilterActive.setTextColor(Color.WHITE);
226 - } else if (CouponItem.STATUS_FAVORITE.equals(status)) { 185 +// } else if (CouponItem.STATUS_FAVORITE.equals(status)) {
227 - mBtnFilterFavorites.setBackgroundResource(R.drawable.shape_rectangle_rounded_black); 186 +// mBtnFilterFavorites.setBackgroundResource(R.drawable.shape_rectangle_rounded_black);
228 - mBtnFilterFavorites.setTextColor(Color.WHITE); 187 +// mBtnFilterFavorites.setTextColor(Color.WHITE);
229 - } else if (CouponItem.STATUS_REDEEMED.equals(status)) { 188 +// } else if (CouponItem.STATUS_REDEEMED.equals(status)) {
230 - mBtnFilterRedeemed.setBackgroundResource(R.drawable.shape_rectangle_rounded_black); 189 +// mBtnFilterRedeemed.setBackgroundResource(R.drawable.shape_rectangle_rounded_black);
231 - mBtnFilterRedeemed.setTextColor(Color.WHITE); 190 +// mBtnFilterRedeemed.setTextColor(Color.WHITE);
232 - } 191 +// }
233 192
234 // Apply filter to adapter 193 // Apply filter to adapter
235 - mCouponsAdapter.filterByStatus(status); 194 +// mCouponsAdapter.filterByStatus(status);
236 } 195 }
237 196
238 // =========================================================== 197 // ===========================================================
239 // Inner and Anonymous Classes 198 // Inner and Anonymous Classes
240 // =========================================================== 199 // ===========================================================
200 +
201 + private final CallbackReceiver<ArrayList<Coupon>> mCouponsCallback = new CallbackReceiver<ArrayList<Coupon>>() {
202 + @Override
203 + public void onSuccess(ArrayList<Coupon> result) {
204 + mPbLoading.setVisibility(View.GONE);
205 + setupMyCouponsSection();
206 + }
207 +
208 + @Override
209 + public void onFailure(int errorCode) {
210 + mPbLoading.setVisibility(View.GONE);
211 + }
212 + };
241 } 213 }
......
...@@ -5,32 +5,40 @@ import android.content.ClipData; ...@@ -5,32 +5,40 @@ import android.content.ClipData;
5 import android.content.ClipboardManager; 5 import android.content.ClipboardManager;
6 import android.content.Context; 6 import android.content.Context;
7 import android.content.Intent; 7 import android.content.Intent;
8 -import android.graphics.Color;
9 -import android.os.Build;
10 import android.os.Bundle; 8 import android.os.Bundle;
9 +import android.text.TextUtils;
11 import android.view.View; 10 import android.view.View;
12 -import android.view.WindowInsetsController;
13 import android.widget.ImageView; 11 import android.widget.ImageView;
14 import android.widget.LinearLayout; 12 import android.widget.LinearLayout;
15 import android.widget.TextView; 13 import android.widget.TextView;
16 import android.widget.Toast; 14 import android.widget.Toast;
17 15
16 +import androidx.core.text.HtmlCompat;
17 +
18 +import com.bumptech.glide.Glide;
19 +import com.bumptech.glide.load.engine.DiskCacheStrategy;
20 +
21 +import java.text.ParseException;
22 +import java.text.SimpleDateFormat;
23 +import java.util.Date;
24 +import java.util.Locale;
25 +
18 import ly.warp.sdk.R; 26 import ly.warp.sdk.R;
19 -import ly.warp.sdk.io.models.OfferItem; 27 +import ly.warp.sdk.io.models.Coupon;
20 import ly.warp.sdk.utils.WarpUtils; 28 import ly.warp.sdk.utils.WarpUtils;
21 29
22 public class SingleCouponActivity extends Activity implements View.OnClickListener { 30 public class SingleCouponActivity extends Activity implements View.OnClickListener {
23 // =========================================================== 31 // ===========================================================
24 // Constants 32 // Constants
25 // =========================================================== 33 // ===========================================================
26 - public static final String EXTRA_OFFER_ITEM = "offer_item"; 34 + public static final String EXTRA_OFFER_ITEM = "coupon_item";
27 35
28 // =========================================================== 36 // ===========================================================
29 // Fields 37 // Fields
30 // =========================================================== 38 // ===========================================================
31 39
32 private ImageView mIvBack; 40 private ImageView mIvBack;
33 - private OfferItem mOfferItem; 41 + private Coupon mOfferItem;
34 private TextView mTvSmallDescription; 42 private TextView mTvSmallDescription;
35 private TextView mTvFullDescription; 43 private TextView mTvFullDescription;
36 private TextView mTvEndDate; 44 private TextView mTvEndDate;
...@@ -80,10 +88,9 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen ...@@ -80,10 +88,9 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen
80 super.onCreate(savedInstanceState); 88 super.onCreate(savedInstanceState);
81 setContentView(R.layout.activity_single_coupon); 89 setContentView(R.layout.activity_single_coupon);
82 90
83 - // Get offer item from intent
84 Intent intent = getIntent(); 91 Intent intent = getIntent();
85 if (intent != null && intent.hasExtra(EXTRA_OFFER_ITEM)) { 92 if (intent != null && intent.hasExtra(EXTRA_OFFER_ITEM)) {
86 - mOfferItem = (OfferItem) intent.getSerializableExtra(EXTRA_OFFER_ITEM); 93 + mOfferItem = (Coupon) intent.getSerializableExtra(EXTRA_OFFER_ITEM);
87 } 94 }
88 95
89 initViews(); 96 initViews();
...@@ -151,34 +158,35 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen ...@@ -151,34 +158,35 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen
151 mTvEndDate, mTvFullDescription, mTvCouponCodeTitle, mTvQrCodeTitle, mTvTermsText, 158 mTvEndDate, mTvFullDescription, mTvCouponCodeTitle, mTvQrCodeTitle, mTvTermsText,
152 mTVMoreTitle, mTvMoreButton); 159 mTVMoreTitle, mTvMoreButton);
153 160
154 - // Populate views with offer data
155 if (mOfferItem != null) { 161 if (mOfferItem != null) {
156 - mTvValue.setText(mOfferItem.getValue()); 162 + if (mOfferItem.getExpiration() != null && !mOfferItem.getExpiration().isEmpty()) {
157 -// mTvSmallDescription.setText(mOfferItem.getDescription()); 163 + String formattedDate = formatValidityDate(mOfferItem.getExpiration());
158 -
159 - // Store the full description text
160 - mFullDescriptionText = mOfferItem.getFullDescription();
161 - mTvFullDescription.setText(mFullDescriptionText);
162 -
163 - // Format and set the end date
164 - String endDate = mOfferItem.getEndDate();
165 - if (endDate != null && !endDate.isEmpty()) {
166 - // Convert from DD/MM/YYYY to DD-MM-YYYY
167 - String formattedDate = endDate.replace("/", "-");
168 mTvEndDate.setText(getString(R.string.demo_valid_until, formattedDate)); 164 mTvEndDate.setText(getString(R.string.demo_valid_until, formattedDate));
165 + mTvEndDate.setVisibility(View.VISIBLE);
166 + } else {
167 + mTvEndDate.setVisibility(View.GONE);
169 } 168 }
170 169
171 - // Load image (in a real app, you would use an image loading library) 170 + if (mOfferItem.getCouponsetDetails() != null && mOfferItem.getCouponsetDetails().getImg_preview() != null && !TextUtils.isEmpty(mOfferItem.getCouponsetDetails().getImg_preview())) {
172 - // For demo purposes, we'll use a placeholder 171 + Glide.with(this)
173 - int imageResId = getResources().getIdentifier( 172 +// .setDefaultRequestOptions(
174 - mOfferItem.getImageUrl().replace(".png", ""), 173 +// RequestOptions
175 - "drawable", 174 +// .placeholderOf(R.drawable.demo_logo)
176 - getPackageName()); 175 +// .error(R.drawable.demo_logo))
177 - 176 + .load(mOfferItem.getCouponsetDetails().getImg_preview())
178 - if (imageResId != 0) { 177 + .diskCacheStrategy(DiskCacheStrategy.DATA)
179 - mIvImage.setImageResource(imageResId); 178 + .into(mIvImage);
180 } 179 }
181 180
181 + if (mOfferItem.getCouponsetDetails() != null && mOfferItem.getCouponsetDetails().getName() != null && !TextUtils.isEmpty(mOfferItem.getCouponsetDetails().getName()))
182 + mTvValue.setText(mOfferItem.getCouponsetDetails().getName());
183 + if (mOfferItem.getCouponsetDetails() != null && mOfferItem.getCouponsetDetails().getShort_description() != null && !TextUtils.isEmpty(mOfferItem.getCouponsetDetails().getShort_description()))
184 + mTvSmallDescription.setText(mOfferItem.getCouponsetDetails().getShort_description());
185 + if (mOfferItem.getCouponsetDetails() != null && mOfferItem.getCouponsetDetails().getDescription() != null && !TextUtils.isEmpty(mOfferItem.getCouponsetDetails().getDescription()))
186 + mTvFullDescription.setText(HtmlCompat.fromHtml(mOfferItem.getCouponsetDetails().getDescription(), HtmlCompat.FROM_HTML_MODE_COMPACT));
187 + if (mOfferItem.getCouponsetDetails() != null && mOfferItem.getCouponsetDetails().getTerms() != null && !TextUtils.isEmpty(mOfferItem.getCouponsetDetails().getTerms()))
188 + mTvTermsText.setText(HtmlCompat.fromHtml(mOfferItem.getCouponsetDetails().getTerms(), HtmlCompat.FROM_HTML_MODE_COMPACT));
189 +
182 // Setup the More button 190 // Setup the More button
183 setupMoreButton(); 191 setupMoreButton();
184 192
...@@ -193,18 +201,26 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen ...@@ -193,18 +201,26 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen
193 } 201 }
194 } 202 }
195 203
196 - /** 204 + private String formatValidityDate(String endDate) {
197 - * Sets up the coupon code expandable section 205 + try {
198 - */ 206 + SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
199 - private void setupCouponCodeSection() { 207 + Date date = inputFormat.parse(endDate);
200 - // Set coupon code - using a hardcoded value for demo purposes 208 + SimpleDateFormat outputFormat = new SimpleDateFormat("dd-MM", Locale.getDefault());
201 - // In a real app, this would come from the offer item 209 + return "έως " + outputFormat.format(date);
202 - String couponCode = "coupons_ab"; 210 + } catch (ParseException e) {
203 - if (mOfferItem != null && mOfferItem.getId() != null) { 211 + try {
204 - // Use offer ID as part of the coupon code for demo purposes 212 + SimpleDateFormat inputFormat2 = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
205 - couponCode = "coupon_" + mOfferItem.getId().toLowerCase(); 213 + Date date = inputFormat2.parse(endDate);
214 + SimpleDateFormat outputFormat = new SimpleDateFormat("dd-MM", Locale.getDefault());
215 + return "έως " + outputFormat.format(date);
216 + } catch (ParseException e2) {
217 + return endDate;
218 + }
219 + }
206 } 220 }
207 - mTvCouponCode.setText(couponCode); 221 +
222 + private void setupCouponCodeSection() {
223 + mTvCouponCode.setText(mOfferItem.getCoupon());
208 224
209 // Set click listener for the header to expand/collapse 225 // Set click listener for the header to expand/collapse
210 mCouponCodeHeader.setOnClickListener(new View.OnClickListener() { 226 mCouponCodeHeader.setOnClickListener(new View.OnClickListener() {
...@@ -231,9 +247,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen ...@@ -231,9 +247,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen
231 }); 247 });
232 } 248 }
233 249
234 - /**
235 - * Toggles between expanded and collapsed states for the coupon code section
236 - */
237 private void toggleCouponCodeExpansion() { 250 private void toggleCouponCodeExpansion() {
238 if (mIsCouponCodeExpanded) { 251 if (mIsCouponCodeExpanded) {
239 // Collapse the content 252 // Collapse the content
...@@ -248,9 +261,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen ...@@ -248,9 +261,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen
248 } 261 }
249 } 262 }
250 263
251 - /**
252 - * Copies the coupon code to the clipboard
253 - */
254 private void copyCouponCodeToClipboard() { 264 private void copyCouponCodeToClipboard() {
255 String couponCode = mTvCouponCode.getText().toString(); 265 String couponCode = mTvCouponCode.getText().toString();
256 266
...@@ -267,9 +277,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen ...@@ -267,9 +277,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen
267 Toast.makeText(this, R.string.demo_copy_success, Toast.LENGTH_SHORT).show(); 277 Toast.makeText(this, R.string.demo_copy_success, Toast.LENGTH_SHORT).show();
268 } 278 }
269 279
270 - /**
271 - * Sets up the "More" button for expanding/collapsing the description text
272 - */
273 private void setupMoreButton() { 280 private void setupMoreButton() {
274 // Wait for layout to be ready to check if text is truncated 281 // Wait for layout to be ready to check if text is truncated
275 mTvFullDescription.post(new Runnable() { 282 mTvFullDescription.post(new Runnable() {
...@@ -292,9 +299,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen ...@@ -292,9 +299,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen
292 }); 299 });
293 } 300 }
294 301
295 - /**
296 - * Toggles between expanded and collapsed states for the description text
297 - */
298 private void toggleDescriptionExpansion() { 302 private void toggleDescriptionExpansion() {
299 if (mIsDescriptionExpanded) { 303 if (mIsDescriptionExpanded) {
300 // Collapse the text 304 // Collapse the text
...@@ -309,9 +313,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen ...@@ -309,9 +313,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen
309 } 313 }
310 } 314 }
311 315
312 - /**
313 - * Sets up the QR code expandable section
314 - */
315 private void setupQrCodeSection() { 316 private void setupQrCodeSection() {
316 // Set click listener for the header to expand/collapse 317 // Set click listener for the header to expand/collapse
317 mQrCodeHeader.setOnClickListener(new View.OnClickListener() { 318 mQrCodeHeader.setOnClickListener(new View.OnClickListener() {
...@@ -330,9 +331,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen ...@@ -330,9 +331,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen
330 }); 331 });
331 } 332 }
332 333
333 - /**
334 - * Toggles between expanded and collapsed states for the QR code section
335 - */
336 private void toggleQrCodeExpansion() { 334 private void toggleQrCodeExpansion() {
337 if (mIsQrCodeExpanded) { 335 if (mIsQrCodeExpanded) {
338 // Collapse the content 336 // Collapse the content
...@@ -347,9 +345,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen ...@@ -347,9 +345,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen
347 } 345 }
348 } 346 }
349 347
350 - /**
351 - * Sets up the Terms of Use expandable section
352 - */
353 private void setupTermsSection() { 348 private void setupTermsSection() {
354 // Set click listener for the header to expand/collapse 349 // Set click listener for the header to expand/collapse
355 mTermsHeader.setOnClickListener(new View.OnClickListener() { 350 mTermsHeader.setOnClickListener(new View.OnClickListener() {
...@@ -368,9 +363,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen ...@@ -368,9 +363,6 @@ public class SingleCouponActivity extends Activity implements View.OnClickListen
368 }); 363 });
369 } 364 }
370 365
371 - /**
372 - * Toggles between expanded and collapsed states for the Terms of Use section
373 - */
374 private void toggleTermsExpansion() { 366 private void toggleTermsExpansion() {
375 if (mIsTermsExpanded) { 367 if (mIsTermsExpanded) {
376 // Collapse the content 368 // Collapse the content
......
1 +package ly.warp.sdk.activities;
2 +
3 +import android.app.Activity;
4 +import android.content.Intent;
5 +import android.os.Bundle;
6 +import android.text.TextUtils;
7 +import android.view.View;
8 +import android.widget.ImageView;
9 +import android.widget.LinearLayout;
10 +import android.widget.TextView;
11 +
12 +import androidx.core.text.HtmlCompat;
13 +
14 +import com.bumptech.glide.Glide;
15 +import com.bumptech.glide.load.engine.DiskCacheStrategy;
16 +
17 +import java.text.ParseException;
18 +import java.text.SimpleDateFormat;
19 +import java.util.Date;
20 +import java.util.Locale;
21 +
22 +import ly.warp.sdk.R;
23 +import ly.warp.sdk.io.models.Couponset;
24 +import ly.warp.sdk.utils.WarpUtils;
25 +
26 +public class SingleCouponsetActivity extends Activity implements View.OnClickListener {
27 + // ===========================================================
28 + // Constants
29 + // ===========================================================
30 + public static final String EXTRA_OFFER_ITEM = "offer_item";
31 +
32 + // ===========================================================
33 + // Fields
34 + // ===========================================================
35 +
36 + private ImageView mIvBack;
37 + private Couponset mOfferItem;
38 + private TextView mTvSmallDescription;
39 + private TextView mTvFullDescription;
40 + private TextView mTvEndDate;
41 + private TextView mTvValue;
42 + private TextView mTvMoreButton;
43 + private ImageView mIvImage;
44 +
45 + private boolean mIsDescriptionExpanded = false;
46 +
47 + // Terms of Use section
48 + private LinearLayout mTermsContainer;
49 + private LinearLayout mTermsHeader;
50 + private LinearLayout mTermsContent;
51 + private ImageView mIvTermsArrow;
52 + private TextView mTvTermsText;
53 + private boolean mIsTermsExpanded = false;
54 +
55 + private TextView mTvHeaderTitle, mTvTermsTitle, mTvShopsTitle, mTvWebsiteTitle, mTVMoreTitle;
56 +
57 +
58 + // ===========================================================
59 + // Methods for/from SuperClass/Interfaces
60 + // ===========================================================
61 +
62 + @Override
63 + public void onCreate(Bundle savedInstanceState) {
64 + super.onCreate(savedInstanceState);
65 + setContentView(R.layout.activity_single_couponset);
66 +
67 + Intent intent = getIntent();
68 + if (intent != null && intent.hasExtra(EXTRA_OFFER_ITEM)) {
69 + mOfferItem = (Couponset) intent.getSerializableExtra(EXTRA_OFFER_ITEM);
70 + }
71 +
72 + initViews();
73 +
74 + WarpUtils.applyEdgeToEdge(this,
75 + findViewById(R.id.header_layout),
76 + null);
77 + }
78 +
79 + @Override
80 + public void onResume() {
81 + super.onResume();
82 + }
83 +
84 + @Override
85 + public void onClick(View v) {
86 + if (v.getId() == R.id.iv_back) {
87 + onBackPressed();
88 + }
89 + }
90 +
91 + // ===========================================================
92 + // Methods
93 + // ===========================================================
94 +
95 + private void initViews() {
96 + mIvBack = findViewById(R.id.iv_back);
97 + mIvBack.setOnClickListener(this);
98 +
99 + // Initialize views
100 + mTvSmallDescription = findViewById(R.id.tv_coupon_small_description);
101 + mTvFullDescription = findViewById(R.id.tv_coupon_full_description);
102 + mTvEndDate = findViewById(R.id.tv_coupon_end_date);
103 + mTvValue = findViewById(R.id.tv_coupon_value);
104 + mIvImage = findViewById(R.id.iv_coupon_image);
105 + mTvMoreButton = findViewById(R.id.tv_more_button);
106 +
107 + // Initialize Terms of Use section
108 + mTermsContainer = findViewById(R.id.terms_container);
109 + mTermsHeader = findViewById(R.id.terms_header);
110 + mTermsContent = findViewById(R.id.terms_content);
111 + mIvTermsArrow = findViewById(R.id.iv_terms_arrow);
112 + mTvTermsText = findViewById(R.id.tv_terms_text);
113 +
114 + mTvHeaderTitle = findViewById(R.id.tv_header_title);
115 + mTvTermsTitle = findViewById(R.id.tv_terms_title);
116 + mTvShopsTitle = findViewById(R.id.tv_shops_title);
117 + mTvWebsiteTitle = findViewById(R.id.tv_website_title);
118 + mTVMoreTitle = findViewById(R.id.tv_more_title);
119 +
120 + WarpUtils.renderCustomFont(this, R.font.ping_lcg_bold, mTvHeaderTitle, mTvValue,
121 + mTvTermsTitle, mTvShopsTitle, mTvWebsiteTitle);
122 +
123 + WarpUtils.renderCustomFont(this, R.font.ping_lcg_regular, mTvSmallDescription,
124 + mTvEndDate, mTvFullDescription, mTvTermsText,
125 + mTVMoreTitle, mTvMoreButton);
126 +
127 + if (mOfferItem != null) {
128 + if (mOfferItem.getEndDate() != null && !mOfferItem.getEndDate().isEmpty()) {
129 + String formattedDate = formatValidityDate(mOfferItem.getEndDate());
130 + mTvEndDate.setText(getString(R.string.demo_valid_until, formattedDate));
131 + mTvEndDate.setVisibility(View.VISIBLE);
132 + } else {
133 + mTvEndDate.setVisibility(View.GONE);
134 + }
135 +
136 + if (!TextUtils.isEmpty(mOfferItem.getImg_preview())) {
137 + Glide.with(this)
138 +// .setDefaultRequestOptions(
139 +// RequestOptions
140 +// .placeholderOf(R.drawable.demo_logo)
141 +// .error(R.drawable.demo_logo))
142 + .load(mOfferItem.getImg_preview())
143 + .diskCacheStrategy(DiskCacheStrategy.DATA)
144 + .into(mIvImage);
145 + }
146 +
147 + if (!TextUtils.isEmpty(mOfferItem.getName()))
148 + mTvValue.setText(mOfferItem.getName());
149 + if (!TextUtils.isEmpty(mOfferItem.getShort_description()))
150 + mTvSmallDescription.setText(mOfferItem.getShort_description());
151 + if (!TextUtils.isEmpty(mOfferItem.getDescription()))
152 + mTvFullDescription.setText(HtmlCompat.fromHtml(mOfferItem.getDescription(), HtmlCompat.FROM_HTML_MODE_COMPACT));
153 + if (!TextUtils.isEmpty(mOfferItem.getTerms()))
154 + mTvTermsText.setText(HtmlCompat.fromHtml(mOfferItem.getTerms(), HtmlCompat.FROM_HTML_MODE_COMPACT));
155 +
156 + // Setup the More button
157 + setupMoreButton();
158 +
159 + // Setup Terms of Use section
160 + setupTermsSection();
161 + }
162 + }
163 +
164 + private void setupMoreButton() {
165 + // Wait for layout to be ready to check if text is truncated
166 + mTvFullDescription.post(new Runnable() {
167 + @Override
168 + public void run() {
169 + // Check if text is truncated (more than 4 lines)
170 + if (mTvFullDescription.getLineCount() > 3) {
171 + // Show the More button
172 + mTvMoreButton.setVisibility(View.VISIBLE);
173 +
174 + // Set click listener for the More button
175 + mTvMoreButton.setOnClickListener(new View.OnClickListener() {
176 + @Override
177 + public void onClick(View v) {
178 + toggleDescriptionExpansion();
179 + }
180 + });
181 + }
182 + }
183 + });
184 + }
185 +
186 + private void toggleDescriptionExpansion() {
187 + if (mIsDescriptionExpanded) {
188 + // Collapse the text
189 + mTvFullDescription.setMaxLines(4);
190 + mTvMoreButton.setText(R.string.demo_more);
191 + mIsDescriptionExpanded = false;
192 + } else {
193 + // Expand the text
194 + mTvFullDescription.setMaxLines(Integer.MAX_VALUE);
195 + mTvMoreButton.setText(R.string.demo_less);
196 + mIsDescriptionExpanded = true;
197 + }
198 + }
199 +
200 + private void setupTermsSection() {
201 + // Set click listener for the header to expand/collapse
202 + mTermsHeader.setOnClickListener(new View.OnClickListener() {
203 + @Override
204 + public void onClick(View v) {
205 + toggleTermsExpansion();
206 + }
207 + });
208 +
209 + // Set click listener for the entire container as well
210 + mTermsContainer.setOnClickListener(new View.OnClickListener() {
211 + @Override
212 + public void onClick(View v) {
213 + toggleTermsExpansion();
214 + }
215 + });
216 + }
217 +
218 + private void toggleTermsExpansion() {
219 + if (mIsTermsExpanded) {
220 + // Collapse the content
221 + mTermsContent.setVisibility(View.GONE);
222 + mIvTermsArrow.setImageResource(R.drawable.ic_arrow_down);
223 + mIsTermsExpanded = false;
224 + } else {
225 + // Expand the content
226 + mTermsContent.setVisibility(View.VISIBLE);
227 + mIvTermsArrow.setImageResource(R.drawable.ic_arrow_up);
228 + mIsTermsExpanded = true;
229 + }
230 + }
231 +
232 + private String formatValidityDate(String endDate) {
233 + try {
234 + SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
235 + Date date = inputFormat.parse(endDate);
236 + SimpleDateFormat outputFormat = new SimpleDateFormat("dd-MM", Locale.getDefault());
237 + return "έως " + outputFormat.format(date);
238 + } catch (ParseException e) {
239 + try {
240 + SimpleDateFormat inputFormat2 = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
241 + Date date = inputFormat2.parse(endDate);
242 + SimpleDateFormat outputFormat = new SimpleDateFormat("dd-MM", Locale.getDefault());
243 + return "έως " + outputFormat.format(date);
244 + } catch (ParseException e2) {
245 + return endDate;
246 + }
247 + }
248 + }
249 +
250 + // ===========================================================
251 + // Inner and Anonymous Classes
252 + // ===========================================================
253 +}
1 package ly.warp.sdk.io.adapters; 1 package ly.warp.sdk.io.adapters;
2 2
3 import android.content.Context; 3 import android.content.Context;
4 +import android.text.TextUtils;
4 import android.util.TypedValue; 5 import android.util.TypedValue;
5 import android.view.LayoutInflater; 6 import android.view.LayoutInflater;
6 import android.view.View; 7 import android.view.View;
...@@ -12,17 +13,17 @@ import androidx.annotation.NonNull; ...@@ -12,17 +13,17 @@ import androidx.annotation.NonNull;
12 import androidx.recyclerview.widget.RecyclerView; 13 import androidx.recyclerview.widget.RecyclerView;
13 14
14 import com.bumptech.glide.Glide; 15 import com.bumptech.glide.Glide;
16 +import com.bumptech.glide.load.engine.DiskCacheStrategy;
15 import com.bumptech.glide.load.resource.bitmap.CenterCrop; 17 import com.bumptech.glide.load.resource.bitmap.CenterCrop;
16 18
17 import java.text.ParseException; 19 import java.text.ParseException;
18 import java.text.SimpleDateFormat; 20 import java.text.SimpleDateFormat;
19 import java.util.ArrayList; 21 import java.util.ArrayList;
20 import java.util.Date; 22 import java.util.Date;
21 -import java.util.List;
22 import java.util.Locale; 23 import java.util.Locale;
23 24
24 import ly.warp.sdk.R; 25 import ly.warp.sdk.R;
25 -import ly.warp.sdk.io.models.CouponItem; 26 +import ly.warp.sdk.io.models.Coupon;
26 import ly.warp.sdk.utils.TopRoundedCornersTransformation; 27 import ly.warp.sdk.utils.TopRoundedCornersTransformation;
27 import ly.warp.sdk.utils.WarpUtils; 28 import ly.warp.sdk.utils.WarpUtils;
28 29
...@@ -31,18 +32,19 @@ import ly.warp.sdk.utils.WarpUtils; ...@@ -31,18 +32,19 @@ import ly.warp.sdk.utils.WarpUtils;
31 */ 32 */
32 public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.CouponViewHolder> { 33 public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.CouponViewHolder> {
33 34
34 - private final List<CouponItem> allCouponItems; 35 + private final ArrayList<Coupon> allCouponItems;
35 - private List<CouponItem> filteredCouponItems; 36 + private ArrayList<Coupon> filteredCouponItems;
36 private final Context context; 37 private final Context context;
37 private OnCouponClickListener listener; 38 private OnCouponClickListener listener;
38 - private String currentFilter = null; 39 + private int currentFilter = 0;
39 40
40 /** 41 /**
41 * Interface for handling coupon item clicks 42 * Interface for handling coupon item clicks
42 */ 43 */
43 public interface OnCouponClickListener { 44 public interface OnCouponClickListener {
44 - void onCouponClick(CouponItem couponItem, int position); 45 + void onCouponClick(Coupon couponItem, int position);
45 - void onFavoriteClick(CouponItem couponItem, int position); 46 +
47 + void onFavoriteClick(Coupon couponItem, int position);
46 } 48 }
47 49
48 /** 50 /**
...@@ -51,10 +53,10 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.CouponView ...@@ -51,10 +53,10 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.CouponView
51 * @param context The context 53 * @param context The context
52 * @param couponItems List of coupon items to display 54 * @param couponItems List of coupon items to display
53 */ 55 */
54 - public CouponAdapter(Context context, List<CouponItem> couponItems) { 56 + public CouponAdapter(Context context, ArrayList<Coupon> couponItems) {
55 this.context = context; 57 this.context = context;
56 this.allCouponItems = couponItems; 58 this.allCouponItems = couponItems;
57 - this.filteredCouponItems = new ArrayList<>(couponItems); 59 + this.filteredCouponItems = couponItems;
58 } 60 }
59 61
60 /** 62 /**
...@@ -71,17 +73,15 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.CouponView ...@@ -71,17 +73,15 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.CouponView
71 * 73 *
72 * @param status The status to filter by (active, favorite, redeemed) or null for all 74 * @param status The status to filter by (active, favorite, redeemed) or null for all
73 */ 75 */
74 - public void filterByStatus(String status) { 76 + public void filterByStatus(int status) {
75 currentFilter = status; 77 currentFilter = status;
76 filteredCouponItems.clear(); 78 filteredCouponItems.clear();
77 79
78 - if (status == null) { 80 + if (status == 0) {
79 - // Show all coupons
80 filteredCouponItems.addAll(allCouponItems); 81 filteredCouponItems.addAll(allCouponItems);
81 } else { 82 } else {
82 - // Filter by status 83 + for (Coupon coupon : allCouponItems) {
83 - for (CouponItem coupon : allCouponItems) { 84 + if (status == coupon.getStatus()) {
84 - if (status.equals(coupon.getStatus())) {
85 filteredCouponItems.add(coupon); 85 filteredCouponItems.add(coupon);
86 } 86 }
87 } 87 }
...@@ -99,7 +99,7 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.CouponView ...@@ -99,7 +99,7 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.CouponView
99 99
100 @Override 100 @Override
101 public void onBindViewHolder(@NonNull CouponViewHolder holder, int position) { 101 public void onBindViewHolder(@NonNull CouponViewHolder holder, int position) {
102 - CouponItem couponItem = filteredCouponItems.get(position); 102 + Coupon couponItem = filteredCouponItems.get(position);
103 holder.bind(couponItem, position); 103 holder.bind(couponItem, position);
104 } 104 }
105 105
...@@ -149,102 +149,71 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.CouponView ...@@ -149,102 +149,71 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.CouponView
149 }); 149 });
150 } 150 }
151 151
152 - void bind(CouponItem couponItem, int position) { 152 + void bind(Coupon couponItem, int position) {
153 - // Set coupon data to views 153 + tvTitle.setText(!TextUtils.isEmpty(couponItem.getCouponsetDetails().getName()) ? couponItem.getCouponsetDetails().getName() : "");
154 - tvTitle.setText(couponItem.getTitle()); 154 + tvDescription.setText(!TextUtils.isEmpty(couponItem.getCouponsetDetails().getShort_description()) ? couponItem.getCouponsetDetails().getShort_description() : "");
155 - tvDescription.setText(couponItem.getDescription()); 155 + tvPrice.setText(!TextUtils.isEmpty(couponItem.getDiscount()) ? couponItem.getDiscount() : "");
156 - tvPrice.setText(couponItem.getValue()); 156 + if (couponItem.getCouponsetDetails().getEndDate() != null && !couponItem.getCouponsetDetails().getEndDate().isEmpty()) {
157 - tvValidity.setText(formatValidityDate(couponItem.getEndDate())); 157 + tvValidity.setText(formatValidityDate(couponItem.getCouponsetDetails().getEndDate()));
158 - 158 + tvValidity.setVisibility(View.VISIBLE);
159 - // Set heart icon based on favorite status
160 - if (couponItem.isFavorite()) {
161 - // Use pressed/filled heart for Favorites
162 - ivFavorite.setImageResource(R.drawable.demo_heart_pressed);
163 } else { 159 } else {
164 - // Use default/empty heart for other statuses 160 + tvValidity.setVisibility(View.GONE);
165 - ivFavorite.setImageResource(R.drawable.demo_heart);
166 } 161 }
167 162
168 - // Load images from resources 163 + // Set heart icon based on favorite status
169 - loadOfferImage(couponItem.getImageUrl()); 164 +// if (couponItem.isFavorite()) {
170 - loadLogoImage(couponItem.getLogoUrl()); 165 +// // Use pressed/filled heart for Favorites
166 +// ivFavorite.setImageResource(R.drawable.demo_heart_pressed);
167 +// } else {
168 +// // Use default/empty heart for other statuses
169 +// ivFavorite.setImageResource(R.drawable.demo_heart);
170 +// }
171 +
172 + loadCouponImage(couponItem.getCouponsetDetails().getImg_preview());
173 + loadMerchantLogo(couponItem.getMerchantDetails().getImgPreview());
171 } 174 }
172 175
173 - /**
174 - * Format the end date to "έως dd-MM" format
175 - *
176 - * @param endDate The end date in "dd/MM/yyyy" format
177 - * @return Formatted date string
178 - */
179 private String formatValidityDate(String endDate) { 176 private String formatValidityDate(String endDate) {
180 try { 177 try {
181 - SimpleDateFormat inputFormat = new SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()); 178 + SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
182 Date date = inputFormat.parse(endDate); 179 Date date = inputFormat.parse(endDate);
183 -
184 SimpleDateFormat outputFormat = new SimpleDateFormat("dd-MM", Locale.getDefault()); 180 SimpleDateFormat outputFormat = new SimpleDateFormat("dd-MM", Locale.getDefault());
185 return "έως " + outputFormat.format(date); 181 return "έως " + outputFormat.format(date);
186 -
187 } catch (ParseException e) { 182 } catch (ParseException e) {
188 - // Fallback to original if parsing fails 183 + try {
184 + SimpleDateFormat inputFormat2 = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
185 + Date date = inputFormat2.parse(endDate);
186 + SimpleDateFormat outputFormat = new SimpleDateFormat("dd-MM", Locale.getDefault());
187 + return "έως " + outputFormat.format(date);
188 + } catch (ParseException e2) {
189 return endDate; 189 return endDate;
190 } 190 }
191 } 191 }
192 -
193 - /**
194 - * Load offer image with rounded top corners using Glide
195 - *
196 - * @param imageName The image resource name
197 - */
198 - private void loadOfferImage(String imageName) {
199 - try {
200 - // Remove file extension if present
201 - if (imageName.contains(".")) {
202 - imageName = imageName.substring(0, imageName.lastIndexOf('.'));
203 } 192 }
204 193
205 - // Get resource ID by name 194 + private void loadCouponImage(String imageUrl) {
206 - int resourceId = context.getResources().getIdentifier( 195 + if (imageUrl != null && !imageUrl.isEmpty()) {
207 - imageName, "drawable", context.getPackageName());
208 -
209 - if (resourceId != 0) {
210 - // Convert 9dp to pixels
211 int radiusInPixels = (int) TypedValue.applyDimension( 196 int radiusInPixels = (int) TypedValue.applyDimension(
212 TypedValue.COMPLEX_UNIT_DIP, 9, 197 TypedValue.COMPLEX_UNIT_DIP, 9,
213 context.getResources().getDisplayMetrics()); 198 context.getResources().getDisplayMetrics());
214 199
215 - // Load with Glide and apply transformations
216 Glide.with(context) 200 Glide.with(context)
217 - .load(resourceId) 201 + .load(imageUrl)
202 + .diskCacheStrategy(DiskCacheStrategy.DATA)
218 .transform(new CenterCrop(), new TopRoundedCornersTransformation(radiusInPixels)) 203 .transform(new CenterCrop(), new TopRoundedCornersTransformation(radiusInPixels))
219 .into(ivOfferImage); 204 .into(ivOfferImage);
220 } 205 }
221 - } catch (Exception e) {
222 - e.printStackTrace();
223 - }
224 } 206 }
225 207
226 - /** 208 + private void loadMerchantLogo(String logoUrl) {
227 - * Load logo image without transformations 209 + if (logoUrl != null && !logoUrl.isEmpty()) {
228 - * 210 + ivLogo.setVisibility(View.VISIBLE);
229 - * @param imageName The image resource name 211 + Glide.with(context)
230 - */ 212 + .load(logoUrl)
231 - private void loadLogoImage(String imageName) { 213 + .diskCacheStrategy(DiskCacheStrategy.DATA)
232 - try { 214 + .into(ivLogo);
233 - // Remove file extension if present 215 + } else {
234 - if (imageName.contains(".")) { 216 + ivLogo.setVisibility(View.GONE);
235 - imageName = imageName.substring(0, imageName.lastIndexOf('.'));
236 - }
237 -
238 - // Get resource ID by name
239 - int resourceId = context.getResources().getIdentifier(
240 - imageName, "drawable", context.getPackageName());
241 -
242 - if (resourceId != 0) {
243 - // Load logo normally without transformations
244 - ivLogo.setImageResource(resourceId);
245 - }
246 - } catch (Exception e) {
247 - e.printStackTrace();
248 } 217 }
249 } 218 }
250 } 219 }
......
...@@ -73,71 +73,81 @@ public class Coupon implements Parcelable, Serializable { ...@@ -73,71 +73,81 @@ public class Coupon implements Parcelable, Serializable {
73 73
74 /* Member variables of the Campaign object */ 74 /* Member variables of the Campaign object */
75 75
76 - private String barcode = ""; 76 + private String barcode = null;
77 - private String category = ""; 77 + private String category = null;
78 - private String coupon = ""; 78 + private String coupon = null;
79 - private String created = ""; 79 + private String created = null;
80 - private String description = ""; 80 + private String description = null;
81 - private String discount = ""; 81 + private String discount = null;
82 - private String expiration = ""; 82 + private String expiration = null;
83 - private String image = ""; 83 + private String image = null;
84 - private String name = ""; 84 + private String name = null;
85 private int status = 0; 85 private int status = 0;
86 - private String transactionDate = ""; 86 + private String transactionDate = null;
87 - private String transactionUuid = ""; 87 + private String transactionUuid = null;
88 - private JSONObject changesDates = new JSONObject(); 88 + private JSONObject changesDates = null;
89 - private String couponsetUuid = ""; 89 + private String couponsetUuid = null;
90 - private String merchantUuid = ""; 90 + private String merchantUuid = null;
91 - private String innerText = ""; 91 + private String innerText = null;
92 private Date expirationDate = new Date(); 92 private Date expirationDate = new Date();
93 private Date redeemDate = new Date(); 93 private Date redeemDate = new Date();
94 - private String discount_type = ""; 94 + private String discount_type = null;
95 private double final_price = 0.0d; 95 private double final_price = 0.0d;
96 - private String short_description = ""; 96 + private String short_description = null;
97 - private String terms = ""; 97 + private String terms = null;
98 - private Couponset couponsetDetails = new Couponset(true); 98 + private Couponset couponsetDetails = null;
99 - private Merchant merchantDetails = new Merchant(true); 99 + private Merchant merchantDetails = null;
100 - private RedeemMerchantDetails redeemDetails = new RedeemMerchantDetails(); 100 + private RedeemMerchantDetails redeemDetails = null;
101 +
102 + /**
103 + * Helper method to get a nullable String from a JSONObject.
104 + * Returns null if the JSON value is null, otherwise returns the String value.
105 + */
106 + private static String optNullableString(JSONObject json, String key) {
107 + return json.isNull(key) ? null : json.optString(key);
108 + }
101 109
102 public Coupon() { 110 public Coupon() {
103 - this.barcode = ""; 111 + this.barcode = null;
104 - this.category = ""; 112 + this.category = null;
105 - this.coupon = ""; 113 + this.coupon = null;
106 - this.created = ""; 114 + this.created = null;
107 - this.description = ""; 115 + this.description = null;
108 - this.discount = ""; 116 + this.discount = null;
109 - this.expiration = ""; 117 + this.expiration = null;
110 - this.image = ""; 118 + this.image = null;
111 - this.name = ""; 119 + this.name = null;
112 this.status = 0; 120 this.status = 0;
113 - this.transactionDate = ""; 121 + this.transactionDate = null;
114 - this.transactionUuid = ""; 122 + this.transactionUuid = null;
115 - this.changesDates = new JSONObject(); 123 + this.changesDates = null;
116 - this.couponsetUuid = ""; 124 + this.couponsetUuid = null;
117 - this.merchantUuid = ""; 125 + this.merchantUuid = null;
118 - this.innerText = ""; 126 + this.innerText = null;
119 this.expirationDate = new Date(); 127 this.expirationDate = new Date();
120 this.redeemDate = new Date(); 128 this.redeemDate = new Date();
121 - this.discount_type = ""; 129 + this.discount_type = null;
122 this.final_price = 0.0d; 130 this.final_price = 0.0d;
123 - this.short_description = ""; 131 + this.short_description = null;
124 - this.terms = ""; 132 + this.terms = null;
125 - this.redeemDetails = new RedeemMerchantDetails(); 133 + this.couponsetDetails = null;
134 + this.merchantDetails = null;
135 + this.redeemDetails = null;
126 } 136 }
127 137
128 public Coupon(boolean isUniversal) { 138 public Coupon(boolean isUniversal) {
129 - this.barcode = ""; 139 + this.barcode = null;
130 - this.coupon = ""; 140 + this.coupon = null;
131 - this.discount = ""; 141 + this.discount = null;
132 - this.expiration = ""; 142 + this.expiration = null;
133 this.status = 0; 143 this.status = 0;
134 - this.changesDates = new JSONObject(); 144 + this.changesDates = null;
135 - this.couponsetUuid = ""; 145 + this.couponsetUuid = null;
136 - this.merchantUuid = ""; 146 + this.merchantUuid = null;
137 this.redeemDate = new Date(); 147 this.redeemDate = new Date();
138 - this.couponsetDetails = new Couponset(isUniversal); 148 + this.couponsetDetails = null;
139 - this.merchantDetails = new Merchant(isUniversal); 149 + this.merchantDetails = null;
140 - this.redeemDetails = new RedeemMerchantDetails(); 150 + this.redeemDetails = null;
141 } 151 }
142 152
143 /** 153 /**
...@@ -158,21 +168,21 @@ public class Coupon implements Parcelable, Serializable { ...@@ -158,21 +168,21 @@ public class Coupon implements Parcelable, Serializable {
158 */ 168 */
159 public Coupon(JSONObject json) { 169 public Coupon(JSONObject json) {
160 if (json != null) { 170 if (json != null) {
161 - this.barcode = json.optString(BARCODE); 171 + this.barcode = optNullableString(json, BARCODE);
162 - this.category = json.optString(CATEGORY); 172 + this.category = optNullableString(json, CATEGORY);
163 - this.coupon = json.optString(COUPON); 173 + this.coupon = optNullableString(json, COUPON);
164 - this.created = json.optString(CREATED); 174 + this.created = optNullableString(json, CREATED);
165 - this.description = json.optString(DESCRIPTION); 175 + this.description = optNullableString(json, DESCRIPTION);
166 - this.discount = json.optString(DISCOUNT); 176 + this.discount = optNullableString(json, DISCOUNT);
167 - if (this.discount.contains(",")) { 177 + if (this.discount != null && this.discount.contains(",")) {
168 this.discount = this.discount.replace(",", "."); 178 this.discount = this.discount.replace(",", ".");
169 } 179 }
170 - this.expiration = json.optString(EXPIRATION); 180 + this.expiration = optNullableString(json, EXPIRATION);
171 - this.image = json.optString(IMAGE); 181 + this.image = optNullableString(json, IMAGE);
172 - this.name = json.optString(NAME); 182 + this.name = optNullableString(json, NAME);
173 this.status = json.optInt(STATUS); 183 this.status = json.optInt(STATUS);
174 - this.transactionDate = json.optString(TRANSACTION_DATE); 184 + this.transactionDate = optNullableString(json, TRANSACTION_DATE);
175 - this.transactionUuid = json.optString(TRANSACTION_UUID); 185 + this.transactionUuid = optNullableString(json, TRANSACTION_UUID);
176 this.changesDates = json.optJSONObject(CHANGES_DATES); 186 this.changesDates = json.optJSONObject(CHANGES_DATES);
177 if (this.changesDates != null) { 187 if (this.changesDates != null) {
178 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm"); 188 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
...@@ -185,13 +195,13 @@ public class Coupon implements Parcelable, Serializable { ...@@ -185,13 +195,13 @@ public class Coupon implements Parcelable, Serializable {
185 e.printStackTrace(); 195 e.printStackTrace();
186 } 196 }
187 } 197 }
188 - this.couponsetUuid = json.optString(COUPONSET_UUID); 198 + this.couponsetUuid = optNullableString(json, COUPONSET_UUID);
189 - this.merchantUuid = json.optString(MERCHANT_UUID); 199 + this.merchantUuid = optNullableString(json, MERCHANT_UUID);
190 - this.innerText = json.optString(INNER_TEXT); 200 + this.innerText = optNullableString(json, INNER_TEXT);
191 - this.discount_type = json.isNull(DISCOUNT_TYPE) ? "" : json.optString(DISCOUNT_TYPE); 201 + this.discount_type = optNullableString(json, DISCOUNT_TYPE);
192 - this.final_price = json.optDouble(FINAL_PRICE); 202 + this.final_price = json.isNull(FINAL_PRICE) ? 0.0d : json.optDouble(FINAL_PRICE);
193 - this.short_description = json.optString(SHORT_DESCRIPTION); 203 + this.short_description = optNullableString(json, SHORT_DESCRIPTION);
194 - this.terms = json.optString(TERMS); 204 + this.terms = optNullableString(json, TERMS);
195 JSONObject tempRedeemDetails = json.optJSONObject("redeemed_merchant_details"); 205 JSONObject tempRedeemDetails = json.optJSONObject("redeemed_merchant_details");
196 if (tempRedeemDetails != null) { 206 if (tempRedeemDetails != null) {
197 this.redeemDetails = new RedeemMerchantDetails(tempRedeemDetails); 207 this.redeemDetails = new RedeemMerchantDetails(tempRedeemDetails);
...@@ -201,7 +211,7 @@ public class Coupon implements Parcelable, Serializable { ...@@ -201,7 +211,7 @@ public class Coupon implements Parcelable, Serializable {
201 211
202 public Coupon(JSONObject json, boolean isUniversal) { 212 public Coupon(JSONObject json, boolean isUniversal) {
203 if (json != null) { 213 if (json != null) {
204 - this.barcode = json.optString(BARCODE); 214 + this.barcode = optNullableString(json, BARCODE);
205 this.changesDates = json.optJSONObject(CHANGES_DATES); 215 this.changesDates = json.optJSONObject(CHANGES_DATES);
206 if (this.changesDates != null) { 216 if (this.changesDates != null) {
207 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm"); 217 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
...@@ -214,14 +224,14 @@ public class Coupon implements Parcelable, Serializable { ...@@ -214,14 +224,14 @@ public class Coupon implements Parcelable, Serializable {
214 e.printStackTrace(); 224 e.printStackTrace();
215 } 225 }
216 } 226 }
217 - this.coupon = json.optString(COUPON); 227 + this.coupon = optNullableString(json, COUPON);
218 - this.couponsetUuid = json.optString(COUPONSET_UUID); 228 + this.couponsetUuid = optNullableString(json, COUPONSET_UUID);
219 - this.discount = json.optString(DISCOUNT); 229 + this.discount = optNullableString(json, DISCOUNT);
220 - if (this.discount.contains(",")) { 230 + if (this.discount != null && this.discount.contains(",")) {
221 this.discount = this.discount.replace(",", "."); 231 this.discount = this.discount.replace(",", ".");
222 } 232 }
223 - this.expiration = json.optString(EXPIRATION); 233 + this.expiration = optNullableString(json, EXPIRATION);
224 - this.merchantUuid = json.optString(MERCHANT_UUID); 234 + this.merchantUuid = optNullableString(json, MERCHANT_UUID);
225 this.status = json.optInt(STATUS); 235 this.status = json.optInt(STATUS);
226 JSONObject tempCouponsetDetails = json.optJSONObject("couponset_details"); 236 JSONObject tempCouponsetDetails = json.optJSONObject("couponset_details");
227 if (tempCouponsetDetails != null) { 237 if (tempCouponsetDetails != null) {
...@@ -235,19 +245,6 @@ public class Coupon implements Parcelable, Serializable { ...@@ -235,19 +245,6 @@ public class Coupon implements Parcelable, Serializable {
235 if (tempRedeemDetails != null) { 245 if (tempRedeemDetails != null) {
236 this.redeemDetails = new RedeemMerchantDetails(tempRedeemDetails); 246 this.redeemDetails = new RedeemMerchantDetails(tempRedeemDetails);
237 } 247 }
238 -
239 -// this.category = json.optString(CATEGORY);
240 -// this.created = json.optString(CREATED);
241 -// this.description = json.optString(DESCRIPTION);
242 -// this.image = json.optString(IMAGE);
243 -// this.name = json.optString(NAME);
244 -// this.transactionDate = json.optString(TRANSACTION_DATE);
245 -// this.transactionUuid = json.optString(TRANSACTION_UUID);
246 -// this.innerText = json.optString(INNER_TEXT);
247 -// this.discount_type = json.isNull(DISCOUNT_TYPE) ? "" : json.optString(DISCOUNT_TYPE);
248 -// this.final_price = json.optDouble(FINAL_PRICE);
249 -// this.short_description = json.optString(SHORT_DESCRIPTION);
250 -// this.terms = json.optString(TERMS);
251 } 248 }
252 } 249 }
253 250
...@@ -258,24 +255,24 @@ public class Coupon implements Parcelable, Serializable { ...@@ -258,24 +255,24 @@ public class Coupon implements Parcelable, Serializable {
258 private static final String REDEEMED_DATE = "redeemed_date"; 255 private static final String REDEEMED_DATE = "redeemed_date";
259 256
260 257
261 - private String imgPreview = ""; 258 + private String imgPreview = null;
262 - private String name = ""; 259 + private String name = null;
263 - private String uuid = ""; 260 + private String uuid = null;
264 - private String redeemedDate = ""; 261 + private String redeemedDate = null;
265 262
266 public RedeemMerchantDetails() { 263 public RedeemMerchantDetails() {
267 - this.imgPreview = ""; 264 + this.imgPreview = null;
268 - this.name = ""; 265 + this.name = null;
269 - this.uuid = ""; 266 + this.uuid = null;
270 - this.redeemedDate = ""; 267 + this.redeemedDate = null;
271 } 268 }
272 269
273 public RedeemMerchantDetails(JSONObject json) { 270 public RedeemMerchantDetails(JSONObject json) {
274 if (json != null) { 271 if (json != null) {
275 - this.imgPreview = json.isNull(IMG_PREVIEW) ? "" : json.optString(IMG_PREVIEW); 272 + this.imgPreview = json.isNull(IMG_PREVIEW) ? null : json.optString(IMG_PREVIEW);
276 - this.name = json.isNull(NAME) ? "" : json.optString(NAME); 273 + this.name = json.isNull(NAME) ? null : json.optString(NAME);
277 - this.uuid = json.isNull(UUID) ? "" : json.optString(UUID); 274 + this.uuid = json.isNull(UUID) ? null : json.optString(UUID);
278 - this.redeemedDate = json.isNull(REDEEMED_DATE) ? "" : json.optString(REDEEMED_DATE); 275 + this.redeemedDate = json.isNull(REDEEMED_DATE) ? null : json.optString(REDEEMED_DATE);
279 } 276 }
280 } 277 }
281 278
...@@ -332,6 +329,23 @@ public class Coupon implements Parcelable, Serializable { ...@@ -332,6 +329,23 @@ public class Coupon implements Parcelable, Serializable {
332 this.final_price = source.readDouble(); 329 this.final_price = source.readDouble();
333 this.short_description = source.readString(); 330 this.short_description = source.readString();
334 this.terms = source.readString(); 331 this.terms = source.readString();
332 + try {
333 + String changesDatesStr = source.readString();
334 + this.changesDates = changesDatesStr != null ? new JSONObject(changesDatesStr) : null;
335 + } catch (JSONException e) {
336 + this.changesDates = null;
337 + }
338 + this.couponsetDetails = source.readParcelable(Couponset.class.getClassLoader());
339 + this.merchantDetails = source.readParcelable(Merchant.class.getClassLoader());
340 + // Read RedeemMerchantDetails fields
341 + byte hasRedeemDetails = source.readByte();
342 + if (hasRedeemDetails == 1) {
343 + this.redeemDetails = new RedeemMerchantDetails();
344 + this.redeemDetails.setImgPreview(source.readString());
345 + this.redeemDetails.setName(source.readString());
346 + this.redeemDetails.setUuid(source.readString());
347 + this.redeemDetails.setRedeemedDate(source.readString());
348 + }
335 } 349 }
336 350
337 @Override 351 @Override
...@@ -355,6 +369,19 @@ public class Coupon implements Parcelable, Serializable { ...@@ -355,6 +369,19 @@ public class Coupon implements Parcelable, Serializable {
355 dest.writeDouble(this.final_price); 369 dest.writeDouble(this.final_price);
356 dest.writeString(this.short_description); 370 dest.writeString(this.short_description);
357 dest.writeString(this.terms); 371 dest.writeString(this.terms);
372 + dest.writeString(this.changesDates != null ? this.changesDates.toString() : null);
373 + dest.writeParcelable(this.couponsetDetails, flags);
374 + dest.writeParcelable(this.merchantDetails, flags);
375 + // Write RedeemMerchantDetails fields
376 + if (this.redeemDetails != null) {
377 + dest.writeByte((byte) 1);
378 + dest.writeString(this.redeemDetails.getImgPreview());
379 + dest.writeString(this.redeemDetails.getName());
380 + dest.writeString(this.redeemDetails.getUuid());
381 + dest.writeString(this.redeemDetails.getRedeemedDate());
382 + } else {
383 + dest.writeByte((byte) 0);
384 + }
358 } 385 }
359 386
360 /** 387 /**
...@@ -365,26 +392,26 @@ public class Coupon implements Parcelable, Serializable { ...@@ -365,26 +392,26 @@ public class Coupon implements Parcelable, Serializable {
365 public JSONObject toJSONObject() { 392 public JSONObject toJSONObject() {
366 JSONObject jObj = new JSONObject(); 393 JSONObject jObj = new JSONObject();
367 try { 394 try {
368 - jObj.putOpt(BARCODE, this.barcode); 395 + jObj.put(BARCODE, this.barcode != null ? this.barcode : JSONObject.NULL);
369 - jObj.putOpt(CATEGORY, this.category); 396 + jObj.put(CATEGORY, this.category != null ? this.category : JSONObject.NULL);
370 - jObj.putOpt(COUPON, this.coupon); 397 + jObj.put(COUPON, this.coupon != null ? this.coupon : JSONObject.NULL);
371 - jObj.putOpt(CREATED, this.created); 398 + jObj.put(CREATED, this.created != null ? this.created : JSONObject.NULL);
372 - jObj.putOpt(DESCRIPTION, this.description); 399 + jObj.put(DESCRIPTION, this.description != null ? this.description : JSONObject.NULL);
373 - jObj.putOpt(DISCOUNT, this.discount); 400 + jObj.put(DISCOUNT, this.discount != null ? this.discount : JSONObject.NULL);
374 - jObj.putOpt(EXPIRATION, this.expiration); 401 + jObj.put(EXPIRATION, this.expiration != null ? this.expiration : JSONObject.NULL);
375 - jObj.putOpt(IMAGE, this.image); 402 + jObj.put(IMAGE, this.image != null ? this.image : JSONObject.NULL);
376 - jObj.putOpt(NAME, this.name); 403 + jObj.put(NAME, this.name != null ? this.name : JSONObject.NULL);
377 jObj.putOpt(STATUS, this.status); 404 jObj.putOpt(STATUS, this.status);
378 - jObj.putOpt(TRANSACTION_DATE, this.transactionDate); 405 + jObj.put(TRANSACTION_DATE, this.transactionDate != null ? this.transactionDate : JSONObject.NULL);
379 - jObj.putOpt(TRANSACTION_UUID, this.transactionUuid); 406 + jObj.put(TRANSACTION_UUID, this.transactionUuid != null ? this.transactionUuid : JSONObject.NULL);
380 - jObj.putOpt(CHANGES_DATES, this.changesDates); 407 + jObj.put(CHANGES_DATES, this.changesDates != null ? this.changesDates : JSONObject.NULL);
381 - jObj.putOpt(COUPONSET_UUID, this.couponsetUuid); 408 + jObj.put(COUPONSET_UUID, this.couponsetUuid != null ? this.couponsetUuid : JSONObject.NULL);
382 - jObj.putOpt(MERCHANT_UUID, this.merchantUuid); 409 + jObj.put(MERCHANT_UUID, this.merchantUuid != null ? this.merchantUuid : JSONObject.NULL);
383 - jObj.putOpt(INNER_TEXT, this.innerText); 410 + jObj.put(INNER_TEXT, this.innerText != null ? this.innerText : JSONObject.NULL);
384 - jObj.putOpt(DISCOUNT_TYPE, this.discount_type); 411 + jObj.put(DISCOUNT_TYPE, this.discount_type != null ? this.discount_type : JSONObject.NULL);
385 jObj.putOpt(FINAL_PRICE, this.final_price); 412 jObj.putOpt(FINAL_PRICE, this.final_price);
386 - jObj.putOpt(SHORT_DESCRIPTION, this.short_description); 413 + jObj.put(SHORT_DESCRIPTION, this.short_description != null ? this.short_description : JSONObject.NULL);
387 - jObj.putOpt(TERMS, this.terms); 414 + jObj.put(TERMS, this.terms != null ? this.terms : JSONObject.NULL);
388 } catch (JSONException e) { 415 } catch (JSONException e) {
389 if (WarpConstants.DEBUG) { 416 if (WarpConstants.DEBUG) {
390 e.printStackTrace(); 417 e.printStackTrace();
......
...@@ -5,7 +5,6 @@ import java.util.concurrent.TimeUnit; ...@@ -5,7 +5,6 @@ import java.util.concurrent.TimeUnit;
5 import ly.warp.sdk.Warply; 5 import ly.warp.sdk.Warply;
6 import ly.warp.sdk.utils.WarplyProperty; 6 import ly.warp.sdk.utils.WarplyProperty;
7 import okhttp3.OkHttpClient; 7 import okhttp3.OkHttpClient;
8 -import okhttp3.logging.HttpLoggingInterceptor;
9 import retrofit2.Retrofit; 8 import retrofit2.Retrofit;
10 import retrofit2.converter.gson.GsonConverterFactory; 9 import retrofit2.converter.gson.GsonConverterFactory;
11 10
...@@ -49,11 +48,11 @@ public class ApiClient { ...@@ -49,11 +48,11 @@ public class ApiClient {
49 48
50 private static OkHttpClient getClient() { 49 private static OkHttpClient getClient() {
51 /* Logs Enabled */ 50 /* Logs Enabled */
52 - HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); 51 +// HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
53 - interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); 52 +// interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
54 53
55 return new OkHttpClient.Builder() 54 return new OkHttpClient.Builder()
56 - .addInterceptor(interceptor) // Logs Enabled 55 +// .addInterceptor(interceptor) // Logs Enabled
57 .connectTimeout(30, TimeUnit.SECONDS) 56 .connectTimeout(30, TimeUnit.SECONDS)
58 .writeTimeout(30, TimeUnit.SECONDS) 57 .writeTimeout(30, TimeUnit.SECONDS)
59 .readTimeout(30, TimeUnit.SECONDS) 58 .readTimeout(30, TimeUnit.SECONDS)
......
...@@ -971,7 +971,7 @@ public class WarpUtils { ...@@ -971,7 +971,7 @@ public class WarpUtils {
971 if (topView != null) { 971 if (topView != null) {
972 topView.setPadding( 972 topView.setPadding(
973 topView.getPaddingLeft(), 973 topView.getPaddingLeft(),
974 - insets.top, 974 + topView.getPaddingTop() + insets.top,
975 topView.getPaddingRight(), 975 topView.getPaddingRight(),
976 topView.getPaddingBottom() 976 topView.getPaddingBottom()
977 ); 977 );
...@@ -983,7 +983,7 @@ public class WarpUtils { ...@@ -983,7 +983,7 @@ public class WarpUtils {
983 bottomView.getPaddingLeft(), 983 bottomView.getPaddingLeft(),
984 bottomView.getPaddingTop(), 984 bottomView.getPaddingTop(),
985 bottomView.getPaddingRight(), 985 bottomView.getPaddingRight(),
986 - insets.bottom 986 + bottomView.getPaddingBottom() + insets.bottom
987 ); 987 );
988 } 988 }
989 989
......
...@@ -35,6 +35,7 @@ import ly.warp.sdk.Warply; ...@@ -35,6 +35,7 @@ import ly.warp.sdk.Warply;
35 import ly.warp.sdk.db.WarplyDBHelper; 35 import ly.warp.sdk.db.WarplyDBHelper;
36 import ly.warp.sdk.io.models.BannerItem; 36 import ly.warp.sdk.io.models.BannerItem;
37 import ly.warp.sdk.io.models.Campaign; 37 import ly.warp.sdk.io.models.Campaign;
38 +import ly.warp.sdk.io.models.Coupon;
38 import ly.warp.sdk.io.models.CouponList; 39 import ly.warp.sdk.io.models.CouponList;
39 import ly.warp.sdk.io.models.Couponset; 40 import ly.warp.sdk.io.models.Couponset;
40 import ly.warp.sdk.utils.managers.WarplyManager; 41 import ly.warp.sdk.utils.managers.WarplyManager;
...@@ -54,6 +55,7 @@ public class WarplyManagerHelper { ...@@ -54,6 +55,7 @@ public class WarplyManagerHelper {
54 // =========================================================== 55 // ===========================================================
55 56
56 private static CouponList mCouponRedeemedList = new CouponList(); 57 private static CouponList mCouponRedeemedList = new CouponList();
58 + private static ArrayList<Coupon> mCouponList = new ArrayList<Coupon>();
57 private static ArrayList<Campaign> mCampaignListAll = new ArrayList<Campaign>(); 59 private static ArrayList<Campaign> mCampaignListAll = new ArrayList<Campaign>();
58 private static ArrayList<BannerItem> mBannerListAll = new ArrayList<BannerItem>(); 60 private static ArrayList<BannerItem> mBannerListAll = new ArrayList<BannerItem>();
59 private static LinkedHashMap<String, ArrayList<Couponset>> mCouponsetCategorizedMap = new LinkedHashMap<>(); 61 private static LinkedHashMap<String, ArrayList<Couponset>> mCouponsetCategorizedMap = new LinkedHashMap<>();
...@@ -98,6 +100,15 @@ public class WarplyManagerHelper { ...@@ -98,6 +100,15 @@ public class WarplyManagerHelper {
98 mCouponRedeemedList.addAll(couponRedeemedList); 100 mCouponRedeemedList.addAll(couponRedeemedList);
99 } 101 }
100 102
103 + public static void setCoupons(ArrayList<Coupon> couponList) {
104 + mCouponList.clear();
105 + mCouponList.addAll(couponList);
106 + }
107 +
108 + public static ArrayList<Coupon> getCoupons() {
109 + return mCouponList;
110 + }
111 +
101 public static String constructCampaignUrl(Campaign item) { 112 public static String constructCampaignUrl(Campaign item) {
102 WarplyManager.getSingleCampaign(item.getSessionUUID()); 113 WarplyManager.getSingleCampaign(item.getSessionUUID());
103 String url = item.getIndexUrl(); 114 String url = item.getIndexUrl();
......
...@@ -49,7 +49,6 @@ public class WarplyProperty { ...@@ -49,7 +49,6 @@ public class WarplyProperty {
49 public static final String KEY_LOGIN_TYPE = "LoginType"; 49 public static final String KEY_LOGIN_TYPE = "LoginType";
50 public static final String KEY_DL_URL_SCHEME = "DL_URL_SCHEME"; 50 public static final String KEY_DL_URL_SCHEME = "DL_URL_SCHEME";
51 public static final String KEY_BASE_URL = "BaseURL"; 51 public static final String KEY_BASE_URL = "BaseURL";
52 - public static final String KEY_VERIFY_URL = "VerifyURL";
53 52
54 // =========================================================== 53 // ===========================================================
55 // Methods 54 // Methods
...@@ -204,10 +203,6 @@ public class WarplyProperty { ...@@ -204,10 +203,6 @@ public class WarplyProperty {
204 return getWarplyProperty(context, KEY_BASE_URL); 203 return getWarplyProperty(context, KEY_BASE_URL);
205 } 204 }
206 205
207 - public static String getVerifyUrl(Context context) {
208 - return getWarplyProperty(context, KEY_VERIFY_URL);
209 - }
210 -
211 public static boolean isSendPackages(Context context) { 206 public static boolean isSendPackages(Context context) {
212 return Boolean.parseBoolean(getWarplyProperty(context, KEY_SEND_PACKAGES)); 207 return Boolean.parseBoolean(getWarplyProperty(context, KEY_SEND_PACKAGES));
213 } 208 }
......
...@@ -61,7 +61,6 @@ import ly.warp.sdk.io.models.BannerItem; ...@@ -61,7 +61,6 @@ import ly.warp.sdk.io.models.BannerItem;
61 import ly.warp.sdk.io.models.Campaign; 61 import ly.warp.sdk.io.models.Campaign;
62 import ly.warp.sdk.io.models.Content; 62 import ly.warp.sdk.io.models.Content;
63 import ly.warp.sdk.io.models.Coupon; 63 import ly.warp.sdk.io.models.Coupon;
64 -import ly.warp.sdk.io.models.CouponList;
65 import ly.warp.sdk.io.models.Couponset; 64 import ly.warp.sdk.io.models.Couponset;
66 import ly.warp.sdk.io.models.Merchant; 65 import ly.warp.sdk.io.models.Merchant;
67 import ly.warp.sdk.io.models.User; 66 import ly.warp.sdk.io.models.User;
...@@ -371,7 +370,7 @@ public class WarplyManager { ...@@ -371,7 +370,7 @@ public class WarplyManager {
371 }); 370 });
372 } 371 }
373 372
374 - public static void getUserCouponsWithCouponsets(final CallbackReceiver<CouponList> receiver) { 373 + public static void getCoupons(final CallbackReceiver<ArrayList<Coupon>> receiver) {
375 WarpUtils.log("************* WARPLY User Coupons Request ********************"); 374 WarpUtils.log("************* WARPLY User Coupons Request ********************");
376 WarpUtils.log("[WARP Trace] WARPLY User Coupons Request is active"); 375 WarpUtils.log("[WARP Trace] WARPLY User Coupons Request is active");
377 WarpUtils.log("**************************************************"); 376 WarpUtils.log("**************************************************");
...@@ -379,18 +378,18 @@ public class WarplyManager { ...@@ -379,18 +378,18 @@ public class WarplyManager {
379 ApiService service = ApiClient.getRetrofitInstance().create(ApiService.class); 378 ApiService service = ApiClient.getRetrofitInstance().create(ApiService.class);
380 ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1)); 379 ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1));
381 380
382 - SettableFuture<CouponList> futureUniversal = SettableFuture.create(); 381 + SettableFuture<ArrayList<Coupon>> futureUniversal = SettableFuture.create();
383 - ListenableFuture<CouponList> futureCoupons = getCouponsUniversalRetro(service, 0, futureUniversal); 382 + ListenableFuture<ArrayList<Coupon>> futureCoupons = getCouponsUniversalRetro(service, 0, futureUniversal);
384 383
385 ListenableFuture<List<Object>> allResultsFuture = Futures.allAsList(futureCoupons); 384 ListenableFuture<List<Object>> allResultsFuture = Futures.allAsList(futureCoupons);
386 - ListenableFuture<CouponList> mergedResultFuture = Futures.transformAsync(allResultsFuture, results -> { 385 + ListenableFuture<ArrayList<Coupon>> mergedResultFuture = Futures.transformAsync(allResultsFuture, results -> {
387 - CouponList resultCoupons = (CouponList) results.get(0); 386 + ArrayList<Coupon> resultCoupons = (ArrayList<Coupon>) results.get(0);
388 return executorService.submit(() -> resultCoupons); 387 return executorService.submit(() -> resultCoupons);
389 }, executorService); 388 }, executorService);
390 389
391 - Futures.addCallback(mergedResultFuture, new FutureCallback<CouponList>() { 390 + Futures.addCallback(mergedResultFuture, new FutureCallback<ArrayList<Coupon>>() {
392 @Override 391 @Override
393 - public void onSuccess(CouponList mergedResult) { 392 + public void onSuccess(ArrayList<Coupon> mergedResult) {
394 executorService.shutdownNow(); 393 executorService.shutdownNow();
395 new Handler(Looper.getMainLooper()).post(() -> receiver.onSuccess(mergedResult)); 394 new Handler(Looper.getMainLooper()).post(() -> receiver.onSuccess(mergedResult));
396 } 395 }
...@@ -463,7 +462,7 @@ public class WarplyManager { ...@@ -463,7 +462,7 @@ public class WarplyManager {
463 ApiService service = ApiClient.getRetrofitInstance().create(ApiService.class); 462 ApiService service = ApiClient.getRetrofitInstance().create(ApiService.class);
464 ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(2)); 463 ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(2));
465 464
466 - ListenableFuture<ArrayList<Couponset>> futureCouponsets = getCouponsetsRetro(service); 465 + ListenableFuture<ArrayList<Couponset>> futureCouponsets = getCouponsetsRetro(service, 0);
467 ListenableFuture<ArrayList<Merchant>> futureMerchants = getMerchantsRetro(service); 466 ListenableFuture<ArrayList<Merchant>> futureMerchants = getMerchantsRetro(service);
468 467
469 ListenableFuture<List<Object>> allResultsFuture = Futures.allAsList(futureCouponsets, futureMerchants); 468 ListenableFuture<List<Object>> allResultsFuture = Futures.allAsList(futureCouponsets, futureMerchants);
...@@ -488,7 +487,7 @@ public class WarplyManager { ...@@ -488,7 +487,7 @@ public class WarplyManager {
488 }, executorService); 487 }, executorService);
489 } 488 }
490 489
491 - private static ListenableFuture<ArrayList<Couponset>> getCouponsetsRetro(ApiService service/*, Callback<ResponseBody> callback*/) { 490 + private static ListenableFuture<ArrayList<Couponset>> getCouponsetsRetro(ApiService service, int tries/*, Callback<ResponseBody> callback*/) {
492 SettableFuture<ArrayList<Couponset>> future = SettableFuture.create(); 491 SettableFuture<ArrayList<Couponset>> future = SettableFuture.create();
493 492
494 String timeStamp = DateFormat.format("yyyy-MM-dd hh:mm:ss", System.currentTimeMillis()).toString(); 493 String timeStamp = DateFormat.format("yyyy-MM-dd hh:mm:ss", System.currentTimeMillis()).toString();
...@@ -550,6 +549,31 @@ public class WarplyManager { ...@@ -550,6 +549,31 @@ public class WarplyManager {
550 } else { 549 } else {
551 future.set(new ArrayList<Couponset>()); 550 future.set(new ArrayList<Couponset>());
552 } 551 }
552 + } else if (response.code() == 401) {
553 + refreshToken(new WarplyRefreshTokenRequest(), new CallbackReceiver<JSONObject>() {
554 + @Override
555 + public void onSuccess(JSONObject result) {
556 + int status = result.optInt("status", 2);
557 + if (status == 1)
558 + getCouponsetsRetro(service, tries);
559 + else {
560 + if (tries < MAX_RETRIES) {
561 + getCouponsetsRetro(service, (tries + 1));
562 + } else {
563 + future.setException(new Throwable());
564 + }
565 + }
566 + }
567 +
568 + @Override
569 + public void onFailure(int errorCode) {
570 + if (tries < MAX_RETRIES) {
571 + getCouponsetsRetro(service, (tries + 1));
572 + } else {
573 + future.setException(new Throwable());
574 + }
575 + }
576 + });
553 } else if (String.valueOf(response.code()).startsWith("5")) { 577 } else if (String.valueOf(response.code()).startsWith("5")) {
554 future.set(new ArrayList<Couponset>()); 578 future.set(new ArrayList<Couponset>());
555 } else { 579 } else {
...@@ -985,7 +1009,7 @@ public class WarplyManager { ...@@ -985,7 +1009,7 @@ public class WarplyManager {
985 return future; 1009 return future;
986 } 1010 }
987 1011
988 - private static ListenableFuture<CouponList> getCouponsUniversalRetro(ApiService service, int tries, SettableFuture<CouponList> future) { 1012 + private static ListenableFuture<ArrayList<Coupon>> getCouponsUniversalRetro(ApiService service, int tries, SettableFuture<ArrayList<Coupon>> future) {
989 String timeStamp = DateFormat.format("yyyy-MM-dd hh:mm:ss", System.currentTimeMillis()).toString(); 1013 String timeStamp = DateFormat.format("yyyy-MM-dd hh:mm:ss", System.currentTimeMillis()).toString();
990 String apiKey = WarpUtils.getApiKey(Warply.getWarplyContext()); 1014 String apiKey = WarpUtils.getApiKey(Warply.getWarplyContext());
991 String webId = WarpUtils.getWebId(Warply.getWarplyContext()); 1015 String webId = WarpUtils.getWebId(Warply.getWarplyContext());
...@@ -995,17 +1019,8 @@ public class WarplyManager { ...@@ -995,17 +1019,8 @@ public class WarplyManager {
995 jsonParams.put("action", "user_coupons"); 1019 jsonParams.put("action", "user_coupons");
996 JSONArray jArr = new JSONArray(); 1020 JSONArray jArr = new JSONArray();
997 jArr.put("merchant"); 1021 jArr.put("merchant");
998 - jArr.put("redemption");
999 jsonParams.put("details", jArr); 1022 jsonParams.put("details", jArr);
1000 - jsonParams.put("language", WarpUtils.getApplicationLocale(Warply.getWarplyContext())); 1023 +// jsonParams.put("language", WarpUtils.getApplicationLocale(Warply.getWarplyContext()));
1001 -// JSONObject jPagination= new JSONObject();
1002 -// try {
1003 -// jPagination.putOpt("page",1);
1004 -// jPagination.putOpt("per_page", 10);
1005 -// } catch (JSONException e) {
1006 -// throw new RuntimeException(e);
1007 -// }
1008 -// jsonParams.put("pagination", jPagination);
1009 1024
1010 jsonParamsCoupons.put("coupon", jsonParams); 1025 jsonParamsCoupons.put("coupon", jsonParams);
1011 RequestBody couponsRequest = RequestBody.create(MediaType.get("application/json; charset=utf-8"), (new JSONObject(jsonParamsCoupons)).toString()); 1026 RequestBody couponsRequest = RequestBody.create(MediaType.get("application/json; charset=utf-8"), (new JSONObject(jsonParamsCoupons)).toString());
...@@ -1032,32 +1047,29 @@ public class WarplyManager { ...@@ -1032,32 +1047,29 @@ public class WarplyManager {
1032 } 1047 }
1033 1048
1034 if (jCouponsBody != null) { 1049 if (jCouponsBody != null) {
1035 - CouponList mCouponRedeemedList = new CouponList(); 1050 + ArrayList<Coupon> mCouponList = new ArrayList<Coupon>();
1036 1051
1037 final ExecutorService executorCoupons = Executors.newFixedThreadPool(1); 1052 final ExecutorService executorCoupons = Executors.newFixedThreadPool(1);
1038 JSONArray finalJCouponsBody = jCouponsBody; 1053 JSONArray finalJCouponsBody = jCouponsBody;
1039 executorCoupons.submit(() -> { 1054 executorCoupons.submit(() -> {
1040 for (int i = 0; i < finalJCouponsBody.length(); ++i) { 1055 for (int i = 0; i < finalJCouponsBody.length(); ++i) {
1041 Coupon tempCoupon = new Coupon(finalJCouponsBody.optJSONObject(i), true); 1056 Coupon tempCoupon = new Coupon(finalJCouponsBody.optJSONObject(i), true);
1042 - 1057 + mCouponList.add(tempCoupon);
1043 - if (tempCoupon.getStatus() == 0) {
1044 - mCouponRedeemedList.add(tempCoupon);
1045 - }
1046 } 1058 }
1047 1059
1048 - WarplyManagerHelper.setCouponRedeemedList(mCouponRedeemedList); 1060 + WarplyManagerHelper.setCoupons(mCouponList);
1049 1061
1050 - Collections.sort(mCouponRedeemedList, (coupon1, coupon2) -> coupon1.getExpirationDate().compareTo(coupon2.getExpirationDate())); 1062 + Collections.sort(mCouponList, (coupon1, coupon2) -> coupon1.getExpirationDate().compareTo(coupon2.getExpirationDate()));
1051 1063
1052 executorCoupons.shutdownNow(); 1064 executorCoupons.shutdownNow();
1053 1065
1054 - future.set(mCouponRedeemedList); 1066 + future.set(mCouponList);
1055 }); 1067 });
1056 } else { 1068 } else {
1057 - future.set(new CouponList()); 1069 + future.set(new ArrayList<Coupon>());
1058 } 1070 }
1059 } else { 1071 } else {
1060 - future.set(new CouponList()); 1072 + future.set(new ArrayList<Coupon>());
1061 } 1073 }
1062 } else if (response.code() == 401) { 1074 } else if (response.code() == 401) {
1063 refreshToken(new WarplyRefreshTokenRequest(), new CallbackReceiver<JSONObject>() { 1075 refreshToken(new WarplyRefreshTokenRequest(), new CallbackReceiver<JSONObject>() {
...@@ -1071,7 +1083,7 @@ public class WarplyManager { ...@@ -1071,7 +1083,7 @@ public class WarplyManager {
1071 if (tries < MAX_RETRIES) { 1083 if (tries < MAX_RETRIES) {
1072 getCouponsUniversalRetro(service, (tries + 1), future); 1084 getCouponsUniversalRetro(service, (tries + 1), future);
1073 } else { 1085 } else {
1074 -// future.set(new CouponList()); 1086 +// future.set(new ArrayList<Coupon>());
1075 future.setException(new Throwable()); 1087 future.setException(new Throwable());
1076 } 1088 }
1077 } 1089 }
...@@ -1083,20 +1095,20 @@ public class WarplyManager { ...@@ -1083,20 +1095,20 @@ public class WarplyManager {
1083 if (tries < MAX_RETRIES) { 1095 if (tries < MAX_RETRIES) {
1084 getCouponsUniversalRetro(service, (tries + 1), future); 1096 getCouponsUniversalRetro(service, (tries + 1), future);
1085 } else { 1097 } else {
1086 -// future.set(new CouponList()); 1098 +// future.set(new ArrayList<Coupon>());
1087 future.setException(new Throwable()); 1099 future.setException(new Throwable());
1088 } 1100 }
1089 } 1101 }
1090 }); 1102 });
1091 } else { 1103 } else {
1092 -// future.set(new CouponList()); 1104 +// future.set(new ArrayList<Coupon>());
1093 future.setException(new Throwable()); 1105 future.setException(new Throwable());
1094 } 1106 }
1095 } 1107 }
1096 1108
1097 @Override 1109 @Override
1098 public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) { 1110 public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {
1099 -// future.set(new CouponList()); 1111 +// future.set(new ArrayList<Coupon>());
1100 future.setException(new Throwable()); 1112 future.setException(new Throwable());
1101 } 1113 }
1102 }); 1114 });
......
...@@ -25,7 +25,8 @@ ...@@ -25,7 +25,8 @@
25 android:background="@color/custom_grey_light" 25 android:background="@color/custom_grey_light"
26 android:orientation="horizontal" 26 android:orientation="horizontal"
27 android:paddingHorizontal="16dp" 27 android:paddingHorizontal="16dp"
28 - android:paddingVertical="16dp"> 28 + android:paddingBottom="16dp"
29 + android:paddingTop="16dp">
29 30
30 <LinearLayout 31 <LinearLayout
31 android:layout_width="0dp" 32 android:layout_width="0dp"
...@@ -104,12 +105,19 @@ ...@@ -104,12 +105,19 @@
104 </RelativeLayout> 105 </RelativeLayout>
105 </androidx.constraintlayout.widget.ConstraintLayout> 106 </androidx.constraintlayout.widget.ConstraintLayout>
106 107
108 + <LinearLayout
109 + android:id="@+id/ll_sections_container"
110 + android:layout_width="match_parent"
111 + android:layout_height="wrap_content"
112 + android:layout_marginBottom="48dp"
113 + android:paddingBottom="16dp"
114 + android:orientation="vertical" />
115 +
107 <RelativeLayout 116 <RelativeLayout
108 android:id="@+id/rl_sections_loading" 117 android:id="@+id/rl_sections_loading"
109 android:layout_width="match_parent" 118 android:layout_width="match_parent"
110 android:layout_height="wrap_content" 119 android:layout_height="wrap_content"
111 android:background="@android:color/transparent" 120 android:background="@android:color/transparent"
112 - android:layout_marginTop="48dp"
113 android:translationZ="100dp" 121 android:translationZ="100dp"
114 android:visibility="gone" 122 android:visibility="gone"
115 tools:visibility="visible"> 123 tools:visibility="visible">
...@@ -122,13 +130,6 @@ ...@@ -122,13 +130,6 @@
122 android:indeterminateTint="@color/custom_light_blue" 130 android:indeterminateTint="@color/custom_light_blue"
123 android:indeterminateTintMode="src_atop" /> 131 android:indeterminateTintMode="src_atop" />
124 </RelativeLayout> 132 </RelativeLayout>
125 -
126 - <LinearLayout
127 - android:id="@+id/ll_sections_container"
128 - android:layout_width="match_parent"
129 - android:layout_height="wrap_content"
130 - android:layout_marginBottom="48dp"
131 - android:orientation="vertical" />
132 </LinearLayout> 133 </LinearLayout>
133 </androidx.core.widget.NestedScrollView> 134 </androidx.core.widget.NestedScrollView>
134 </RelativeLayout> 135 </RelativeLayout>
......
...@@ -223,6 +223,25 @@ ...@@ -223,6 +223,25 @@
223 android:clipToPadding="false" 223 android:clipToPadding="false"
224 android:nestedScrollingEnabled="false" 224 android:nestedScrollingEnabled="false"
225 android:paddingHorizontal="16dp" /> 225 android:paddingHorizontal="16dp" />
226 +
227 + <RelativeLayout
228 + android:id="@+id/pb_loading"
229 + android:layout_width="match_parent"
230 + android:layout_height="wrap_content"
231 + android:background="@android:color/transparent"
232 + android:layout_marginTop="64dp"
233 + android:translationZ="100dp"
234 + android:visibility="gone"
235 + tools:visibility="visible">
236 +
237 + <ProgressBar
238 + android:layout_width="40dp"
239 + android:layout_height="40dp"
240 + android:layout_centerInParent="true"
241 + android:indeterminate="true"
242 + android:indeterminateTint="@color/custom_light_blue"
243 + android:indeterminateTintMode="src_atop" />
244 + </RelativeLayout>
226 </RelativeLayout> 245 </RelativeLayout>
227 </LinearLayout> 246 </LinearLayout>
228 </androidx.core.widget.NestedScrollView> 247 </androidx.core.widget.NestedScrollView>
......
...@@ -59,8 +59,7 @@ ...@@ -59,8 +59,7 @@
59 android:id="@+id/iv_coupon_image" 59 android:id="@+id/iv_coupon_image"
60 android:layout_width="match_parent" 60 android:layout_width="match_parent"
61 android:layout_height="match_parent" 61 android:layout_height="match_parent"
62 - android:scaleType="fitXY" 62 + android:scaleType="centerCrop"
63 - tools:src="@drawable/demo_home_banner1"
64 app:layout_constraintBottom_toBottomOf="parent" 63 app:layout_constraintBottom_toBottomOf="parent"
65 app:layout_constraintEnd_toEndOf="parent" 64 app:layout_constraintEnd_toEndOf="parent"
66 app:layout_constraintStart_toStartOf="parent" 65 app:layout_constraintStart_toStartOf="parent"
...@@ -280,7 +279,7 @@ ...@@ -280,7 +279,7 @@
280 android:layout_width="0dp" 279 android:layout_width="0dp"
281 android:layout_height="wrap_content" 280 android:layout_height="wrap_content"
282 android:layout_weight="1" 281 android:layout_weight="1"
283 - android:text="@string/demo_qr_code" 282 + android:text="@string/demo_barcode_code"
284 android:textColor="@color/custom_black2" 283 android:textColor="@color/custom_black2"
285 android:textSize="15sp" /> 284 android:textSize="15sp" />
286 285
...@@ -303,10 +302,10 @@ ...@@ -303,10 +302,10 @@
303 <ImageView 302 <ImageView
304 android:id="@+id/iv_qr_code" 303 android:id="@+id/iv_qr_code"
305 android:layout_width="200dp" 304 android:layout_width="200dp"
306 - android:layout_height="200dp" 305 + android:layout_height="120dp"
307 android:layout_marginVertical="16dp" 306 android:layout_marginVertical="16dp"
308 android:scaleType="fitCenter" 307 android:scaleType="fitCenter"
309 - android:src="@drawable/demo_qr" /> 308 + android:src="@drawable/demo_barcode" />
310 </LinearLayout> 309 </LinearLayout>
311 </LinearLayout> 310 </LinearLayout>
312 311
......
1 +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 + xmlns:app="http://schemas.android.com/apk/res-auto"
3 + xmlns:tools="http://schemas.android.com/tools"
4 + android:layout_width="match_parent"
5 + android:layout_height="match_parent"
6 + android:background="@color/custom_grey_light">
7 +
8 + <ScrollView
9 + android:id="@+id/coupon_scrollview"
10 + android:layout_width="match_parent"
11 + android:layout_height="match_parent"
12 + android:fillViewport="true">
13 +
14 + <LinearLayout
15 + android:id="@+id/home_content_container"
16 + android:layout_width="match_parent"
17 + android:layout_height="wrap_content"
18 + android:orientation="vertical">
19 +
20 + <LinearLayout
21 + android:id="@+id/header_layout"
22 + android:layout_width="match_parent"
23 + android:layout_height="wrap_content"
24 + android:layout_gravity="center"
25 + android:background="@color/white"
26 + android:orientation="horizontal"
27 + android:padding="16dp">
28 +
29 + <LinearLayout
30 + android:layout_width="0dp"
31 + android:layout_height="wrap_content"
32 + android:gravity="center_vertical"
33 + android:layout_weight="1"
34 + android:orientation="horizontal">
35 +
36 + <ImageView
37 + android:id="@+id/iv_back"
38 + android:layout_width="16dp"
39 + android:layout_height="16dp"
40 + android:layout_marginEnd="24dp"
41 + android:src="@drawable/ic_back" />
42 +
43 + <TextView
44 + android:id="@+id/tv_header_title"
45 + android:layout_width="wrap_content"
46 + android:layout_height="wrap_content"
47 + android:includeFontPadding="false"
48 + android:text="@string/demo_offer"
49 + android:textColor="@color/custom_black4"
50 + android:textSize="16sp" />
51 + </LinearLayout>
52 + </LinearLayout>
53 +
54 + <androidx.constraintlayout.widget.ConstraintLayout
55 + android:layout_width="match_parent"
56 + android:layout_height="210dp">
57 +
58 + <ImageView
59 + android:id="@+id/iv_coupon_image"
60 + android:layout_width="match_parent"
61 + android:layout_height="match_parent"
62 + android:scaleType="centerCrop"
63 + app:layout_constraintBottom_toBottomOf="parent"
64 + app:layout_constraintEnd_toEndOf="parent"
65 + app:layout_constraintStart_toStartOf="parent"
66 + app:layout_constraintTop_toTopOf="parent" />
67 +
68 + <LinearLayout
69 + android:layout_width="wrap_content"
70 + android:layout_height="wrap_content"
71 + android:layout_marginTop="8dp"
72 + android:layout_marginEnd="8dp"
73 + android:background="@drawable/demo_shape_white_border_grey"
74 + android:orientation="horizontal"
75 + android:paddingHorizontal="8dp"
76 + android:paddingVertical="3dp"
77 + app:layout_constraintEnd_toEndOf="parent"
78 + app:layout_constraintTop_toTopOf="parent">
79 +
80 + <ImageView
81 + android:layout_width="12dp"
82 + android:layout_height="12dp"
83 + android:layout_marginEnd="4dp"
84 + android:src="@drawable/ic_info" />
85 +
86 + <TextView
87 + android:id="@+id/tv_more_title"
88 + android:layout_width="wrap_content"
89 + android:layout_height="wrap_content"
90 + android:includeFontPadding="false"
91 + android:text="@string/demo_more"
92 + android:textColor="@color/custom_black4"
93 + android:textSize="11sp" />
94 + </LinearLayout>
95 + </androidx.constraintlayout.widget.ConstraintLayout>
96 +
97 + <LinearLayout
98 + android:id="@+id/ll_coupon_info"
99 + android:layout_width="match_parent"
100 + android:layout_height="wrap_content"
101 + android:orientation="vertical"
102 + android:paddingHorizontal="16dp"
103 + android:paddingVertical="16dp"
104 + android:layout_marginBottom="48dp">
105 +
106 + <androidx.constraintlayout.widget.ConstraintLayout
107 + android:layout_width="match_parent"
108 + android:layout_height="wrap_content"
109 + android:gravity="center_vertical"
110 + android:orientation="horizontal">
111 +
112 + <LinearLayout
113 + android:id="@+id/ll_info"
114 + android:layout_width="0dp"
115 + android:layout_height="wrap_content"
116 + android:orientation="vertical"
117 + android:paddingEnd="16dp"
118 + app:layout_constraintBottom_toBottomOf="parent"
119 + app:layout_constraintEnd_toStartOf="@+id/ll_buttons"
120 + app:layout_constraintStart_toStartOf="parent"
121 + app:layout_constraintTop_toTopOf="parent">
122 +
123 + <TextView
124 + android:id="@+id/tv_coupon_value"
125 + android:layout_width="wrap_content"
126 + android:layout_height="wrap_content"
127 + android:includeFontPadding="false"
128 + android:maxLines="1"
129 + android:textColor="@color/custom_pink2"
130 + android:textSize="23sp"
131 + tools:text="@string/demo_more" />
132 +
133 + <TextView
134 + android:id="@+id/tv_coupon_small_description"
135 + android:layout_width="wrap_content"
136 + android:layout_height="wrap_content"
137 + android:layout_marginTop="2dp"
138 + android:ellipsize="end"
139 + android:includeFontPadding="false"
140 + android:maxLines="1"
141 + android:text="@string/demo_purchases"
142 + android:textColor="@color/custom_black5"
143 + android:textSize="17sp" />
144 + </LinearLayout>
145 +
146 + <LinearLayout
147 + android:id="@+id/ll_buttons"
148 + android:layout_width="wrap_content"
149 + android:layout_height="wrap_content"
150 + android:orientation="horizontal"
151 + app:layout_constraintBottom_toBottomOf="parent"
152 + app:layout_constraintEnd_toEndOf="parent"
153 + app:layout_constraintTop_toTopOf="parent">
154 +
155 + <ImageView
156 + android:layout_width="40dp"
157 + android:layout_height="40dp"
158 + android:layout_marginEnd="8dp"
159 + android:src="@drawable/demo_heart_border" />
160 +
161 + <ImageView
162 + android:layout_width="40dp"
163 + android:layout_height="40dp"
164 + android:src="@drawable/demo_folder" />
165 + </LinearLayout>
166 + </androidx.constraintlayout.widget.ConstraintLayout>
167 +
168 + <TextView
169 + android:id="@+id/tv_coupon_end_date"
170 + android:layout_width="wrap_content"
171 + android:layout_height="wrap_content"
172 + android:layout_marginTop="14dp"
173 + android:includeFontPadding="false"
174 + android:textColor="@color/custom_black5"
175 + android:textSize="13sp"
176 + tools:text="@string/demo_purchases" />
177 +
178 + <TextView
179 + android:id="@+id/tv_coupon_full_description"
180 + android:layout_width="match_parent"
181 + android:layout_height="wrap_content"
182 + android:layout_marginTop="16dp"
183 + android:ellipsize="end"
184 + android:lineSpacingExtra="2dp"
185 + android:maxLines="4"
186 + android:textColor="@color/custom_black5"
187 + android:textSize="17sp"
188 + tools:text="Πάρτε τα πακέτα κινητής στη μισή τιμή μόνο αυτό το μήνα. Απεριόριστα λεπτά προς όλα τα δίκτυα, SMS και 50GB δεδομένα υψηλής ταχύτητας. Ισχύει για νέους συνδρομητές και ανανεώσεις συμβολαίων. Δωρεάν ενεργοποίηση και τεχνική υποστήριξη." />
189 +
190 + <TextView
191 + android:id="@+id/tv_more_button"
192 + android:layout_width="wrap_content"
193 + android:layout_height="wrap_content"
194 + android:layout_gravity="end"
195 + android:layout_marginTop="2dp"
196 + android:text="@string/demo_more"
197 + android:textColor="@color/custom_skyblue"
198 + android:textSize="15sp"
199 + android:visibility="gone" />
200 +
201 + <LinearLayout
202 + android:id="@+id/terms_container"
203 + android:layout_width="match_parent"
204 + android:layout_height="wrap_content"
205 + android:layout_marginTop="48dp"
206 + android:orientation="vertical"
207 + android:paddingHorizontal="2dp">
208 +
209 + <LinearLayout
210 + android:id="@+id/terms_header"
211 + android:layout_width="match_parent"
212 + android:layout_height="wrap_content"
213 + android:gravity="center_vertical"
214 + android:orientation="horizontal">
215 +
216 + <TextView
217 + android:id="@+id/tv_terms_title"
218 + android:layout_width="wrap_content"
219 + android:layout_height="wrap_content"
220 + android:text="@string/demo_terms"
221 + android:textColor="@color/custom_black2"
222 + android:textSize="15sp"/>
223 +
224 + <ImageView
225 + android:id="@+id/iv_terms_arrow"
226 + android:layout_width="8dp"
227 + android:layout_height="8dp"
228 + android:layout_marginStart="4dp"
229 + android:layout_marginTop="2dp"
230 + android:src="@drawable/ic_arrow_down" />
231 + </LinearLayout>
232 +
233 + <LinearLayout
234 + android:id="@+id/terms_content"
235 + android:layout_width="match_parent"
236 + android:layout_height="wrap_content"
237 + android:layout_marginTop="16dp"
238 + android:orientation="vertical"
239 + android:visibility="gone">
240 +
241 + <TextView
242 + android:id="@+id/tv_terms_text"
243 + android:layout_width="match_parent"
244 + android:layout_height="wrap_content"
245 + android:layout_marginBottom="8dp"
246 + android:lineSpacingExtra="4dp"
247 + android:text="@string/demo_lorem_ipsum"
248 + android:textColor="@color/custom_black2"
249 + android:textSize="15sp" />
250 + </LinearLayout>
251 + </LinearLayout>
252 +
253 + <LinearLayout
254 + android:layout_width="match_parent"
255 + android:layout_height="55dp"
256 + android:layout_marginTop="48dp"
257 + android:background="@drawable/selector_button_black"
258 + android:gravity="center"
259 + android:orientation="horizontal">
260 +
261 + <TextView
262 + android:id="@+id/tv_shops_title"
263 + android:layout_width="wrap_content"
264 + android:layout_height="wrap_content"
265 + android:text="@string/demo_shops"
266 + android:textColor="@color/white"
267 + android:textSize="15sp"
268 + android:textStyle="bold" />
269 + </LinearLayout>
270 +
271 + <LinearLayout
272 + android:layout_width="match_parent"
273 + android:layout_height="55dp"
274 + android:layout_marginTop="20dp"
275 + android:background="@drawable/selector_button_transparent_black_border"
276 + android:gravity="center"
277 + android:orientation="horizontal">
278 +
279 + <TextView
280 + android:id="@+id/tv_website_title"
281 + android:layout_width="wrap_content"
282 + android:layout_height="wrap_content"
283 + android:text="@string/demo_website"
284 + android:textColor="@color/custom_black2"
285 + android:textSize="15sp"
286 + android:textStyle="bold" />
287 + </LinearLayout>
288 + </LinearLayout>
289 + </LinearLayout>
290 + </ScrollView>
291 +</RelativeLayout>
...@@ -87,7 +87,6 @@ ...@@ -87,7 +87,6 @@
87 app:layout_constraintBottom_toBottomOf="parent" 87 app:layout_constraintBottom_toBottomOf="parent"
88 tools:text="έως 30-09" /> 88 tools:text="έως 30-09" />
89 89
90 - <!-- Brand Logo -->
91 <ImageView 90 <ImageView
92 android:id="@+id/iv_logo" 91 android:id="@+id/iv_logo"
93 android:layout_width="80dp" 92 android:layout_width="80dp"
......
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
16 android:layout_height="wrap_content" 16 android:layout_height="wrap_content"
17 android:background="@android:color/white" 17 android:background="@android:color/white"
18 android:paddingHorizontal="16dp" 18 android:paddingHorizontal="16dp"
19 - android:paddingVertical="16dp"> 19 + android:paddingBottom="16dp"
20 + android:paddingTop="16dp">
20 21
21 <ImageView 22 <ImageView
22 android:id="@+id/user_img" 23 android:id="@+id/user_img"
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
15 <string name="demo_valid_until">Η προσφορά ισχύει έως %1$s</string> 15 <string name="demo_valid_until">Η προσφορά ισχύει έως %1$s</string>
16 <string name="demo_coupon_code">Κωδικός Κουπονιού</string> 16 <string name="demo_coupon_code">Κωδικός Κουπονιού</string>
17 <string name="demo_qr_code">QR Κουπονιού</string> 17 <string name="demo_qr_code">QR Κουπονιού</string>
18 + <string name="demo_barcode_code">Barcode Κουπονιού</string>
18 <string name="demo_terms">Όροι Χρήσης</string> 19 <string name="demo_terms">Όροι Χρήσης</string>
19 <string name="demo_copy_success">Ο κωδικός αντιγράφηκε στο πρόχειρο</string> 20 <string name="demo_copy_success">Ο κωδικός αντιγράφηκε στο πρόχειρο</string>
20 <string name="demo_lorem_ipsum">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</string> 21 <string name="demo_lorem_ipsum">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</string>
......