Showing
25 changed files
with
1633 additions
and
495 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 | ... | ... |
skill.md
0 → 100644
| 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,59 +115,6 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup | ... | @@ -114,59 +115,6 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup |
| 114 | mIvProfile.setOnClickListener(this); | 115 | mIvProfile.setOnClickListener(this); |
| 115 | } | 116 | } |
| 116 | 117 | ||
| 117 | - private void setupCouponsetSections(LinkedHashMap<String, ArrayList<Couponset>> categorizedMap) { | ||
| 118 | - mSectionsContainer.removeAllViews(); | ||
| 119 | - | ||
| 120 | - if (categorizedMap == null || categorizedMap.isEmpty()) { | ||
| 121 | - mSectionsLoading.setVisibility(View.GONE); | ||
| 122 | - return; | ||
| 123 | - } | ||
| 124 | - | ||
| 125 | - LayoutInflater inflater = LayoutInflater.from(this); | ||
| 126 | - int spacingInPixels = (int) TypedValue.applyDimension( | ||
| 127 | - TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()); | ||
| 128 | - | ||
| 129 | - for (Map.Entry<String, ArrayList<Couponset>> entry : categorizedMap.entrySet()) { | ||
| 130 | - String categoryName = entry.getKey(); | ||
| 131 | - ArrayList<Couponset> couponsets = entry.getValue(); | ||
| 132 | - | ||
| 133 | - if (couponsets == null || couponsets.isEmpty()) { | ||
| 134 | - continue; | ||
| 135 | - } | ||
| 136 | - | ||
| 137 | - View sectionView = inflater.inflate(R.layout.item_couponset_section, mSectionsContainer, false); | ||
| 138 | - | ||
| 139 | - TextView tvTitle = sectionView.findViewById(R.id.tv_section_title); | ||
| 140 | - String titleText = categoryName + " (" + couponsets.size() + ")"; | ||
| 141 | - tvTitle.setText(titleText); | ||
| 142 | - WarpUtils.renderCustomFont(this, R.font.ping_lcg_bold, tvTitle); | ||
| 143 | - | ||
| 144 | - TextView tvAll = sectionView.findViewById(R.id.tv_section_all); | ||
| 145 | - WarpUtils.renderCustomFont(this, R.font.ping_lcg_regular, tvAll); | ||
| 146 | - | ||
| 147 | - RecyclerView rvSection = sectionView.findViewById(R.id.rv_section_list); | ||
| 148 | - LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); | ||
| 149 | - rvSection.setLayoutManager(layoutManager); | ||
| 150 | - rvSection.setHasFixedSize(true); | ||
| 151 | - rvSection.addItemDecoration(new HorizontalSpaceItemDecoration(spacingInPixels)); | ||
| 152 | - | ||
| 153 | - List<Couponset> displayList = couponsets.size() > MAX_ITEMS_PER_SECTION | ||
| 154 | - ? new ArrayList<>(couponsets.subList(0, MAX_ITEMS_PER_SECTION)) | ||
| 155 | - : couponsets; | ||
| 156 | - | ||
| 157 | - CouponsetAdapter adapter = new CouponsetAdapter(this, displayList); | ||
| 158 | - adapter.setOnCouponsetClickListener(this); | ||
| 159 | - rvSection.setAdapter(adapter); | ||
| 160 | - | ||
| 161 | - mSectionsContainer.addView(sectionView); | ||
| 162 | - } | ||
| 163 | - mSectionsLoading.setVisibility(View.GONE); | ||
| 164 | - } | ||
| 165 | - | ||
| 166 | - // =========================================================== | ||
| 167 | - // Banner Methods | ||
| 168 | - // =========================================================== | ||
| 169 | - | ||
| 170 | private void setupBannerCarousel() { | 118 | private void setupBannerCarousel() { |
| 171 | mBannerViewPager = findViewById(R.id.banner_viewpager); | 119 | mBannerViewPager = findViewById(R.id.banner_viewpager); |
| 172 | mDotsContainer = findViewById(R.id.dots_container); | 120 | mDotsContainer = findViewById(R.id.dots_container); |
| ... | @@ -176,7 +124,17 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup | ... | @@ -176,7 +124,17 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup |
| 176 | startActivity(WarpViewActivity.createIntentFromURL(this, WarplyManagerHelper.constructCampaignUrl(campaign))); | 124 | startActivity(WarpViewActivity.createIntentFromURL(this, WarplyManagerHelper.constructCampaignUrl(campaign))); |
| 177 | }); | 125 | }); |
| 178 | mBannerAdapter.setOnBannerContentClickListener(article -> { | 126 | mBannerAdapter.setOnBannerContentClickListener(article -> { |
| 179 | - //TODO: click 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 | + } | ||
| 180 | }); | 138 | }); |
| 181 | 139 | ||
| 182 | // Set the number of pages to preload for adjacent items | 140 | // Set the number of pages to preload for adjacent items |
| ... | @@ -220,9 +178,68 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup | ... | @@ -220,9 +178,68 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup |
| 220 | } | 178 | } |
| 221 | } | 179 | } |
| 222 | 180 | ||
| 223 | - // =========================================================== | 181 | + private void setupCouponsetSections(LinkedHashMap<String, ArrayList<Couponset>> categorizedMap) { |
| 224 | - // Callbacks | 182 | + mSectionsContainer.removeAllViews(); |
| 225 | - // =========================================================== | 183 | + |
| 184 | + if (categorizedMap == null || categorizedMap.isEmpty()) { | ||
| 185 | + mSectionsLoading.setVisibility(View.GONE); | ||
| 186 | + return; | ||
| 187 | + } | ||
| 188 | + | ||
| 189 | + LayoutInflater inflater = LayoutInflater.from(this); | ||
| 190 | + int spacingInPixels = (int) TypedValue.applyDimension( | ||
| 191 | + TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()); | ||
| 192 | + | ||
| 193 | + for (Map.Entry<String, ArrayList<Couponset>> entry : categorizedMap.entrySet()) { | ||
| 194 | + String categoryName = entry.getKey(); | ||
| 195 | + ArrayList<Couponset> couponsets = entry.getValue(); | ||
| 196 | + | ||
| 197 | + if (couponsets == null || couponsets.isEmpty()) { | ||
| 198 | + continue; | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + View sectionView = inflater.inflate(R.layout.item_couponset_section, mSectionsContainer, false); | ||
| 202 | + | ||
| 203 | + TextView tvTitle = sectionView.findViewById(R.id.tv_section_title); | ||
| 204 | + String titleText = categoryName + " (" + couponsets.size() + ")"; | ||
| 205 | + tvTitle.setText(titleText); | ||
| 206 | + WarpUtils.renderCustomFont(this, R.font.ping_lcg_bold, tvTitle); | ||
| 207 | + | ||
| 208 | + TextView tvAll = sectionView.findViewById(R.id.tv_section_all); | ||
| 209 | + WarpUtils.renderCustomFont(this, R.font.ping_lcg_regular, tvAll); | ||
| 210 | + | ||
| 211 | + RecyclerView rvSection = sectionView.findViewById(R.id.rv_section_list); | ||
| 212 | + LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); | ||
| 213 | + rvSection.setLayoutManager(layoutManager); | ||
| 214 | + rvSection.setHasFixedSize(true); | ||
| 215 | + rvSection.addItemDecoration(new HorizontalSpaceItemDecoration(spacingInPixels)); | ||
| 216 | + | ||
| 217 | + List<Couponset> displayList = couponsets.size() > MAX_ITEMS_PER_SECTION | ||
| 218 | + ? new ArrayList<>(couponsets.subList(0, MAX_ITEMS_PER_SECTION)) | ||
| 219 | + : couponsets; | ||
| 220 | + | ||
| 221 | + CouponsetAdapter adapter = new CouponsetAdapter(this, displayList); | ||
| 222 | + adapter.setOnCouponsetClickListener(this); | ||
| 223 | + rvSection.setAdapter(adapter); | ||
| 224 | + | ||
| 225 | + mSectionsContainer.addView(sectionView); | ||
| 226 | + } | ||
| 227 | + mSectionsLoading.setVisibility(View.GONE); | ||
| 228 | + } | ||
| 229 | + | ||
| 230 | + private Couponset findCouponsetByUuid(String uuid) { | ||
| 231 | + LinkedHashMap<String, ArrayList<Couponset>> categorizedMap = WarplyManagerHelper.getCouponsetCategorizedMap(); | ||
| 232 | + if (categorizedMap != null) { | ||
| 233 | + for (ArrayList<Couponset> couponsets : categorizedMap.values()) { | ||
| 234 | + for (Couponset couponset : couponsets) { | ||
| 235 | + if (uuid.equals(couponset.getUuid())) { | ||
| 236 | + return couponset; | ||
| 237 | + } | ||
| 238 | + } | ||
| 239 | + } | ||
| 240 | + } | ||
| 241 | + return null; | ||
| 242 | + } | ||
| 226 | 243 | ||
| 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 | ... | ... |
| ... | @@ -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 | + } | ||
| 206 | } | 219 | } |
| 207 | - mTvCouponCode.setText(couponCode); | 220 | + } |
| 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,22 +73,20 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.CouponView | ... | @@ -71,22 +73,20 @@ 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 | } |
| 88 | } | 88 | } |
| 89 | - | 89 | + |
| 90 | notifyDataSetChanged(); | 90 | notifyDataSetChanged(); |
| 91 | } | 91 | } |
| 92 | 92 | ||
| ... | @@ -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 { |
| 189 | - return endDate; | 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; | ||
| 190 | + } | ||
| 190 | } | 191 | } |
| 191 | } | 192 | } |
| 192 | 193 | ||
| 193 | - /** | 194 | + private void loadCouponImage(String imageUrl) { |
| 194 | - * Load offer image with rounded top corners using Glide | 195 | + if (imageUrl != null && !imageUrl.isEmpty()) { |
| 195 | - * | 196 | + int radiusInPixels = (int) TypedValue.applyDimension( |
| 196 | - * @param imageName The image resource name | 197 | + TypedValue.COMPLEX_UNIT_DIP, 9, |
| 197 | - */ | 198 | + context.getResources().getDisplayMetrics()); |
| 198 | - private void loadOfferImage(String imageName) { | 199 | + |
| 199 | - try { | 200 | + Glide.with(context) |
| 200 | - // Remove file extension if present | 201 | + .load(imageUrl) |
| 201 | - if (imageName.contains(".")) { | 202 | + .diskCacheStrategy(DiskCacheStrategy.DATA) |
| 202 | - imageName = imageName.substring(0, imageName.lastIndexOf('.')); | 203 | + .transform(new CenterCrop(), new TopRoundedCornersTransformation(radiusInPixels)) |
| 203 | - } | 204 | + .into(ivOfferImage); |
| 204 | - | ||
| 205 | - // Get resource ID by name | ||
| 206 | - int resourceId = context.getResources().getIdentifier( | ||
| 207 | - imageName, "drawable", context.getPackageName()); | ||
| 208 | - | ||
| 209 | - if (resourceId != 0) { | ||
| 210 | - // Convert 9dp to pixels | ||
| 211 | - int radiusInPixels = (int) TypedValue.applyDimension( | ||
| 212 | - TypedValue.COMPLEX_UNIT_DIP, 9, | ||
| 213 | - context.getResources().getDisplayMetrics()); | ||
| 214 | - | ||
| 215 | - // Load with Glide and apply transformations | ||
| 216 | - Glide.with(context) | ||
| 217 | - .load(resourceId) | ||
| 218 | - .transform(new CenterCrop(), new TopRoundedCornersTransformation(radiusInPixels)) | ||
| 219 | - .into(ivOfferImage); | ||
| 220 | - } | ||
| 221 | - } catch (Exception e) { | ||
| 222 | - e.printStackTrace(); | ||
| 223 | } | 205 | } |
| 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 | }); | ... | ... |
17.6 KB
| ... | @@ -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> | ... | ... |
-
Please register or login to post a comment