summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-06-17 07:20:34 -0300
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-06-17 07:20:34 -0300
commitb6db007471f0103ef7982ac8cc4852f62b51c494 (patch)
tree11774361884e3e795c06b32b22e37c73b903fbd5
parentd18c279cc25cee041bcf5b8231878769b7ae74ac (diff)
parentff929470e7dbe3aa5aa5ceafb724c68bb64b7498 (diff)
downloadmullvadvpn-b6db007471f0103ef7982ac8cc4852f62b51c494.tar.xz
mullvadvpn-b6db007471f0103ef7982ac8cc4852f62b51c494.zip
Merge branch 'account-screen-on-android'
-rw-r--r--android/build.gradle1
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/AccountCache.kt55
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/AccountFragment.kt81
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt11
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/RemainingTimeLabel.kt72
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt37
-rw-r--r--android/src/main/res/anim/fragment_enter_from_right.xml8
-rw-r--r--android/src/main/res/anim/fragment_exit_to_right.xml8
-rw-r--r--android/src/main/res/anim/fragment_half_enter_from_left.xml8
-rw-r--r--android/src/main/res/anim/fragment_half_exit_to_left.xml8
-rw-r--r--android/src/main/res/drawable/cell_button_background.xml14
-rw-r--r--android/src/main/res/drawable/icon_back.xml13
-rw-r--r--android/src/main/res/drawable/icon_chevron.xml6
-rw-r--r--android/src/main/res/layout/account.xml102
-rw-r--r--android/src/main/res/layout/settings.xml39
-rw-r--r--android/src/main/res/values/colors.xml1
-rw-r--r--android/src/main/res/values/plurals.xml14
-rw-r--r--android/src/main/res/values/strings.xml6
18 files changed, 481 insertions, 3 deletions
diff --git a/android/build.gradle b/android/build.gradle
index 914ee21e88..5e22c4decb 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -61,6 +61,7 @@ dependencies {
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.21'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
+ implementation 'joda-time:joda-time:2.10.2'
}
buildscript {
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/AccountCache.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/AccountCache.kt
new file mode 100644
index 0000000000..af0e02da7a
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/AccountCache.kt
@@ -0,0 +1,55 @@
+package net.mullvad.mullvadvpn
+
+import kotlinx.coroutines.async
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+
+import org.joda.time.format.DateTimeFormat
+import org.joda.time.DateTime
+
+import net.mullvad.mullvadvpn.model.Settings
+
+val EXPIRY_FORMAT = DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss z")
+
+class AccountCache(val parentActivity: MainActivity) {
+ private var daemon = parentActivity.asyncDaemon
+
+ var settings = parentActivity.asyncSettings
+ set(value) {
+ field = value
+ accountNumber = fetchAccountNumber()
+ accountExpiry = fetchAccountExpiry()
+ }
+
+ var accountNumber = fetchAccountNumber()
+ private set
+ var accountExpiry = fetchAccountExpiry()
+ private set
+
+ fun onDestroy() {
+ accountExpiry.cancel()
+ accountNumber.cancel()
+ }
+
+ private fun fetchAccountNumber() = GlobalScope.async(Dispatchers.Default) {
+ settings.await().accountToken
+ }
+
+ private fun fetchAccountExpiry() = GlobalScope.async(Dispatchers.Default) {
+ val accountNumber = accountNumber.await()
+
+ if (accountNumber != null) {
+ val accountData = daemon.await().getAccountData(accountNumber)
+ val accountExpiry = accountData?.expiry
+
+ if (accountExpiry != null) {
+ DateTime.parse(accountExpiry, EXPIRY_FORMAT)
+ } else {
+ null
+ }
+ } else {
+ null
+ }
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/AccountFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/AccountFragment.kt
new file mode 100644
index 0000000000..3dda9e2f8b
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/AccountFragment.kt
@@ -0,0 +1,81 @@
+package net.mullvad.mullvadvpn
+
+import java.text.DateFormat
+
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.Job
+
+import android.content.Context
+import android.os.Bundle
+import android.support.v4.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+
+import org.joda.time.DateTime
+
+class AccountFragment : Fragment() {
+ private lateinit var parentActivity: MainActivity
+
+ private lateinit var accountExpiryContainer: View
+ private lateinit var accountExpiryDisplay: TextView
+ private lateinit var accountNumberContainer: View
+ private lateinit var accountNumberDisplay: TextView
+
+ private var updateViewJob: Job? = null
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+
+ parentActivity = context as MainActivity
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val view = inflater.inflate(R.layout.account, container, false)
+
+ view.findViewById<View>(R.id.back).setOnClickListener {
+ parentActivity.onBackPressed()
+ }
+
+ accountExpiryContainer = view.findViewById<View>(R.id.account_expiry_container)
+ accountNumberContainer = view.findViewById<View>(R.id.account_number_container)
+
+ accountExpiryDisplay = view.findViewById<TextView>(R.id.account_expiry_display)
+ accountNumberDisplay = view.findViewById<TextView>(R.id.account_number_display)
+
+ updateViewJob = updateView()
+
+ return view
+ }
+
+ private fun updateView() = GlobalScope.launch(Dispatchers.Main) {
+ val accountCache = parentActivity.accountCache
+ val accountNumber = accountCache.accountNumber.await()
+
+ if (accountNumber != null) {
+ accountNumberDisplay.setText(accountCache.accountNumber.await())
+ accountNumberContainer.visibility = View.VISIBLE
+
+ val accountExpiry = accountCache.accountExpiry.await()
+
+ if (accountExpiry != null) {
+ accountExpiryDisplay.setText(formatExpiry(accountExpiry))
+ accountExpiryContainer.visibility = View.VISIBLE
+ }
+ }
+ }
+
+ private fun formatExpiry(expiry: DateTime): String {
+ val expiryInstant = expiry.toDate()
+ val formatter = DateFormat.getDateTimeInstance()
+
+ return formatter.format(expiryInstant)
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
index fb2a5d6d77..71416c92c4 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
@@ -36,6 +36,8 @@ class MainActivity : FragmentActivity() {
val settings
get() = runBlocking { asyncSettings.await() }
+ val accountCache = AccountCache(this)
+
var selectedRelayItem: RelayItem? = null
private val restoreSelectedRelayListItemJob = restoreSelectedRelayListItem()
@@ -81,6 +83,8 @@ class MainActivity : FragmentActivity() {
}
override fun onDestroy() {
+ accountCache.onDestroy()
+
restoreSelectedRelayListItemJob.cancel()
waitForDaemonJob?.cancel()
asyncSettings.cancel()
@@ -104,6 +108,13 @@ class MainActivity : FragmentActivity() {
}
}
+ fun refetchSettings() {
+ if (asyncSettings.isCompleted) {
+ asyncSettings = fetchSettings()
+ accountCache.settings = asyncSettings
+ }
+ }
+
private fun addInitialFragment() {
supportFragmentManager?.beginTransaction()?.apply {
add(R.id.main_fragment, LaunchFragment())
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/RemainingTimeLabel.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/RemainingTimeLabel.kt
new file mode 100644
index 0000000000..6e772c1675
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/RemainingTimeLabel.kt
@@ -0,0 +1,72 @@
+package net.mullvad.mullvadvpn
+
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+
+import org.joda.time.format.DateTimeFormat
+import org.joda.time.DateTime
+import org.joda.time.Duration
+import org.joda.time.PeriodType
+
+import android.view.View
+import android.widget.TextView
+
+class RemainingTimeLabel(val parentActivity: MainActivity, val view: View) {
+ private val accountCache = parentActivity.accountCache
+
+ private val expiredColor = parentActivity.getColor(R.color.red)
+ private val normalColor = parentActivity.getColor(R.color.white60)
+
+ private val resources = parentActivity.resources
+
+ private val label = view.findViewById<TextView>(R.id.remaining_time)
+
+ private var updateJob = updateLabel()
+
+ fun onResume() {
+ if (updateJob.isCompleted) {
+ parentActivity.refetchSettings()
+ updateJob = updateLabel()
+ }
+ }
+
+ fun onDestroy() {
+ updateJob.cancel()
+ }
+
+ private fun updateLabel() = GlobalScope.launch(Dispatchers.Main) {
+ val expiry = accountCache.accountExpiry.await()
+
+ if (expiry != null) {
+ val remainingTime = Duration(DateTime.now(), expiry)
+
+ if (remainingTime.isShorterThan(Duration.ZERO)) {
+ label.setText(R.string.out_of_time)
+ label.setTextColor(expiredColor)
+ } else {
+ val remainingTimeInfo =
+ remainingTime.toPeriodTo(expiry, PeriodType.yearMonthDayTime())
+
+ if (remainingTimeInfo.years > 0) {
+ label.setText(getRemainingText(R.plurals.years_left, remainingTimeInfo.years))
+ } else if (remainingTimeInfo.months > 0) {
+ label.setText(getRemainingText(R.plurals.months_left, remainingTimeInfo.months))
+ } else if (remainingTimeInfo.days > 0) {
+ label.setText(getRemainingText(R.plurals.days_left, remainingTimeInfo.days))
+ } else {
+ label.setText(R.string.less_than_a_day_left)
+ }
+
+ label.setTextColor(normalColor)
+ }
+ } else {
+ label.text = ""
+ }
+ }
+
+ private fun getRemainingText(pluralId: Int, quantity: Int): String {
+ return resources.getQuantityString(pluralId, quantity, quantity)
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt
index a958978daa..ee89ed7945 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt
@@ -10,6 +10,15 @@ import android.widget.Button
import android.widget.ImageButton
class SettingsFragment : Fragment() {
+ private lateinit var parentActivity: MainActivity
+ private lateinit var remainingTimeLabel: RemainingTimeLabel
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+
+ parentActivity = context as MainActivity
+ }
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -25,6 +34,34 @@ class SettingsFragment : Fragment() {
activity?.finishAndRemoveTask()
}
+ view.findViewById<View>(R.id.account).setOnClickListener { openAccountSettings() }
+
+ remainingTimeLabel = RemainingTimeLabel(parentActivity, view)
+
return view
}
+
+ override fun onResume() {
+ super.onResume()
+ remainingTimeLabel.onResume()
+ }
+
+ override fun onDestroyView() {
+ remainingTimeLabel.onDestroy()
+ super.onDestroyView()
+ }
+
+ private fun openAccountSettings() {
+ fragmentManager?.beginTransaction()?.apply {
+ setCustomAnimations(
+ R.anim.fragment_enter_from_right,
+ R.anim.fragment_half_exit_to_left,
+ R.anim.fragment_half_enter_from_left,
+ R.anim.fragment_exit_to_right
+ )
+ replace(R.id.main_fragment, AccountFragment())
+ addToBackStack(null)
+ commit()
+ }
+ }
}
diff --git a/android/src/main/res/anim/fragment_enter_from_right.xml b/android/src/main/res/anim/fragment_enter_from_right.xml
new file mode 100644
index 0000000000..c8e79bd2ee
--- /dev/null
+++ b/android/src/main/res/anim/fragment_enter_from_right.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <translate
+ android:fromXDelta="100%p"
+ android:toXDelta="0"
+ android:duration="450"
+ />
+</set>
diff --git a/android/src/main/res/anim/fragment_exit_to_right.xml b/android/src/main/res/anim/fragment_exit_to_right.xml
new file mode 100644
index 0000000000..9d5970e110
--- /dev/null
+++ b/android/src/main/res/anim/fragment_exit_to_right.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <translate
+ android:fromXDelta="0"
+ android:toXDelta="100%p"
+ android:duration="450"
+ />
+</set>
diff --git a/android/src/main/res/anim/fragment_half_enter_from_left.xml b/android/src/main/res/anim/fragment_half_enter_from_left.xml
new file mode 100644
index 0000000000..178181d7fb
--- /dev/null
+++ b/android/src/main/res/anim/fragment_half_enter_from_left.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <translate
+ android:fromXDelta="-50%p"
+ android:toXDelta="0"
+ android:duration="450"
+ />
+</set>
diff --git a/android/src/main/res/anim/fragment_half_exit_to_left.xml b/android/src/main/res/anim/fragment_half_exit_to_left.xml
new file mode 100644
index 0000000000..ba05a62afe
--- /dev/null
+++ b/android/src/main/res/anim/fragment_half_exit_to_left.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <translate
+ android:fromXDelta="0%p"
+ android:toXDelta="-50%p"
+ android:duration="450"
+ />
+</set>
diff --git a/android/src/main/res/drawable/cell_button_background.xml b/android/src/main/res/drawable/cell_button_background.xml
new file mode 100644
index 0000000000..8791c01141
--- /dev/null
+++ b/android/src/main/res/drawable/cell_button_background.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="false">
+ <shape android:shape="rectangle">
+ <solid android:color="@color/blue"/>
+ </shape>
+ </item>
+
+ <item android:state_pressed="true">
+ <shape android:shape="rectangle">
+ <solid android:color="@color/blue80"/>
+ </shape>
+ </item>
+</selector>
diff --git a/android/src/main/res/drawable/icon_back.xml b/android/src/main/res/drawable/icon_back.xml
new file mode 100644
index 0000000000..a314fe65bd
--- /dev/null
+++ b/android/src/main/res/drawable/icon_back.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,24C5.3731,24 -0,18.6269 -0,12C-0,5.3731 5.3731,0 12,0C18.6269,0 24,5.3731 24,12C24,18.6269 18.6269,24 12,24ZM7.0055,11.9979C6.9755,12.2732 7.0685,12.5604 7.2852,12.7774L13.2129,18.7118C13.5936,19.0929 14.2231,19.0908 14.6233,18.7027L14.6942,18.634C15.0925,18.2478 15.1055,17.6196 14.7109,17.218L9.5805,11.9979L14.7109,6.7777C15.1055,6.3762 15.0925,5.7479 14.6942,5.3618L14.6233,5.293C14.2231,4.9049 13.5936,4.9028 13.2129,5.2839L7.2852,11.2184C7.0685,11.4353 6.9755,11.7225 7.0055,11.9979L7.0055,11.9979Z"
+ android:strokeWidth="1"
+ android:fillColor="#FFFFFF"
+ android:fillAlpha="0.6"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+</vector>
diff --git a/android/src/main/res/drawable/icon_chevron.xml b/android/src/main/res/drawable/icon_chevron.xml
index f10c8d04c7..d5b96fc6e5 100644
--- a/android/src/main/res/drawable/icon_chevron.xml
+++ b/android/src/main/res/drawable/icon_chevron.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
+ android:width="14dp"
android:height="24dp"
- android:viewportWidth="24.0"
+ android:viewportWidth="14.0"
android:viewportHeight="24.0"
>
- <group android:translateX="8.5" android:translateY="6.0">
+ <group android:translateX="5.0" android:translateY="6.0">
<path android:fillColor="#FFFFFF"
android:pathData="M0.335204989,1.95371785 L4.23669259,6 L0.335204989,10.0462822 C-0.111734996,10.4932221 -0.111734996,11.217855 0.335204989,11.664795 C0.782144974,12.111735 1.49826561,12.111735 1.9452056,11.664795 L6.66818642,6.80553188 C6.88657769,6.58714061 6.99779844,6.29559541 6.99881099,6.00303766 C6.99779844,5.70440459 6.88657769,5.41285939 6.66818642,5.19446812 L1.9452056,0.335204989 C1.49826561,-0.111734996 0.782144974,-0.111734996 0.335204989,0.335204989 C-0.111734996,0.782144974 -0.111734996,1.50677786 0.335204989,1.95371785 Z"
/>
diff --git a/android/src/main/res/layout/account.xml b/android/src/main/res/layout/account.xml
new file mode 100644
index 0000000000..2e7bba0340
--- /dev/null
+++ b/android/src/main/res/layout/account.xml
@@ -0,0 +1,102 @@
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/darkBlue"
+ android:orientation="vertical"
+ android:gravity="left"
+ android:elevation="2dp"
+ >
+ <LinearLayout android:id="@+id/back"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="12dp"
+ android:orientation="horizontal"
+ android:gravity="center_vertical | left"
+ android:clickable="true"
+ android:background="?android:attr/selectableItemBackground"
+ >
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginRight="8dp"
+ android:src="@drawable/icon_back"
+ />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/white60"
+ android:textSize="13sp"
+ android:textStyle="bold"
+ android:text="@string/settings"
+ />
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="24dp"
+ android:orientation="vertical"
+ >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="24dp"
+ android:textColor="@color/white"
+ android:textSize="32sp"
+ android:textStyle="bold"
+ android:text="@string/settings_account"
+ />
+ <LinearLayout android:id="@+id/account_number_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="24dp"
+ android:orientation="vertical"
+ android:visibility="invisible"
+ >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="9dp"
+ android:textColor="@color/white60"
+ android:textSize="13sp"
+ android:textStyle="bold"
+ android:text="@string/account_number"
+ />
+ <TextView android:id="@+id/account_number_display"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/white"
+ android:textSize="16sp"
+ android:textStyle="bold"
+ android:text=""
+ />
+ </LinearLayout>
+ <LinearLayout android:id="@+id/account_expiry_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="24dp"
+ android:orientation="vertical"
+ android:visibility="invisible"
+ >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="9dp"
+ android:textColor="@color/white60"
+ android:textSize="13sp"
+ android:textStyle="bold"
+ android:text="@string/paid_until"
+ />
+ <TextView android:id="@+id/account_expiry_display"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/white"
+ android:textSize="16sp"
+ android:textStyle="bold"
+ android:text=""
+ />
+ </LinearLayout>
+ </LinearLayout>
+</LinearLayout>
diff --git a/android/src/main/res/layout/settings.xml b/android/src/main/res/layout/settings.xml
index 0c98ad539e..664a2dc790 100644
--- a/android/src/main/res/layout/settings.xml
+++ b/android/src/main/res/layout/settings.xml
@@ -27,6 +27,45 @@
android:textStyle="bold"
android:text="@string/settings"
/>
+ <LinearLayout android:id="@+id/account"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="16dp"
+ android:background="@drawable/cell_button_background"
+ android:clickable="true"
+ android:gravity="center"
+ >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingHorizontal="8dp"
+ android:paddingVertical="17dp"
+ android:textColor="@color/white"
+ android:textSize="20sp"
+ android:textStyle="bold"
+ android:text="@string/settings_account"
+ />
+ <TextView android:id="@+id/remaining_time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingHorizontal="8dp"
+ android:gravity="right"
+ android:textColor="@color/white60"
+ android:textSize="13sp"
+ android:textStyle="bold"
+ android:text=""
+ android:textAllCaps="true"
+ />
+ <ImageView
+ android:layout_width="14dp"
+ android:layout_height="24dp"
+ android:layout_weight="0"
+ android:alpha="0.6"
+ android:src="@drawable/icon_chevron"
+ />
+ </LinearLayout>
<Button android:id="@+id/quit_button"
android:layout_marginTop="24dp"
android:layout_marginLeft="24dp"
diff --git a/android/src/main/res/values/colors.xml b/android/src/main/res/values/colors.xml
index 7d2ef85101..0e7b0cea4e 100644
--- a/android/src/main/res/values/colors.xml
+++ b/android/src/main/res/values/colors.xml
@@ -1,6 +1,7 @@
<resources>
<color name="colorPrimary">#294D73</color>
<color name="blue">#294D73</color>
+ <color name="blue80">#CC294D73</color>
<color name="blue40">#66294D73</color>
<color name="blue20">#33294D73</color>
<color name="darkBlue">#192E45</color>
diff --git a/android/src/main/res/values/plurals.xml b/android/src/main/res/values/plurals.xml
new file mode 100644
index 0000000000..5395e2f4fa
--- /dev/null
+++ b/android/src/main/res/values/plurals.xml
@@ -0,0 +1,14 @@
+<resources>
+ <plurals name="years_left">
+ <item quantity="one">%d year left</item>
+ <item quantity="other">%d years left</item>
+ </plurals>
+ <plurals name="months_left">
+ <item quantity="one">%d month left</item>
+ <item quantity="other">%d months left</item>
+ </plurals>
+ <plurals name="days_left">
+ <item quantity="one">%d day left</item>
+ <item quantity="other">%d days left</item>
+ </plurals>
+</resources>
diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml
index 18dea4cd12..a3a033e747 100644
--- a/android/src/main/res/values/strings.xml
+++ b/android/src/main/res/values/strings.xml
@@ -13,8 +13,14 @@
<string name="login_fail_description">Invalid account number, try again</string>
<string name="settings">Settings</string>
+ <string name="settings_account">Account</string>
+ <string name="less_than_a_day_left">less than a day left</string>
+ <string name="out_of_time">Out of time</string>
<string name="quit">Quit</string>
+ <string name="account_number">Account number</string>
+ <string name="paid_until">Paid until</string>
+
<string name="unsecured_connection">Unsecured connection</string>
<string name="creating_secure_connection">Creating secure connection</string>
<string name="secure_connection">Secure connection</string>