Compare commits
5 Commits
18677b5ae5
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d60f62c20 | |||
| 1246a35771 | |||
| 08060001e1 | |||
| 2b03af6e52 | |||
| 6aaa648128 |
@@ -1,6 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="21" />
|
||||
<bytecodeTargetLevel target="21">
|
||||
<module name="PSP.p3_pardakht_novin" target="21" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
</project>
|
||||
@@ -6,18 +6,65 @@
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="tis_sep">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="tis_app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DialogSelection />
|
||||
<DropdownSelection timestamp="2026-06-08T07:38:22.052645700Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Amir Mousavi\.android\avd\Pixel_9_Pro.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection>
|
||||
<targets>
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=TP030078028" />
|
||||
</handle>
|
||||
</Target>
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Amir Mousavi\.android\avd\Pixel_9_Pro.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</targets>
|
||||
</DialogSelection>
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="stage_app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2026-05-12T16:21:58.593804400Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=TP030090489" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="pardakht_novin_app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2026-06-08T07:38:22.052645700Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Amir Mousavi\.android\avd\Pixel_9_Pro.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection>
|
||||
<targets>
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=TP030078028" />
|
||||
</handle>
|
||||
</Target>
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Amir Mousavi\.android\avd\Pixel_9_Pro.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</targets>
|
||||
</DialogSelection>
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,7 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="project">
|
||||
<words>
|
||||
<w>Novin</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
@@ -13,6 +13,9 @@
|
||||
<option value="$PROJECT_DIR$/core" />
|
||||
<option value="$PROJECT_DIR$/design_system" />
|
||||
<option value="$PROJECT_DIR$/p3" />
|
||||
<option value="$PROJECT_DIR$/p3/p3_pardakht_novin" />
|
||||
<option value="$PROJECT_DIR$/p3/p3_sepehr" />
|
||||
<option value="$PROJECT_DIR$/pardakht_novin_app" />
|
||||
<option value="$PROJECT_DIR$/ps4" />
|
||||
<option value="$PROJECT_DIR$/stage_app" />
|
||||
<option value="$PROJECT_DIR$/tis_app" />
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="externalSystemId" value="Gradle" />
|
||||
<option name="version" value="2.3.10" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MarkdownSettings">
|
||||
<option name="previewPanelProviderInfo">
|
||||
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@@ -4,6 +4,7 @@
|
||||
<option name="approvalStates">
|
||||
<map>
|
||||
<entry key="20260504-162003-d0f92a5d-1a15-429a-82cc-cad6ed3c0e3e" value="true" />
|
||||
<entry key="20260608-113233-cbd224e1-fbde-45e9-8023-20976cbde20f" value="true" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# Agent Instructions: PSP Project
|
||||
|
||||
This document provides essential context and guidelines for AI agents working on the PSP (Payment Service Provider) project.
|
||||
|
||||
## Project Purpose
|
||||
A multi-module Android application for specialized POS (Point of Sale) hardware, providing a bridge between a Web-based POS interface and native hardware features (Thermal Printing & Payment Terminal).
|
||||
|
||||
## Architecture Overview
|
||||
The project follows **Dependency Inversion** principles. Features are defined as interfaces in `:core` and implemented in hardware-specific modules like `:p3` or `:ps4`.
|
||||
|
||||
### Key Modules
|
||||
- **`:core`**: Shared domain models (`PrintEntity`, `PaymentResultEntity`) and interfaces (`PspService`, `Printer`).
|
||||
- **`:design_system`**: Contains `PspWebView` and the `PspJavaScriptInterface` (Web-Native Bridge).
|
||||
- **`:p3` / `:ps4`**: Hardware SDK integrations.
|
||||
- **`:app` / `:tis_app` / `:stage_app`**: Application entry points and Hilt dependency configuration.
|
||||
|
||||
## Critical Classes & Interfaces
|
||||
|
||||
### 1. Web-Native Bridge
|
||||
- **`PspWebView.kt`**: The core component. Uses a `WebViewManager` for pooling and is highly optimized for weak hardware.
|
||||
- **`PspJavaScriptInterface.kt`**: Defines the methods exposed to JavaScript (`pay`, `print`, `reload`, `uuid`).
|
||||
- **Important**: Methods must be annotated with `@JavascriptInterface` and `@Keep`.
|
||||
- **Note**: Numbers from JS arrive as `Double`.
|
||||
|
||||
### 2. Printer Implementation (`:p3`)
|
||||
- **`P3Printer.kt`**: Implements the `Printer` interface.
|
||||
- **`POIPrinterManager`**: The vendor SDK class.
|
||||
- **Font Handling**: Use `getFontPath(resourceName: String)` to copy fonts from `res/font` to internal storage before passing the path to the SDK.
|
||||
- **Logo Rendering**: Use `drawableToBitmap()` to handle VectorDrawables correctly.
|
||||
|
||||
### 3. Payment Service (`:p3`)
|
||||
- **`Ps3Service.kt`**: Implementation of `PspService`.
|
||||
- **`IapPosService.kt`**: Handles communication with the underlying POS payment application.
|
||||
|
||||
## Common Agent Tasks
|
||||
|
||||
### Adding a new Bridge Method
|
||||
1. Add the method to `PspJavaScriptInterface.kt` with `@JavascriptInterface` and `@Keep`.
|
||||
2. Update the constructor of `PspJavaScriptInterface` to take a lambda.
|
||||
3. Update `PspWebView.kt` to pass the lambda from the UI layer.
|
||||
|
||||
### Optimizing for Weak Hardware
|
||||
When performance issues arise in the WebView:
|
||||
1. Check `WebViewManager.kt` settings.
|
||||
2. Ensure `setLayerType(View.LAYER_TYPE_HARDWARE, null)` is set.
|
||||
3. Keep `setOffscreenPreRaster(false)` for very weak hardware to save RAM.
|
||||
4. Ensure `loadUrl` is NOT called in the `AndroidView.update` block (use `LaunchedEffect` instead).
|
||||
|
||||
## Development Rules
|
||||
- **RTL Support**: The printer uses Persian/Arabic text. Always ensure numbers are converted to Persian digits using `toPersianDigits()` where appropriate.
|
||||
- **Dependency Injection**: Use Hilt for all service injections. Do not instantiate implementation classes (`P3Printer`, `Ps3Service`) directly in the app modules.
|
||||
- **WebView Lifecycle**: Always release WebViews back to the `WebViewManager` in `onDispose`.
|
||||
|
||||
## Mocking in `:stage_app`
|
||||
- Use `StageViewModel` to return success/failure mocks for payment results without requiring physical hardware.
|
||||
- Use the `PaymentResultEntity.success()` or `PaymentResultEntity.failure()` factory methods.
|
||||
@@ -0,0 +1,49 @@
|
||||
# PSP (Payment Service Provider) Project
|
||||
|
||||
This project is a multi-module Android application designed to provide payment and printing services on specialized POS hardware. It uses a modular architecture to separate core logic, hardware SDKs, and different application variants.
|
||||
|
||||
## Module Structure
|
||||
|
||||
### Core Modules
|
||||
* **`:core`**: The backbone of the project. Contains shared domain models (`PaymentResultEntity`, `PrintEntity`), interfaces for services (`PspService`, `Printer`), and common utilities. It has no dependencies on other modules.
|
||||
* **`:design_system`**: Contains shared UI components and utilities. The most critical component is `PspWebView`, a highly optimized WebView wrapper used across all app variants for rendering the web-based POS interface. It depends on `:core`.
|
||||
|
||||
### Hardware Implementation Modules
|
||||
These modules contain the specific SDK integrations for different POS hardware.
|
||||
* **`:p3`**: Implementation of `PspService` and `Printer` for P3 hardware. Includes SDK-specific logic for handling payments and thermal printing.
|
||||
* **`:ps4`**: Implementation for PS4 hardware variants.
|
||||
|
||||
### Application Variants
|
||||
* **`:app`**: The primary production application.
|
||||
* **`:tis_app`**: A specialized version of the application targeting the TIS environment (`https://tis.shift-am.ir`).
|
||||
* **`:stage_app`**: A development and staging variant used for testing. It often uses mock implementations (like `StageViewModel`) to simulate POS behavior without requiring physical hardware.
|
||||
|
||||
## Architecture & Relationships
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Apps
|
||||
tis_app --> core
|
||||
tis_app --> design_system
|
||||
stage_app --> core
|
||||
stage_app --> design_system
|
||||
app --> core
|
||||
app --> design_system
|
||||
end
|
||||
|
||||
subgraph Hardware SDKs
|
||||
p3 --> core
|
||||
ps4 --> core
|
||||
end
|
||||
|
||||
design_system --> core
|
||||
```
|
||||
|
||||
1. **Dependency Inversion**: Apps depend on interfaces defined in `:core`. The actual hardware implementation (`:p3` or `:ps4`) is injected via Hilt, allowing the same app logic to run on different devices.
|
||||
2. **Web-Native Bridge**: The `design_system` module provides `PspJavaScriptInterface`, which allows the Web POS (running inside `PspWebView`) to communicate with native hardware features like the printer and payment terminal.
|
||||
3. **Hardware Performance**: The WebView is optimized for weak POS hardware with features like pooled instances, hardware acceleration, and lean rendering profiles.
|
||||
|
||||
## Key Features
|
||||
* **Thermal Printing**: Optimized for RTL (Persian) text and high-performance bitmap rendering for logos (including VectorDrawable support).
|
||||
* **Payment Integration**: Standardized bridge for processing payments and returning structured results to the web-front-end.
|
||||
* **Weak Hardware Optimization**: Custom WebView configurations to handle smooth animations (drawers/sheets) on low-spec devices.
|
||||
@@ -5,4 +5,5 @@ plugins {
|
||||
alias(libs.plugins.kotlin.compose) apply false
|
||||
id("com.google.dagger.hilt.android") version "2.59.2" apply false
|
||||
alias(libs.plugins.ksp) apply false
|
||||
alias(libs.plugins.jetbrains.kotlin.jvm) apply false
|
||||
}
|
||||
@@ -12,5 +12,5 @@ interface PspService {
|
||||
fun decodePosResponse(
|
||||
id: String?,
|
||||
activityResult: ActivityResult
|
||||
): PaymentResultEntity?
|
||||
): PaymentResultEntity
|
||||
}
|
||||
@@ -1,15 +1,48 @@
|
||||
package com.example.core.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class PaymentResultEntity(
|
||||
val id : String? = null,
|
||||
val status : PaymentResultStatus,
|
||||
val terminalId : String,
|
||||
val stan : String,
|
||||
val rrn : String,
|
||||
val responseCode : String,
|
||||
val customerCardNO : String,
|
||||
val transactionDateTime : String,
|
||||
val description : String?,
|
||||
val id: String? = null,
|
||||
val status: PaymentResultStatus,
|
||||
val message: String?,
|
||||
@SerializedName("data")
|
||||
val paymentResultDataEntity: PaymentResultDataEntity? = null,
|
||||
) {
|
||||
data class PaymentResultDataEntity(
|
||||
val terminalId: String,
|
||||
val stan: String,
|
||||
val rrn: String,
|
||||
val responseCode: String,
|
||||
val customerCardNO: String,
|
||||
val transactionDateTime: String,
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun success(
|
||||
id: String? = null,
|
||||
message: String? = "Success",
|
||||
data: PaymentResultDataEntity? = null
|
||||
): PaymentResultEntity {
|
||||
return PaymentResultEntity(
|
||||
id = id,
|
||||
status = PaymentResultStatus.SUCCESS,
|
||||
message = message,
|
||||
paymentResultDataEntity = data
|
||||
)
|
||||
}
|
||||
|
||||
)
|
||||
fun failure(
|
||||
id: String? = null,
|
||||
message: String? = "Failure",
|
||||
data: PaymentResultDataEntity? = null
|
||||
): PaymentResultEntity {
|
||||
return PaymentResultEntity(
|
||||
id = id,
|
||||
status = PaymentResultStatus.FAILURE,
|
||||
message = message,
|
||||
paymentResultDataEntity = data
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.example.core.model
|
||||
|
||||
enum class PaymentResultStatus {
|
||||
OK,
|
||||
ERROR
|
||||
|
||||
SUCCESS,
|
||||
FAILURE
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.webkit.WebSettings
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -37,19 +38,21 @@ fun PspWebView(
|
||||
onPayClick: (amount: Long, id: String?) -> Unit,
|
||||
onDeviceInfo: () -> String,
|
||||
onPrintClick: (printEntities: List<PrintEntity>) -> Unit,
|
||||
onWebViewReady: (WebView) -> Unit = {},
|
||||
onPageFinished: (WebView) -> Unit = {},
|
||||
) {
|
||||
|
||||
val canGoBack = remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
var lastBackTime by remember { mutableLongStateOf(0L) }
|
||||
var webView by remember { mutableStateOf<WebView?>(null) }
|
||||
var webViewInstance by remember { mutableStateOf<WebView?>(null) }
|
||||
|
||||
|
||||
// Use rememberUpdatedState to ensure the bridge always calls the latest callbacks
|
||||
val currentOnPayClick by rememberUpdatedState(onPayClick)
|
||||
val currentOnPrintClick by rememberUpdatedState(onPrintClick)
|
||||
val currentOnDeviceInfo by rememberUpdatedState(onDeviceInfo)
|
||||
val currentOnWebViewReady by rememberUpdatedState(onWebViewReady)
|
||||
val currentOnPageFinished by rememberUpdatedState(onPageFinished)
|
||||
|
||||
val bridge = remember {
|
||||
PspJavaScriptInterface(
|
||||
@@ -60,8 +63,8 @@ fun PspWebView(
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
if (canGoBack.value) {
|
||||
webView?.goBack()
|
||||
if (webViewInstance?.canGoBack() == true) {
|
||||
webViewInstance?.goBack()
|
||||
} else {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastBackTime < 2000) {
|
||||
@@ -69,47 +72,57 @@ fun PspWebView(
|
||||
} else {
|
||||
lastBackTime = now
|
||||
scope.launch {
|
||||
SnackbarController.sendEvent(
|
||||
SnackbarEvent(
|
||||
message = "برای خروج، دوباره بر روی بازگشت کلیک کنید."
|
||||
)
|
||||
)
|
||||
SnackbarController.sendEvent(SnackbarEvent("برای خروج، دوباره کلیک کنید."))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Manage pooling and lifecycle
|
||||
DisposableEffect(Unit) {
|
||||
webView = WebViewManager.getWebView(context)
|
||||
onDispose {
|
||||
webView?.let { WebViewManager.releaseWebView(it) }
|
||||
webViewInstance?.let { WebViewManager.releaseWebView(it) }
|
||||
}
|
||||
}
|
||||
|
||||
AndroidView(
|
||||
modifier = modifier,
|
||||
factory = { ctx ->
|
||||
(webView ?: WebViewManager.getWebView(ctx)).apply {
|
||||
val wv = WebViewManager.getWebView(ctx).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
overScrollMode = View.OVER_SCROLL_NEVER
|
||||
|
||||
webViewClient = object : WebViewClient() {
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
canGoBack.value = view?.canGoBack() ?: false
|
||||
view?.let(currentOnPageFinished)
|
||||
}
|
||||
}
|
||||
|
||||
addJavascriptInterface(bridge, "NativeBridge")
|
||||
settings.apply {
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
cacheMode = WebSettings.LOAD_NO_CACHE
|
||||
allowFileAccess = true
|
||||
}
|
||||
|
||||
loadUrl(url)
|
||||
removeJavascriptInterface("NativeBridge")
|
||||
addJavascriptInterface(bridge, "NativeBridge")
|
||||
}
|
||||
|
||||
webViewInstance = wv
|
||||
currentOnWebViewReady(wv) // CRITICAL: Send the ACTUAL instance being shown
|
||||
wv
|
||||
},
|
||||
update = {
|
||||
it.loadUrl(url)
|
||||
if (it.url != url) {
|
||||
it.loadUrl(url)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ hilt = "2.59.2"
|
||||
ksp = "2.3.6"
|
||||
retrofit = "2.11.0"
|
||||
okhttp = "4.12.0"
|
||||
jetbrainsKotlinJvm = "2.3.10"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
@@ -46,3 +47,4 @@ android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" }
|
||||
|
||||
@@ -5,7 +5,7 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.p3"
|
||||
namespace = "com.example.p3_pardakht_novin"
|
||||
compileSdk {
|
||||
version = release(36) {
|
||||
minorApiLevel = 1
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.p3
|
||||
package com.example.p3_pardakht_novin
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.p3
|
||||
package com.example.p3_pardakht_novin
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -6,15 +6,16 @@ import android.content.pm.PackageManager
|
||||
import androidx.activity.result.ActivityResult
|
||||
import com.example.core.model.PaymentResultEntity
|
||||
import com.example.core.model.PaymentResultStatus
|
||||
import com.example.p3.models.IapAccountSplit
|
||||
import com.example.p3_pardakht_novin.models.IapAccountSplit
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class IapPosService @Inject constructor(
|
||||
private val context: Context
|
||||
class P3PardakhtNovinIapPosService @Inject constructor(
|
||||
@param:ApplicationContext private val context: Context
|
||||
) {
|
||||
private val gson = Gson()
|
||||
|
||||
@@ -157,25 +158,35 @@ class IapPosService @Inject constructor(
|
||||
): PaymentResultEntity {
|
||||
val result = activityResult.data?.getStringExtra("Result")
|
||||
|
||||
|
||||
if (result.isNullOrEmpty()) {
|
||||
throw Exception("POS app returned no result.")
|
||||
return PaymentResultEntity.failure(
|
||||
id = id,
|
||||
message = "پرداخت لغو شد!"
|
||||
)
|
||||
}
|
||||
return try {
|
||||
val type = object : TypeToken<Map<String, Any>>() {}.type
|
||||
val data = gson.fromJson<Map<String, Any>>(result, type)
|
||||
PaymentResultEntity(
|
||||
id = id,
|
||||
status = if (data["Status"] == "OK") PaymentResultStatus.OK else PaymentResultStatus.ERROR,
|
||||
terminalId = data["TerminalId"].toString(),
|
||||
stan = data["STAN"].toString(),
|
||||
rrn = data["RRN"].toString(),
|
||||
responseCode = data["ResponseCode"].toString(),
|
||||
customerCardNO = data["CustomerCardNO"].toString(),
|
||||
transactionDateTime = data["TransactionDateTime"].toString(),
|
||||
description = data["Description"].toString()
|
||||
status = if (data["Status"] == "OK") PaymentResultStatus.SUCCESS else PaymentResultStatus.FAILURE,
|
||||
paymentResultDataEntity = PaymentResultEntity.PaymentResultDataEntity(
|
||||
terminalId = data["TerminalId"].toString(),
|
||||
stan = data["STAN"].toString(),
|
||||
rrn = data["RRN"].toString(),
|
||||
responseCode = data["ResponseCode"].toString(),
|
||||
customerCardNO = data["CustomerCardNO"].toString(),
|
||||
transactionDateTime = data["TransactionDateTime"].toString(),
|
||||
),
|
||||
message = data["Description"].toString()
|
||||
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
throw Exception("POS response is not a JSON object.")
|
||||
PaymentResultEntity.failure(
|
||||
id = id,
|
||||
message = e.message ?: "در هنگام پرداخت خطایی رخ داده است!"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
package com.example.p3_pardakht_novin
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.util.Log
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.createBitmap
|
||||
import com.example.core.domain.Printer
|
||||
import com.example.core.model.PrintEntity
|
||||
import com.pos.sdk.printer.POIPrinterManager
|
||||
import com.pos.sdk.printer.POIPrinterManager.IPrinterListener
|
||||
import com.pos.sdk.printer.models.BitmapPrintLine
|
||||
import com.pos.sdk.printer.models.PrintLine
|
||||
import com.pos.sdk.printer.models.TextPrintLine
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class P3PardakhtNovinPrinter @Inject constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
) : Printer {
|
||||
companion object {
|
||||
private const val TAG = "Printer"
|
||||
|
||||
// Printer state constants (should ideally come from the SDK if available)
|
||||
private const val PRINTER_STATE_ERROR = 4
|
||||
private const val DEFAULT_PRINT_GRAY = 2000
|
||||
private const val DEFAULT_LINE_SPACE = 5
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a sample receipt for demonstration.
|
||||
*/
|
||||
fun printSampleReceipt(context: Context) {
|
||||
// Use applicationContext to avoid potential memory leaks
|
||||
|
||||
val printerManager = initialP3Printer()
|
||||
|
||||
printerManager?.apply {
|
||||
setPrintGray(DEFAULT_PRINT_GRAY)
|
||||
setLineSpace(DEFAULT_LINE_SPACE)
|
||||
|
||||
// Header
|
||||
addPrintLine(TextPrintLine("This is an example of a receipt", PrintLine.CENTER))
|
||||
|
||||
// Reusable logo bitmap
|
||||
val logoBitmap = drawableToBitmap(context, android.R.drawable.btn_plus)
|
||||
val logoLine = logoBitmap?.let { BitmapPrintLine(it, PrintLine.CENTER) }
|
||||
|
||||
logoLine?.let { addPrintLine(it) }
|
||||
|
||||
// Address
|
||||
val addressFont = getFontPath("font")
|
||||
if (addressFont.isNotEmpty()) {
|
||||
setPrintFont(addressFont)
|
||||
} else {
|
||||
setPrintFont("/system/fonts/ComingSoon.ttf")
|
||||
}
|
||||
addPrintLine(
|
||||
TextPrintLine(
|
||||
"Floor ** , Building **, No.*** LONG DONG Avenue, Pudong New District, Shanghai, China",
|
||||
PrintLine.LEFT,
|
||||
20
|
||||
)
|
||||
)
|
||||
|
||||
// Transaction Details
|
||||
val detailFont = getFontPath("font")
|
||||
if (detailFont.isNotEmpty()) {
|
||||
setPrintFont(detailFont)
|
||||
} else {
|
||||
setPrintFont("/system/fonts/DroidSansMono.ttf")
|
||||
}
|
||||
// addPrintLine(createPrintRow("24 June 2025", " Assistant 6", "815002", size = 18))
|
||||
// addPrintLine(createPrintRow("Item", "Quantity", "Price", size = 24, isBold = true))
|
||||
// addPrintLine(createPrintRow("Tomato", "1", "$2.08", size = 24))
|
||||
// addPrintLine(createPrintRow("Orange", "1", "$1.06", size = 24))
|
||||
//
|
||||
// Footer
|
||||
addPrintLine(TextPrintLine("Total $3.14", PrintLine.RIGHT))
|
||||
addPrintLine(TextPrintLine(""))
|
||||
logoLine?.let { addPrintLine(it) }
|
||||
addPrintLine(TextPrintLine(""))
|
||||
|
||||
addPrintLine(
|
||||
TextPrintLine(
|
||||
"Did you know you could have earned Rewards points on this purchase?",
|
||||
PrintLine.CENTER
|
||||
)
|
||||
)
|
||||
addPrintLine(
|
||||
TextPrintLine(
|
||||
"Simply sign up today for a Membership Card!",
|
||||
PrintLine.CENTER
|
||||
)
|
||||
)
|
||||
|
||||
logoLine?.let { addPrintLine(it) }
|
||||
addPrintLine(TextPrintLine(" ", 0, 100))
|
||||
|
||||
val listener = object : IPrinterListener {
|
||||
override fun onStart() {
|
||||
Log.d(TAG, "Print started")
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
Log.d(TAG, "Print finished")
|
||||
close()
|
||||
}
|
||||
|
||||
override fun onError(code: Int, msg: String?) {
|
||||
Log.e(TAG, "Print error - code: $code, message: $msg")
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
beginPrint(listener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initialP3Printer(): POIPrinterManager? {
|
||||
val printerManager = POIPrinterManager(context.applicationContext)
|
||||
printerManager.open()
|
||||
|
||||
val state = printerManager.printerState
|
||||
Log.d(TAG, "Printer state = $state")
|
||||
|
||||
if (state == PRINTER_STATE_ERROR) {
|
||||
Log.e(TAG, "Printer is in an error state ($state). Aborting print.")
|
||||
printerManager.close()
|
||||
return null
|
||||
}
|
||||
|
||||
return printerManager
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a row with left, center, and right aligned text.
|
||||
*/
|
||||
private fun createPrintRow(
|
||||
label: String,
|
||||
value: String,
|
||||
size: Int,
|
||||
isBold: Boolean = false
|
||||
): List<TextPrintLine> {
|
||||
return if (value.hasPersianLetters()) {
|
||||
|
||||
listOf(
|
||||
TextPrintLine(label, PrintLine.LEFT, size, true),
|
||||
TextPrintLine(value, PrintLine.RIGHT, size, isBold)
|
||||
)
|
||||
} else {
|
||||
|
||||
listOf(
|
||||
TextPrintLine(label, PrintLine.LEFT, size, true),
|
||||
TextPrintLine(value, PrintLine.LEFT, size, isBold)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFontPath(resourceName: String): String {
|
||||
return try {
|
||||
// Find resource ID by name (e.g., "font") in the "font" resource folder
|
||||
val resId = context.resources.getIdentifier(resourceName, "font", context.packageName)
|
||||
if (resId == 0) {
|
||||
Log.e(TAG, "Font resource not found: $resourceName")
|
||||
return ""
|
||||
}
|
||||
|
||||
val fileName = "$resourceName.ttf"
|
||||
val fontFile = File(context.filesDir, fileName)
|
||||
if (!fontFile.exists()) {
|
||||
context.resources.openRawResource(resId).use { input ->
|
||||
fontFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
fontFile.absolutePath
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error loading font from resources: $resourceName", e)
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
override fun print(
|
||||
printEntities: List<PrintEntity>,
|
||||
@DrawableRes icon: Int
|
||||
) {
|
||||
val printerManager = initialP3Printer()
|
||||
|
||||
printerManager?.apply {
|
||||
setPrintGray(DEFAULT_PRINT_GRAY)
|
||||
setLineSpace(DEFAULT_LINE_SPACE)
|
||||
|
||||
// Reusable logo bitmap
|
||||
val logoBitmap = drawableToBitmap(context, icon, 200)
|
||||
if (logoBitmap != null) {
|
||||
val logoLine = BitmapPrintLine(logoBitmap, PrintLine.CENTER)
|
||||
addPrintLine(logoLine)
|
||||
addPrintLine(TextPrintLine(" ", 0, 10))
|
||||
}
|
||||
|
||||
val customFontPath = getFontPath("font")
|
||||
if (customFontPath.isNotEmpty()) {
|
||||
setPrintFont(customFontPath)
|
||||
} else {
|
||||
setPrintFont("/system/fonts/DroidSansMono.ttf") // Fallback
|
||||
}
|
||||
|
||||
val divider = drawableToBitmap(context, com.example.core.R.drawable.horizontal_line_svgrepo_com__1_)
|
||||
|
||||
printEntities.forEachIndexed { index, printEntity ->
|
||||
|
||||
// Header
|
||||
printEntity.title?.let {title->
|
||||
addPrintLine(
|
||||
TextPrintLine(
|
||||
title,
|
||||
if (title.hasPersianLetters()) PrintLine.LEFT else PrintLine.RIGHT
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
printEntity.items?.let {items->
|
||||
items.forEach { itemEntity ->
|
||||
addPrintLine(
|
||||
createPrintRow(
|
||||
label = itemEntity.label,
|
||||
value = itemEntity.value,
|
||||
size = 16,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (divider != null && printEntities.lastIndex > index) {
|
||||
val dividerLine = BitmapPrintLine(divider, PrintLine.CENTER)
|
||||
addPrintLine(TextPrintLine(" ", 0, 10))
|
||||
addPrintLine(dividerLine)
|
||||
addPrintLine(TextPrintLine(" ", 0, 10))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
addPrintLine(TextPrintLine(" ", 0, 60))
|
||||
|
||||
val listener = object : IPrinterListener {
|
||||
override fun onStart() {
|
||||
Log.d(TAG, "Print started")
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
Log.d(TAG, "Print finished")
|
||||
close()
|
||||
}
|
||||
|
||||
override fun onError(code: Int, msg: String?) {
|
||||
Log.e(TAG, "Print error - code: $code, message: $msg")
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
beginPrint(listener)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// private fun fixForPrinter(input: String): String {
|
||||
// val LRM = '\u200E' // کنترل جهت
|
||||
//
|
||||
// return input
|
||||
// .toPersianDigits() // همونی که قبل دادم
|
||||
// .replace(Regex("(\\d+)")) { matchResult ->
|
||||
// "$LRM${matchResult.value}$LRM"
|
||||
// }
|
||||
// }
|
||||
|
||||
private fun String.hasPersianLetters(): Boolean {
|
||||
return Regex("[\\u0621-\\u0628\\u062A-\\u063A\\u0641-\\u0642\\u0644-\\u0648\\u067E\\u0686\\u0698\\u06A9\\u06AF\\u06CC\\u0647]").containsMatchIn(
|
||||
this
|
||||
)
|
||||
}
|
||||
|
||||
private fun drawableToBitmap(context: Context, @DrawableRes drawableId: Int): Bitmap? {
|
||||
val drawable = ContextCompat.getDrawable(context, drawableId) ?: return null
|
||||
|
||||
if (drawable is BitmapDrawable) {
|
||||
return drawable.bitmap
|
||||
}
|
||||
|
||||
val bitmap = createBitmap(
|
||||
drawable.intrinsicWidth.coerceAtLeast(1),
|
||||
drawable.intrinsicHeight.coerceAtLeast(1)
|
||||
)
|
||||
val canvas = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
drawable.draw(canvas)
|
||||
|
||||
return bitmap
|
||||
}
|
||||
|
||||
private fun drawableToBitmap(
|
||||
context: Context,
|
||||
@DrawableRes drawableId: Int,
|
||||
maxWidth: Int = 300
|
||||
): Bitmap? {
|
||||
val drawable = ContextCompat.getDrawable(context, drawableId) ?: return null
|
||||
|
||||
val original = if (drawable is BitmapDrawable) {
|
||||
drawable.bitmap
|
||||
} else {
|
||||
val bitmap = createBitmap(
|
||||
drawable.intrinsicWidth.coerceAtLeast(1),
|
||||
drawable.intrinsicHeight.coerceAtLeast(1)
|
||||
)
|
||||
val canvas = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
drawable.draw(canvas)
|
||||
bitmap
|
||||
}
|
||||
|
||||
// 👇 scale با حفظ نسبت
|
||||
val ratio = maxWidth.toFloat() / original.width
|
||||
val width = maxWidth
|
||||
val height = (original.height * ratio).toInt()
|
||||
|
||||
return Bitmap.createScaledBitmap(original, width, height, true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.example.p3_pardakht_novin
|
||||
|
||||
import android.device.PrinterManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.util.Log
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonParser
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Helper class for interacting with the POS Printer.
|
||||
*/
|
||||
@Singleton
|
||||
class P3PardakhtNovinPrinterHelper @Inject constructor() {
|
||||
private var printer: PrinterManager? = null
|
||||
private val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
|
||||
private val textPaint = Paint().apply {
|
||||
textSize = 24f
|
||||
color = Color.BLACK
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
init {
|
||||
detectPrinter()
|
||||
}
|
||||
|
||||
private fun detectPrinter() {
|
||||
try {
|
||||
// Attempt to instantiate the manager
|
||||
val manager = PrinterManager()
|
||||
|
||||
try {
|
||||
// Test the implementation.
|
||||
// We use getStatus() to see if it's the system implementation or the stub.
|
||||
val status = manager.getStatus()
|
||||
printer = manager
|
||||
Log.d("PrinterHelper", "PrinterManager initialized. Initial status: $status")
|
||||
} catch (e: Exception) {
|
||||
val errorMsg = e.cause?.message ?: e.message ?: ""
|
||||
Log.w("PrinterHelper", "getStatus() failed with: $errorMsg")
|
||||
|
||||
if (errorMsg.contains("stub", ignoreCase = true)) {
|
||||
Log.e("PrinterHelper", "CRITICAL: The SDK JAR is being bundled in your APK. You must Clean and Rebuild.")
|
||||
} else {
|
||||
// If it's not a 'stub' error, it might just be the hardware state
|
||||
printer = manager
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.e("PrinterHelper", "PrinterManager not available on this device: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun getStatusMessage(code: Int): String {
|
||||
return when (code) {
|
||||
0 -> "Printer is healthy"
|
||||
-1 -> "No paper in printer"
|
||||
2 -> "Device is overheating"
|
||||
-3 -> "Battery is too low to print"
|
||||
else -> "Hardware error code: $code"
|
||||
}
|
||||
}
|
||||
|
||||
fun printJson(jsonData: String, onStatus: (String) -> Unit) {
|
||||
try {
|
||||
val jsonElement: JsonElement = JsonParser.parseString(jsonData)
|
||||
val prettyJson = gson.toJson(jsonElement)
|
||||
printLines(prettyJson.split("\n"), onStatus)
|
||||
} catch (e: Exception) {
|
||||
onStatus("Invalid JSON data provided.")
|
||||
}
|
||||
}
|
||||
|
||||
fun printLines(lines: List<String>, onStatus: (String) -> Unit) {
|
||||
if (printer == null) {
|
||||
detectPrinter()
|
||||
}
|
||||
|
||||
val currentPrinter = printer
|
||||
if (currentPrinter == null) {
|
||||
onStatus("Printer hardware not supported or SDK issue.")
|
||||
return
|
||||
}
|
||||
|
||||
Log.d("PrinterHelper", "Starting print job...")
|
||||
var bitmap: Bitmap? = null
|
||||
try {
|
||||
currentPrinter.open()
|
||||
currentPrinter.setupPage(384, -1)
|
||||
currentPrinter.clearPage()
|
||||
|
||||
val lineHeight = 32
|
||||
val totalHeight = (lines.size * lineHeight) + 40
|
||||
|
||||
bitmap = Bitmap.createBitmap(384, totalHeight, Bitmap.Config.ARGB_8888)
|
||||
bitmap.eraseColor(Color.WHITE)
|
||||
val canvas = Canvas(bitmap)
|
||||
|
||||
var yPos = 30f
|
||||
for (line in lines) {
|
||||
canvas.drawText(line, 10f, yPos, textPaint)
|
||||
yPos += lineHeight
|
||||
}
|
||||
|
||||
currentPrinter.drawBitmap(bitmap, 0, 0)
|
||||
val printResult = currentPrinter.printPage(0)
|
||||
|
||||
if (printResult == 0) {
|
||||
currentPrinter.paperFeed(20)
|
||||
onStatus("Print Job Completed Successfully")
|
||||
} else {
|
||||
onStatus(getStatusMessage(printResult))
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
onStatus("Printer Error: ${e.localizedMessage}")
|
||||
Log.e("PrinterHelper", "Unexpected error during printing", e)
|
||||
} finally {
|
||||
bitmap?.recycle()
|
||||
try {
|
||||
currentPrinter.close()
|
||||
} catch (e: Throwable) {}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fun printText(): Int {
|
||||
val manager = printer
|
||||
val mCenterPaint = Paint().apply {
|
||||
textSize = 30f
|
||||
color = Color.BLACK
|
||||
}
|
||||
var mBitmap: Bitmap? = null
|
||||
manager.setupPage(384, -1)
|
||||
manager.clearPage()
|
||||
|
||||
val mconfig = Bitmap.Config.ARGB_8888
|
||||
mBitmap = Bitmap.createBitmap(384, 2000, mconfig)
|
||||
mBitmap.eraseColor(-0x1) // 0xffffffff
|
||||
|
||||
val mCanvas = Canvas(mBitmap)
|
||||
|
||||
mCanvas.drawText("سلام", 330f, 30f, mCenterPaint)
|
||||
mCanvas.drawText("حال شما خوب هست", 165f, 60f, mCenterPaint)
|
||||
mCanvas.drawText("سلام", 330f, 90f, mCenterPaint)
|
||||
mCanvas.drawText("حال شما خوب هست", 165f, 120f, mCenterPaint)
|
||||
|
||||
var mirrored: Bitmap? = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.width, 130)
|
||||
manager.drawBitmap(mirrored, 0, 0)
|
||||
|
||||
val ret = manager.printPage(0)
|
||||
manager.paperFeed(15)
|
||||
|
||||
mBitmap?.let {
|
||||
it.recycle()
|
||||
mBitmap = null
|
||||
}
|
||||
|
||||
mirrored?.let {
|
||||
it.recycle()
|
||||
mirrored = null
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.example.p3_pardakht_novin
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.activity.result.ActivityResult
|
||||
import com.example.core.domain.PspService
|
||||
import com.example.core.model.PaymentResultEntity
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import javax.inject.Inject
|
||||
|
||||
class P3PardakhtNovinService @Inject constructor(
|
||||
private val p3PardakhtNovinIapPosService: P3PardakhtNovinIapPosService,
|
||||
private val p3PardakhtNovinPrinterHelper: P3PardakhtNovinPrinterHelper
|
||||
) : PspService {
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
override fun pay(amount: Long): Intent {
|
||||
return p3PardakhtNovinIapPosService.startPurchase(amount)
|
||||
|
||||
}
|
||||
|
||||
override fun print(lines: List<String>, onMessage: (String) -> Unit) {
|
||||
p3PardakhtNovinPrinterHelper.printLines(lines) { status ->
|
||||
onMessage("P3 Printer: $status")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun decodePosResponse(
|
||||
id: String?,
|
||||
activityResult: ActivityResult
|
||||
): PaymentResultEntity {
|
||||
return try {
|
||||
p3PardakhtNovinIapPosService.decodeResponse(
|
||||
id = id,
|
||||
activityResult = activityResult
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
PaymentResultEntity.failure(
|
||||
id = id,
|
||||
e.message
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.example.p3_pardakht_novin.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object P3PardakhtNovinModule {
|
||||
// P3PardakhtNovinIapPosService is provided via @Inject constructor
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.p3.models
|
||||
package com.example.p3_pardakht_novin.models
|
||||
|
||||
data class IapAccountSplit(
|
||||
val index: Int,
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.p3
|
||||
package com.example.p3_pardakht_novin
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,49 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.hilt)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.p3_sepehr"
|
||||
compileSdk {
|
||||
version = release(36) {
|
||||
minorApiLevel = 1
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core"))
|
||||
implementation(libs.hilt.android)
|
||||
ksp(libs.hilt.compiler)
|
||||
|
||||
implementation(libs.retrofit.converter.gson)
|
||||
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.material)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.example.p3_pardakht_novin
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.example.ps3.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.PRINTER" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.example.p3_pardakht_novin
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.activity.result.ActivityResult
|
||||
import com.example.core.model.PaymentResultEntity
|
||||
import com.example.core.model.PaymentResultStatus
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class P3SepehrIapPosService @Inject constructor(
|
||||
@param:ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
fun isPosAppInstalled(packageName: String = "com.dml.sima7.sepehr"): Boolean {
|
||||
return try {
|
||||
context.packageManager.getPackageInfo(packageName, 0)
|
||||
true
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun startPurchase(
|
||||
amount: Long,
|
||||
packageName: String = "com.dml.sima7.sepehr"
|
||||
): Intent {
|
||||
return Intent("com.dml.sima7.sepehr.activity.Intent_SwipeCardActivity").apply {
|
||||
setPackage(packageName)
|
||||
putExtra("amount", amount.toString())
|
||||
setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeResponse(
|
||||
id: String?,
|
||||
activityResult: ActivityResult
|
||||
): PaymentResultEntity {
|
||||
val data = activityResult.data
|
||||
|
||||
if (data == null) {
|
||||
return PaymentResultEntity.failure(
|
||||
id = id,
|
||||
message = "پاسخی از دستگاه دریافت نشد!"
|
||||
)
|
||||
}
|
||||
|
||||
val resultCode = data.getStringExtra("resultCode")
|
||||
val resultDescription = data.getStringExtra("resultDescription") ?: "خطای نامشخص"
|
||||
|
||||
// resultCode "00" usually means success in most Iranian PSPs
|
||||
val isSuccess = resultCode == "00"
|
||||
|
||||
return if (isSuccess) {
|
||||
PaymentResultEntity(
|
||||
id = id,
|
||||
status = PaymentResultStatus.SUCCESS,
|
||||
paymentResultDataEntity = PaymentResultEntity.PaymentResultDataEntity(
|
||||
terminalId = data.getStringExtra("terminalNo") ?: "",
|
||||
stan = data.getStringExtra("traceNo") ?: "",
|
||||
rrn = data.getStringExtra("refrenceCode") ?: "",
|
||||
responseCode = resultCode,
|
||||
customerCardNO = data.getStringExtra("maskedPan") ?: "",
|
||||
transactionDateTime = data.getStringExtra("DateTime") ?: "",
|
||||
),
|
||||
message = resultDescription
|
||||
)
|
||||
} else {
|
||||
PaymentResultEntity.failure(
|
||||
id = id,
|
||||
message = resultDescription
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.p3
|
||||
package com.example.p3_pardakht_novin
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
@@ -20,7 +20,7 @@ import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class P3Printer @Inject constructor(
|
||||
class P3SepehrPrinter @Inject constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
) : Printer {
|
||||
companion object {
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.p3
|
||||
package com.example.p3_pardakht_novin
|
||||
|
||||
import android.device.PrinterManager
|
||||
import android.graphics.Bitmap
|
||||
@@ -16,7 +16,7 @@ import javax.inject.Singleton
|
||||
* Helper class for interacting with the POS Printer.
|
||||
*/
|
||||
@Singleton
|
||||
class PrinterHelper @Inject constructor() {
|
||||
class P3SepehrPrinterHelper @Inject constructor() {
|
||||
private var printer: PrinterManager? = null
|
||||
private val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.p3
|
||||
package com.example.p3_pardakht_novin
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.activity.result.ActivityResult
|
||||
@@ -8,20 +8,20 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import javax.inject.Inject
|
||||
|
||||
class P3Service @Inject constructor(
|
||||
private val iapPosService: IapPosService,
|
||||
private val printerHelper: PrinterHelper
|
||||
class P3SepehrService @Inject constructor(
|
||||
private val p3SepehrIapPosService: P3SepehrIapPosService,
|
||||
private val p3SepehrPrinterHelper: P3SepehrPrinterHelper
|
||||
) : PspService {
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
override fun pay(amount: Long): Intent {
|
||||
return iapPosService.startPurchase(amount)
|
||||
return p3SepehrIapPosService.startPurchase(amount)
|
||||
|
||||
}
|
||||
|
||||
override fun print(lines: List<String>, onMessage: (String) -> Unit) {
|
||||
printerHelper.printLines(lines) { status ->
|
||||
p3SepehrPrinterHelper.printLines(lines) { status ->
|
||||
onMessage("P3 Printer: $status")
|
||||
}
|
||||
}
|
||||
@@ -30,14 +30,18 @@ class P3Service @Inject constructor(
|
||||
override fun decodePosResponse(
|
||||
id: String?,
|
||||
activityResult: ActivityResult
|
||||
): PaymentResultEntity? {
|
||||
): PaymentResultEntity {
|
||||
return try {
|
||||
iapPosService.decodeResponse(
|
||||
p3SepehrIapPosService.decodeResponse(
|
||||
id = id,
|
||||
activityResult = activityResult
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
e.printStackTrace()
|
||||
PaymentResultEntity.failure(
|
||||
id = id,
|
||||
e.message
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.example.p3.di
|
||||
package com.example.p3_pardakht_novin.di
|
||||
|
||||
import android.content.Context
|
||||
import com.example.p3.IapPosService
|
||||
import com.example.p3_pardakht_novin.P3SepehrIapPosService
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -11,11 +11,11 @@ import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object P3Module {
|
||||
object P3SepehrModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIapPosService(@ApplicationContext context: Context): IapPosService {
|
||||
return IapPosService(context)
|
||||
fun provideIapPosService(@ApplicationContext context: Context): P3SepehrIapPosService {
|
||||
return P3SepehrIapPosService(context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.example.p3_pardakht_novin.models
|
||||
|
||||
data class IapAccountSplit(
|
||||
val index: Int,
|
||||
val percent: Int
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.example.p3_pardakht_novin
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,92 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.hilt)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.pardakht_novin_app"
|
||||
compileSdk {
|
||||
version = release(36) {
|
||||
minorApiLevel = 1
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.example.pardakht_novin_app"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
flavorDimensions.add("module")
|
||||
productFlavors {
|
||||
create("p3_pardakht_novin") {
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
dimension = "module"
|
||||
applicationId = "com.example.pardakht_novin_app_p3_pardakht_novin"
|
||||
manifestPlaceholders["appName"] = "پرداخت نوین"
|
||||
}
|
||||
create("p3_sepehr") {
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
dimension = "module"
|
||||
applicationId = "com.example.pardakht_novin_app_p3_sepehr"
|
||||
manifestPlaceholders["appName"] = "تیس (سپهر)"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core"))
|
||||
implementation(project(":design_system"))
|
||||
|
||||
"p3_pardakht_novinImplementation"(project(":p3:p3_pardakht_novin"))
|
||||
"p3_sepehrImplementation"(project(":p3:p3_sepehr"))
|
||||
|
||||
implementation(libs.hilt.android)
|
||||
ksp(libs.hilt.compiler)
|
||||
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation(libs.androidx.compose.ui)
|
||||
implementation(libs.androidx.compose.ui.graphics)
|
||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.retrofit.converter.gson)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.example.pardakht_novin_app
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.example.pardakht_novin_app", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent>
|
||||
<package android:name="ir.co.pna.pos" />
|
||||
<intent>
|
||||
<action android:name="ir.co.pna.pos.view.cart.IAPCActivity" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<uses-permission android:name="android.permission.PRINTER" />
|
||||
<uses-permission android:name="com.pos.permission.PRINTER" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name=".ui.PspApplication"
|
||||
android:allowBackup="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.PSP"
|
||||
android:usesCleartextTraffic="true"
|
||||
|
||||
>
|
||||
<uses-library
|
||||
android:name="android.device"
|
||||
android:required="false" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.PardakhtNovinMainActivity"
|
||||
android:exported="true"
|
||||
android:label="${appName}"
|
||||
android:theme="@style/Theme.PSP">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
After Width: | Height: | Size: 116 KiB |
@@ -0,0 +1,69 @@
|
||||
package com.example.pardakht_novin_app.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.example.design_system.theme.tis.TisTheme
|
||||
import com.example.design_system.util.ObserveAsEvents
|
||||
import com.example.design_system.util.snackbar.SnackbarController
|
||||
import com.example.pardakht_novin_app.ui.screen.PardakhtNovinWebViewScreen
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PardakhtNovinMainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
TisTheme {
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val snackbarHostState = remember {
|
||||
SnackbarHostState()
|
||||
}
|
||||
ObserveAsEvents(
|
||||
flow = SnackbarController.events,
|
||||
snackbarHostState
|
||||
) { event ->
|
||||
scope.launch {
|
||||
snackbarHostState.currentSnackbarData?.dismiss()
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = event.message,
|
||||
actionLabel = event.action?.name,
|
||||
duration = event.duration
|
||||
)
|
||||
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
event.action?.action?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = {
|
||||
SnackbarHost(
|
||||
hostState = snackbarHostState
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
|
||||
|
||||
PardakhtNovinWebViewScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.example.pardakht_novin_app.ui
|
||||
|
||||
import android.app.Application
|
||||
import com.example.design_system.util.WebViewManager
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
||||
@HiltAndroidApp
|
||||
class PspApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
WebViewManager.preload(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.example.pardakht_novin_app.ui.screen
|
||||
|
||||
import android.util.Log
|
||||
import android.webkit.WebView
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.example.core.getAndroidId
|
||||
import com.example.core.getDeviceName
|
||||
import com.example.design_system.PspWebView
|
||||
import com.example.pardakht_novin_app.ui.viewmodel.WebViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val tisWebUrl = "https://tis.shift-am.ir"
|
||||
private const val tisWebViewTag = "TisWebViewScreen"
|
||||
|
||||
@Composable
|
||||
fun PardakhtNovinWebViewScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: WebViewModel = hiltViewModel()
|
||||
) {
|
||||
|
||||
val context = LocalContext.current
|
||||
var webView: WebView? by remember { mutableStateOf(null) }
|
||||
var currentPaymentId by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var isPaymentProcessing by rememberSaveable { mutableStateOf(false) }
|
||||
var isPrinting by rememberSaveable { mutableStateOf(false) }
|
||||
var initialCallbackSent by rememberSaveable { mutableStateOf(false) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val deviceInfo by remember {
|
||||
mutableStateOf(
|
||||
viewModel.deviceInfoJson(
|
||||
androidId = getAndroidId(context),
|
||||
getDeviceName()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// launcher to handle POS app result
|
||||
val posLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
val paymentResultEntity = viewModel.paymentResult(
|
||||
id = currentPaymentId,
|
||||
activityResult = result
|
||||
)
|
||||
val jsResult = viewModel.postPaymentResult(paymentResultEntity)
|
||||
webView?.let {
|
||||
it.post {
|
||||
it.evaluateJavascript(jsResult, null)
|
||||
}
|
||||
} ?: run {
|
||||
Log.w(tisWebViewTag, "Skipping onPaymentResult JS callback: WebView is not ready yet.")
|
||||
}
|
||||
isPaymentProcessing = false
|
||||
}
|
||||
|
||||
PspWebView(
|
||||
url = tisWebUrl,
|
||||
modifier = modifier,
|
||||
onPayClick = { amount: Long, id: String? ->
|
||||
if (isPaymentProcessing) return@PspWebView
|
||||
isPaymentProcessing = true
|
||||
currentPaymentId = id
|
||||
viewModel.pay(amount)
|
||||
.also(posLauncher::launch)
|
||||
},
|
||||
onPrintClick = { printEntities ->
|
||||
if (isPrinting) return@PspWebView
|
||||
isPrinting = true
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
viewModel.print(printEntities)
|
||||
} finally {
|
||||
isPrinting = false
|
||||
}
|
||||
}
|
||||
},
|
||||
onDeviceInfo = {
|
||||
deviceInfo
|
||||
},
|
||||
onWebViewReady = { readyWebView ->
|
||||
webView = readyWebView
|
||||
},
|
||||
onPageFinished = { loadedWebView ->
|
||||
if (!initialCallbackSent) {
|
||||
loadedWebView.evaluateJavascript(viewModel.postPaymentResult(null), null)
|
||||
initialCallbackSent = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.example.pardakht_novin_app.ui.viewmodel
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.example.core.domain.PaymentResult
|
||||
import com.example.core.domain.Printer
|
||||
import com.example.core.domain.PspService
|
||||
import com.example.core.model.DeviceInfo
|
||||
import com.example.core.model.PaymentResultEntity
|
||||
import com.example.core.model.PrintEntity
|
||||
import com.example.pardakht_novin_app.R
|
||||
import com.google.gson.Gson
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class WebViewModel @Inject constructor(
|
||||
private val pspService: PspService,
|
||||
private val paymentResult: PaymentResult,
|
||||
private val printer: Printer,
|
||||
private val gson: Gson
|
||||
) : ViewModel() {
|
||||
|
||||
|
||||
fun postPaymentResult(paymentResultEntity: PaymentResultEntity?): String {
|
||||
return paymentResult.post(paymentResultEntity)
|
||||
}
|
||||
|
||||
fun pay(amount: Long): Intent = pspService.pay(amount)
|
||||
|
||||
fun paymentResult(
|
||||
id: String?,
|
||||
activityResult: ActivityResult
|
||||
): PaymentResultEntity {
|
||||
return pspService.decodePosResponse(
|
||||
id = id,
|
||||
activityResult = activityResult
|
||||
)
|
||||
}
|
||||
|
||||
fun print(printEntities: List<PrintEntity>) {
|
||||
printer.print(
|
||||
printEntities = printEntities,
|
||||
icon = R.drawable.ic_pardakht_novin
|
||||
)
|
||||
}
|
||||
|
||||
fun deviceInfoJson(androidId: String, deviceName: String): String {
|
||||
return gson.toJson(
|
||||
DeviceInfo(
|
||||
androidId = androidId,
|
||||
deviceName = deviceName
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,381 @@
|
||||
<!--
|
||||
~ Copyright (C) 2026 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="454dp"
|
||||
android:height="454dp"
|
||||
android:viewportWidth="454"
|
||||
android:viewportHeight="454">
|
||||
<path
|
||||
android:pathData="M0,0C149.82,0 299.64,0 454,0C454,149.82 454,299.64 454,454C304.18,454 154.36,454 0,454C0,304.18 0,154.36 0,0Z"
|
||||
android:fillColor="#FEFEFE"/>
|
||||
<path
|
||||
android:pathData="M346,64C350.79,65.65 354.79,68.25 359,71C359.75,71.49 360.5,71.98 361.27,72.48C368.13,76.99 374.6,81.86 381,87C382.08,87.85 383.16,88.7 384.25,89.55C390.02,94.16 390.02,94.16 392,96C392,96.66 392,97.32 392,98C392.54,98.21 393.09,98.43 393.65,98.64C396.85,100.49 399.15,102.99 401.63,105.69C402.15,106.26 402.68,106.83 403.22,107.42C408.91,113.79 413.55,120.73 418,128C418.55,128.89 418.55,128.89 419.11,129.8C426.12,141.3 431.3,153.21 435.25,166.06C435.5,166.85 435.74,167.64 436,168.46C439.56,180.53 440.41,192.61 440.38,205.13C440.37,205.85 440.37,206.57 440.37,207.32C440.33,218.96 439.46,229.84 436,241C435.76,241.81 435.52,242.61 435.27,243.44C419.51,295.08 377.17,334.7 330.63,359.54C329.14,360.32 329.14,360.32 327.63,361.13C326.7,361.62 325.78,362.11 324.84,362.61C311.99,369.3 298.62,374.02 284.75,378.06C284.02,378.28 283.29,378.49 282.53,378.71C252.7,387.39 223.01,390.69 192,390C191.14,389.99 190.29,389.97 189.41,389.96C153.63,389.27 117.06,379.94 86,362C86,361.01 86,360.02 86,359C87.12,358.42 88.23,357.83 89.38,357.23C110.46,346.2 131.29,334.71 152,323C153.25,322.29 153.25,322.29 154.53,321.57C168.91,313.41 182.98,304.74 197,296C198.07,295.33 198.07,295.33 199.17,294.65C220.79,281.11 241.36,266.28 261,250C261.89,249.26 261.89,249.26 262.81,248.5C272.92,240.08 282.52,231.12 292,222C292.59,221.44 293.17,220.87 293.78,220.29C299.88,214.41 305.59,208.34 310.87,201.69C312.26,199.93 313.68,198.2 315.13,196.5C332.26,176.36 347.38,151.63 351,125C351.16,123.8 351.16,123.8 351.33,122.57C353.38,105.03 351.23,87.31 346.8,70.31C346,67 346,67 346,64Z"
|
||||
android:fillColor="#8677B7"/>
|
||||
<path
|
||||
android:pathData="M178.94,92.56C180.12,92.55 181.3,92.54 182.52,92.53C202.81,92.73 222.63,98.75 237.5,112.94C241.59,117.25 244.54,121.61 247,127C247.46,127.96 247.46,127.96 247.93,128.94C254.79,144 250.83,161.34 245.53,176.22C234.2,206.34 209.51,232.77 185,253C183.78,254.02 183.78,254.02 182.53,255.06C172.34,263.49 161.9,271.51 151,279C150.05,279.67 149.1,280.35 148.13,281.04C139.73,286.97 131.06,292.38 122.23,297.66C120.18,298.89 118.13,300.14 116.09,301.39C69.82,329.49 69.82,329.49 52,333C40.18,328.3 30.02,311.19 25.17,300.11C11.95,268.2 11.44,233.78 23.93,201.58C31.3,183.8 41.89,168.95 55,155C56.22,153.61 57.42,152.21 58.63,150.81C62.21,146.7 65.87,142.57 70,139C70.66,139 71.32,139 72,139C72,138.34 72,137.68 72,137C74.09,135.19 76.18,133.49 78.38,131.81C79.02,131.32 79.66,130.82 80.32,130.31C109.57,107.86 141.53,92.77 178.94,92.56Z"
|
||||
android:fillColor="#897BB8"/>
|
||||
<path
|
||||
android:pathData="M408,115C412.37,118.99 415.34,123.31 418.38,128.38C418.86,129.17 419.34,129.97 419.83,130.79C427.61,143.91 433.02,157.3 437,172C437.34,173.26 437.69,174.53 438.04,175.83C442.54,196.27 442.2,221.02 436,241C435.76,241.81 435.52,242.61 435.27,243.44C422.99,283.68 393.77,317.45 359.94,341.56C359.19,342.1 358.44,342.64 357.66,343.19C356.96,343.69 356.25,344.18 355.53,344.69C354.9,345.14 354.26,345.58 353.61,346.04C352,347 352,347 350,347C352.09,335.73 354.76,324.66 357.58,313.55C360.91,300.48 364.2,287.42 367.13,274.25C369.42,263.95 372.25,253.82 375.16,243.68C378.08,233.5 380.64,223.28 382.9,212.93C385.13,202.79 387.97,192.79 390.7,182.77C393.89,171 396.96,159.22 399.74,147.34C401.18,141.17 402.71,135.02 404.25,128.88C404.51,127.84 404.77,126.8 405.03,125.73C405.28,124.76 405.52,123.8 405.77,122.8C405.98,121.95 406.19,121.09 406.42,120.21C406.88,118.46 407.43,116.72 408,115Z"
|
||||
android:fillColor="#978DC2"/>
|
||||
<path
|
||||
android:pathData="M263,248C263.99,249.49 263.99,249.49 265,251C263.24,252.86 261.52,254.35 259.44,255.88C256.83,257.8 256.83,257.8 256.06,260.63C255.71,261.41 255.36,262.19 255,263C252,264 252,264 249,265C247.7,267.49 247.7,267.49 247,270C245.67,270.67 244.33,271.33 243,272C243,271.01 243,270.02 243,269C242.34,269.33 241.68,269.66 241,270C240.67,270.99 240.34,271.98 240,273C239.01,273 238.02,273 237,273C237,273.66 237,274.32 237,275C235,277 235,277 233,278C232.67,278.66 232.34,279.32 232,280C231.28,279.83 230.56,279.67 229.81,279.5C226.92,279.7 226.92,279.7 225.55,281.78C224.3,283.82 223.11,285.88 222,288C221.01,287.34 220.02,286.68 219,286C217.89,286.64 216.77,287.28 215.63,287.94C213.77,289.01 211.92,290.04 210,291C209.67,291.99 209.34,292.98 209,294C209.66,294.33 210.32,294.66 211,295C209.02,295.99 209.02,295.99 207,297C207,298.65 207,300.3 207,302C206.01,302.49 206.01,302.49 205,303C204.01,306.46 203.21,309.89 202.54,313.43C202.3,314.61 202.3,314.61 202.06,315.81C201.93,316.54 201.8,317.27 201.66,318.02C201,320 201,320 198,323C198.66,318.71 199.32,314.42 200,310C198.69,313.94 197.63,317.36 196.88,321.38C196.71,322.25 196.54,323.12 196.37,324.02C196.19,325 196.19,325 196,326C196.33,325.34 196.66,324.68 197,324C197.66,324.33 198.32,324.66 199,325C199.2,331.05 199.2,331.05 199,333C198.34,333.66 197.68,334.32 197,335C195.89,338.35 194.95,341.73 194,345.13C193,348 193,348 191,349C190.67,348.34 190.34,347.68 190,347C189.51,348.48 189.51,348.48 189,350C189.99,350 190.98,350 192,350C191,353 191,353 189,355C189.66,355 190.32,355 191,355C190.79,355.62 190.59,356.24 190.38,356.88C190.19,357.93 190.19,357.93 190,359C190.66,359.66 191.32,360.32 192,361C191.63,363.63 191.63,363.63 191,366C190.34,366 189.68,366 189,366C187.12,369.27 186.51,372.28 186,376C185.34,375.01 184.68,374.02 184,373C183.67,374.65 183.34,376.3 183,378C183.66,378 184.32,378 185,378C184.67,380.31 184.34,382.62 184,385C190.22,386.99 195.79,387.11 202.3,387C207.9,387 213.43,387.48 219,388C219.33,387.34 219.66,386.68 220,386C222.66,384.13 223.78,383.97 227.06,384.44C228.03,384.62 229,384.81 230,385C230.99,384.67 231.98,384.34 233,384C233.66,384.33 234.32,384.66 235,385C242.32,385.55 248.93,383.81 256,382C256,381.34 256,380.68 256,380C257.18,379.57 257.18,379.57 258.38,379.13C261.1,378.15 261.1,378.15 263,376C263.99,376 264.98,376 266,376C266.33,376.99 266.66,377.98 267,379C269,380.21 269,380.21 271,381C249.86,392.77 215.56,390.52 192,390C191.14,389.99 190.29,389.97 189.41,389.96C153.63,389.27 117.06,379.94 86,362C86,361.01 86,360.02 86,359C87.12,358.42 88.23,357.83 89.38,357.23C110.46,346.2 131.29,334.71 152,323C152.84,322.53 153.67,322.05 154.53,321.57C168.91,313.41 182.98,304.74 197,296C197.72,295.55 198.43,295.11 199.17,294.65C232.85,273.65 232.85,273.65 263,248ZM187,358C186.17,360.89 186,362.89 186,366C186.99,365.67 187.98,365.34 189,365C188.67,362.69 188.34,360.38 188,358C187.67,358 187.34,358 187,358Z"
|
||||
android:fillColor="#9287C0"/>
|
||||
<path
|
||||
android:pathData="M148,325C148.99,326.48 148.99,326.48 150,328C147.35,329.46 146.11,330 143,330C143,331.65 143,333.3 143,335C138.25,336 138.25,336 136,336C134.41,337.45 132.99,339.01 131.56,340.63C129.11,342.78 126.6,342.91 123.44,343.38C120.69,344.08 119.71,344.78 118,347C117.5,348.48 117.5,348.48 117,350C112.88,352.25 108.6,352.53 104,353C104,353.99 104,354.98 104,356C103.34,356.33 102.68,356.66 102,357C102.33,357.99 102.66,358.98 103,360C102.34,360.66 101.68,361.32 101,362C100.34,360.68 99.68,359.36 99,358C98.01,358.49 98.01,358.49 97,359C97.7,361.51 97.7,361.51 99,364C101.04,364.89 101.04,364.89 103,365C103,365.66 103,366.32 103,367C106.51,368.69 106.51,368.69 110.31,368.63C110.87,368.42 111.43,368.21 112,368C112.33,368.99 112.66,369.98 113,371C115.97,370.51 115.97,370.51 119,370C119.33,370.66 119.66,371.32 120,372C119.34,372 118.68,372 118,372C118.33,372.66 118.66,373.32 119,374C119.99,374.33 120.98,374.66 122,375C122,375.66 122,376.32 122,377C116.11,376.41 111.12,374.15 105.75,371.81C104.82,371.41 103.88,371.01 102.92,370.59C96.63,367.83 90.67,364.9 85,361C87.78,357.66 91.52,356 95.31,354.06C96.86,353.26 98.4,352.45 99.94,351.65C100.76,351.22 101.58,350.79 102.43,350.35C117.82,342.26 132.94,333.69 148,325ZM114,347C115,349 115,349 115,349Z"
|
||||
android:fillColor="#ACA6D2"/>
|
||||
<path
|
||||
android:pathData="M239,183C239.99,183.49 239.99,183.49 241,184C240.46,188.83 238.19,192.42 235.63,196.44C235.18,197.14 234.73,197.84 234.27,198.57C218.72,222.67 197.41,243.32 175,261C174.34,260.67 173.68,260.34 173,260C174.31,257.5 174.31,257.5 176,255C176.99,255 177.98,255 179,255C179.33,254.01 179.66,253.02 180,252C181.32,252 182.64,252 184,252C184.08,251.11 184.17,250.23 184.25,249.31C185.25,244.9 187.42,242.52 190.63,239.44C192.22,237.77 193.06,236.09 194,234C194.66,234.33 195.32,234.66 196,235C197.22,235.07 197.22,235.07 199,234C198.67,233.01 198.34,232.02 198,231C199.1,230.21 200.21,229.42 201.31,228.63C201.93,228.18 202.54,227.74 203.18,227.29C204.77,226.17 206.38,225.08 208,224C208.08,223.36 208.17,222.72 208.25,222.06C208.5,221.38 208.74,220.7 209,220C210.15,219.53 211.31,219.05 212.5,218.56C214.23,217.79 214.23,217.79 216,217C216.83,214.53 216.83,214.53 217,212C218.56,210.19 218.56,210.19 220,209C220.99,209.66 221.98,210.32 223,211C223,210.01 223,209.02 223,208C223.66,208 224.32,208 225,208C224.75,207.42 224.51,206.85 224.25,206.25C224.17,205.51 224.09,204.76 224,204C225.75,201.94 225.75,201.94 228,200C229.76,198.31 230.91,197.19 232,195C232.66,195 233.32,195 234,195C233.47,193.64 233.47,193.64 232.94,192.25C232.04,189.12 231.87,187.95 233,185C233.99,185 234.98,185 236,185C236.49,185.99 236.49,185.99 237,187C237,186.01 237,185.02 237,184C237.66,183.67 238.32,183.34 239,183Z"
|
||||
android:fillColor="#786AA5"/>
|
||||
<path
|
||||
android:pathData="M263,248C263.66,248.99 264.32,249.98 265,251C263.24,252.86 261.52,254.35 259.44,255.88C256.83,257.8 256.83,257.8 256.06,260.63C255.71,261.41 255.36,262.19 255,263C252,264 252,264 249,265C247.7,267.49 247.7,267.49 247,270C245.67,270.67 244.33,271.33 243,272C243,271.01 243,270.02 243,269C242.01,269.49 242.01,269.49 241,270C240.51,271.48 240.51,271.48 240,273C239.01,273 238.02,273 237,273C237,273.66 237,274.32 237,275C235,277 235,277 233,278C232.67,278.66 232.34,279.32 232,280C231.28,279.83 230.56,279.67 229.81,279.5C226.92,279.7 226.92,279.7 225.55,281.78C224.3,283.82 223.11,285.88 222,288C221.01,287.34 220.02,286.68 219,286C217.89,286.64 216.77,287.28 215.63,287.94C213.77,289.01 211.92,290.04 210,291C209.67,291.99 209.34,292.98 209,294C209.66,294.33 210.32,294.66 211,295C209.68,295.66 208.36,296.32 207,297C207,298.65 207,300.3 207,302C206.01,302.49 206.01,302.49 205,303C204.27,305.31 203.6,307.65 203,310C201.73,306.36 202.27,303.61 203,299.88C204.02,295.81 204.02,295.81 203,292C203.57,291.65 204.15,291.29 204.74,290.93C224.79,278.36 246.2,264.8 263,248Z"
|
||||
android:fillColor="#A098BF"/>
|
||||
<path
|
||||
android:pathData="M51,164C51.99,164.33 52.98,164.66 54,165C53.67,165.99 53.34,166.98 53,168C52.2,168.27 51.39,168.54 50.56,168.81C49.72,169.2 48.87,169.6 48,170C47.31,171.99 46.64,173.99 46,176C44.06,176.63 44.06,176.63 42,177C39.62,179.55 39,180.44 39,184C38.02,185.02 37.02,186.02 36,187C35.3,188.32 34.63,189.65 34,191C33.23,192.24 33.23,192.24 32.44,193.5C30.95,195.81 30.95,195.81 30.56,197.88C29.67,201.26 27.79,204 26,207C26.66,207.33 27.32,207.66 28,208C26.51,208.49 26.51,208.49 25,209C24.88,210.61 24.88,210.61 24.75,212.25C23.95,216.27 23.12,217.48 20,220C20.66,220.33 21.32,220.66 22,221C21.86,221.56 21.71,222.11 21.56,222.69C21.17,224.3 20.84,225.93 20.56,227.56C20,230 20,230 18,233C17.62,234.91 17.62,234.91 17.55,236.99C17.51,237.77 17.46,238.56 17.42,239.36C17.38,240.19 17.35,241.02 17.31,241.88C17.27,242.72 17.23,243.57 17.19,244.44C16.93,250.6 17.18,256.51 17.94,262.63C17.96,263.41 17.98,264.19 18,265C17.01,265.99 17.01,265.99 16,267C11.2,232.75 20.98,200.02 40.33,171.71C42.37,169.62 44.25,168.93 47,168C48.34,167.35 49.68,166.69 51,166C51,165.34 51,164.68 51,164Z"
|
||||
android:fillColor="#978EBB"/>
|
||||
<path
|
||||
android:pathData="M262,376C263.32,376 264.64,376 266,376C266.49,377.48 266.49,377.48 267,379C269,380.21 269,380.21 271,381C247.62,394.02 206.89,391.73 181,389C181.33,387.68 181.66,386.36 182,385C182.56,385.14 183.12,385.29 183.7,385.44C190.01,386.9 195.9,387.11 202.37,387C207.94,387 213.46,387.48 219,388C219.33,387.34 219.66,386.68 220,386C222.66,384.13 223.78,383.97 227.06,384.44C228.03,384.62 229,384.81 230,385C230.99,384.67 231.98,384.34 233,384C233.66,384.33 234.32,384.66 235,385C242.32,385.55 248.93,383.81 256,382C256,381.34 256,380.68 256,380C256.8,379.71 257.61,379.42 258.44,379.13C261.13,378.27 261.13,378.27 262,376Z"
|
||||
android:fillColor="#968EBD"/>
|
||||
<path
|
||||
android:pathData="M218,101C228.37,103.66 237.67,111.56 243.57,120.33C244.82,122.52 245.94,124.72 247,127C247.46,127.96 247.46,127.96 247.93,128.95C253.39,140.97 251.97,155.74 248,168C247.67,168 247.34,168 247,168C247.05,167.32 247.09,166.63 247.14,165.93C249,147.12 249,147.12 245,129C243.36,127.64 241.69,126.3 240,125C239.69,121.75 239.69,121.75 240,119C239.22,119.19 238.43,119.37 237.63,119.56C235,120 235,120 233,119C232.41,116.35 232.26,113.71 232,111C229.69,111.33 227.38,111.66 225,112C224.79,110.89 224.59,109.77 224.38,108.63C223.38,104.73 223.38,104.73 219.88,103.56C218.93,103.38 217.98,103.19 217,103C217.33,102.34 217.66,101.68 218,101Z"
|
||||
android:fillColor="#786AA3"/>
|
||||
<path
|
||||
android:pathData="M304,214C304,215.65 304,217.3 304,219C303.34,219 302.68,219 302,219C301.72,219.96 301.72,219.96 301.44,220.94C300,223 300,223 296.38,223.75C294.7,223.87 294.7,223.87 293,224C293.33,225.65 293.66,227.3 294,229C292.35,229.33 290.7,229.66 289,230C289,230.66 289,231.32 289,232C288.24,232.43 287.47,232.87 286.69,233.31C283.64,235.23 282.65,236.85 281,240C280.34,240.99 279.68,241.98 279,243C279.31,245.15 279.31,245.15 280,247C278.68,246.67 277.36,246.34 276,246C276,246.66 276,247.32 276,248C275.34,248 274.68,248 274,248C273.67,246.68 273.34,245.36 273,244C272.67,245.32 272.34,246.64 272,248C271.38,248.14 270.76,248.29 270.13,248.44C267.97,248.93 267.97,248.93 266,250C267.24,245.33 268.61,242.96 272.31,239.88C273.2,239.11 274.09,238.35 275.01,237.56C276,236.71 276.98,235.87 278,235C280.23,233 282.43,230.97 284.63,228.94C285.18,228.43 285.73,227.92 286.3,227.4C290.13,223.86 293.82,220.21 297.41,216.43C299.47,214.57 301.24,214 304,214Z"
|
||||
android:fillColor="#8D82B6"/>
|
||||
<path
|
||||
android:pathData="M355,70C366.58,74.34 375.9,82.74 385.26,90.57C386.81,91.84 388.39,93.06 389.98,94.27C390.64,94.84 391.31,95.41 392,96C392,96.66 392,97.32 392,98C392.66,98 393.32,98 394,98C394.66,99.98 395.32,101.96 396,104C393.63,104.63 393.63,104.63 391,105C389.81,103.81 388.64,102.61 387.5,101.38C386.09,99.77 386.09,99.77 384,100C383.34,98.35 382.68,96.7 382,95C380.35,95.33 378.7,95.66 377,96C377,93.03 377,90.06 377,87C376.01,86.67 375.02,86.34 374,86C373.67,86.33 373.34,86.66 373,87C371.67,87.36 370.34,87.7 369,88C369.66,87.01 370.32,86.02 371,85C369.99,82.43 369.99,82.43 368,80C364.4,79.1 364.4,79.1 361,79C360.67,77.35 360.34,75.7 360,74C358.68,74 357.36,74 356,74C355.67,72.68 355.34,71.36 355,70Z"
|
||||
android:fillColor="#77689F"/>
|
||||
<path
|
||||
android:pathData="M204,227C205.49,227.49 205.49,227.49 207,228C207,228.66 207,229.32 207,230C206.4,230.19 205.8,230.37 205.19,230.56C202.48,232.34 202.09,234.14 200.96,237.07C199.12,240.76 196.03,243.28 193,246C192.38,246.57 191.76,247.13 191.12,247.71C185.92,252.36 180.47,256.68 175,261C174.34,260.67 173.68,260.34 173,260C174.31,257.5 174.31,257.5 176,255C176.99,255 177.98,255 179,255C179.33,254.01 179.66,253.02 180,252C181.32,252 182.64,252 184,252C184.08,251.11 184.17,250.23 184.25,249.31C185.25,244.9 187.42,242.52 190.63,239.44C192.22,237.77 193.06,236.09 194,234C194.66,234.33 195.32,234.66 196,235C197.22,235.07 197.22,235.07 199,234C198.67,233.01 198.34,232.02 198,231C200.81,228.94 200.81,228.94 204,227Z"
|
||||
android:fillColor="#8071AE"/>
|
||||
<path
|
||||
android:pathData="M148,325C148.66,325.99 149.32,326.98 150,328C147.35,329.46 146.11,330 143,330C143,331.65 143,333.3 143,335C138.25,336 138.25,336 136,336C134.41,337.45 132.99,339.01 131.56,340.63C129.11,342.78 126.6,342.91 123.44,343.38C120.69,344.08 119.71,344.78 118,347C117.67,347.99 117.34,348.98 117,350C114.94,351.19 114.94,351.19 113,352C112.87,348.76 112.82,346.95 114,344C117.1,341.34 120.65,339.7 124.31,337.94C126.32,336.91 128.33,335.88 130.34,334.86C131.31,334.37 132.29,333.88 133.29,333.38C136.73,331.63 140.05,329.72 143.38,327.75C144.29,327.21 145.21,326.67 146.15,326.11C147.06,325.56 147.06,325.56 148,325Z"
|
||||
android:fillColor="#A49BC5"/>
|
||||
<path
|
||||
android:pathData="M72,137C72.99,137.33 73.98,137.66 75,138C75,138.66 75,139.32 75,140C74.34,140 73.68,140 73,140C73,140.66 73,141.32 73,142C71.35,142.33 69.7,142.66 68,143C67.92,143.58 67.83,144.15 67.75,144.75C66.79,147.62 65.13,148.87 63,151C61.75,153.63 61.75,153.63 61,156C60.34,156 59.68,156 59,156C58.73,156.58 58.46,157.15 58.19,157.75C56.96,160.07 55.75,162.04 54,164C53.01,164 52.02,164 51,164C51,164.66 51,165.32 51,166C49.47,167.29 49.47,167.29 47.5,168.63C46.85,169.07 46.2,169.52 45.53,169.98C45.03,170.31 44.52,170.65 44,171C43.67,170.34 43.34,169.68 43,169C47.87,162.87 52.87,156.93 58.19,151.19C58.67,150.66 59.16,150.14 59.66,149.59C61.1,148.05 62.55,146.52 64,145C64.54,144.43 65.07,143.86 65.63,143.28C67.04,141.81 68.52,140.4 70,139C70.66,139 71.32,139 72,139C72,138.34 72,137.68 72,137Z"
|
||||
android:fillColor="#9085B4"/>
|
||||
<path
|
||||
android:pathData="M263,248C263.66,248.99 264.32,249.98 265,251C263.24,252.86 261.52,254.35 259.44,255.88C256.83,257.8 256.83,257.8 256.06,260.63C255.71,261.41 255.36,262.19 255,263C252,264 252,264 249,265C247.7,267.49 247.7,267.49 247,270C245.67,270.67 244.33,271.33 243,272C243,271.01 243,270.02 243,269C241.02,269.99 239.04,270.98 237,272C237,271.01 237,270.02 237,269C238.9,267.33 240.77,265.85 242.81,264.38C244,263.49 245.19,262.6 246.38,261.71C246.99,261.26 247.59,260.81 248.21,260.35C253.32,256.5 258.48,252.52 263,248Z"
|
||||
android:fillColor="#9A90C0"/>
|
||||
<path
|
||||
android:pathData="M377,232C378.42,235.83 377.57,238.56 376.52,242.43C376.36,243.06 376.19,243.69 376.02,244.33C375.48,246.37 374.93,248.4 374.38,250.44C373.63,253.24 372.88,256.05 372.14,258.85C371.96,259.55 371.77,260.24 371.58,260.96C369.74,267.91 368.1,274.91 366.55,281.93C365.21,287.79 365.21,287.79 363,290C362.67,286.37 362.34,282.74 362,279C363.32,278.34 364.64,277.68 366,277C366.65,274.31 366.65,274.31 366.79,272.26C367.05,269.48 367.49,266.75 367.94,264C368.09,263.03 368.24,262.06 368.4,261.06C369.07,257.63 370.01,254.35 371,251C371.42,249.47 371.84,247.94 372.25,246.41C372.47,245.63 372.68,244.84 372.9,244.04C373.35,242.41 373.79,240.78 374.23,239.14C375.89,233.11 375.89,233.11 377,232Z"
|
||||
android:fillColor="#7263A3"/>
|
||||
<path
|
||||
android:pathData="M281,232C282.32,232.66 283.64,233.32 285,234C284.55,234.39 284.09,234.78 283.63,235.19C281.31,237.76 280,240.51 280,244C280,244.99 280,245.98 280,247C278.68,246.67 277.36,246.34 276,246C276,246.66 276,247.32 276,248C275.34,248 274.68,248 274,248C273.67,246.68 273.34,245.36 273,244C272.67,245.32 272.34,246.64 272,248C271.38,248.14 270.76,248.29 270.13,248.44C267.97,248.93 267.97,248.93 266,250C267.28,245 269.01,242.62 272.94,239.31C274.25,238.19 274.25,238.19 275.59,237.05C276.39,236.37 277.18,235.7 278,235C279.01,234.01 280.02,233.02 281,232Z"
|
||||
android:fillColor="#9388BD"/>
|
||||
<path
|
||||
android:pathData="M132,103C131.44,103.25 130.89,103.5 130.31,103.75C127.6,105.22 125.36,107.03 123,109C122.34,108.67 121.68,108.34 121,108C121,108.66 121,109.32 121,110C120.01,110.5 120.01,110.5 119,111C117.87,113.02 117.87,113.02 117,115C116.67,114.34 116.34,113.68 116,113C115.35,114.56 114.7,116.12 114.06,117.69C113.7,118.56 113.34,119.43 112.97,120.32C112.04,122.89 111.5,125.33 111,128C110.37,129.68 109.71,131.35 109,133C108.67,133 108.34,133 108,133C108.24,131.36 108.47,129.73 108.71,128.09C108.9,126.71 109.07,125.33 109.22,123.94C110.61,111.22 110.61,111.22 114.59,107.52C119.4,104.39 126.32,100.59 132,103Z"
|
||||
android:fillColor="#9D91C8"/>
|
||||
<path
|
||||
android:pathData="M332,174C332.66,174 333.32,174 334,174C333.67,175.65 333.34,177.3 333,179C332.34,179 331.68,179 331,179C330.73,179.58 330.46,180.15 330.19,180.75C328.98,183.03 327.59,184.97 326,187C326.33,187.66 326.66,188.32 327,189C326.28,189.16 325.56,189.33 324.81,189.5C321.23,191.41 320.18,193.61 318.31,197.13C316.77,199.32 315.41,199.91 313,201C311.67,203.14 310.91,205.25 310.06,207.63C309,210 309,210 306,212C305.01,211.67 304.02,211.34 303,211C303.7,210.21 304.41,209.42 305.13,208.61C313.17,199.46 320.45,189.85 327.56,179.98C328.04,179.32 328.51,178.67 329,178C329.51,177.26 330.03,176.51 330.56,175.75C331.03,175.17 331.51,174.59 332,174Z"
|
||||
android:fillColor="#857CA9"/>
|
||||
<path
|
||||
android:pathData="M113,110C112.45,114.57 111.58,118.67 110,123C108.35,123.33 106.7,123.66 105,124C104.34,122.68 103.68,121.36 103,120C101.02,120.33 99.04,120.66 97,121C98.51,116.75 101.17,115.41 105,113.25C106.07,112.64 107.14,112.02 108.25,111.39C111,110 111,110 113,110Z"
|
||||
android:fillColor="#806EB4"/>
|
||||
<path
|
||||
android:pathData="M100,159C100.33,159 100.66,159 101,159C100.98,159.78 100.96,160.57 100.94,161.38C100.81,163.98 100.81,163.98 102,166C101.53,166.58 101.05,167.15 100.56,167.75C99.05,169.93 98.46,171.43 98,174C98.66,174 99.32,174 100,174C99,176 99,176 97,177C96.42,178.78 96.42,178.78 96.06,180.94C95.19,185.27 94.01,189.43 92.68,193.64C91.64,197.23 91.03,200.73 90.63,204.44C89.97,208.16 88.74,210.66 87,214C86.23,216.32 85.61,218.64 85,221C83.36,216.95 84.73,213.46 85.91,209.47C86.12,208.74 86.33,208 86.55,207.24C87,205.66 87.46,204.08 87.91,202.51C89.1,198.43 90.25,194.35 91.41,190.27C91.64,189.46 91.87,188.66 92.1,187.84C94.16,180.52 95.98,173.15 97.64,165.73C98.91,160.09 98.91,160.09 100,159Z"
|
||||
android:fillColor="#A094CA"/>
|
||||
<path
|
||||
android:pathData="M75,250C75.33,250.66 75.66,251.32 76,252C76.66,252 77.32,252 78,252C78,253.65 78,255.3 78,257C77.01,257.49 77.01,257.49 76,258C76.33,258.99 76.66,259.98 77,261C76.34,261.66 75.68,262.32 75,263C74.67,262.67 74.34,262.34 74,262C73.88,262.65 73.76,263.3 73.63,263.98C72.71,268.8 71.62,273.36 70,278C69.66,279.66 69.33,281.33 69,283C68.49,285.27 67.96,287.54 67.44,289.81C67.17,290.97 66.9,292.14 66.62,293.33C66.42,294.21 66.21,295.09 66,296C65.34,296 64.68,296 64,296C64.58,290.74 65.44,285.76 66.75,280.63C66.92,279.95 67.09,279.28 67.27,278.59C69.06,271.62 71,264.69 72.95,257.77C73.67,255.18 74.36,252.6 75,250Z"
|
||||
android:fillColor="#A39ACB"/>
|
||||
<path
|
||||
android:pathData="M245,124C252.04,134.56 252.5,147.56 250.33,159.78C249.63,162.55 248.88,165.28 248,168C247.67,168 247.34,168 247,168C247.05,167.32 247.09,166.63 247.14,165.93C248.37,147.26 248.37,147.26 245,129C244.34,126.71 244.34,126.71 244,125C244.33,124.67 244.66,124.34 245,124Z"
|
||||
android:fillColor="#8274A7"/>
|
||||
<path
|
||||
android:pathData="M154,95C152.25,98.88 152.25,98.88 150,100C149.28,101.64 148.61,103.31 148,105C147.67,104.34 147.34,103.68 147,103C145.58,103.28 145.58,103.28 144.13,103.56C141,104 141,104 139,103C137.82,102.94 137.82,102.94 136.13,103.69C135.42,103.79 134.72,103.89 134,104C132.37,102.52 132.37,102.52 131,101C133.75,100.19 136.5,99.37 139.25,98.56C140.03,98.33 140.8,98.1 141.61,97.87C142.74,97.53 142.74,97.53 143.89,97.19C144.93,96.89 144.93,96.89 145.99,96.57C151.52,95 151.52,95 154,95Z"
|
||||
android:fillColor="#9487BF"/>
|
||||
<path
|
||||
android:pathData="M398,105C402.13,106.59 405.65,109.24 408,113C408.78,119.1 406.12,125.37 404,131C403.34,130.34 402.68,129.68 402,129C402.24,126.84 402.24,126.84 402.88,124.38C403.08,123.56 403.28,122.74 403.49,121.9C403.74,120.96 403.74,120.96 404,120C403.34,120 402.68,120 402,120C401.67,117.36 401.34,114.72 401,112C400.01,111.67 399.02,111.34 398,111C398.33,110.01 398.66,109.02 399,108C398.67,107.01 398.34,106.02 398,105Z"
|
||||
android:fillColor="#72639D"/>
|
||||
<path
|
||||
android:pathData="M62,321C65.34,320.93 67.8,320.93 71,322C73.34,321.72 75.68,321.39 78,321C73.81,324.78 69.48,326.71 64.31,328.75C63.16,329.22 63.16,329.22 61.97,329.71C56.29,332 56.29,332 54,332C54,330.68 54,329.36 54,328C54.99,327.67 55.98,327.34 57,327C57.66,327.33 58.32,327.66 59,328C59.33,327.34 59.66,326.68 60,326C60.33,326 60.66,326 61,326C61.33,324.35 61.66,322.7 62,321Z"
|
||||
android:fillColor="#A69CCC"/>
|
||||
<path
|
||||
android:pathData="M94,176C94.33,176 94.66,176 95,176C94.41,181.44 93.47,186.61 92.04,191.89C91.85,192.61 91.66,193.32 91.46,194.06C91.06,195.55 90.65,197.05 90.24,198.54C89.62,200.82 89.02,203.11 88.41,205.4C88.02,206.85 87.62,208.31 87.23,209.77C87.05,210.45 86.87,211.13 86.69,211.83C85.72,215.3 84.9,217.76 82,220C81.5,218.51 81.5,218.51 81,217C81.66,216.34 82.32,215.68 83,215C82.97,213.92 82.97,213.92 82.94,212.81C83,209.79 83.88,207.87 85.14,205.16C86.59,201.51 87.38,197.69 88.25,193.87C88.97,191.12 89.89,188.6 91,186C90.34,185.34 89.68,184.68 89,184C89.99,184 90.98,184 92,184C92.14,182.87 92.29,181.73 92.44,180.56C93,177 93,177 94,176Z"
|
||||
android:fillColor="#796BA8"/>
|
||||
<path
|
||||
android:pathData="M18,220C20.83,222.83 20.8,225.06 21,229C20.52,229.55 20.05,230.1 19.56,230.67C17.18,234.22 17.39,237.74 17.25,241.94C17.21,242.78 17.18,243.62 17.14,244.49C16.91,250.62 17.19,256.54 17.94,262.63C17.96,263.41 17.98,264.19 18,265C17.34,265.66 16.68,266.32 16,267C13.76,251.05 15.2,235.79 18,220Z"
|
||||
android:fillColor="#A29BBF"/>
|
||||
<path
|
||||
android:pathData="M205,294C204.64,300.4 204.64,300.4 201.81,303.06C198.17,304.28 196.47,303.47 193,302C192.34,302 191.68,302 191,302C192.25,298.24 193.53,297.75 196.88,295.75C197.76,295.21 198.64,294.67 199.55,294.11C202,293 202,293 205,294Z"
|
||||
android:fillColor="#8877BD"/>
|
||||
<path
|
||||
android:pathData="M353,334C353.84,334.49 353.84,334.49 354.69,335C357.3,336.13 359.18,336.16 362,336C361.69,339.19 361.43,340.61 359,342.8C358.2,343.3 357.39,343.8 356.56,344.31C355.76,344.82 354.96,345.34 354.13,345.86C352,347 352,347 350,347C350.31,345.21 350.62,343.42 350.94,341.63C351.11,340.63 351.29,339.63 351.46,338.6C352,336 352,336 353,334Z"
|
||||
android:fillColor="#A09BC4"/>
|
||||
<path
|
||||
android:pathData="M182,385C182.56,385.14 183.12,385.29 183.7,385.44C188.89,386.64 193.66,387.09 199,387C204.93,386.9 210.26,387.52 216,389C216,389.33 216,389.66 216,390C212.23,390.03 208.46,390.05 204.69,390.06C203.63,390.07 202.58,390.08 201.5,390.09C194.61,390.11 187.85,389.76 181,389C181.33,387.68 181.66,386.36 182,385Z"
|
||||
android:fillColor="#AAA4C6"/>
|
||||
<path
|
||||
android:pathData="M349,137C350.56,141.85 349.48,146.22 348,151C346.67,152.67 345.33,154.33 344,156C343.65,157.68 343.37,159.37 343.13,161.06C342.4,165.58 342.4,165.58 340.55,167.54C338.88,168.63 338.88,168.63 336,170C336.64,165.14 338.68,161.21 340.81,156.88C343.95,150.37 346.74,143.87 349,137Z"
|
||||
android:fillColor="#8378A7"/>
|
||||
<path
|
||||
android:pathData="M100,368C109.38,368 109.38,368 113,369C113,369.66 113,370.32 113,371C114.98,370.67 116.96,370.34 119,370C119.33,370.66 119.66,371.32 120,372C119.34,372 118.68,372 118,372C118.33,372.66 118.66,373.32 119,374C119.99,374.33 120.98,374.66 122,375C122,375.66 122,376.32 122,377C117.03,376.37 112.79,374.93 108.19,373.06C107.17,372.67 107.17,372.67 106.13,372.26C101.13,370.25 101.13,370.25 100,368Z"
|
||||
android:fillColor="#A49EC5"/>
|
||||
<path
|
||||
android:pathData="M230,385C230.99,384.67 231.98,384.34 233,384C235.37,384.92 237.7,385.92 240,387C240,387.33 240,387.66 240,388C232.77,388.88 225.66,389.13 218.38,389.06C217.37,389.06 216.37,389.05 215.33,389.05C212.89,389.04 210.44,389.02 208,389C208,388.67 208,388.34 208,388C211.63,388 215.26,388 219,388C219.33,387.34 219.66,386.68 220,386C223.53,383.52 225.92,384.22 230,385Z"
|
||||
android:fillColor="#9C97B9"/>
|
||||
<path
|
||||
android:pathData="M17,269C17.99,269.49 17.99,269.49 19,270C18.67,270.66 18.34,271.32 18,272C19.98,272.49 19.98,272.49 22,273C22.93,275.79 23.02,278.22 22.94,281.13C22.55,285.01 22.55,285.01 25,287C25,290 25,290 23,293C19.45,285.56 16.68,277.32 17,269Z"
|
||||
android:fillColor="#A098C4"/>
|
||||
<path
|
||||
android:pathData="M31,310C34.88,311.75 34.88,311.75 36,314C36.66,313.34 37.32,312.68 38,312C38.19,313.09 38.37,314.19 38.56,315.31C39.58,318.95 39.58,318.95 41.69,320.13C43.84,321.06 43.84,321.06 47,322C46.67,323.32 46.34,324.64 46,326C46.66,326 47.32,326 48,326C47.67,326.99 47.34,327.98 47,329C40.4,323.94 35.68,317.82 31,311C31,310.67 31,310.34 31,310Z"
|
||||
android:fillColor="#9389B6"/>
|
||||
<path
|
||||
android:pathData="M239,183C239.66,183.33 240.32,183.66 241,184C240.42,188.06 239.11,190.85 236.94,194.31C236.39,195.2 235.84,196.08 235.28,196.99C234.86,197.65 234.43,198.32 234,199C233.34,197.68 232.68,196.36 232,195C232.66,195 233.32,195 234,195C233.47,193.64 233.47,193.64 232.94,192.25C232.04,189.12 231.87,187.95 233,185C233.99,185 234.98,185 236,185C236.33,185.66 236.66,186.32 237,187C237,186.01 237,185.02 237,184C237.66,183.67 238.32,183.34 239,183Z"
|
||||
android:fillColor="#7968AD"/>
|
||||
<path
|
||||
android:pathData="M383,94C387.75,94.75 387.75,94.75 390,97C390.66,97.08 391.32,97.17 392,97.25C392.66,97.5 393.32,97.75 394,98C395.25,101.06 395.25,101.06 396,104C393.63,104.63 393.63,104.63 391,105C389.81,103.81 388.64,102.61 387.5,101.38C386.09,99.77 386.09,99.77 384,100C383.67,98.02 383.34,96.04 383,94Z"
|
||||
android:fillColor="#7764AD"/>
|
||||
<path
|
||||
android:pathData="M95,309C95.66,309.33 96.32,309.66 97,310C96,313 96,313 94.33,314.07C93.62,314.39 92.91,314.71 92.18,315.04C91.04,315.57 91.04,315.57 89.87,316.11C89.07,316.46 88.26,316.82 87.44,317.19C86.63,317.56 85.82,317.92 84.99,318.3C83,319.21 81,320.11 79,321C79.33,319.02 79.66,317.04 80,315C81.65,315 83.3,315 85,315C85,314.01 85,313.02 85,312C86.09,311.88 87.19,311.75 88.31,311.63C92.07,311.25 92.07,311.25 95,309Z"
|
||||
android:fillColor="#9E97C1"/>
|
||||
<path
|
||||
android:pathData="M122,288C123.14,288.25 123.14,288.25 124.31,288.5C127.04,289.09 127.04,289.09 130,289C130.66,289.33 131.32,289.66 132,290C132,290.66 132,291.32 132,292C129.88,293.51 129.88,293.51 127.13,295.13C126.22,295.66 125.32,296.2 124.38,296.76C122,298 122,298 120,298C120.46,294.53 120.89,291.33 122,288Z"
|
||||
android:fillColor="#958CBE"/>
|
||||
<path
|
||||
android:pathData="M114,345C113.25,349.75 113.25,349.75 111,352C108.68,352.4 106.34,352.74 104,353C104,353.99 104,354.98 104,356C102.35,356 100.7,356 99,356C99,354.68 99,353.36 99,352C101.12,350.83 103.25,349.66 105.38,348.5C106.28,348 106.28,348 107.2,347.49C111.77,345 111.77,345 114,345Z"
|
||||
android:fillColor="#A9A3C7"/>
|
||||
<path
|
||||
android:pathData="M148,325C148.66,325.99 149.32,326.98 150,328C147.35,329.46 146.11,330 143,330C143,331.65 143,333.3 143,335C139.16,335.81 135.94,336 132,336C132,335.34 132,334.68 132,334C137.28,331.03 142.56,328.06 148,325Z"
|
||||
android:fillColor="#9E97C3"/>
|
||||
<path
|
||||
android:pathData="M131,378C131.33,377.34 131.66,376.68 132,376C132.99,376 133.98,376 135,376C135.33,377.32 135.66,378.64 136,380C138.64,380.33 141.28,380.66 144,381C143.67,381.99 143.34,382.98 143,384C136.07,382.02 129.14,380.04 122,378C122,377.67 122,377.34 122,377C125.78,375.95 127.65,375.89 131,378Z"
|
||||
android:fillColor="#A29AC1"/>
|
||||
<path
|
||||
android:pathData="M187,244C187.99,244.99 188.98,245.98 190,247C185.55,252.58 180.76,256.79 175,261C174.34,260.67 173.68,260.34 173,260C174.31,257.5 174.31,257.5 176,255C176.99,255 177.98,255 179,255C179.33,254.01 179.66,253.02 180,252C181.32,252 182.64,252 184,252C184.12,251.05 184.25,250.1 184.38,249.13C185,246 185,246 187,244Z"
|
||||
android:fillColor="#8076A5"/>
|
||||
<path
|
||||
android:pathData="M360,304C360.66,304.33 361.32,304.66 362,305C361.43,308.02 360.74,310.97 359.94,313.94C359.25,316.6 359,318.51 359,321.31C359,324 359,324 357,327C356.3,328.99 355.63,330.99 355,333C354.67,333 354.34,333 354,333C354.6,326.26 355.85,319.97 357.56,313.44C357.8,312.52 358.03,311.61 358.27,310.67C358.84,308.45 359.42,306.22 360,304Z"
|
||||
android:fillColor="#A99FCF"/>
|
||||
<path
|
||||
android:pathData="M388,198C388.66,198.99 389.32,199.98 390,201C389.31,204.16 388.51,207.01 387.02,209.89C385.86,212.3 385.12,214.68 384.38,217.25C383.98,218.59 383.98,218.59 383.59,219.95C383.39,220.63 383.2,221.3 383,222C382.67,222 382.34,222 382,222C382.31,215.08 382.9,203.1 388,198Z"
|
||||
android:fillColor="#A094C9"/>
|
||||
<path
|
||||
android:pathData="M290,371C290.99,371.49 290.99,371.49 292,372C291.01,372.49 291.01,372.49 290,373C290,372.34 290,371.68 290,371ZM297,372C297,372.66 297,373.32 297,374C294.4,374.87 291.79,375.72 289.19,376.56C288.45,376.81 287.71,377.06 286.95,377.31C281.34,379.11 281.34,379.11 278,378C278,377.34 278,376.68 278,376C278.89,375.82 278.89,375.82 279.79,375.63C283.24,374.9 286.48,374.21 289.75,372.88C292.3,371.88 294.28,371.84 297,372Z"
|
||||
android:fillColor="#9088B4"/>
|
||||
<path
|
||||
android:pathData="M245,167C245.66,167.66 246.32,168.32 247,169C246.65,174.14 244.98,178.8 242,183C241.34,182.67 240.68,182.34 240,182C240,180.02 240,178.04 240,176C240.66,175.67 241.32,175.34 242,175C242.66,175.33 243.32,175.66 244,176C244,174.68 244,173.36 244,172C243.34,172 242.68,172 242,172C243.88,168.13 243.88,168.13 245,167Z"
|
||||
android:fillColor="#7A6AA6"/>
|
||||
<path
|
||||
android:pathData="M23,294C23.99,294.33 24.98,294.66 26,295C26,295.66 26,296.32 26,297C26.99,297.33 27.98,297.66 29,298C32,301 32,301 32.2,302.95C32.13,304.96 32.07,306.98 32,309C31.34,309 30.68,309 30,309C28.83,306.69 27.66,304.38 26.5,302.06C26.17,301.4 25.83,300.74 25.49,300.06C23,295.11 23,295.11 23,294Z"
|
||||
android:fillColor="#998FBE"/>
|
||||
<path
|
||||
android:pathData="M199,96C204.99,95.68 208.64,96.32 214,99C214,99.99 214,100.98 214,102C213.01,102.33 212.02,102.66 211,103C210.67,103.33 210.34,103.66 210,104C208.32,103.71 206.66,103.38 205,103C205.66,102.01 206.32,101.02 207,100C206.34,99.34 205.68,98.68 205,98C204.01,98.33 203.02,98.66 202,99C202,98.34 202,97.68 202,97C201.01,96.67 200.02,96.34 199,96Z"
|
||||
android:fillColor="#8072AD"/>
|
||||
<path
|
||||
android:pathData="M377,320C378.98,320.66 380.96,321.32 383,322C379.67,325.33 376.33,328.67 373,332C371.68,331.34 370.36,330.68 369,330C370.32,330 371.64,330 373,330C373.1,329.36 373.21,328.72 373.31,328.06C373.54,327.38 373.77,326.7 374,326C374.99,325.67 375.98,325.34 377,325C377,323.35 377,321.7 377,320Z"
|
||||
android:fillColor="#9F99C1"/>
|
||||
<path
|
||||
android:pathData="M208,291C208.33,291.99 208.66,292.98 209,294C209.66,294.33 210.32,294.66 211,295C209.02,295.99 209.02,295.99 207,297C207,298.65 207,300.3 207,302C206.01,302.49 206.01,302.49 205,303C204.27,305.31 203.6,307.65 203,310C201.61,306.19 202.36,303.26 203.25,299.38C203.5,298.23 203.76,297.08 204.02,295.9C205,293 205,293 208,291Z"
|
||||
android:fillColor="#A59BCD"/>
|
||||
<path
|
||||
android:pathData="M402,112C403.98,112.99 403.98,112.99 406,114C406.37,125.52 406.37,125.52 404,131C403.34,130.34 402.68,129.68 402,129C402.24,126.84 402.24,126.84 402.88,124.38C403.18,123.15 403.18,123.15 403.49,121.9C403.66,121.27 403.83,120.65 404,120C403.34,120 402.68,120 402,120C402,117.36 402,114.72 402,112Z"
|
||||
android:fillColor="#6D5BA2"/>
|
||||
<path
|
||||
android:pathData="M197,324C197.66,324.33 198.32,324.66 199,325C199.2,331.05 199.2,331.05 199,333C198.34,333.66 197.68,334.32 197,335C196.4,336.52 195.86,338.07 195.38,339.63C195.11,340.44 194.85,341.26 194.59,342.1C194.39,342.73 194.2,343.35 194,344C193.67,344 193.34,344 193,344C192.76,339.16 193.6,335.26 195,330.63C195.37,329.38 195.74,328.13 196.13,326.85C196.41,325.91 196.7,324.97 197,324Z"
|
||||
android:fillColor="#A59BCD"/>
|
||||
<path
|
||||
android:pathData="M132,103C131.16,103.37 131.16,103.37 130.31,103.75C127.6,105.22 125.36,107.03 123,109C122.34,108.67 121.68,108.34 121,108C121,108.66 121,109.32 121,110C119.35,109.67 117.7,109.34 116,109C118.24,106.5 120.39,105.13 123.44,103.75C124.2,103.39 124.96,103.04 125.75,102.67C128.28,101.92 129.54,102.13 132,103Z"
|
||||
android:fillColor="#9D93BC"/>
|
||||
<path
|
||||
android:pathData="M360,298C360.33,298 360.66,298 361,298C360.61,306.38 358.47,314.01 356,322C355.67,322 355.34,322 355,322C355.08,321.37 355.17,320.75 355.25,320.1C355.36,319.28 355.46,318.47 355.56,317.63C355.67,316.81 355.77,316 355.88,315.16C356.18,312.95 356.18,312.95 355,311C355.66,311 356.32,311 357,311C357.12,309.78 357.25,308.57 357.38,307.31C357.79,303.94 358.55,301.1 360,298Z"
|
||||
android:fillColor="#796BA7"/>
|
||||
<path
|
||||
android:pathData="M112,112C113,114 113,114 112.44,116.16C112.15,116.98 111.86,117.79 111.56,118.63C111.28,119.44 110.99,120.26 110.69,121.1C110.35,122.04 110.35,122.04 110,123C108.35,123.33 106.7,123.66 105,124C105.51,121.83 106,120 107,118C107.1,117.34 107.21,116.68 107.31,116C108,114 108,114 110.06,112.75C110.7,112.5 111.34,112.25 112,112Z"
|
||||
android:fillColor="#7460A7"/>
|
||||
<path
|
||||
android:pathData="M346,64C349.99,65.37 352.8,67.26 356,70C354.82,70.12 354.82,70.12 353.63,70.25C350.85,70.73 350.85,70.73 349.69,73.06C349.46,73.7 349.23,74.34 349,75C346.88,71.47 346.47,68.06 346,64Z"
|
||||
android:fillColor="#8B7EAE"/>
|
||||
<path
|
||||
android:pathData="M418,130C421.38,133.04 423.27,136.05 425.25,140.13C426.03,141.71 426.03,141.71 426.83,143.32C428,146 428,146 428,148C427.34,148 426.68,148 426,148C424.98,146.34 423.98,144.68 423,143C422.34,142.67 421.68,142.34 421,142C420.66,139.67 420.33,137.33 420,135C419.34,134.34 418.68,133.68 418,133C418,132.01 418,131.02 418,130Z"
|
||||
android:fillColor="#948DB4"/>
|
||||
<path
|
||||
android:pathData="M178.25,309.88C178.83,310.25 179.4,310.62 180,311C179.22,311.25 178.43,311.49 177.63,311.75C174.68,313.15 174.1,314.01 173,317C172.44,316.77 171.89,316.55 171.31,316.31C168.09,315.88 166.68,317.31 164,319C163.34,318.67 162.68,318.34 162,318C163.58,316.87 165.16,315.75 166.75,314.63C167.63,314 168.51,313.37 169.42,312.73C175.36,308.75 175.36,308.75 178.25,309.88Z"
|
||||
android:fillColor="#8E86AF"/>
|
||||
<path
|
||||
android:pathData="M112,304C105.07,307.46 105.07,307.46 98,311C98.66,308.69 99.32,306.38 100,304C101.44,303.8 102.87,303.62 104.31,303.44C105.11,303.33 105.91,303.23 106.74,303.12C109,303 109,303 112,304Z"
|
||||
android:fillColor="#9890BF"/>
|
||||
<path
|
||||
android:pathData="M403,134C403.66,134.33 404.32,134.66 405,135C404.58,141.19 403.35,146.29 401,152C400.32,154 399.65,156 399,158C397.91,154.69 398.12,152.58 399,149.23C399.34,147.9 399.34,147.9 399.69,146.54C400.06,145.16 400.06,145.16 400.44,143.75C400.68,142.81 400.92,141.88 401.17,140.91C401.77,138.61 402.38,136.3 403,134Z"
|
||||
android:fillColor="#9E91CB"/>
|
||||
<path
|
||||
android:pathData="M230,385C230.99,384.67 231.98,384.34 233,384C233.66,384.33 234.32,384.66 235,385C235,385.66 235,386.32 235,387C233.27,387.17 231.54,387.34 229.81,387.5C228.85,387.59 227.89,387.69 226.89,387.78C224.25,387.98 221.65,388.04 219,388C221.44,383.11 225.31,384.1 230,385Z"
|
||||
android:fillColor="#9C94C9"/>
|
||||
<path
|
||||
android:pathData="M59,326C61.64,326 64.28,326 67,326C64.58,328.87 62.4,329.71 58.81,330.69C57.91,330.94 57.01,331.19 56.08,331.45C55.39,331.63 54.71,331.81 54,332C54,330.68 54,329.36 54,328C54.99,327.67 55.98,327.34 57,327C57.66,327.33 58.32,327.66 59,328C59,327.34 59,326.68 59,326Z"
|
||||
android:fillColor="#A9A3C3"/>
|
||||
<path
|
||||
android:pathData="M144,281C143.88,282.81 143.88,282.81 143,285C139.94,286.75 139.94,286.75 137,288C137,285.69 137,283.38 137,281C139.67,279.67 141.17,280.33 144,281Z"
|
||||
android:fillColor="#938ABF"/>
|
||||
<path
|
||||
android:pathData="M408,115C408.99,115.99 409.98,116.98 411,118C410.55,119.03 410.09,120.06 409.63,121.13C408.59,123.52 407.74,125.82 406.94,128.31C406,131 406,131 404,133C405.04,126.89 406.16,120.92 408,115Z"
|
||||
android:fillColor="#A498C6"/>
|
||||
<path
|
||||
android:pathData="M200,309C201.08,312.24 200.96,312.9 200,316.01C199.66,317.15 199.66,317.15 199.31,318.31C199.06,319.09 198.82,319.88 198.56,320.69C198.32,321.48 198.08,322.28 197.83,323.1C197.23,325.07 196.61,327.04 196,329C195.67,329 195.34,329 195,329C195.5,324.51 196.12,320.07 196.94,315.63C197.09,314.79 197.24,313.96 197.4,313.1C198,311 198,311 200,309Z"
|
||||
android:fillColor="#7F74AD"/>
|
||||
<path
|
||||
android:pathData="M385,202C386.1,205.31 385.9,206.19 385,209.45C384.66,210.72 384.66,210.72 384.31,212.02C384.06,212.9 383.82,213.78 383.56,214.69C383.32,215.58 383.08,216.47 382.83,217.39C382.22,219.6 381.62,221.8 381,224C380.67,224 380.34,224 380,224C380.25,215.85 381.01,209.19 385,202Z"
|
||||
android:fillColor="#6F5F9E"/>
|
||||
<path
|
||||
android:pathData="M412,283C412.66,284.32 413.32,285.64 414,287C409.55,292.94 409.55,292.94 405,299C404,297 404,297 404.94,293.88C405.29,292.93 405.64,291.98 406,291C406.66,291 407.32,291 408,291C408.66,289.68 409.32,288.36 410,287C410.66,287 411.32,287 412,287C412,285.68 412,284.36 412,283Z"
|
||||
android:fillColor="#928CAF"/>
|
||||
<path
|
||||
android:pathData="M205,225C205.66,225 206.32,225 207,225C207,225.66 207,226.32 207,227C207.99,227 208.98,227 210,227C208.35,231.29 205.26,233.88 202,237C201.34,236.67 200.68,236.34 200,236C201.81,233 201.81,233 204,230C204.99,230 205.98,230 207,230C206.34,228.35 205.68,226.7 205,225Z"
|
||||
android:fillColor="#766AA1"/>
|
||||
<path
|
||||
android:pathData="M432,159C433.84,161.76 434.68,163.75 435.63,166.88C435.89,167.72 436.15,168.56 436.41,169.43C437.01,172.04 437.11,174.33 437,177C435,176 435,176 434.15,173.71C431.59,164.11 431.59,164.11 432,159Z"
|
||||
android:fillColor="#938DB0"/>
|
||||
<path
|
||||
android:pathData="M295,368C297.64,368 300.28,368 303,368C303,368.66 303,369.32 303,370C303.66,370.33 304.32,370.66 305,371C303.02,371.66 301.04,372.32 299,373C299,372.34 299,371.68 299,371C297.35,371 295.7,371 294,371C294.33,370.01 294.66,369.02 295,368Z"
|
||||
android:fillColor="#8C81BD"/>
|
||||
<path
|
||||
android:pathData="M61,306C62.97,309.93 61.05,314.9 60,319C58.84,321.83 57.51,324.33 56,327C54.72,324.43 55.19,323.62 55.96,320.9C56.3,319.7 56.3,319.7 56.64,318.47C56.88,317.64 57.13,316.8 57.38,315.94C57.61,315.1 57.84,314.26 58.09,313.39C59.86,307.14 59.86,307.14 61,306Z"
|
||||
android:fillColor="#A9A0CF"/>
|
||||
<path
|
||||
android:pathData="M198,295C198.66,295.99 199.32,296.98 200,298C199.34,298 198.68,298 198,298C198,299.65 198,301.3 198,303C195.69,302.67 193.38,302.34 191,302C192.33,298 194.5,297.16 198,295Z"
|
||||
android:fillColor="#8D82B3"/>
|
||||
<path
|
||||
android:pathData="M106,131C107.34,134.71 106.66,137.36 105.69,141.13C105.42,142.18 105.16,143.23 104.89,144.32C104,147 104,147 102,149C102.23,146.84 102.47,144.69 102.72,142.53C102.9,140.91 103.07,139.28 103.22,137.66C103.41,135.76 103.7,133.88 104,132C104.66,131.67 105.32,131.34 106,131Z"
|
||||
android:fillColor="#7464A7"/>
|
||||
<path
|
||||
android:pathData="M353,334C353.99,334.66 354.98,335.32 356,336C356,336.66 356,337.32 356,338C355.34,338 354.68,338 354,338C354,338.66 354,339.32 354,340C354.66,340.33 355.32,340.66 356,341C355.53,341.43 355.05,341.87 354.56,342.31C352.76,344.02 352.76,344.02 352,347C351.34,347 350.68,347 350,347C350.31,345.21 350.62,343.42 350.94,341.63C351.11,340.63 351.29,339.63 351.46,338.6C352,336 352,336 353,334Z"
|
||||
android:fillColor="#A9A4D2"/>
|
||||
<path
|
||||
android:pathData="M432,243C432.66,243.33 433.32,243.66 434,244C433.01,248.84 432.04,253.48 430,258C429.34,258 428.68,258 428,258C428.65,254.48 429.51,251.11 430.56,247.69C430.83,246.8 431.1,245.92 431.38,245.01C431.58,244.35 431.79,243.68 432,243Z"
|
||||
android:fillColor="#928BAF"/>
|
||||
<path
|
||||
android:pathData="M190,303C189.22,303.41 188.43,303.83 187.63,304.25C184.94,306.04 184.12,307.05 183,310C181.35,309.67 179.7,309.34 178,309C185.11,301.37 185.11,301.37 190,303Z"
|
||||
android:fillColor="#9086B1"/>
|
||||
<path
|
||||
android:pathData="M118,296C118.66,296.99 119.32,297.98 120,299C117.69,300.32 115.38,301.64 113,303C112.67,301.35 112.34,299.7 112,298C113.98,297.34 115.96,296.68 118,296Z"
|
||||
android:fillColor="#9990BF"/>
|
||||
<path
|
||||
android:pathData="M204,227C204.99,227.33 205.98,227.66 207,228C207,228.66 207,229.32 207,230C206.36,230.29 205.72,230.58 205.06,230.88C202.85,231.83 202.85,231.83 202,234C201.01,234 200.02,234 199,234C198.67,233.01 198.34,232.02 198,231C200.81,228.94 200.81,228.94 204,227Z"
|
||||
android:fillColor="#7C6AB7"/>
|
||||
<path
|
||||
android:pathData="M355,70C358.46,71.25 361.15,72.68 364,75C363.34,76.32 362.68,77.64 362,79C361.34,78.34 360.68,77.68 360,77C360,76.01 360,75.02 360,74C358.68,74 357.36,74 356,74C355.67,72.68 355.34,71.36 355,70Z"
|
||||
android:fillColor="#7E6FA6"/>
|
||||
<path
|
||||
android:pathData="M201,310C201.66,310.33 202.32,310.66 203,311C202.74,312.44 202.47,313.88 202.19,315.31C202.04,316.11 201.89,316.91 201.73,317.74C200.92,320.23 199.98,321.32 198,323C198.31,321.21 198.62,319.42 198.94,317.63C199.11,316.63 199.29,315.63 199.46,314.6C200,312 200,312 201,310Z"
|
||||
android:fillColor="#A49BCB"/>
|
||||
<path
|
||||
android:pathData="M105,141C105.33,141 105.66,141 106,141C106.49,147.7 105.08,152.07 102,158C100.44,154.03 101.64,151.19 102.94,147.25C103.32,146.08 103.7,144.91 104.09,143.7C104.39,142.81 104.69,141.92 105,141Z"
|
||||
android:fillColor="#9C90C8"/>
|
||||
<path
|
||||
android:pathData="M422,266C422.66,266.33 423.32,266.66 424,267C423.44,270.95 422.26,273.71 420,277C419.67,277 419.34,277 419,277C419,275.35 419,273.7 419,272C419.66,272 420.32,272 421,272C420.97,270.79 420.97,270.79 420.94,269.56C421,267 421,267 422,266Z"
|
||||
android:fillColor="#9088B0"/>
|
||||
<path
|
||||
android:pathData="M65,146C64.46,149.8 62.8,151.47 60,154C59.01,153.34 58.02,152.68 57,152C57.95,150.99 58.91,150 59.88,149C60.41,148.44 60.94,147.89 61.49,147.31C63,146 63,146 65,146Z"
|
||||
android:fillColor="#8A80AB"/>
|
||||
<path
|
||||
android:pathData="M98,354C98,354.66 98,355.32 98,356C96.4,356.87 94.8,357.72 93.19,358.56C92.29,359.04 91.4,359.51 90.48,360C88,361 88,361 85,360C86.79,358.99 88.58,358 90.38,357C91.37,356.44 92.37,355.89 93.4,355.31C96,354 96,354 98,354Z"
|
||||
android:fillColor="#ACA8C4"/>
|
||||
<path
|
||||
android:pathData="M93,121C93.99,121 94.98,121 96,121C95.79,121.62 95.59,122.24 95.38,122.88C95.25,123.58 95.13,124.28 95,125C95.66,125.66 96.32,126.32 97,127C94.33,128.33 92.83,127.67 90,127C91.88,122.13 91.88,122.13 93,121Z"
|
||||
android:fillColor="#7F6FAC"/>
|
||||
<path
|
||||
android:pathData="M232,111C232.99,111.33 233.98,111.66 235,112C236.19,115.06 236.19,115.06 237,118C236.34,118.66 235.68,119.32 235,120C234.34,119.67 233.68,119.34 233,119C232.41,116.35 232.26,113.71 232,111Z"
|
||||
android:fillColor="#7B66B3"/>
|
||||
<path
|
||||
android:pathData="M140,98C140,98.99 140,99.98 140,101C137.5,102.69 137.5,102.69 135,104C133.68,103.01 132.36,102.02 131,101C133.97,100.01 136.94,99.02 140,98Z"
|
||||
android:fillColor="#9A92BC"/>
|
||||
<path
|
||||
android:pathData="M224,280C224.66,280 225.32,280 226,280C224.38,285.75 224.38,285.75 221,288C220.67,286.35 220.34,284.7 220,283C221.32,282.67 222.64,282.34 224,282C224,281.34 224,280.68 224,280Z"
|
||||
android:fillColor="#A396CE"/>
|
||||
<path
|
||||
android:pathData="M93.31,358.25C94.15,358.62 94.15,358.62 95,359C94.01,359 93.02,359 92,359C92,360.98 92,362.96 92,365C90.02,364.01 88.04,363.02 86,362C90.43,357.94 90.43,357.94 93.31,358.25Z"
|
||||
android:fillColor="#B5AFD3"/>
|
||||
<path
|
||||
android:pathData="M329,355C330.32,355 331.64,355 333,355C332.88,356.88 332.88,356.88 332,359C328.94,360.25 328.94,360.25 326,361C327,359 328,357 329,355Z"
|
||||
android:fillColor="#8175AA"/>
|
||||
<path
|
||||
android:pathData="M70,264C70.33,264 70.66,264 71,264C71.41,269.42 70.04,273.07 68,278C67.67,278 67.34,278 67,278C66.65,272.35 67.57,268.97 70,264Z"
|
||||
android:fillColor="#8073AD"/>
|
||||
<path
|
||||
android:pathData="M378,235C378.66,235.99 379.32,236.98 380,238C379.25,241.13 379.25,241.13 378,244C377.01,244.49 377.01,244.49 376,245C376.14,243.52 376.29,242.04 376.44,240.56C376.52,239.74 376.6,238.92 376.68,238.07C377,236 377,236 378,235Z"
|
||||
android:fillColor="#A094C5"/>
|
||||
<path
|
||||
android:pathData="M110,120C112,123 112,123 111.75,125.38C111,128.01 110.08,130.48 109,133C108.67,133 108.34,133 108,133C108.61,128.66 109.26,124.32 110,120Z"
|
||||
android:fillColor="#9588C6"/>
|
||||
<path
|
||||
android:pathData="M402,112C403.98,112.99 403.98,112.99 406,114C405.34,115.98 404.68,117.96 404,120C403.34,120 402.68,120 402,120C402,117.36 402,114.72 402,112Z"
|
||||
android:fillColor="#715EB0"/>
|
||||
<path
|
||||
android:pathData="M113,110C112.19,111.94 112.19,111.94 111,114C110.01,114.33 109.02,114.66 108,115C107.34,115.66 106.68,116.32 106,117C106,115.68 106,114.36 106,113C110.07,110 110.07,110 113,110Z"
|
||||
android:fillColor="#796D9B"/>
|
||||
<path
|
||||
android:pathData="M400,299C400.99,299 401.98,299 403,299C402.65,302.26 401.98,303.02 399.44,305.25C398.63,305.83 397.83,306.4 397,307C397,306.01 397,305.02 397,304C397.99,303.67 398.98,303.34 400,303C400,301.68 400,300.36 400,299Z"
|
||||
android:fillColor="#9993B9"/>
|
||||
<path
|
||||
android:pathData="M21,284C23,285.31 23,285.31 25,287C25,290 25,290 23,293C21.14,289.87 20.8,287.63 21,284Z"
|
||||
android:fillColor="#9B8FC1"/>
|
||||
<path
|
||||
android:pathData="M249,260C249.66,260.33 250.32,260.66 251,261C250,264 250,264 248.13,265.19C246,266 246,266 243,266C245,264 247,262 249,260Z"
|
||||
android:fillColor="#958DB3"/>
|
||||
<path
|
||||
android:pathData="M239,183C239.66,183.33 240.32,183.66 241,184C240.5,187.69 239.9,189.6 237,192C237,189.33 237,186.67 237,184C237.66,183.67 238.32,183.34 239,183Z"
|
||||
android:fillColor="#8174A5"/>
|
||||
<path
|
||||
android:pathData="M391,310C391.99,310.33 392.98,310.66 394,311C392.72,312.71 391.38,314.37 390,316C389.34,316 388.68,316 388,316C388.57,313.13 388.86,312.14 391,310Z"
|
||||
android:fillColor="#9E97C2"/>
|
||||
<path
|
||||
android:pathData="M202,293C202.99,293.33 203.98,293.66 205,294C204.67,295.65 204.34,297.3 204,299C203.67,298.34 203.34,297.68 203,297C201.68,297 200.36,297 199,297C199.33,296.01 199.66,295.02 200,294C200.66,293.67 201.32,293.34 202,293Z"
|
||||
android:fillColor="#887AAB"/>
|
||||
<path
|
||||
android:pathData="M281,232C281.33,232.66 281.66,233.32 282,234C281.67,234.99 281.34,235.98 281,237C280.01,237.33 279.02,237.66 278,238C277.67,238.66 277.34,239.32 277,240C276.01,239.67 275.02,239.34 274,239C276.31,236.69 278.62,234.38 281,232Z"
|
||||
android:fillColor="#9790B2"/>
|
||||
<path
|
||||
android:pathData="M299,215C299.66,215.99 300.32,216.98 301,218C300.67,218.66 300.34,219.32 300,220C298.67,220.35 297.34,220.69 296,221C295.01,221.66 294.02,222.32 293,223C294.44,219.63 296.33,217.49 299,215Z"
|
||||
android:fillColor="#857CA7"/>
|
||||
<path
|
||||
android:pathData="M316,196C316.33,196.99 316.66,197.98 317,199C316.38,199.25 315.76,199.49 315.13,199.75C312.65,201.21 312.05,202.38 311,205C310.34,204.67 309.68,204.34 309,204C311.31,201.36 313.62,198.72 316,196Z"
|
||||
android:fillColor="#8179A1"/>
|
||||
<path
|
||||
android:pathData="M426,149C426.99,149 427.98,149 429,149C430.06,150.81 430.06,150.81 431,153C430.67,153.99 430.34,154.98 430,156C429.01,155.67 428.02,155.34 427,155C426.67,153.02 426.34,151.04 426,149Z"
|
||||
android:fillColor="#948CB4"/>
|
||||
<path
|
||||
android:pathData="M77,133C77.99,133.33 78.98,133.66 80,134C79.34,134 78.68,134 78,134C77.88,134.62 77.75,135.24 77.63,135.88C77,138 77,138 75,140C74.34,139.67 73.68,139.34 73,139C74.15,136.53 75.05,134.95 77,133Z"
|
||||
android:fillColor="#8277A8"/>
|
||||
<path
|
||||
android:pathData="M112,112C112.33,112.66 112.66,113.32 113,114C112.67,114.99 112.34,115.98 112,117C111.34,117 110.68,117 110,117C109.67,117.66 109.34,118.32 109,119C109,118.34 109,117.68 109,117C108.34,117 107.68,117 107,117C108,114 108,114 110.06,112.81C110.7,112.54 111.34,112.28 112,112Z"
|
||||
android:fillColor="#7B69AC"/>
|
||||
<path
|
||||
android:pathData="M148,325C148.66,325.99 149.32,326.98 150,328C147.65,329.43 146.52,330.09 143.75,329.63C143.17,329.42 142.6,329.21 142,329C143.98,327.68 145.96,326.36 148,325Z"
|
||||
android:fillColor="#9A94B7"/>
|
||||
<path
|
||||
android:pathData="M243,266C243.33,266.99 243.66,267.98 244,269C241.09,270.26 239.2,271 236,271C240.75,266 240.75,266 243,266Z"
|
||||
android:fillColor="#9992B8"/>
|
||||
<path
|
||||
android:pathData="M101,116C101.99,116.33 102.98,116.66 104,117C102.19,119 102.19,119 100,121C99.01,121 98.02,121 97,121C98.75,117.13 98.75,117.13 101,116Z"
|
||||
android:fillColor="#7C6E9E"/>
|
||||
<path
|
||||
android:pathData="M156,321C156,322.65 156,324.3 156,326C154.35,326 152.7,326 151,326C153.26,321 153.26,321 156,321Z"
|
||||
android:fillColor="#9B95B9"/>
|
||||
<path
|
||||
android:pathData="M435,231C435.66,231 436.32,231 437,231C436.67,233.97 436.34,236.94 436,240C435.67,240 435.34,240 435,240C434.12,233.85 434.12,233.85 434,232C434.33,231.67 434.66,231.34 435,231Z"
|
||||
android:fillColor="#9087B2"/>
|
||||
<path
|
||||
android:pathData="M438,220C438.33,220 438.66,220 439,220C439.2,226.05 439.2,226.05 439,228C438.34,228.66 437.68,229.32 437,230C435.84,225.95 436.16,223.78 438,220Z"
|
||||
android:fillColor="#9087B3"/>
|
||||
<path
|
||||
android:pathData="M228,200C228.33,200.66 228.66,201.32 229,202C228.07,204.03 227.07,206.04 226,208C225.34,206.68 224.68,205.36 224,204C225.32,202.68 226.64,201.36 228,200Z"
|
||||
android:fillColor="#7964B1"/>
|
||||
<path
|
||||
android:pathData="M235,191C235.66,191.99 236.32,192.98 237,194C235.68,195.65 234.36,197.3 233,199C232.67,197.68 232.34,196.36 232,195C232.66,195 233.32,195 234,195C234.33,193.68 234.66,192.36 235,191Z"
|
||||
android:fillColor="#7D71A8"/>
|
||||
<path
|
||||
android:pathData="M394,99C395.98,100.65 397.96,102.3 400,104C399.01,104.66 398.02,105.32 397,106C394,102.38 394,102.38 394,99Z"
|
||||
android:fillColor="#7F72A4"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">pardakht_novin_app</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.PSP" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
</resources>
|
||||
@@ -2,8 +2,8 @@ package com.example.tis_app.di
|
||||
|
||||
import com.example.core.domain.Printer
|
||||
import com.example.core.domain.PspService
|
||||
import com.example.p3.P3Printer
|
||||
import com.example.p3.P3Service
|
||||
import com.example.p3_pardakht_novin.P3PardakhtNovinPrinter
|
||||
import com.example.p3_pardakht_novin.P3PardakhtNovinService
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -13,8 +13,8 @@ import dagger.hilt.components.SingletonComponent
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class PspModule {
|
||||
@Binds
|
||||
abstract fun bindPspService(impl: P3Service): PspService
|
||||
abstract fun bindPspService(impl: P3PardakhtNovinService): PspService
|
||||
|
||||
@Binds
|
||||
abstract fun bindPrinter(impl: P3Printer): Printer
|
||||
abstract fun bindPrinter(impl: P3PardakhtNovinPrinter): Printer
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.example.tis_sep.di
|
||||
|
||||
import com.example.core.PspService
|
||||
import com.example.ps4.Ps4Service
|
||||
import com.example.ps4.P4Printer
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class PspModule {
|
||||
@Binds
|
||||
abstract fun bindPspService(impl: Ps4Service): PspService
|
||||
|
||||
@Binds
|
||||
abstract fun bindPrinter(impl: P4Printer): Printer
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.example.tis_sep.di
|
||||
|
||||
import android.content.Intent
|
||||
import com.example.core.PspService
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object PspModule {
|
||||
@Provides
|
||||
fun providePspService(): PspService = object : PspService {
|
||||
override fun pay(amount: Double, onMessage: (String) -> Unit) {
|
||||
onMessage("Standard: Payment not supported")
|
||||
}
|
||||
override fun print(text: String, onMessage: (String) -> Unit) {
|
||||
onMessage("Standard: Printing not supported")
|
||||
}
|
||||
override fun createPurchaseIntent(amount: Long): Intent? = null
|
||||
override fun decodePosResponse(response: String?): Map<String, Any>? = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.example.pardakht_novin_app
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@@ -18,5 +18,5 @@ class Ps4Service @Inject constructor() : PspService {
|
||||
override fun decodePosResponse(
|
||||
id: String?,
|
||||
activityResult: ActivityResult
|
||||
): PaymentResultEntity? = null
|
||||
): PaymentResultEntity = PaymentResultEntity.success()
|
||||
}
|
||||
|
||||
@@ -25,8 +25,13 @@ dependencyResolutionManagement {
|
||||
rootProject.name = "PSP"
|
||||
include(":app")
|
||||
include(":tis_app")
|
||||
include(":p3")
|
||||
include(":ps4")
|
||||
include(":core")
|
||||
include(":design_system")
|
||||
include(":stage_app")
|
||||
|
||||
// P3 Parent and children
|
||||
include(":p3")
|
||||
include(":p3:p3_pardakht_novin")
|
||||
include(":p3:p3_sepehr")
|
||||
include(":pardakht_novin_app")
|
||||
|
||||
@@ -23,13 +23,19 @@ android {
|
||||
|
||||
flavorDimensions.add("module")
|
||||
productFlavors {
|
||||
create("p3") {
|
||||
create("p3_novin") {
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
dimension = "module"
|
||||
applicationId = "com.example.stage_app_p3"
|
||||
manifestPlaceholders["appName"] = "TisP3"
|
||||
|
||||
applicationId = "com.example.stage_app_p3_novin"
|
||||
manifestPlaceholders["appName"] = "TisP3Novin"
|
||||
}
|
||||
create("p3_sepehr") {
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
dimension = "module"
|
||||
applicationId = "com.example.stage_app_p3_sepehr"
|
||||
manifestPlaceholders["appName"] = "TisP3Sepehr"
|
||||
}
|
||||
create("ps4") {
|
||||
versionCode = 1
|
||||
@@ -44,17 +50,21 @@ android {
|
||||
dimension = "module"
|
||||
applicationId = "com.example.stage_app"
|
||||
manifestPlaceholders["appName"] = "Tis"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
@@ -66,14 +76,14 @@ android {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core"))
|
||||
implementation(project(":design_system"))
|
||||
"p3Implementation"(project(":p3"))
|
||||
|
||||
"p3_novinImplementation"(project(":p3:p3_pardakht_novin"))
|
||||
"p3_sepehrImplementation"(project(":p3:p3_sepehr"))
|
||||
"ps4Implementation"(project(":ps4"))
|
||||
|
||||
implementation(libs.hilt.android)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.example.stage_app.screen
|
||||
|
||||
import android.util.Log
|
||||
import android.webkit.WebView
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -7,6 +8,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -17,7 +20,8 @@ import com.example.core.getDeviceName
|
||||
import com.example.design_system.PspWebView
|
||||
import com.example.stage_app.viewmodel.StageViewModel
|
||||
|
||||
private const val tisIp = "192.168.128.73:5000"
|
||||
private const val tisWebUrl = "http://192.168.128.73:5000"
|
||||
private const val stageWebViewTag = "StageWebViewScreen"
|
||||
|
||||
@Composable
|
||||
fun StageWebViewScreen(
|
||||
@@ -28,6 +32,18 @@ fun StageWebViewScreen(
|
||||
val context = LocalContext.current
|
||||
var webView: WebView? by remember { mutableStateOf(null) }
|
||||
var currentPaymentId by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var isPaymentProcessing by rememberSaveable { mutableStateOf(false) }
|
||||
var isPrinting by rememberSaveable { mutableStateOf(false) }
|
||||
var initialCallbackSent by rememberSaveable { mutableStateOf(false) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val deviceInfo by remember {
|
||||
mutableStateOf(
|
||||
viewModel.deviceInfoJson(
|
||||
androidId = getAndroidId(context),
|
||||
getDeviceName()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// launcher to handle POS app result
|
||||
val posLauncher = rememberLauncherForActivityResult(
|
||||
@@ -36,36 +52,53 @@ fun StageWebViewScreen(
|
||||
val paymentResultEntity = viewModel.paymentResult(
|
||||
id = currentPaymentId,
|
||||
activityResult = result
|
||||
|
||||
)
|
||||
webView?.post {
|
||||
webView?.evaluateJavascript(
|
||||
viewModel.postPaymentResult(paymentResultEntity = paymentResultEntity),
|
||||
null
|
||||
)
|
||||
}
|
||||
val jsResult = viewModel.postPaymentResult(paymentResultEntity)
|
||||
webView?.let {
|
||||
it.post {
|
||||
it.evaluateJavascript(jsResult, null)
|
||||
}
|
||||
} ?: run {
|
||||
Log.w(stageWebViewTag, "Skipping onPaymentResult JS callback: WebView is not ready yet.")
|
||||
|
||||
}
|
||||
isPaymentProcessing = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
PspWebView(
|
||||
url = tisIp,
|
||||
webView = webView,
|
||||
url = tisWebUrl,
|
||||
modifier = modifier,
|
||||
onWebView = { updatedWebView -> webView = updatedWebView },
|
||||
onPayClick = { amount: Long, id: String? ->
|
||||
if (isPaymentProcessing) return@PspWebView
|
||||
isPaymentProcessing = true
|
||||
currentPaymentId = id
|
||||
viewModel.pay(amount)
|
||||
.also(posLauncher::launch)
|
||||
},
|
||||
onPrintClick = { printEntities ->
|
||||
viewModel.print(printEntities)
|
||||
|
||||
if (isPrinting) return@PspWebView
|
||||
isPrinting = true
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
viewModel.print(printEntities)
|
||||
} finally {
|
||||
isPrinting = false
|
||||
}
|
||||
}
|
||||
},
|
||||
onDeviceInfo = {
|
||||
viewModel.deviceInfoJson(
|
||||
androidId = getAndroidId(context),
|
||||
getDeviceName()
|
||||
)
|
||||
deviceInfo
|
||||
},
|
||||
onWebViewReady = { readyWebView ->
|
||||
webView = readyWebView
|
||||
},
|
||||
onPageFinished = { loadedWebView ->
|
||||
if (!initialCallbackSent) {
|
||||
loadedWebView.evaluateJavascript(viewModel.postPaymentResult(null), null)
|
||||
initialCallbackSent = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -31,11 +31,24 @@ class StageViewModel @Inject constructor(
|
||||
fun paymentResult(
|
||||
id: String?,
|
||||
activityResult: ActivityResult
|
||||
): PaymentResultEntity? {
|
||||
return pspService.decodePosResponse(
|
||||
): PaymentResultEntity {
|
||||
val mockPaymentResult = PaymentResultEntity.success(
|
||||
id = id,
|
||||
activityResult = activityResult
|
||||
message = "تراکنش با موفقیت انجام شد",
|
||||
data = PaymentResultEntity.PaymentResultDataEntity(
|
||||
terminalId = "123456",
|
||||
stan = "123456",
|
||||
rrn = "123456789012",
|
||||
responseCode = "00",
|
||||
customerCardNO = "603799******1234",
|
||||
transactionDateTime = "1403/05/20 12:30:45"
|
||||
)
|
||||
)
|
||||
return mockPaymentResult
|
||||
// return pspService.decodePosResponse(
|
||||
// id = id,
|
||||
// activityResult = activityResult
|
||||
// )
|
||||
}
|
||||
|
||||
private val mock = listOf<PrintEntity>(
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.example.tis_app.di
|
||||
|
||||
import com.example.core.domain.Printer
|
||||
import com.example.core.domain.PspService
|
||||
import com.example.p3.P3Printer
|
||||
import com.example.p3.P3Service
|
||||
import com.example.p3_pardakht_novin.P3Printer
|
||||
import com.example.p3_pardakht_novin.P3Service
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
||||
@@ -22,28 +22,27 @@ android {
|
||||
|
||||
flavorDimensions.add("module")
|
||||
productFlavors {
|
||||
create("p3") {
|
||||
create("p3_pardakht_novin") {
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
dimension = "module"
|
||||
applicationId = "com.example.tis_app_p3"
|
||||
manifestPlaceholders["appName"] = "تیس"
|
||||
applicationId = "com.example.tis_app_p3_pardakht_novin"
|
||||
manifestPlaceholders["appName"] = "تیس (نوین)"
|
||||
}
|
||||
create("ps4") {
|
||||
create("p3_sepehr") {
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
dimension = "module"
|
||||
applicationId = "com.example.tis_app_p4"
|
||||
manifestPlaceholders["appName"] = "تیس" }
|
||||
create("standard") {
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
dimension = "module"
|
||||
applicationId = "com.example.tis_app"
|
||||
manifestPlaceholders["appName"] = "تیس" }
|
||||
applicationId = "com.example.tis_app_p3_sepehr"
|
||||
manifestPlaceholders["appName"] = "تیس (سپهر)"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
@@ -66,9 +65,9 @@ android {
|
||||
dependencies {
|
||||
implementation(project(":core"))
|
||||
implementation(project(":design_system"))
|
||||
"p3Implementation"(project(":p3"))
|
||||
|
||||
"ps4Implementation"(project(":ps4"))
|
||||
"p3_pardakht_novinImplementation"(project(":p3:p3_pardakht_novin"))
|
||||
"p3_sepehrImplementation"(project(":p3:p3_sepehr"))
|
||||
|
||||
implementation(libs.hilt.android)
|
||||
ksp(libs.hilt.compiler)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.example.tis_app.screen
|
||||
|
||||
import android.util.Log
|
||||
import android.webkit.WebView
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -17,10 +18,10 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.example.core.getAndroidId
|
||||
import com.example.core.getDeviceName
|
||||
import com.example.design_system.PspWebView
|
||||
import com.example.design_system.util.WebViewManager
|
||||
import com.example.tis_app.viewmodel.WebViewModel
|
||||
|
||||
private const val tisWebUrl = "https://tis.shift-am.ir"
|
||||
private const val tisWebUrl = "192.168.128.73:5000"
|
||||
private const val tisWebViewTag = "TisWebViewScreen"
|
||||
|
||||
@Composable
|
||||
fun TisWebViewScreen(
|
||||
@@ -29,10 +30,11 @@ fun TisWebViewScreen(
|
||||
) {
|
||||
|
||||
val context = LocalContext.current
|
||||
var webView: WebView by remember { mutableStateOf(WebViewManager.getWebView(context)) }
|
||||
var webView: WebView? by remember { mutableStateOf(null) }
|
||||
var currentPaymentId by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var isPaymentProcessing by rememberSaveable { mutableStateOf(false) }
|
||||
var isPrinting by rememberSaveable { mutableStateOf(false) }
|
||||
var initialCallbackSent by rememberSaveable { mutableStateOf(false) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val deviceInfo by remember {
|
||||
mutableStateOf(
|
||||
@@ -50,17 +52,16 @@ fun TisWebViewScreen(
|
||||
val paymentResultEntity = viewModel.paymentResult(
|
||||
id = currentPaymentId,
|
||||
activityResult = result
|
||||
|
||||
)
|
||||
|
||||
webView.post {
|
||||
webView.evaluateJavascript(
|
||||
viewModel.postPaymentResult(paymentResultEntity = paymentResultEntity),
|
||||
null
|
||||
)
|
||||
val jsResult = viewModel.postPaymentResult(paymentResultEntity)
|
||||
webView?.let {
|
||||
it.post {
|
||||
it.evaluateJavascript(jsResult, null)
|
||||
}
|
||||
} ?: run {
|
||||
Log.w(tisWebViewTag, "Skipping onPaymentResult JS callback: WebView is not ready yet.")
|
||||
}
|
||||
isPaymentProcessing = false
|
||||
|
||||
}
|
||||
|
||||
PspWebView(
|
||||
@@ -86,6 +87,15 @@ fun TisWebViewScreen(
|
||||
},
|
||||
onDeviceInfo = {
|
||||
deviceInfo
|
||||
},
|
||||
onWebViewReady = { readyWebView ->
|
||||
webView = readyWebView
|
||||
},
|
||||
onPageFinished = { loadedWebView ->
|
||||
if (!initialCallbackSent) {
|
||||
loadedWebView.evaluateJavascript(viewModel.postPaymentResult(null), null)
|
||||
initialCallbackSent = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class WebViewModel @Inject constructor(
|
||||
fun paymentResult(
|
||||
id: String?,
|
||||
activityResult: ActivityResult
|
||||
): PaymentResultEntity? {
|
||||
): PaymentResultEntity {
|
||||
return pspService.decodePosResponse(
|
||||
id = id,
|
||||
activityResult = activityResult
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.example.tis_app.di
|
||||
|
||||
import com.example.core.domain.Printer
|
||||
import com.example.core.domain.PspService
|
||||
import com.example.p3_pardakht_novin.P3PardakhtNovinPrinter
|
||||
import com.example.p3_pardakht_novin.P3PardakhtNovinService
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class PspModule {
|
||||
@Binds
|
||||
abstract fun bindPspService(impl: P3PardakhtNovinService): PspService
|
||||
|
||||
@Binds
|
||||
abstract fun bindPrinter(impl: P3PardakhtNovinPrinter): Printer
|
||||
}
|
||||