Compare commits
2
Commits
main
..
f3b227f09a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3b227f09a | ||
|
|
c76f8aa08f |
Generated
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
+18
-2
@@ -15,10 +15,22 @@ android {
|
|||||||
applicationId = "com.approagency.pharmacy"
|
applicationId = "com.approagency.pharmacy"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 1
|
versionCode = 2
|
||||||
versionName = "1.0"
|
versionName = "1.0.2+MK"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
// ---- Myket in-app billing ----
|
||||||
|
val marketApplicationId = "ir.mservices.market"
|
||||||
|
val marketBindAddress = "ir.mservices.market.InAppBillingService.BIND"
|
||||||
|
manifestPlaceholders["marketApplicationId"] = marketApplicationId
|
||||||
|
manifestPlaceholders["marketBindAddress"] = marketBindAddress
|
||||||
|
manifestPlaceholders["marketPermission"] = "$marketApplicationId.BILLING"
|
||||||
|
buildConfigField(
|
||||||
|
"String",
|
||||||
|
"IAB_PUBLIC_KEY",
|
||||||
|
"\"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCDSaqJLDPxQZBZD/LGRsZOJ8m1hLVFedg/tnqebQusikVTNYSEjmP86i1TtXu/2b4E8hpkrqyNlNVvEg1lrHCpnxCh0tOubYiG6X/GEQtgOtIRuBzO3HM/r9w1HQm4gZOJc6DJO46Sxp3YbryJujp+pG8p7bU6zHp4zcqydCJ14QIDAQAB\""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -39,6 +51,7 @@ android {
|
|||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
|
buildConfig = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +93,9 @@ dependencies {
|
|||||||
// SMS User Consent API (OTP auto-fill)
|
// SMS User Consent API (OTP auto-fill)
|
||||||
implementation(libs.play.services.auth.api.phone)
|
implementation(libs.play.services.auth.api.phone)
|
||||||
|
|
||||||
|
// Myket in-app billing
|
||||||
|
implementation("com.github.myketstore:myket-billing-client:1.6")
|
||||||
|
|
||||||
// Koin for DI
|
// Koin for DI
|
||||||
implementation(libs.koin.android)
|
implementation(libs.koin.android)
|
||||||
implementation(libs.koin.androidx.compose.v410)
|
implementation(libs.koin.androidx.compose.v410)
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
"type": "SINGLE",
|
"type": "SINGLE",
|
||||||
"filters": [],
|
"filters": [],
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"versionCode": 1,
|
"versionCode": 2,
|
||||||
"versionName": "1.0",
|
"versionName": "1.0.2+MK",
|
||||||
"outputFile": "app-release.apk"
|
"outputFile": "app-release.apk"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<!-- دیدن و اتصال به سرویس پرداختِ مایکت روی اندروید ۱۱+ -->
|
||||||
|
<queries>
|
||||||
|
<package android:name="ir.mservices.market" />
|
||||||
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".DrugApp"
|
android:name=".DrugApp"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package com.approagency.pharmacy.data.billing
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import com.approagency.pharmacy.BuildConfig
|
||||||
|
import com.approagency.pharmacy.data.local.SessionManager
|
||||||
|
import com.approagency.pharmacy.domain.billing.PurchaseGateway
|
||||||
|
import com.approagency.pharmacy.domain.billing.PurchaseResult
|
||||||
|
import com.approagency.pharmacy.domain.model.SubscriptionProduct
|
||||||
|
import ir.myket.billingclient.IabHelper
|
||||||
|
import ir.myket.billingclient.util.Purchase
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import java.util.UUID
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* درگاهِ پرداختِ مایکت بر پایهٔ کتابخانهٔ `myket-billing-client` (IabHelper).
|
||||||
|
*
|
||||||
|
* جریان (مطابق همان مدلی که در اپ فلاترِ آپرواجنسی استفاده شده):
|
||||||
|
* 1. اتصال به سرویس مایکت ([ensureSetup]).
|
||||||
|
* 2. شروع خرید با SKU = [SubscriptionProduct.uuid] و دریافت توکنِ خرید.
|
||||||
|
* 3. توکن به سرور فرستاده میشود (در لایهٔ اشتراک) و پس از تأیید، خرید [consume]
|
||||||
|
* میشود تا کاربر بتواند دورهٔ بعد دوباره خرید کند.
|
||||||
|
*
|
||||||
|
* چون IabHelper در هر لحظه فقط یک عملیاتِ ناهمگام را میپذیرد، همهٔ فراخوانیها
|
||||||
|
* با [mutex] ترتیبی میشوند.
|
||||||
|
*/
|
||||||
|
class MyketPurchaseGateway(
|
||||||
|
context: Context,
|
||||||
|
private val session: SessionManager
|
||||||
|
) : PurchaseGateway {
|
||||||
|
|
||||||
|
override val name: String = "myket"
|
||||||
|
override val isAvailable: Boolean = true
|
||||||
|
|
||||||
|
private val appContext = context.applicationContext
|
||||||
|
private val mutex = Mutex()
|
||||||
|
|
||||||
|
@Volatile private var helper: IabHelper? = null
|
||||||
|
@Volatile private var setupDone = false
|
||||||
|
|
||||||
|
/** خریدهای منتظرِ مصرف، برحسبِ توکن. */
|
||||||
|
private val pending = mutableMapOf<String, Purchase>()
|
||||||
|
|
||||||
|
override suspend fun purchase(
|
||||||
|
activity: Activity,
|
||||||
|
product: SubscriptionProduct
|
||||||
|
): Result<PurchaseResult> = mutex.withLock {
|
||||||
|
val sku = product.uuid?.takeIf { it.isNotBlank() }
|
||||||
|
?: return Result.failure(IllegalArgumentException("شناسهٔ محصول (SKU) نامعتبر است."))
|
||||||
|
|
||||||
|
ensureSetup().onFailure { return Result.failure(it) }
|
||||||
|
val helper = helper ?: return Result.failure(IllegalStateException("اتصال به مایکت برقرار نشد."))
|
||||||
|
|
||||||
|
// developerPayload = شماره موبایلِ کاربر برای شناساییِ خریدار؛ در صورت نبود، توکن تصادفی.
|
||||||
|
val payload = session.account.value.mobile?.takeIf { it.isNotBlank() }
|
||||||
|
?: UUID.randomUUID().toString()
|
||||||
|
|
||||||
|
val outcome = runCatching {
|
||||||
|
suspendCancellableCoroutine { cont ->
|
||||||
|
helper.launchPurchaseFlow(
|
||||||
|
activity,
|
||||||
|
sku,
|
||||||
|
IabHelper.OnIabPurchaseFinishedListener { result, purchase ->
|
||||||
|
when {
|
||||||
|
result.isSuccess && purchase != null ->
|
||||||
|
cont.resume(PurchaseOutcome.Success(purchase))
|
||||||
|
result.response == IabHelper.BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED ->
|
||||||
|
cont.resume(PurchaseOutcome.AlreadyOwned)
|
||||||
|
else ->
|
||||||
|
cont.resume(PurchaseOutcome.Error(result.message ?: "خطای پرداخت مایکت"))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
payload
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.getOrElse { return Result.failure(it) }
|
||||||
|
|
||||||
|
val purchase = when (outcome) {
|
||||||
|
is PurchaseOutcome.Success -> outcome.purchase
|
||||||
|
PurchaseOutcome.AlreadyOwned -> recoverOwned(sku)
|
||||||
|
?: return Result.failure(IllegalStateException("این محصول قبلاً خریداری شده است."))
|
||||||
|
is PurchaseOutcome.Error -> return Result.failure(IllegalStateException(outcome.message))
|
||||||
|
}
|
||||||
|
|
||||||
|
val token = purchase.token
|
||||||
|
if (token.isNullOrBlank()) {
|
||||||
|
return Result.failure(IllegalStateException("توکن خرید دریافت نشد."))
|
||||||
|
}
|
||||||
|
pending[token] = purchase
|
||||||
|
Result.success(PurchaseResult(purchaseToken = token, orderId = purchase.orderId))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun consume(purchaseToken: String): Result<Unit> = mutex.withLock {
|
||||||
|
val purchase = pending[purchaseToken] ?: return Result.success(Unit)
|
||||||
|
val helper = helper ?: return Result.success(Unit)
|
||||||
|
runCatching {
|
||||||
|
suspendCancellableCoroutine { cont ->
|
||||||
|
helper.consumeAsync(purchase, IabHelper.OnConsumeFinishedListener { _, result ->
|
||||||
|
if (result.isSuccess) cont.resume(Unit)
|
||||||
|
else cont.resumeWithException(IllegalStateException(result.message ?: "خطای مصرفِ خرید"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}.onSuccess { pending.remove(purchaseToken) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** اتصال به سرویس مایکت (یکبار). */
|
||||||
|
private suspend fun ensureSetup(): Result<Unit> {
|
||||||
|
if (setupDone && helper != null) return Result.success(Unit)
|
||||||
|
val newHelper = IabHelper(appContext, BuildConfig.IAB_PUBLIC_KEY)
|
||||||
|
.apply { enableDebugLogging(BuildConfig.DEBUG) }
|
||||||
|
return runCatching {
|
||||||
|
suspendCancellableCoroutine { cont ->
|
||||||
|
newHelper.startSetup(IabHelper.OnIabSetupFinishedListener { result ->
|
||||||
|
if (result.isSuccess) cont.resume(Unit)
|
||||||
|
else cont.resumeWithException(
|
||||||
|
IllegalStateException(result.message ?: "اتصال به مایکت ناموفق بود")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}.onSuccess {
|
||||||
|
helper = newHelper
|
||||||
|
setupDone = true
|
||||||
|
}.onFailure {
|
||||||
|
runCatching { newHelper.dispose() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** بازیابیِ خریدِ مصرفنشدهٔ موجود (وقتی محصول قبلاً خریده شده). */
|
||||||
|
private suspend fun recoverOwned(sku: String): Purchase? {
|
||||||
|
val helper = helper ?: return null
|
||||||
|
return runCatching {
|
||||||
|
suspendCancellableCoroutine<Purchase?> { cont ->
|
||||||
|
helper.queryInventoryAsync(
|
||||||
|
false,
|
||||||
|
listOf(sku),
|
||||||
|
IabHelper.QueryInventoryFinishedListener { result, inventory ->
|
||||||
|
cont.resume(if (result.isSuccess) inventory.getPurchase(sku) else null)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed interface PurchaseOutcome {
|
||||||
|
data class Success(val purchase: Purchase) : PurchaseOutcome
|
||||||
|
data object AlreadyOwned : PurchaseOutcome
|
||||||
|
data class Error(val message: String) : PurchaseOutcome
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,4 +22,6 @@ class StubPurchaseGateway : PurchaseGateway {
|
|||||||
product: SubscriptionProduct
|
product: SubscriptionProduct
|
||||||
): Result<PurchaseResult> =
|
): Result<PurchaseResult> =
|
||||||
Result.failure(IllegalStateException("درگاه پرداخت روی این نسخه فعال نیست."))
|
Result.failure(IllegalStateException("درگاه پرداخت روی این نسخه فعال نیست."))
|
||||||
|
|
||||||
|
override suspend fun consume(purchaseToken: String): Result<Unit> = Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.approagency.pharmacy.di
|
package com.approagency.pharmacy.di
|
||||||
|
|
||||||
|
|
||||||
import com.approagency.pharmacy.data.billing.StubPurchaseGateway
|
import com.approagency.pharmacy.data.billing.MyketPurchaseGateway
|
||||||
import com.approagency.pharmacy.data.local.LocalDrugDataSource
|
import com.approagency.pharmacy.data.local.LocalDrugDataSource
|
||||||
import com.approagency.pharmacy.data.local.SessionManager
|
import com.approagency.pharmacy.data.local.SessionManager
|
||||||
import com.approagency.pharmacy.data.local.database.LabDatabase
|
import com.approagency.pharmacy.data.local.database.LabDatabase
|
||||||
@@ -28,7 +28,6 @@ import com.approagency.pharmacy.domain.usecase.SearchDrugsYabUseCase
|
|||||||
import com.approagency.pharmacy.domain.usecase.SearchTestsUseCase
|
import com.approagency.pharmacy.domain.usecase.SearchTestsUseCase
|
||||||
import com.approagency.pharmacy.presentation.account.AccountSheetController
|
import com.approagency.pharmacy.presentation.account.AccountSheetController
|
||||||
import com.approagency.pharmacy.presentation.account.OtpAutoFillBus
|
import com.approagency.pharmacy.presentation.account.OtpAutoFillBus
|
||||||
import com.approagency.pharmacy.presentation.legal.TermsController
|
|
||||||
import com.approagency.pharmacy.presentation.viewModel.AccountViewModel
|
import com.approagency.pharmacy.presentation.viewModel.AccountViewModel
|
||||||
import com.approagency.pharmacy.presentation.viewModel.DrugDetailViewModel
|
import com.approagency.pharmacy.presentation.viewModel.DrugDetailViewModel
|
||||||
import com.approagency.pharmacy.presentation.viewModel.HomeViewModel
|
import com.approagency.pharmacy.presentation.viewModel.HomeViewModel
|
||||||
@@ -116,8 +115,8 @@ val appModule= module {
|
|||||||
|
|
||||||
single<AuthRepository> { AuthRepositoryImpl(get(), get()) }
|
single<AuthRepository> { AuthRepositoryImpl(get(), get()) }
|
||||||
|
|
||||||
// درگاه پرداخت: روی main استاب؛ شاخههای myket/bazar جایگزین میکنند.
|
// درگاه پرداخت: روی شاخهٔ myket پیادهسازی مایکت.
|
||||||
single<PurchaseGateway> { StubPurchaseGateway() }
|
single<PurchaseGateway> { MyketPurchaseGateway(androidContext(), get()) }
|
||||||
|
|
||||||
|
|
||||||
factory { DrugHtmlParser() }
|
factory { DrugHtmlParser() }
|
||||||
@@ -192,9 +191,6 @@ val appModule= module {
|
|||||||
// پلِ تحویل کدِ خواندهشده از پیامک به شیت ورود
|
// پلِ تحویل کدِ خواندهشده از پیامک به شیت ورود
|
||||||
single { OtpAutoFillBus() }
|
single { OtpAutoFillBus() }
|
||||||
|
|
||||||
// کنترلر نمایش صفحهٔ شرایط و قوانین
|
|
||||||
single { TermsController() }
|
|
||||||
|
|
||||||
viewModel {
|
viewModel {
|
||||||
AccountViewModel(get(), get(), get())
|
AccountViewModel(get(), get(), get())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ interface PurchaseGateway {
|
|||||||
* نیاز به [activity] برای باز کردن جریان پرداخت فروشگاه دارد.
|
* نیاز به [activity] برای باز کردن جریان پرداخت فروشگاه دارد.
|
||||||
*/
|
*/
|
||||||
suspend fun purchase(activity: Activity, product: SubscriptionProduct): Result<PurchaseResult>
|
suspend fun purchase(activity: Activity, product: SubscriptionProduct): Result<PurchaseResult>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* مصرفِ خرید پس از تأییدِ سرور، تا کاربر بتواند دوباره (دورهٔ بعد) خرید کند.
|
||||||
|
* فقط برای درگاههایی که مدلِ مصرفشدنی دارند (مثل مایکت) معنا دارد؛ برای
|
||||||
|
* بقیه میتواند بیاثر باشد.
|
||||||
|
*/
|
||||||
|
suspend fun consume(purchaseToken: String): Result<Unit>
|
||||||
}
|
}
|
||||||
|
|
||||||
/** نتیجهی یک خرید موفق در فروشگاه. */
|
/** نتیجهی یک خرید موفق در فروشگاه. */
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import com.approagency.pharmacy.R
|
|
||||||
import com.approagency.pharmacy.data.local.AccountState
|
import com.approagency.pharmacy.data.local.AccountState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,7 +51,7 @@ fun AccountAppBar(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text(text = stringResource(R.string.app_name), style = MaterialTheme.typography.titleMedium)
|
Text(text = "دارویاب", style = MaterialTheme.typography.titleMedium)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ import com.approagency.pharmacy.presentation.account.AccountDrawer
|
|||||||
import com.approagency.pharmacy.presentation.account.AccountSheet
|
import com.approagency.pharmacy.presentation.account.AccountSheet
|
||||||
import com.approagency.pharmacy.presentation.account.AccountSheetController
|
import com.approagency.pharmacy.presentation.account.AccountSheetController
|
||||||
import com.approagency.pharmacy.presentation.common.ConfirmBottomSheet
|
import com.approagency.pharmacy.presentation.common.ConfirmBottomSheet
|
||||||
import com.approagency.pharmacy.presentation.legal.TermsController
|
|
||||||
import com.approagency.pharmacy.presentation.legal.TermsDialog
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
|
|
||||||
@@ -36,12 +34,10 @@ fun MainContainer(
|
|||||||
) {
|
) {
|
||||||
val session: SessionManager = koinInject()
|
val session: SessionManager = koinInject()
|
||||||
val sheetController: AccountSheetController = koinInject()
|
val sheetController: AccountSheetController = koinInject()
|
||||||
val termsController: TermsController = koinInject()
|
|
||||||
val authRepository: AuthRepository = koinInject()
|
val authRepository: AuthRepository = koinInject()
|
||||||
|
|
||||||
val account by session.account.collectAsState()
|
val account by session.account.collectAsState()
|
||||||
val sheetVisible by sheetController.visible.collectAsState()
|
val sheetVisible by sheetController.visible.collectAsState()
|
||||||
val termsVisible by termsController.visible.collectAsState()
|
|
||||||
val themeMode by session.themeMode.collectAsState()
|
val themeMode by session.themeMode.collectAsState()
|
||||||
val fontScale by session.fontScale.collectAsState()
|
val fontScale by session.fontScale.collectAsState()
|
||||||
|
|
||||||
@@ -66,10 +62,6 @@ fun MainContainer(
|
|||||||
onThemeModeChange = { session.setThemeMode(it) },
|
onThemeModeChange = { session.setThemeMode(it) },
|
||||||
fontScale = fontScale,
|
fontScale = fontScale,
|
||||||
onFontScaleChange = { session.setFontScale(it) },
|
onFontScaleChange = { session.setFontScale(it) },
|
||||||
onShowTerms = {
|
|
||||||
scope.launch { drawerState.close() }
|
|
||||||
termsController.show()
|
|
||||||
},
|
|
||||||
onLogout = { showLogoutConfirm = true }
|
onLogout = { showLogoutConfirm = true }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -94,10 +86,6 @@ fun MainContainer(
|
|||||||
AccountSheet(onDismiss = { sheetController.hide() })
|
AccountSheet(onDismiss = { sheetController.hide() })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (termsVisible) {
|
|
||||||
TermsDialog(onDismiss = { termsController.hide() })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showLogoutConfirm) {
|
if (showLogoutConfirm) {
|
||||||
ConfirmBottomSheet(
|
ConfirmBottomSheet(
|
||||||
title = "خروج از حساب",
|
title = "خروج از حساب",
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.Article
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.Logout
|
import androidx.compose.material.icons.automirrored.filled.Logout
|
||||||
import androidx.compose.material.icons.filled.BrightnessAuto
|
import androidx.compose.material.icons.filled.BrightnessAuto
|
||||||
import androidx.compose.material.icons.filled.CalendarMonth
|
import androidx.compose.material.icons.filled.CalendarMonth
|
||||||
@@ -36,7 +35,6 @@ import androidx.compose.material3.SegmentedButtonDefaults
|
|||||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -67,7 +65,6 @@ fun AccountDrawer(
|
|||||||
onThemeModeChange: (ThemeMode) -> Unit,
|
onThemeModeChange: (ThemeMode) -> Unit,
|
||||||
fontScale: FontScale,
|
fontScale: FontScale,
|
||||||
onFontScaleChange: (FontScale) -> Unit,
|
onFontScaleChange: (FontScale) -> Unit,
|
||||||
onShowTerms: () -> Unit,
|
|
||||||
onLogout: () -> Unit
|
onLogout: () -> Unit
|
||||||
) {
|
) {
|
||||||
ModalDrawerSheet(
|
ModalDrawerSheet(
|
||||||
@@ -141,26 +138,7 @@ fun AccountDrawer(
|
|||||||
Spacer(Modifier.height(MaterialTheme.dime.sm))
|
Spacer(Modifier.height(MaterialTheme.dime.sm))
|
||||||
FontScaleSelector(selected = fontScale, onSelect = onFontScaleChange)
|
FontScaleSelector(selected = fontScale, onSelect = onFontScaleChange)
|
||||||
|
|
||||||
Spacer(Modifier.height(MaterialTheme.dime.lg))
|
Spacer(Modifier.height(MaterialTheme.dime.xl))
|
||||||
|
|
||||||
TextButton(
|
|
||||||
onClick = onShowTerms,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.AutoMirrored.Filled.Article,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
Spacer(Modifier.width(MaterialTheme.dime.sm))
|
|
||||||
Text(
|
|
||||||
text = "شرایط و قوانین",
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(Modifier.height(MaterialTheme.dime.md))
|
|
||||||
|
|
||||||
if (account.isLoggedIn) {
|
if (account.isLoggedIn) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import com.approagency.pharmacy.presentation.common.CustomModalBottomSheet
|
import com.approagency.pharmacy.presentation.common.CustomModalBottomSheet
|
||||||
import com.approagency.pharmacy.presentation.common.OtpTextField
|
import com.approagency.pharmacy.presentation.common.OtpTextField
|
||||||
import com.approagency.pharmacy.presentation.common.PrimaryButton
|
import com.approagency.pharmacy.presentation.common.PrimaryButton
|
||||||
import com.approagency.pharmacy.presentation.legal.TermsController
|
|
||||||
import com.approagency.pharmacy.presentation.viewModel.AccountPhase
|
import com.approagency.pharmacy.presentation.viewModel.AccountPhase
|
||||||
import com.approagency.pharmacy.presentation.viewModel.AccountViewModel
|
import com.approagency.pharmacy.presentation.viewModel.AccountViewModel
|
||||||
import com.vada.caller.ui.theme.dime
|
import com.vada.caller.ui.theme.dime
|
||||||
@@ -52,7 +51,6 @@ fun AccountSheet(
|
|||||||
val account by viewModel.account.collectAsState()
|
val account by viewModel.account.collectAsState()
|
||||||
val activity = LocalContext.current as? Activity
|
val activity = LocalContext.current as? Activity
|
||||||
val otpAutoFillBus: OtpAutoFillBus = koinInject()
|
val otpAutoFillBus: OtpAutoFillBus = koinInject()
|
||||||
val termsController: TermsController = koinInject()
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) { viewModel.onSheetOpened() }
|
LaunchedEffect(Unit) { viewModel.onSheetOpened() }
|
||||||
|
|
||||||
@@ -111,7 +109,6 @@ fun AccountSheet(
|
|||||||
}
|
}
|
||||||
Spacer(Modifier.height(MaterialTheme.dime.md))
|
Spacer(Modifier.height(MaterialTheme.dime.md))
|
||||||
PrimaryButton(text = "ارسال کد", isLoading = ui.busy, onClick = { viewModel.sendOtp() })
|
PrimaryButton(text = "ارسال کد", isLoading = ui.busy, onClick = { viewModel.sendOtp() })
|
||||||
TermsConsentNote(onShowTerms = { termsController.show() })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountPhase.EnterOtp -> {
|
AccountPhase.EnterOtp -> {
|
||||||
@@ -202,15 +199,3 @@ private fun SheetTitle(text: String) {
|
|||||||
Text(text = text, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
Text(text = text, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
||||||
Spacer(Modifier.height(MaterialTheme.dime.xs))
|
Spacer(Modifier.height(MaterialTheme.dime.xs))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** لینکِ «شرایط و قوانین» زیر فرمِ ورود (برای دسترسی و بازبینیِ فروشگاه). */
|
|
||||||
@Composable
|
|
||||||
private fun TermsConsentNote(onShowTerms: () -> Unit) {
|
|
||||||
TextButton(onClick = onShowTerms, modifier = Modifier.fillMaxWidth()) {
|
|
||||||
Text(
|
|
||||||
text = "با ادامه، شرایط و قوانین را میپذیرید",
|
|
||||||
style = MaterialTheme.typography.labelMedium,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
package com.approagency.pharmacy.presentation.legal
|
|
||||||
|
|
||||||
/** یک بخش از سند شرایط و قوانین. */
|
|
||||||
data class LegalSection(
|
|
||||||
val title: String,
|
|
||||||
val items: List<String>
|
|
||||||
)
|
|
||||||
|
|
||||||
/** محتوای شرایط و قوانینِ اپلیکیشن داروچی (برای نمایش درونبرنامهای). */
|
|
||||||
object AppLegal {
|
|
||||||
|
|
||||||
const val LAST_UPDATED = "۱۴۰۵/۰۳/۲۴"
|
|
||||||
const val SUPPORT_EMAIL = "approagency@gmail.com"
|
|
||||||
|
|
||||||
const val INTRO =
|
|
||||||
"استفاده از اپلیکیشن «داروچی» (که از این پس «برنامه» نامیده میشود) بهمنزلهٔ " +
|
|
||||||
"مطالعه و پذیرشِ کاملِ شرایط و قوانین زیر است. در صورتی که با هر بخشی از این " +
|
|
||||||
"قوانین موافق نیستید، لطفاً از برنامه استفاده نکنید."
|
|
||||||
|
|
||||||
val sections: List<LegalSection> = listOf(
|
|
||||||
LegalSection(
|
|
||||||
title = "۱. معرفی خدمات",
|
|
||||||
items = listOf(
|
|
||||||
"برنامه ابزاری اطلاعرسانی است که امکاناتِ جستوجوی دارو و مشاهدهٔ اطلاعات دارویی، مشاهدهٔ موجودی دارو در داروخانهها و مشاهدهٔ اطلاعات آزمایشهای پزشکی را فراهم میکند.",
|
|
||||||
"این خدمات صرفاً جنبهٔ آموزشی و اطلاعرسانی دارند."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
LegalSection(
|
|
||||||
title = "۲. سلب مسئولیت پزشکی (بسیار مهم)",
|
|
||||||
items = listOf(
|
|
||||||
"اطلاعاتِ ارائهشده در برنامه جایگزینِ مشاورهٔ پزشک، داروساز یا کادر درمان نیست و نباید مبنای تشخیص، تجویز یا قطع/شروع مصرف دارو قرار گیرد.",
|
|
||||||
"مصرفِ هر دارو باید زیر نظر پزشک یا داروساز و مطابق نسخه و دستور ایشان انجام شود.",
|
|
||||||
"اطلاعاتِ دارویی ممکن است کامل، بهروز یا بدون خطا نباشد و مسئولیتِ استفاده از آن بر عهدهٔ کاربر است.",
|
|
||||||
"موجودیِ نمایشدادهشدهٔ داروها بر اساس دادههای منابعِ ثالث است و پیش از مراجعه باید از داروخانه تأیید شود.",
|
|
||||||
"در شرایطِ اورژانسی یا بروزِ عوارض، فوراً به پزشک یا اورژانس مراجعه کنید."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
LegalSection(
|
|
||||||
title = "۳. حساب کاربری و ورود",
|
|
||||||
items = listOf(
|
|
||||||
"ورود به برنامه از طریق شماره موبایل و کدِ یکبارمصرف (OTP) انجام میشود.",
|
|
||||||
"مسئولیتِ حفظِ شماره موبایل، دستگاه و کدِ تأیید بر عهدهٔ کاربر است.",
|
|
||||||
"کاربر موظف است اطلاعاتِ صحیح ارائه دهد و حساب را در اختیار دیگران قرار ندهد."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
LegalSection(
|
|
||||||
title = "۴. جستوجوی رایگان، اشتراک و پرداخت",
|
|
||||||
items = listOf(
|
|
||||||
"برنامه تعدادی جستوجوی رایگان در اختیار کاربر قرار میدهد؛ پس از پایانِ سهمیهٔ رایگان، برای استفادهٔ نامحدود تهیهٔ اشتراک لازم است.",
|
|
||||||
"خرید و پرداختِ اشتراک از طریق درگاهِ پرداختِ درونبرنامهایِ فروشگاه (فروشگاه) انجام میشود و تابعِ قوانینِ همان فروشگاه است.",
|
|
||||||
"قیمت و مدتِ اشتراک پیش از خرید به کاربر نمایش داده میشود.",
|
|
||||||
"بازگشتِ وجه تابعِ سیاستهای فروشگاهِ فروشگاه است و از طریق همان فروشگاه پیگیری میشود.",
|
|
||||||
"در صورتی که پس از پرداختِ موفق اشتراک فعال نشد، با پشتیبانیِ ما تماس بگیرید."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
LegalSection(
|
|
||||||
title = "۵. حریم خصوصی و دادهها",
|
|
||||||
items = listOf(
|
|
||||||
"برای ارائهٔ خدمات، حداقلِ اطلاعاتِ لازم (از جمله شماره موبایل و وضعیتِ اشتراک) دریافت و نگهداری میشود.",
|
|
||||||
"اطلاعاتِ خرید (توکنِ خرید) صرفاً برای فعالسازیِ اشتراک به سرور ارسال میشود.",
|
|
||||||
"اطلاعاتِ کاربران بدون رضایتِ ایشان در اختیارِ اشخاصِ ثالثِ غیرمرتبط قرار نمیگیرد، مگر در مواردی که قانون الزام کند."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
LegalSection(
|
|
||||||
title = "۶. تعهدات کاربر",
|
|
||||||
items = listOf(
|
|
||||||
"استفاده از برنامه صرفاً برای اهدافِ قانونی و شخصی.",
|
|
||||||
"ایجادنکردنِ اخلال در عملکردِ برنامه و خودداری از مهندسیِ معکوس یا دورزدنِ محدودیتها.",
|
|
||||||
"بازنشر یا تجاریسازیِ محتوای برنامه بدون اجازه ممنوع است."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
LegalSection(
|
|
||||||
title = "۷. مالکیت فکری",
|
|
||||||
items = listOf(
|
|
||||||
"تمامِ حقوقِ مربوط به برنامه، نام، نشان، رابط کاربری و محتوای آن متعلق به مالکِ برنامه است و هرگونه استفادهٔ غیرمجاز پیگردِ قانونی دارد."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
LegalSection(
|
|
||||||
title = "۸. محدودیت مسئولیت",
|
|
||||||
items = listOf(
|
|
||||||
"برنامه «همانگونه که هست» ارائه میشود و تضمینی نسبت به بینقص بودن، دردسترس بودنِ دائمی یا صحتِ کاملِ اطلاعات داده نمیشود.",
|
|
||||||
"مالکِ برنامه در قبالِ خساراتِ مستقیم یا غیرمستقیمِ ناشی از استفاده یا اتکا به اطلاعاتِ برنامه مسئولیتی نمیپذیرد."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
LegalSection(
|
|
||||||
title = "۹. تعلیق، تغییرات و قانونِ حاکم",
|
|
||||||
items = listOf(
|
|
||||||
"در صورتِ نقضِ این قوانین، دسترسیِ کاربر ممکن است محدود یا مسدود شود.",
|
|
||||||
"این شرایط ممکن است بهروزرسانی شود و ادامهٔ استفاده بهمنزلهٔ پذیرشِ تغییرات است.",
|
|
||||||
"این شرایط تابعِ قوانینِ جمهوریِ اسلامیِ ایران است."
|
|
||||||
)
|
|
||||||
),
|
|
||||||
LegalSection(
|
|
||||||
title = "۱۰. ارتباط با ما",
|
|
||||||
items = listOf(
|
|
||||||
"در صورتِ هرگونه پرسش یا مشکل از طریقِ ایمیلِ $SUPPORT_EMAIL با ما در تماس باشید."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.approagency.pharmacy.presentation.legal
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
|
|
||||||
/**
|
|
||||||
* کنترلر سراسریِ نمایشِ صفحهٔ «شرایط و قوانین».
|
|
||||||
* هم کشوی کناری و هم شیتِ ورود میتوانند آن را با [show] باز کنند.
|
|
||||||
*/
|
|
||||||
class TermsController {
|
|
||||||
private val _visible = MutableStateFlow(false)
|
|
||||||
val visible: StateFlow<Boolean> = _visible.asStateFlow()
|
|
||||||
|
|
||||||
fun show() { _visible.value = true }
|
|
||||||
fun hide() { _visible.value = false }
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
package com.approagency.pharmacy.presentation.legal
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Close
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
|
||||||
import com.vada.caller.ui.theme.dime
|
|
||||||
|
|
||||||
/** صفحهٔ تمامصفحهٔ «شرایط و قوانین». */
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun TermsDialog(onDismiss: () -> Unit) {
|
|
||||||
Dialog(
|
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
properties = DialogProperties(usePlatformDefaultWidth = false)
|
|
||||||
) {
|
|
||||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.surface) {
|
|
||||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
|
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
|
||||||
TopAppBar(
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
"شرایط و قوانین",
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = onDismiss) {
|
|
||||||
Icon(Icons.Filled.Close, contentDescription = "بستن")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.padding(MaterialTheme.dime.lg),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.dime.md)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "آخرین بهروزرسانی: ${AppLegal.LAST_UPDATED}",
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = AppLegal.INTRO,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
|
|
||||||
AppLegal.sections.forEach { section ->
|
|
||||||
Spacer(Modifier.height(MaterialTheme.dime.xs))
|
|
||||||
Text(
|
|
||||||
text = section.title,
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
section.items.forEach { item ->
|
|
||||||
Text(
|
|
||||||
text = "• $item",
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(Modifier.height(MaterialTheme.dime.lg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -135,6 +135,8 @@ class AccountViewModel(
|
|||||||
}
|
}
|
||||||
repository.subscribe(product.id, result.purchaseToken, gateway.name).fold(
|
repository.subscribe(product.id, result.purchaseToken, gateway.name).fold(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
|
// پس از تأییدِ سرور، خرید را مصرف کن تا دورهٔ بعد قابل خرید باشد.
|
||||||
|
gateway.consume(result.purchaseToken)
|
||||||
_ui.update {
|
_ui.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
purchasingProductId = null,
|
purchasingProductId = null,
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
# شرایط و قوانین استفاده از اپلیکیشن داروچی
|
|
||||||
|
|
||||||
آخرین بهروزرسانی: ۱۴۰۵/۰۳/۲۴
|
|
||||||
|
|
||||||
استفاده از اپلیکیشن «داروچی» (که از این پس «برنامه» نامیده میشود) بهمنزلهٔ
|
|
||||||
مطالعه و پذیرشِ کاملِ شرایط و قوانین زیر است. در صورتی که با هر بخشی از این
|
|
||||||
قوانین موافق نیستید، لطفاً از برنامه استفاده نکنید.
|
|
||||||
|
|
||||||
## ۱. معرفی خدمات
|
|
||||||
|
|
||||||
برنامه ابزاری اطلاعرسانی است که امکانات زیر را در اختیار کاربر قرار میدهد:
|
|
||||||
|
|
||||||
- جستوجوی دارو و مشاهدهٔ اطلاعات دارویی (موارد مصرف، مقدار مصرف، عوارض،
|
|
||||||
تداخلات، هشدارها و موارد مشابه).
|
|
||||||
- مشاهدهٔ موجودی دارو در داروخانهها.
|
|
||||||
- مشاهدهٔ اطلاعات آزمایشها و تستهای پزشکی.
|
|
||||||
|
|
||||||
این خدمات صرفاً جنبهٔ آموزشی و اطلاعرسانی دارند.
|
|
||||||
|
|
||||||
## ۲. سلب مسئولیت پزشکی (بسیار مهم)
|
|
||||||
|
|
||||||
- اطلاعاتِ ارائهشده در برنامه **جایگزینِ مشاورهٔ پزشک، داروساز یا کادر درمان
|
|
||||||
نیست** و نباید مبنای تشخیص، تجویز یا قطع/شروع مصرف دارو قرار گیرد.
|
|
||||||
- مصرفِ هر دارو باید **زیر نظر پزشک یا داروساز** و مطابق نسخه و دستور ایشان
|
|
||||||
انجام شود.
|
|
||||||
- اطلاعاتِ دارویی ممکن است کامل، بهروز یا بدون خطا نباشد. مسئولیتِ هرگونه
|
|
||||||
استفاده از این اطلاعات بر عهدهٔ کاربر است.
|
|
||||||
- موجودیِ نمایشدادهشدهٔ داروها در داروخانهها بر اساس دادههای منابعِ ثالث
|
|
||||||
است و ممکن است با وضعیتِ واقعی متفاوت باشد؛ پیش از مراجعه، موجودی را از
|
|
||||||
داروخانه تأیید کنید.
|
|
||||||
- در شرایطِ اورژانسی یا بروزِ عوارض، فوراً به پزشک یا اورژانس مراجعه کنید.
|
|
||||||
|
|
||||||
## ۳. حساب کاربری و ورود
|
|
||||||
|
|
||||||
- ورود به برنامه از طریق شماره موبایل و کدِ یکبارمصرف (OTP) انجام میشود.
|
|
||||||
- مسئولیتِ حفظِ شماره موبایل، دسترسی به دستگاه و کدِ تأیید بر عهدهٔ کاربر است.
|
|
||||||
- کاربر موظف است اطلاعاتِ صحیح ارائه دهد و حساب را در اختیار دیگران قرار ندهد.
|
|
||||||
|
|
||||||
## ۴. جستوجوی رایگان، اشتراک و پرداخت
|
|
||||||
|
|
||||||
- برنامه تعدادی **جستوجوی رایگان** در اختیار کاربر قرار میدهد. پس از پایانِ
|
|
||||||
سهمیهٔ رایگان، برای ادامهٔ استفادهٔ نامحدود، تهیهٔ **اشتراک** لازم است.
|
|
||||||
- خرید و پرداختِ اشتراک از طریق **درگاهِ پرداختِ درونبرنامهایِ فروشگاه
|
|
||||||
(فروشگاه)** انجام میشود و تابعِ قوانین و شرایطِ همان فروشگاه است.
|
|
||||||
- قیمت و مدتِ اشتراک پیش از خرید به کاربر نمایش داده میشود.
|
|
||||||
- **بازگشتِ وجه** تابعِ سیاستهای فروشگاهِ فروشگاه است. درخواستهای مربوط به
|
|
||||||
عودتِ وجهِ خریدهای درونبرنامهای از طریق همان فروشگاه پیگیری میشود.
|
|
||||||
- در صورتی که پس از پرداختِ موفق، اشتراک فعال نشد، با پشتیبانیِ ما تماس بگیرید.
|
|
||||||
|
|
||||||
## ۵. حریم خصوصی و دادهها
|
|
||||||
|
|
||||||
- برای ارائهٔ خدمات، حداقلِ اطلاعاتِ لازم (از جمله **شماره موبایل** و وضعیتِ
|
|
||||||
اشتراک) دریافت و نگهداری میشود.
|
|
||||||
- اطلاعاتِ خرید (توکنِ خرید) صرفاً برای فعالسازیِ اشتراک به سرور ارسال میشود.
|
|
||||||
- اطلاعاتِ کاربران بدون رضایتِ ایشان در اختیارِ اشخاصِ ثالثِ غیرمرتبط قرار
|
|
||||||
نمیگیرد، مگر در مواردی که قانون الزام کند.
|
|
||||||
- برای آگاهیِ بیشتر، به سند **حریم خصوصی** برنامه مراجعه کنید.
|
|
||||||
|
|
||||||
## ۶. تعهدات کاربر
|
|
||||||
|
|
||||||
کاربر متعهد میشود که:
|
|
||||||
|
|
||||||
- از برنامه صرفاً برای اهدافِ قانونی و شخصی استفاده کند.
|
|
||||||
- در عملکردِ برنامه اخلال ایجاد نکند و از روشهای نفوذ، مهندسیِ معکوس یا دورزدنِ
|
|
||||||
محدودیتها استفاده نکند.
|
|
||||||
- محتوای برنامه را بدون اجازه بازنشر یا تجاریسازی نکند.
|
|
||||||
|
|
||||||
## ۷. مالکیت فکری
|
|
||||||
|
|
||||||
تمامِ حقوقِ مربوط به برنامه، نام، نشان (لوگو)، رابط کاربری و محتوای آن متعلق به
|
|
||||||
مالکِ برنامه است و هرگونه استفادهٔ غیرمجاز پیگردِ قانونی دارد.
|
|
||||||
|
|
||||||
## ۸. محدودیت مسئولیت
|
|
||||||
|
|
||||||
- برنامه «همانگونه که هست» ارائه میشود و هیچ تضمینی نسبت به بینقص بودن،
|
|
||||||
دردسترس بودنِ دائمی یا صحتِ کاملِ اطلاعات داده نمیشود.
|
|
||||||
- مالکِ برنامه در قبالِ خساراتِ مستقیم یا غیرمستقیمِ ناشی از استفاده یا عدمِ
|
|
||||||
امکانِ استفاده از برنامه، یا اتکا به اطلاعاتِ آن، مسئولیتی نمیپذیرد.
|
|
||||||
|
|
||||||
## ۹. تعلیق و خاتمه
|
|
||||||
|
|
||||||
در صورتِ نقضِ این قوانین، دسترسیِ کاربر به برنامه ممکن است بدونِ اطلاعِ قبلی
|
|
||||||
محدود یا مسدود شود.
|
|
||||||
|
|
||||||
## ۱۰. تغییر قوانین
|
|
||||||
|
|
||||||
این شرایط ممکن است در آینده بهروزرسانی شود. ادامهٔ استفاده از برنامه پس از
|
|
||||||
انتشارِ نسخهٔ جدید، بهمنزلهٔ پذیرشِ تغییرات است.
|
|
||||||
|
|
||||||
## ۱۱. قانونِ حاکم
|
|
||||||
|
|
||||||
این شرایط تابعِ قوانینِ جمهوریِ اسلامیِ ایران است و اختلافاتِ احتمالی مطابقِ همین
|
|
||||||
قوانین حلوفصل میشود.
|
|
||||||
|
|
||||||
## ۱۲. ارتباط با ما
|
|
||||||
|
|
||||||
در صورتِ هرگونه پرسش یا مشکل، از طریقِ راههای زیر با ما در تماس باشید:
|
|
||||||
|
|
||||||
- ایمیل پشتیبانی: approagency@gmail.com
|
|
||||||
@@ -18,6 +18,7 @@ dependencyResolutionManagement {
|
|||||||
//google()
|
//google()
|
||||||
//mavenCentral()
|
//mavenCentral()
|
||||||
maven { url = uri("https://maven.myket.ir") }
|
maven { url = uri("https://maven.myket.ir") }
|
||||||
|
maven { url = uri("https://jitpack.io") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user