diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e415027b9..36b395015 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -15,9 +15,6 @@ jobs:
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- - name: Validate Gradle Wrapper
- uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # v1
-
- name: Checkout submodules
run: git submodule update --init --recursive
diff --git a/.github/workflows/validate-gradle-wrapper.yaml b/.github/workflows/validate-gradle-wrapper.yaml
index 30a0360ae..2713f87b7 100644
--- a/.github/workflows/validate-gradle-wrapper.yaml
+++ b/.github/workflows/validate-gradle-wrapper.yaml
@@ -11,4 +11,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- - uses: gradle/wrapper-validation-action@v1
+ - uses: gradle/actions/wrapper-validation@v3
diff --git a/core/src/main/java/net/ivpn/core/common/prefs/EncryptedUserPreference.kt b/core/src/main/java/net/ivpn/core/common/prefs/EncryptedUserPreference.kt
index 837c72bc7..7157b543b 100644
--- a/core/src/main/java/net/ivpn/core/common/prefs/EncryptedUserPreference.kt
+++ b/core/src/main/java/net/ivpn/core/common/prefs/EncryptedUserPreference.kt
@@ -49,6 +49,7 @@ class EncryptedUserPreference @Inject constructor(val preference: Preference) {
private const val BLANK_USERNAME = "BLANK_USERNAME"
private const val BLANK_USERNAME_GENERATED_DATE = "BLANK_USERNAME_GENERATED_DATE"
+ private const val AVAILABLE_PLANS = "AVAILABLE_PLANS"
}
private val sharedPreferences: SharedPreferences = preference.accountPreference
@@ -129,8 +130,19 @@ class EncryptedUserPreference @Inject constructor(val preference: Preference) {
return sharedPreferences.getString(CURRENT_PLAN, "")
}
+ fun putAvailablePlans(json: String?) {
+ sharedPreferences.edit()
+ .putString(AVAILABLE_PLANS, json)
+ .apply()
+ }
+
+ fun getAvailablePlans(): String? {
+ return sharedPreferences.getString(AVAILABLE_PLANS, null)
+ }
+
fun getCapabilityMultiHop(): Boolean {
- return sharedPreferences.getBoolean(USER_MULTI_HOP, false)
+ // return sharedPreferences.getBoolean(USER_MULTI_HOP, false)
+ return true
}
fun getPaymentMethod(): String {
diff --git a/core/src/main/java/net/ivpn/core/common/session/SessionController.kt b/core/src/main/java/net/ivpn/core/common/session/SessionController.kt
index b02125033..1a5dab5db 100644
--- a/core/src/main/java/net/ivpn/core/common/session/SessionController.kt
+++ b/core/src/main/java/net/ivpn/core/common/session/SessionController.kt
@@ -22,6 +22,7 @@ package net.ivpn.core.common.session
along with the IVPN Android app. If not, see .
*/
+import com.google.gson.Gson
import com.wireguard.android.crypto.Keypair
import net.ivpn.core.IVPNApplication
import net.ivpn.core.common.Mapper
@@ -34,6 +35,7 @@ import net.ivpn.core.rest.HttpClientFactory
import net.ivpn.core.rest.IVPNApi
import net.ivpn.core.rest.RequestListener
import net.ivpn.core.rest.Responses
+import net.ivpn.core.rest.data.model.ServicePlan
import net.ivpn.core.rest.data.model.ServiceStatus
import net.ivpn.core.rest.data.model.WireGuard
import net.ivpn.core.rest.data.session.*
@@ -326,6 +328,9 @@ class SessionController @Inject constructor(
settings.isMultiHopEnabled = false
}
}
+ serviceStatus.availablePlans?.let {
+ userPreference.putAvailablePlans(Gson().toJson(it))
+ }
}
private fun handleWireGuardResponse(wireGuard: WireGuard?, keys: Keypair?) {
diff --git a/core/src/main/java/net/ivpn/core/common/utils/StringUtil.java b/core/src/main/java/net/ivpn/core/common/utils/StringUtil.java
index 94803d7d7..2581e9ecd 100644
--- a/core/src/main/java/net/ivpn/core/common/utils/StringUtil.java
+++ b/core/src/main/java/net/ivpn/core/common/utils/StringUtil.java
@@ -81,10 +81,4 @@ public static String formatTimeUntilResumed(long timeUntilResumed) {
return builder.toString();
}
-// public static String formatPlanName(Plan plan) {
-// if (plan == null) {
-// return "";
-// }
-// return "IVPN " + plan.toString();
-// }
}
\ No newline at end of file
diff --git a/core/src/main/java/net/ivpn/core/rest/data/model/ServicePlan.kt b/core/src/main/java/net/ivpn/core/rest/data/model/ServicePlan.kt
new file mode 100644
index 000000000..569ea13ec
--- /dev/null
+++ b/core/src/main/java/net/ivpn/core/rest/data/model/ServicePlan.kt
@@ -0,0 +1,36 @@
+package net.ivpn.core.rest.data.model
+
+/*
+ IVPN Android app
+ https://github.com/ivpn/android-app
+
+ Created by Oleksandr Mykhailenko.
+ Copyright (c) 2023 IVPN Limited.
+
+ This file is part of the IVPN Android app.
+
+ The IVPN Android app is free software: you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ The IVPN Android app is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ details.
+
+ You should have received a copy of the GNU General Public License
+ along with the IVPN Android app. If not, see .
+*/
+
+import com.google.gson.annotations.Expose
+import com.google.gson.annotations.SerializedName
+
+data class ServicePlan(
+ @SerializedName("name")
+ @Expose
+ val name: String,
+
+ @SerializedName("device_limit")
+ @Expose
+ val deviceLimit: Int
+)
diff --git a/core/src/main/java/net/ivpn/core/rest/data/model/ServiceStatus.java b/core/src/main/java/net/ivpn/core/rest/data/model/ServiceStatus.java
index 82aeae72a..93d0e4c90 100644
--- a/core/src/main/java/net/ivpn/core/rest/data/model/ServiceStatus.java
+++ b/core/src/main/java/net/ivpn/core/rest/data/model/ServiceStatus.java
@@ -56,6 +56,9 @@ public class ServiceStatus {
@SerializedName("device_management")
@Expose
private Boolean deviceManagement;
+ @SerializedName("available_plans")
+ @Expose
+ private List availablePlans = null;
public Boolean getIsActive() {
return isActive;
@@ -125,6 +128,14 @@ public Boolean getDeviceManagement() {
return deviceManagement;
}
+ public List getAvailablePlans() {
+ return availablePlans;
+ }
+
+ public void setAvailablePlans(List availablePlans) {
+ this.availablePlans = availablePlans;
+ }
+
@Override
public String toString() {
return "ServiceStatus{" +
@@ -137,6 +148,7 @@ public String toString() {
", isOnFreeTrial='" + isOnFreeTrial + '\'' +
", deviceManagement='" + deviceManagement + '\'' +
", capabilities=" + capabilities +
+ ", availablePlans=" + availablePlans +
'}';
}
}
\ No newline at end of file
diff --git a/core/src/main/java/net/ivpn/core/v2/connect/createSession/CreateSessionFragment.java b/core/src/main/java/net/ivpn/core/v2/connect/createSession/CreateSessionFragment.java
index 7a54b5b82..8df34088d 100644
--- a/core/src/main/java/net/ivpn/core/v2/connect/createSession/CreateSessionFragment.java
+++ b/core/src/main/java/net/ivpn/core/v2/connect/createSession/CreateSessionFragment.java
@@ -100,12 +100,12 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
}
// Device Management enabled, Standard plan
- if (deviceManagement && plan.equals(Plan.STANDARD) && isAccountNewStyle) {
+ if (deviceManagement && !plan.equals(Plan.PRO) && isAccountNewStyle) {
return getDmStandardBinding(inflater, container);
}
// Device Management disabled, Standard plan
- if (!deviceManagement && plan.equals(Plan.STANDARD) && isAccountNewStyle) {
+ if (!deviceManagement && !plan.equals(Plan.PRO) && isAccountNewStyle) {
return getStandardBinding(inflater, container);
}
@@ -171,11 +171,6 @@ private View getLegacyStandardBinding(@NonNull LayoutInflater inflater, @Nullabl
navigator.tryAgain();
}
});
- binding.upgradePlan.setOnClickListener(view -> {
- if (navigator != null) {
- navigator.upgradePlan(upgradeToUrl);
- }
- });
binding.close.setOnClickListener(view -> {
if (navigator != null) {
navigator.cancel();
diff --git a/core/src/main/java/net/ivpn/core/v2/dialog/Dialogs.java b/core/src/main/java/net/ivpn/core/v2/dialog/Dialogs.java
index 93ccd3e9b..428fc0d73 100644
--- a/core/src/main/java/net/ivpn/core/v2/dialog/Dialogs.java
+++ b/core/src/main/java/net/ivpn/core/v2/dialog/Dialogs.java
@@ -80,8 +80,8 @@ public enum Dialogs {
REMOVE_KILL_SWITCH(R.string.dialogs_please_note, R.string.dialogs_remove_kill_switch, R.string.dialogs_to_read_more, R.string.dialogs_ok),
WG_CANT_CHANGE_PORT(R.string.dialogs_please_note, R.string.dialogs_wireguard_impossible_change_port, -1, R.string.dialogs_ok),
WG_QUANTUM_RESISTANCE_INFO(R.string.protocol_wg_quantum_resistance, R.string.protocol_wg_quantum_resistance_info, -1, R.string.dialogs_ok),
- DEVICE_LOGGED_OUT(R.string.dialogs_device_logged_out_title, R.string.dialogs_device_logged_out_message, -1, R.string.dialogs_ok);
-
+ DEVICE_LOGGED_OUT(R.string.dialogs_device_logged_out_title, R.string.dialogs_device_logged_out_message, -1, R.string.dialogs_ok),
+ ACCOUNT_INACTIVE(R.string.dialogs_account_expired_title, R.string.dialogs_account_expired_msg, -1, R.string.dialogs_ok);
private int titleId;
private int messageId;
private int positiveBtnId;
diff --git a/core/src/main/java/net/ivpn/core/v2/signup/Plan.kt b/core/src/main/java/net/ivpn/core/v2/signup/Plan.kt
index dc6cdd9ce..675fa805f 100644
--- a/core/src/main/java/net/ivpn/core/v2/signup/Plan.kt
+++ b/core/src/main/java/net/ivpn/core/v2/signup/Plan.kt
@@ -22,19 +22,106 @@ package net.ivpn.core.common.billing.addfunds
along with the IVPN Android app. If not, see .
*/
-enum class Plan(val skuPath: String, val productName: String) {
- PRO("net.ivpn.subscriptions.pro.", "IVPN Pro"),
- STANDARD("net.ivpn.subscriptions.standard.", "IVPN Standard");
+import net.ivpn.core.rest.data.model.ServicePlan
+
+enum class Plan(
+ val skuPath: String,
+ val productName: String,
+ val title: String,
+ val description: String
+) {
+ STANDARD(
+ skuPath = "net.ivpn.subscriptions.standard.",
+ productName = "IVPN Standard",
+ title = "IVPN Standard",
+ description = "IVPN on 5 devices"
+ ),
+ PLUS(
+ skuPath = "net.ivpn.subscriptions.plus.",
+ productName = "IVPN Plus",
+ title = "IVPN Plus",
+ description = "IVPN on 10 devices, modDNS, Mailx"
+ ),
+ PRO(
+ skuPath = "net.ivpn.subscriptions.pro.",
+ productName = "IVPN Pro",
+ title = "IVPN Pro Suite",
+ description = "IVPN on 10 devices, modDNS, Mailx, Portmaster Pro"
+ );
companion object {
+
fun getPlanByProductName(productName: String?): Plan {
- for (plan in values()) {
- if (plan.productName == productName) {
- return plan
- }
+ if (productName == null) return STANDARD
+
+ return when {
+ productName.contains("Plus", ignoreCase = true) -> PLUS
+ productName.contains("Pro", ignoreCase = true) -> PRO
+ else -> STANDARD
+ }
+ }
+ }
+
+ fun getPlanTitle(): String = title
+
+ fun getDeviceLimit(keyword: String, plans: List): Int {
+ return plans.firstOrNull { it.name.contains(keyword, ignoreCase = true) }?.deviceLimit ?: 0
+ }
+
+ fun getStandardDesc(deviceLimit: Int): String = "IVPN on $deviceLimit devices"
+
+ fun getPlusDesc(deviceLimit: Int): String = "IVPN on $deviceLimit devices, modDNS, Mailx"
+
+ fun getProDesc(deviceLimit: Int): String = "IVPN on $deviceLimit devices, modDNS, Mailx, Portmaster Pro"
+
+ fun getPlanDesc(plans: List = emptyList()): String {
+ if (plans.isEmpty()) return description
+ return when (this) {
+ STANDARD -> getStandardDesc(getDeviceLimit("Standard", plans))
+ PLUS -> getPlusDesc(getDeviceLimit("Plus", plans))
+ PRO -> getProDesc(getDeviceLimit("Pro", plans))
+ }
+ }
+
+ fun isStandard(): Boolean = this == STANDARD
+
+ fun getAltTitleOne(): String =
+ when (this) {
+ STANDARD -> PLUS.title
+ PLUS -> STANDARD.title
+ PRO -> STANDARD.title
+ }
+
+ fun getAltDescOne(plans: List = emptyList()): String {
+ if (plans.isEmpty()) return when (this) {
+ STANDARD -> PLUS.description
+ PLUS -> STANDARD.description
+ PRO -> STANDARD.description
+ }
+ return when (this) {
+ STANDARD -> getPlusDesc(getDeviceLimit("Plus", plans))
+ PLUS -> getStandardDesc(getDeviceLimit("Standard", plans))
+ PRO -> getStandardDesc(getDeviceLimit("Standard", plans))
+ }
+ }
+
+ fun getAltTitleTwo(): String =
+ when (this) {
+ STANDARD -> PRO.title
+ PLUS -> PRO.title
+ PRO -> PLUS.title
}
- return STANDARD
+ fun getAltDescTwo(plans: List = emptyList()): String {
+ if (plans.isEmpty()) return when (this) {
+ STANDARD -> PRO.description
+ PLUS -> PRO.description
+ PRO -> PLUS.description
+ }
+ return when (this) {
+ STANDARD -> getProDesc(getDeviceLimit("Pro", plans))
+ PLUS -> getProDesc(getDeviceLimit("Pro", plans))
+ PRO -> getPlusDesc(getDeviceLimit("Plus", plans))
}
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/net/ivpn/core/v2/viewmodel/AccountViewModel.kt b/core/src/main/java/net/ivpn/core/v2/viewmodel/AccountViewModel.kt
index 62841f6a3..0c1ec83bb 100644
--- a/core/src/main/java/net/ivpn/core/v2/viewmodel/AccountViewModel.kt
+++ b/core/src/main/java/net/ivpn/core/v2/viewmodel/AccountViewModel.kt
@@ -27,13 +27,17 @@ import androidx.databinding.ObservableBoolean
import androidx.databinding.ObservableField
import androidx.databinding.ObservableLong
import androidx.lifecycle.ViewModel
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
import net.ivpn.core.IVPNApplication
+import net.ivpn.core.common.billing.addfunds.Plan
import net.ivpn.core.common.dagger.ApplicationScope
import net.ivpn.core.common.prefs.EncryptedUserPreference
import net.ivpn.core.common.qr.QRController
import net.ivpn.core.common.session.SessionController
import net.ivpn.core.common.session.SessionListenerImpl
import net.ivpn.core.common.utils.DateUtil
+import net.ivpn.core.rest.data.model.ServicePlan
import net.ivpn.core.rest.data.session.SessionErrorResponse
import org.slf4j.LoggerFactory
import javax.inject.Inject
@@ -65,6 +69,8 @@ class AccountViewModel @Inject constructor(
val isActive = ObservableBoolean()
val deviceManagement = ObservableBoolean()
val deviceName = ObservableField()
+ val plan = ObservableField()
+ val availablePlans = ObservableField>(emptyList())
val isExpired = ObservableBoolean()
val isExpiredIn = ObservableBoolean()
@@ -105,6 +111,8 @@ class AccountViewModel @Inject constructor(
paymentMethod = getPaymentMethodValue()
deviceManagement.set(getDeviceManagement())
deviceName.set(getDeviceName())
+ plan.set(getPlanByProductName(accountType.get()))
+ availablePlans.set(getAvailablePlansValue())
updateExpireData()
}
@@ -178,17 +186,22 @@ class AccountViewModel @Inject constructor(
}
fun isAccountStandard(): Boolean {
- return accountType.get()?.equals("IVPN Standard") ?: false
+ return plan.get() == Plan.STANDARD
}
- fun isAccountLegacy(): Boolean {
- return accountType.get()?.equals("Member VPN Pro Account") ?: false
+ fun isAccountLegacyTeam(): Boolean {
+ val user = username.get() ?: return false
+ return user.startsWith("ivpn") && accountType.get()?.contains("Member") == true
}
fun isAccountNewStyle(): Boolean {
return isNewStyleAccount(username.get().toString())
}
+ fun showAddMoreTime(): Boolean {
+ return isAccountStandard() && isAccountNewStyle()
+ }
+
private fun clearLocalCache() {
authenticated.set(false)
}
@@ -281,6 +294,16 @@ class AccountViewModel @Inject constructor(
return userPreference.getDeviceName()
}
+ private fun getPlanByProductName(productName: String?): Plan {
+ return Plan.getPlanByProductName(productName)
+ }
+
+ private fun getAvailablePlansValue(): List {
+ val json = userPreference.getAvailablePlans() ?: return emptyList()
+ val type = object : TypeToken>() {}.type
+ return Gson().fromJson(json, type) ?: emptyList()
+ }
+
interface AccountNavigator {
fun onLogOut()
diff --git a/core/src/main/res/layout/bottom_sheet_legacy_standard.xml b/core/src/main/res/layout/bottom_sheet_legacy_standard.xml
index 7bad55d9b..3ff6139ee 100644
--- a/core/src/main/res/layout/bottom_sheet_legacy_standard.xml
+++ b/core/src/main/res/layout/bottom_sheet_legacy_standard.xml
@@ -91,34 +91,6 @@
-
-
-
-
-
-
-
-
-
@@ -148,7 +149,7 @@
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:alpha="0.7"
- android:text="@string/account_type"
+ android:text="@string/subscription_title"
android:textColor="@color/account_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/line" />
@@ -162,9 +163,42 @@
android:letterSpacing="-0.03"
android:text="@{account.accountType}"
android:textColor="@color/account_text"
- android:textSize="16sp"
+ android:textSize="18sp"
+ android:fontFamily="sans-serif-medium"
+ android:visibility="@{account.isAccountNewStyle() ? View.VISIBLE : View.GONE}"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/account_type_label"
+ app:layout_constraintBottom_toTopOf="@+id/plan_desc"/>
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@+id/account_label" />
@@ -185,7 +220,7 @@
android:alpha="0.7"
android:text="@string/account_active_until"
android:textColor="@color/account_text"
- android:visibility="@{account.accountLegacy ? View.GONE : View.VISIBLE}"
+ android:visibility="@{account.isAccountLegacyTeam() ? View.GONE : View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/line2" />
@@ -199,7 +234,7 @@
android:text="@{account.isActive() ? DateUtil.formatDate(account.availableUntil) : @string/account_no_active_subscription}"
android:textColor="@color/account_text"
android:textSize="16sp"
- android:visibility="@{account.accountLegacy ? View.GONE : View.VISIBLE}"
+ android:visibility="@{account.isAccountLegacyTeam() ? View.GONE : View.VISIBLE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/active_until_label" />
@@ -209,7 +244,7 @@
android:layout_height="1dp"
android:layout_marginTop="24dp"
android:background="@color/account_line"
- android:visibility="@{account.accountLegacy ? View.GONE : View.VISIBLE}"
+ android:visibility="@{account.isAccountLegacyTeam() ? View.GONE : View.VISIBLE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/active_until" />
@@ -224,7 +259,7 @@
android:padding="8dp"
android:text="@string/account_log_out"
android:textAllCaps="true"
- android:textColor="@color/account_logout"
+ android:textColor="@color/primary"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
@@ -243,7 +278,7 @@
android:text="@string/account_add_more_time"
android:textColor="@color/primary"
android:textSize="15sp"
- android:visibility="@{account.accountLegacy ? View.GONE : View.VISIBLE}"
+ android:visibility="@{account.showAddMoreTime() ? View.VISIBLE : View.GONE}"
app:layout_constraintBottom_toBottomOf="@+id/active_until"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/active_until_label" />
@@ -273,86 +308,81 @@
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="@drawable/plan_details_background"
- android:padding="12dp"
+ android:padding="14dp"
+ android:visibility="@{account.isAccountNewStyle() ? View.VISIBLE : View.GONE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/account_label">
+ app:layout_constraintTop_toBottomOf="@+id/plan_desc">
-
-
-
-
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/textView32"/>
+ android:textSize="18sp"
+ android:fontFamily="sans-serif-medium"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/textView31"
+ app:layout_constraintBottom_toTopOf="@+id/textView33"/>
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/textView32"
+ app:layout_constraintBottom_toTopOf="@+id/textView34"/>
+
+
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/textView34"
+ app:layout_constraintBottom_toBottomOf="parent"/>
diff --git a/core/src/main/res/layout/fragment_connect.xml b/core/src/main/res/layout/fragment_connect.xml
index 52efc9b33..7ffed1166 100644
--- a/core/src/main/res/layout/fragment_connect.xml
+++ b/core/src/main/res/layout/fragment_connect.xml
@@ -161,6 +161,7 @@
android:letterSpacing="-0.03"
android:paddingHorizontal="16dp"
android:text="Renew"
+ android:visibility="@{account.showAddMoreTime() ? View.VISIBLE : View.GONE}"
android:textAllCaps="true"
android:textColor="@color/alerts_text"
android:textSize="12sp" />
@@ -202,6 +203,7 @@
android:letterSpacing="-0.03"
android:paddingHorizontal="16dp"
android:text="Renew"
+ android:visibility="@{account.showAddMoreTime() ? View.VISIBLE : View.GONE}"
android:textAllCaps="true"
android:textColor="@color/alerts_text"
android:textSize="12sp" />
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index e3f319882..de2f0299e 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -364,7 +364,7 @@
Retry
Visit Device Management
Enable Device Management
- Switch to IVPN Pro
+ Upgrade your subscription
Cancel login
Error
@@ -490,6 +490,9 @@
You are logged out
You have been redirected to the login page to re-enter your credentials.
+ Your account is expired
+ Please renew your account to use this feature.
+
Are you sure? This will disconnect you from IVPN.
Log out and clear app settings
Log out
@@ -654,8 +657,9 @@
Active until
Log out
Please save your account ID in a safe place, you will use it to connect on another IVPN App or log in to the client area of the IVPN website. You can find this account ID later in the IVPN App Account screen.
- Select your plan to continue
+ Continue to payment
Account ID copied
+ Available on IVPN website:
Device Name
Your current location
diff --git a/store/src/main/java/net/ivpn/client/billing/ConsumableProducts.kt b/store/src/main/java/net/ivpn/client/billing/ConsumableProducts.kt
index 2a46e6346..462de17ee 100644
--- a/store/src/main/java/net/ivpn/client/billing/ConsumableProducts.kt
+++ b/store/src/main/java/net/ivpn/client/billing/ConsumableProducts.kt
@@ -25,37 +25,19 @@ package net.ivpn.core.common.billing
object ConsumableProducts {
- const val ONE_WEEK_STANDARD = "net.ivpn.subscriptions.standard.1week";
- const val ONE_WEEK_PRO = "net.ivpn.subscriptions.pro.1week";
-
- const val ONE_MONTH_STANDARD = "net.ivpn.subscriptions.standard.1month";
- const val ONE_MONTH_PRO = "net.ivpn.subscriptions.pro.1month";
-
+ const val ONE_WEEK_STANDARD = "net.ivpn.subscriptions.standard.1week"
+ const val ONE_MONTH_STANDARD = "net.ivpn.subscriptions.standard.1month"
const val ONE_YEAR_STANDARD = "net.ivpn.subscriptions.standard.1year"
- const val ONE_YEAR_PRO = "net.ivpn.subscriptions.pro.1year"
-
const val TWO_YEARS_STANDARD = "net.ivpn.subscriptions.standard.2year"
- const val TWO_YEARS_PRO = "net.ivpn.subscriptions.pro.2year"
-
const val THREE_YEARS_STANDARD = "net.ivpn.subscriptions.standard.3year"
- const val THREE_YEARS_PRO = "net.ivpn.subscriptions.pro.3year"
fun getConsumableSKUs(): List {
val skuList = mutableListOf()
skuList.add(ONE_WEEK_STANDARD)
- skuList.add(ONE_WEEK_PRO)
-
skuList.add(ONE_MONTH_STANDARD)
- skuList.add(ONE_MONTH_PRO)
-
skuList.add(ONE_YEAR_STANDARD)
- skuList.add(ONE_YEAR_PRO)
-
skuList.add(TWO_YEARS_STANDARD)
- skuList.add(TWO_YEARS_PRO)
-
skuList.add(THREE_YEARS_STANDARD)
- skuList.add(THREE_YEARS_PRO)
return skuList
}
diff --git a/store/src/main/java/net/ivpn/client/signup/SignUpAccountCreatedFragment.kt b/store/src/main/java/net/ivpn/client/signup/SignUpAccountCreatedFragment.kt
index 360b97e08..450918b62 100644
--- a/store/src/main/java/net/ivpn/client/signup/SignUpAccountCreatedFragment.kt
+++ b/store/src/main/java/net/ivpn/client/signup/SignUpAccountCreatedFragment.kt
@@ -38,6 +38,7 @@ import androidx.navigation.ui.setupWithNavController
import net.ivpn.client.R
import net.ivpn.client.StoreIVPNApplication
import net.ivpn.client.databinding.FragmentSignUpFinishBinding
+import net.ivpn.core.common.billing.addfunds.Plan
import net.ivpn.core.common.utils.ToastUtil
import net.ivpn.core.v2.MainActivity
import org.slf4j.LoggerFactory
@@ -110,7 +111,8 @@ class SignUpAccountCreatedFragment : Fragment() {
}
private fun continuePurchase() {
- val action = SignUpAccountCreatedFragmentDirections.actionSignUpAccountCreatedFragmentToSignUpProductFragment()
+ viewModel.selectedPlan.set(Plan.STANDARD)
+ val action = SignUpAccountCreatedFragmentDirections.actionSignUpAccountCreatedFragmentToSignUpPeriodFragment2()
NavHostFragment.findNavController(this).navigate(action)
}
}
\ No newline at end of file
diff --git a/store/src/main/java/net/ivpn/client/signup/SignUpViewModel.kt b/store/src/main/java/net/ivpn/client/signup/SignUpViewModel.kt
index e7cf30daa..210eca3b0 100644
--- a/store/src/main/java/net/ivpn/client/signup/SignUpViewModel.kt
+++ b/store/src/main/java/net/ivpn/client/signup/SignUpViewModel.kt
@@ -23,8 +23,10 @@ package net.ivpn.client.signup
*/
import android.app.Activity
+import android.content.Context
import android.content.Intent
import android.net.Uri
+import androidx.core.content.ContentProviderCompat.requireContext
import androidx.databinding.ObservableField
import androidx.navigation.NavController
import com.android.billingclient.api.BillingClient
@@ -50,6 +52,8 @@ import net.ivpn.core.rest.data.addfunds.NewAccountRequestBody
import net.ivpn.core.rest.data.addfunds.NewAccountResponse
import net.ivpn.core.rest.requests.common.Request
import net.ivpn.core.rest.requests.common.RequestWrapper
+import net.ivpn.core.v2.dialog.DialogBuilder
+import net.ivpn.core.v2.dialog.Dialogs
import net.ivpn.core.v2.signup.SignUpController
import org.slf4j.LoggerFactory
import java.util.Calendar
@@ -153,6 +157,11 @@ class SignUpViewModel @Inject constructor(
override fun signUpWithInactiveAccount(navController: NavController?,
plan: Plan, isAccountNewStyle: Boolean) {
if (isAccountNewStyle) {
+ if (!plan.isStandard()) {
+ DialogBuilder.createNotificationDialog(navController?.context, Dialogs.ACCOUNT_INACTIVE)
+ return
+ }
+
blankAccountID.set(null)
selectedPlan.set(plan)
@@ -163,7 +172,6 @@ class SignUpViewModel @Inject constructor(
}
}
-
override fun reset() {
dataLoading.set(false)
selectedPeriod.set(null)
diff --git a/store/src/main/res/layout/content_sign_up_period.xml b/store/src/main/res/layout/content_sign_up_period.xml
index 1b7521539..ef7f72463 100644
--- a/store/src/main/res/layout/content_sign_up_period.xml
+++ b/store/src/main/res/layout/content_sign_up_period.xml
@@ -90,10 +90,9 @@
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:letterSpacing="0.04"
- android:text="@{Utils.formatPlanName(viewmodel.selectedPlan)}"
- android:textAllCaps="true"
+ android:text="@{viewmodel.selectedPlan.getPlanTitle()}"
android:textColor="@color/sign_up_text_color"
- android:textSize="16sp"
+ android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/divider_1" />
@@ -108,7 +107,7 @@
android:text="@string/sign_up_period_change"
android:textColor="@color/primary"
android:textSize="16sp"
- android:visibility="@{viewmodel.blankAccountID != null ? View.VISIBLE : View.GONE}"
+ android:visibility="@{View.GONE}"
app:layout_constraintBottom_toBottomOf="@+id/standard_plan_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/standard_plan_title" />
diff --git a/store/src/main/res/navigation/store_nav_graph.xml b/store/src/main/res/navigation/store_nav_graph.xml
index 19446e9a2..08422f758 100644
--- a/store/src/main/res/navigation/store_nav_graph.xml
+++ b/store/src/main/res/navigation/store_nav_graph.xml
@@ -11,6 +11,9 @@
+