diff options
Diffstat (limited to 'android/src')
16 files changed, 482 insertions, 0 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectActionButton.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectActionButton.kt new file mode 100644 index 0000000000..4a577cd337 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectActionButton.kt @@ -0,0 +1,50 @@ +package net.mullvad.mullvadvpn + +import android.view.View +import android.widget.Button + +class ConnectActionButton(val parentView: View) { + private val button: Button = parentView.findViewById(R.id.action_button) + + var state = ConnectionState.Disconnected + set(value) { + when (value) { + ConnectionState.Disconnected -> disconnected() + ConnectionState.Connecting -> connecting() + ConnectionState.Connected -> connected() + } + + field = value + } + + var onConnect: (() -> Unit)? = null + var onCancel: (() -> Unit)? = null + var onDisconnect: (() -> Unit)? = null + + init { + button.setOnClickListener { action() } + } + + private fun action() { + when (state) { + ConnectionState.Disconnected -> onConnect?.invoke() + ConnectionState.Connecting -> onCancel?.invoke() + ConnectionState.Connected -> onDisconnect?.invoke() + } + } + + private fun disconnected() { + button.setBackgroundResource(R.drawable.green_button_background) + button.setText(R.string.connect) + } + + private fun connecting() { + button.setBackgroundResource(R.drawable.transparent_red_button_background) + button.setText(R.string.cancel) + } + + private fun connected() { + button.setBackgroundResource(R.drawable.transparent_red_button_background) + button.setText(R.string.disconnect) + } +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt new file mode 100644 index 0000000000..c38321dfc9 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt @@ -0,0 +1,70 @@ +package net.mullvad.mullvadvpn + +import android.os.Bundle +import android.os.Handler +import android.support.v4.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup + +class ConnectFragment : Fragment() { + private lateinit var actionButton: ConnectActionButton + private lateinit var headerBar: HeaderBar + private lateinit var notificationBanner: NotificationBanner + private lateinit var status: ConnectionStatus + + private lateinit var connectHandler: Handler + + private var state = ConnectionState.Disconnected + set(value) { + actionButton.state = value + headerBar.state = value + notificationBanner.state = value + status.state = value + + field = value + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + connectHandler = Handler() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val view = inflater.inflate(R.layout.connect, container, false) + + headerBar = HeaderBar(view, context!!) + notificationBanner = NotificationBanner(view) + status = ConnectionStatus(view, context!!) + + actionButton = ConnectActionButton(view) + actionButton.apply { + onConnect = { connect() } + onCancel = { disconnect() } + onDisconnect = { disconnect() } + } + + return view + } + + private fun connect() { + state = ConnectionState.Connecting + + connectHandler.postDelayed(Runnable { connected() }, 1000) + } + + private fun disconnect() { + state = ConnectionState.Disconnected + + connectHandler.removeCallbacksAndMessages(null) + } + + private fun connected() { + state = ConnectionState.Connected + } +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectionState.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectionState.kt new file mode 100644 index 0000000000..4764ce05fe --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectionState.kt @@ -0,0 +1,7 @@ +package net.mullvad.mullvadvpn + +enum class ConnectionState { + Disconnected, + Connecting, + Connected, +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectionStatus.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectionStatus.kt new file mode 100644 index 0000000000..8090a8dd08 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectionStatus.kt @@ -0,0 +1,46 @@ +package net.mullvad.mullvadvpn + +import android.content.Context +import android.view.View +import android.widget.TextView + +class ConnectionStatus(val parentView: View, val context: Context) { + private val spinner: View = parentView.findViewById(R.id.connecting_spinner) + private val text: TextView = parentView.findViewById(R.id.connection_status) + + private val disconnectedTextColor = context.getColor(R.color.red) + private val connectingTextColor = context.getColor(R.color.white) + private val connectedTextColor = context.getColor(R.color.green) + + var state = ConnectionState.Disconnected + set(value) { + when (value) { + ConnectionState.Disconnected -> disconnected() + ConnectionState.Connecting -> connecting() + ConnectionState.Connected -> connected() + } + + field = value + } + + private fun disconnected() { + spinner.visibility = View.GONE + + text.setTextColor(disconnectedTextColor) + text.setText(R.string.creating_secure_connection) + } + + private fun connecting() { + spinner.visibility = View.VISIBLE + + text.setTextColor(connectingTextColor) + text.setText(R.string.unsecured_connection) + } + + private fun connected() { + spinner.visibility = View.GONE + + text.setTextColor(connectedTextColor) + text.setText(R.string.secure_connection) + } +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/HeaderBar.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/HeaderBar.kt new file mode 100644 index 0000000000..4c46174f51 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/HeaderBar.kt @@ -0,0 +1,30 @@ +package net.mullvad.mullvadvpn + +import android.content.Context +import android.view.View + +class HeaderBar(val parentView: View, val context: Context) { + private val headerBar: View = parentView.findViewById(R.id.header_bar) + + private val securedColor = context.getColor(R.color.green) + private val unsecuredColor = context.getColor(R.color.red) + + var state = ConnectionState.Disconnected + set(value) { + when (value) { + ConnectionState.Disconnected -> unsecured() + ConnectionState.Connecting -> secured() + ConnectionState.Connected -> secured() + } + + field = value + } + + private fun unsecured() { + headerBar.setBackgroundColor(unsecuredColor) + } + + private fun secured() { + headerBar.setBackgroundColor(securedColor) + } +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/LoginFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/LoginFragment.kt index d5a06a9d75..9159c93079 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/LoginFragment.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/LoginFragment.kt @@ -62,6 +62,15 @@ class LoginFragment : Fragment() { loggedInStatus.visibility = View.VISIBLE accountInput.state = LoginState.Success + + Handler().postDelayed(Runnable { openConnectScreen() }, 1000) + } + + private fun openConnectScreen() { + fragmentManager?.beginTransaction()?.apply { + replace(R.id.main_fragment, ConnectFragment()) + commit() + } } private fun loginFailure() { diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/NotificationBanner.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/NotificationBanner.kt new file mode 100644 index 0000000000..445df2d914 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/NotificationBanner.kt @@ -0,0 +1,18 @@ +package net.mullvad.mullvadvpn + +import android.view.View + +class NotificationBanner(val parentView: View) { + private val banner: View = parentView.findViewById(R.id.notification_banner) + + var state = ConnectionState.Disconnected + set(value) { + when (value) { + ConnectionState.Disconnected -> banner.visibility = View.GONE + ConnectionState.Connecting -> banner.visibility = View.VISIBLE + ConnectionState.Connected -> banner.visibility = View.GONE + } + + field = value + } +} diff --git a/android/src/main/res/drawable/green_button_background.xml b/android/src/main/res/drawable/green_button_background.xml new file mode 100644 index 0000000000..6b1252819d --- /dev/null +++ b/android/src/main/res/drawable/green_button_background.xml @@ -0,0 +1,16 @@ +<?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"> + <corners android:radius="4dp"/> + <solid android:color="@color/green"/> + </shape> + </item> + + <item android:state_pressed="true"> + <shape android:shape="rectangle"> + <corners android:radius="4dp"/> + <solid android:color="@color/green90"/> + </shape> + </item> +</selector> diff --git a/android/src/main/res/drawable/icon_chevron.xml b/android/src/main/res/drawable/icon_chevron.xml new file mode 100644 index 0000000000..f10c8d04c7 --- /dev/null +++ b/android/src/main/res/drawable/icon_chevron.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + > + <group android:translateX="8.5" 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" + /> + </group> +</vector> diff --git a/android/src/main/res/drawable/icon_notification_error.xml b/android/src/main/res/drawable/icon_notification_error.xml new file mode 100644 index 0000000000..d285967fe6 --- /dev/null +++ b/android/src/main/res/drawable/icon_notification_error.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval" + > + <solid android:color="@color/red"/> + <size android:width="10dp" android:height="10dp"/> +</shape> diff --git a/android/src/main/res/drawable/transparent_red_button_background.xml b/android/src/main/res/drawable/transparent_red_button_background.xml new file mode 100644 index 0000000000..6f5a86b205 --- /dev/null +++ b/android/src/main/res/drawable/transparent_red_button_background.xml @@ -0,0 +1,16 @@ +<?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"> + <corners android:radius="4dp"/> + <solid android:color="@color/red40"/> + </shape> + </item> + + <item android:state_pressed="true"> + <shape android:shape="rectangle"> + <corners android:radius="4dp"/> + <solid android:color="@color/red45"/> + </shape> + </item> +</selector> diff --git a/android/src/main/res/drawable/white20_button_background.xml b/android/src/main/res/drawable/white20_button_background.xml new file mode 100644 index 0000000000..ee5787d516 --- /dev/null +++ b/android/src/main/res/drawable/white20_button_background.xml @@ -0,0 +1,16 @@ +<?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"> + <corners android:radius="4dp"/> + <solid android:color="@color/white20"/> + </shape> + </item> + + <item android:state_pressed="true"> + <shape android:shape="rectangle"> + <corners android:radius="4dp"/> + <solid android:color="@color/white40"/> + </shape> + </item> +</selector> diff --git a/android/src/main/res/layout/connect.xml b/android/src/main/res/layout/connect.xml new file mode 100644 index 0000000000..8163a9a297 --- /dev/null +++ b/android/src/main/res/layout/connect.xml @@ -0,0 +1,151 @@ +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/main_fragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + > + <LinearLayout android:id="@+id/header_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="0" + android:orientation="horizontal" + android:gravity="center_vertical" + android:padding="12dp" + android:background="@color/red" + > + <ImageView + android:layout_width="49dp" + android:layout_height="50dp" + android:src="@drawable/logo_icon" + /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginHorizontal="8dp" + android:textColor="@color/white60" + android:textSize="24sp" + android:textStyle="bold" + android:text="@string/app_name" + android:textAllCaps="true" + /> + </LinearLayout> + + <LinearLayout android:id="@+id/notification_banner" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingVertical="8dp" + android:background="@color/darkBlue" + android:orientation="horizontal" + android:gravity="center" + android:visibility="gone" + > + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:layout_marginLeft="19dp" + android:src="@drawable/icon_notification_error" + /> + <TextView + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginLeft="7dp" + android:textSize="13sp" + android:textStyle="bold" + android:text="@string/blocking_internet" + android:textAllCaps="true" + /> + </LinearLayout> + + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + /> + + <ProgressBar android:id="@+id/connecting_spinner" + android:layout_width="60dp" + android:layout_height="60dp" + android:layout_gravity="center" + android:layout_marginBottom="14dp" + android:indeterminate="true" + android:indeterminateOnly="true" + android:indeterminateDuration="600" + android:indeterminateDrawable="@drawable/icon_spinner" + android:visibility="invisible" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="0" + android:orientation="vertical" + android:padding="24dp" + android:gravity="start" + > + <TextView android:id="@+id/connection_status" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" + android:textColor="@color/red" + android:textSize="16sp" + android:textStyle="bold" + android:text="@string/unsecured_connection" + android:textAllCaps="true" + /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/white" + android:textSize="34sp" + android:textStyle="bold" + android:text="@string/country" + /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/white" + android:textSize="34sp" + android:textStyle="bold" + android:text="" + android:visibility="invisible" + /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/white" + android:textSize="16sp" + android:textStyle="bold" + android:text="" + android:visibility="invisible" + /> + </LinearLayout> + + <Space + android:layout_width="match_parent" + android:layout_height="32dp" + android:layout_weight="0" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="0" + android:orientation="vertical" + android:padding="24dp" + > + <Button + android:layout_marginVertical="16dp" + android:text="@string/switch_location" + android:drawableRight="@drawable/icon_chevron" + android:paddingRight="8dp" + style="@style/White20Button" + /> + <Button android:id="@+id/action_button" + android:text="@string/connect" + style="@style/GreenButton" + /> + </LinearLayout> +</LinearLayout> diff --git a/android/src/main/res/values/colors.xml b/android/src/main/res/values/colors.xml index 22a7e4108d..905f1e497c 100644 --- a/android/src/main/res/values/colors.xml +++ b/android/src/main/res/values/colors.xml @@ -6,9 +6,13 @@ <color name="darkBlue">#192E45</color> <color name="white">#FFFFFF</color> <color name="white60">#99FFFFFF</color> + <color name="white40">#66FFFFFF</color> <color name="white20">#33FFFFFF</color> <color name="green">#44AD4D</color> + <color name="green90">#E644AD4D</color> <color name="red">#D0021B</color> + <color name="red45">#73021B</color> + <color name="red40">#66021B</color> <color name="textInputBorder">#234161</color> </resources> diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index 59c207b71c..b8d1cdb654 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -9,4 +9,14 @@ <string name="logged_in_title">Login successful</string> <string name="login_fail_title">Login failed</string> <string name="login_fail_description">Invalid account number, try again</string> + + <string name="unsecured_connection">Unsecured connection</string> + <string name="creating_secure_connection">Creating secure connection</string> + <string name="secure_connection">Secure connection</string> + <string name="blocking_internet">Blocking internet</string> + <string name="country">Country</string> + <string name="connect">Secure my connection</string> + <string name="cancel">Cancel</string> + <string name="disconnect">Disconnect</string> + <string name="switch_location">Switch location</string> </resources> diff --git a/android/src/main/res/values/styles.xml b/android/src/main/res/values/styles.xml index 726847ab42..86401714f7 100644 --- a/android/src/main/res/values/styles.xml +++ b/android/src/main/res/values/styles.xml @@ -3,4 +3,21 @@ <item name="colorPrimary">@color/colorPrimary</item> <item name="android:windowBackground">@color/blue</item> </style> + + <style name="Button" parent="Widget.AppCompat.Button.Borderless"> + <item name="android:layout_height">44dp</item> + <item name="android:layout_width">match_parent</item> + <item name="android:textAllCaps">false</item> + <item name="android:textColor">@color/white</item> + <item name="android:textSize">20sp</item> + <item name="android:textStyle">bold</item> + </style> + + <style name="GreenButton" parent="Button"> + <item name="android:background">@drawable/green_button_background</item> + </style> + + <style name="White20Button" parent="Button"> + <item name="android:background">@drawable/white20_button_background</item> + </style> </resources> |
