summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorsaber safavi <saber.safavi@codic.se>2023-05-26 10:14:37 +0200
committersaber safavi <saber.safavi@codic.se>2023-07-13 14:49:50 +0200
commitaadc8490ddd84b9632e225ad6b68758579c81b70 (patch)
treeaf50315734973badf44688cdb0c8869a40d5bef5 /android
parent9dc4ac6e340474524cb7bc88164cdae951ee9c05 (diff)
downloadmullvadvpn-aadc8490ddd84b9632e225ad6b68758579c81b70.tar.xz
mullvadvpn-aadc8490ddd84b9632e225ad6b68758579c81b70.zip
Add settings compose screen
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt44
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt185
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SettingsFragment.kt194
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/AccountExpiryNotification.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/TimeLeftFormatter.kt38
-rw-r--r--android/app/src/main/res/layout/settings.xml81
6 files changed, 271 insertions, 279 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt
new file mode 100644
index 0000000000..dca2a6aeb9
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt
@@ -0,0 +1,44 @@
+package net.mullvad.mullvadvpn.compose.extensions
+
+import android.content.res.Resources
+import net.mullvad.mullvadvpn.R
+import org.joda.time.DateTime
+import org.joda.time.Duration
+import org.joda.time.PeriodType
+
+fun Resources.getExpiryQuantityString(accountExpiry: DateTime): String {
+ val remainingTime = Duration(DateTime.now(), accountExpiry)
+
+ return getExpiryQuantityString(this, accountExpiry, remainingTime)
+}
+
+private fun getExpiryQuantityString(
+ resources: Resources,
+ accountExpiry: DateTime,
+ remainingTime: Duration
+): String {
+ if (remainingTime.isShorterThan(Duration.ZERO)) {
+ return resources.getString(R.string.out_of_time)
+ } else {
+ val remainingTimeInfo =
+ remainingTime.toPeriodTo(accountExpiry, PeriodType.yearMonthDayTime())
+
+ if (remainingTimeInfo.years > 0) {
+ return getRemainingText(resources, R.plurals.years_left, remainingTimeInfo.years)
+ } else if (remainingTimeInfo.months >= 3) {
+ return getRemainingText(resources, R.plurals.months_left, remainingTimeInfo.months)
+ } else if (remainingTimeInfo.months > 0 || remainingTimeInfo.days >= 1) {
+ return getRemainingText(
+ resources,
+ R.plurals.days_left,
+ remainingTime.standardDays.toInt()
+ )
+ } else {
+ return resources.getString(R.string.less_than_a_day_left)
+ }
+ }
+}
+
+private fun getRemainingText(resources: Resources, pluralId: Int, quantity: Int): String {
+ return resources.getQuantityString(pluralId, quantity, quantity)
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt
new file mode 100644
index 0000000000..34a2e560e5
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt
@@ -0,0 +1,185 @@
+package net.mullvad.mullvadvpn.compose.screen
+
+import android.net.Uri
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.background
+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.layout.wrapContentHeight
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import me.onebone.toolbar.ScrollStrategy
+import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.cell.DefaultExternalLinkView
+import net.mullvad.mullvadvpn.compose.cell.NavigationCellBody
+import net.mullvad.mullvadvpn.compose.cell.NavigationComposeCell
+import net.mullvad.mullvadvpn.compose.component.CollapsableAwareToolbarScaffold
+import net.mullvad.mullvadvpn.compose.component.CollapsingTopBar
+import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
+import net.mullvad.mullvadvpn.compose.extensions.itemWithDivider
+import net.mullvad.mullvadvpn.compose.state.SettingsUiState
+import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_TEST_TAG
+import net.mullvad.mullvadvpn.compose.theme.Dimens
+import net.mullvad.mullvadvpn.ui.extension.openLink
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Composable
+private fun PreviewSettings() {
+ SettingsScreen(
+ uiState =
+ SettingsUiState(appVersion = "2222.22", isLoggedIn = true, isUpdateAvailable = true)
+ )
+}
+
+@ExperimentalMaterial3Api
+@Composable
+fun SettingsScreen(
+ uiState: SettingsUiState,
+ onVpnSettingCellClick: () -> Unit = {},
+ onSplitTunnelingCellClick: () -> Unit = {},
+ onReportProblemCellClick: () -> Unit = {},
+ onBackClick: () -> Unit = {}
+) {
+ val context = LocalContext.current
+ val lazyListState = rememberLazyListState()
+ val state = rememberCollapsingToolbarScaffoldState()
+ val progress = state.toolbarState.progress
+
+ CollapsableAwareToolbarScaffold(
+ backgroundColor = MaterialTheme.colorScheme.background,
+ modifier = Modifier.fillMaxSize(),
+ state = state,
+ scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
+ isEnabledWhenCollapsable = true,
+ toolbar = {
+ val scaffoldModifier =
+ Modifier.road(
+ whenCollapsed = Alignment.TopCenter,
+ whenExpanded = Alignment.BottomStart
+ )
+ CollapsingTopBar(
+ backgroundColor = MaterialTheme.colorScheme.secondary,
+ onBackClicked = { onBackClick() },
+ title = stringResource(id = R.string.settings),
+ progress = progress,
+ modifier = scaffoldModifier,
+ backTitle = String(),
+ shouldRotateBackButtonDown = true
+ )
+ },
+ ) {
+ LazyColumn(
+ modifier =
+ Modifier.drawVerticalScrollbar(lazyListState)
+ .testTag(LAZY_LIST_TEST_TAG)
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .animateContentSize(),
+ state = lazyListState
+ ) {
+ item { Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding)) }
+ if (uiState.isLoggedIn) {
+ item {
+ NavigationComposeCell(
+ title = stringResource(id = R.string.settings_vpn),
+ onClick = { onVpnSettingCellClick() }
+ )
+ }
+
+ item {
+ Spacer(modifier = Modifier.height(Dimens.cellVerticalSpacing))
+ NavigationComposeCell(
+ title = stringResource(id = R.string.split_tunneling),
+ onClick = { onSplitTunnelingCellClick() }
+ )
+ Spacer(modifier = Modifier.height(Dimens.cellVerticalSpacing))
+ }
+ }
+ item {
+ NavigationComposeCell(
+ title = stringResource(id = R.string.app_version),
+ onClick = {
+ context.openLink(
+ Uri.parse(context.resources.getString(R.string.download_url))
+ )
+ },
+ bodyView =
+ @Composable {
+ NavigationCellBody(
+ content = uiState.appVersion,
+ contentBodyDescription = stringResource(id = R.string.app_version),
+ isExternalLink = true,
+ )
+ },
+ showWarning = uiState.isUpdateAvailable,
+ )
+ }
+ if (uiState.isUpdateAvailable) {
+ item {
+ Text(
+ text = stringResource(id = R.string.update_available_footer),
+ style = MaterialTheme.typography.labelMedium,
+ color = MaterialTheme.colorScheme.onSecondary,
+ modifier =
+ Modifier.background(MaterialTheme.colorScheme.secondary)
+ .padding(
+ start = Dimens.cellStartPadding,
+ top = Dimens.cellTopPadding,
+ end = Dimens.cellStartPadding,
+ bottom = Dimens.cellLabelVerticalPadding,
+ )
+ )
+ }
+ }
+
+ itemWithDivider {
+ Spacer(modifier = Modifier.height(Dimens.cellVerticalSpacing))
+ NavigationComposeCell(
+ title = stringResource(id = R.string.report_a_problem),
+ onClick = { onReportProblemCellClick() }
+ )
+ }
+
+ itemWithDivider {
+ val faqGuideLabel = stringResource(id = R.string.faqs_and_guides)
+ NavigationComposeCell(
+ title = faqGuideLabel,
+ bodyView = @Composable { DefaultExternalLinkView(faqGuideLabel) },
+ onClick = {
+ context.openLink(
+ Uri.parse(context.resources.getString(R.string.faqs_and_guides_url))
+ )
+ }
+ )
+ }
+
+ itemWithDivider {
+ val privacyPolicyLabel = stringResource(id = R.string.privacy_policy_label)
+ NavigationComposeCell(
+ title = privacyPolicyLabel,
+ bodyView = @Composable { DefaultExternalLinkView(privacyPolicyLabel) },
+ onClick = {
+ context.openLink(
+ Uri.parse(context.resources.getString(R.string.privacy_policy_url))
+ )
+ }
+ )
+ }
+ }
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SettingsFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SettingsFragment.kt
index 1aefb900fb..7758986702 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SettingsFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SettingsFragment.kt
@@ -4,182 +4,66 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.widget.ImageButton
-import androidx.core.content.ContextCompat
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.launch
-import net.mullvad.mullvadvpn.BuildConfig
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.constant.BuildTypes
-import net.mullvad.mullvadvpn.model.DeviceState
-import net.mullvad.mullvadvpn.repository.DeviceRepository
-import net.mullvad.mullvadvpn.ui.CollapsibleTitleController
+import net.mullvad.mullvadvpn.compose.screen.SettingsScreen
+import net.mullvad.mullvadvpn.compose.theme.AppTheme
import net.mullvad.mullvadvpn.ui.NavigationBarPainter
import net.mullvad.mullvadvpn.ui.StatusBarPainter
-import net.mullvad.mullvadvpn.ui.VersionInfo
-import net.mullvad.mullvadvpn.ui.paintNavigationBar
-import net.mullvad.mullvadvpn.ui.paintStatusBar
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
-import net.mullvad.mullvadvpn.ui.serviceconnection.appVersionInfoCache
-import net.mullvad.mullvadvpn.ui.widget.AppVersionCell
-import net.mullvad.mullvadvpn.ui.widget.NavigateCell
-import net.mullvad.mullvadvpn.ui.widget.UrlCell
-import net.mullvad.mullvadvpn.util.JobTracker
-import net.mullvad.mullvadvpn.util.UNKNOWN_STATE_DEBOUNCE_DELAY_MILLISECONDS
-import net.mullvad.mullvadvpn.util.addDebounceForUnknownState
-import net.mullvad.mullvadvpn.util.appVersionCallbackFlow
-import org.koin.android.ext.android.inject
+import net.mullvad.mullvadvpn.viewmodel.SettingsViewModel
+import org.koin.androidx.viewmodel.ext.android.viewModel
class SettingsFragment : BaseFragment(), StatusBarPainter, NavigationBarPainter {
+ private val vm by viewModel<SettingsViewModel>()
- // Injected dependencies
- private val deviceRepository: DeviceRepository by inject()
- private val serviceConnectionManager: ServiceConnectionManager by inject()
-
- private lateinit var appVersionMenu: AppVersionCell
- private lateinit var vpnSettingsMenu: View
- private lateinit var splitTunnelingMenu: View
- private lateinit var titleController: CollapsibleTitleController
-
- @Deprecated("Refactor code to instead rely on Lifecycle.") private val jobTracker = JobTracker()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- lifecycleScope.launchUiSubscriptionsOnResume()
- }
-
+ @OptIn(ExperimentalMaterial3Api::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
- ): View {
- val view = inflater.inflate(R.layout.settings, container, false)
-
- view.findViewById<ImageButton>(R.id.close).setOnClickListener { activity?.onBackPressed() }
-
- vpnSettingsMenu =
- view.findViewById<NavigateCell>(R.id.vpn_settings).apply {
- targetFragment = VpnSettingsFragment::class
- }
-
- splitTunnelingMenu =
- view.findViewById<NavigateCell>(R.id.split_tunneling).apply {
- targetFragment = SplitTunnelingFragment::class
- }
-
- view.findViewById<NavigateCell>(R.id.report_a_problem).apply {
- targetFragment = ProblemReportFragment::class
- }
-
- appVersionMenu = view.findViewById<AppVersionCell>(R.id.app_version)
-
- titleController = CollapsibleTitleController(view)
-
- view.findViewById<UrlCell>(R.id.faqs_and_guides).visibility =
- if (BuildTypes.RELEASE == BuildConfig.BUILD_TYPE) {
- View.GONE
- } else {
- View.VISIBLE
- }
-
- return view
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- initializeUiState()
- }
-
- override fun onResume() {
- super.onResume()
- paintNavigationBar(ContextCompat.getColor(requireContext(), R.color.darkBlue))
- }
-
- override fun onStop() {
- jobTracker.cancelAllJobs()
- super.onStop()
- }
-
- override fun onDestroyView() {
- titleController.onDestroy()
- super.onDestroyView()
- }
-
- private fun initializeUiState() {
- updateLoggedInStatus(deviceRepository.deviceState.value is DeviceState.LoggedIn)
- appVersionMenu.version = BuildConfig.VERSION_NAME
- serviceConnectionManager.appVersionInfoCache().let { cache ->
- updateVersionInfo(
- if (cache != null) {
- VersionInfo(
- currentVersion = cache.version,
- upgradeVersion = cache.upgradeVersion,
- isOutdated = cache.isOutdated,
- isSupported = cache.isSupported
- )
- } else {
- VersionInfo(
- currentVersion = null,
- upgradeVersion = null,
- isOutdated = false,
- isSupported = true
+ ): View? {
+ return inflater.inflate(R.layout.fragment_compose, container, false).apply {
+ findViewById<ComposeView>(R.id.compose_view).setContent {
+ AppTheme {
+ val state = vm.uiState.collectAsState().value
+ SettingsScreen(
+ uiState = state,
+ onVpnSettingCellClick = { openVpnSettingsFragment() },
+ onSplitTunnelingCellClick = { openSplitTunnelingFragment() },
+ onReportProblemCellClick = { openReportProblemFragment() },
+ onBackClick = { activity?.onBackPressed() }
)
}
- )
- }
- }
-
- private fun CoroutineScope.launchUiSubscriptionsOnResume() = launch {
- repeatOnLifecycle(Lifecycle.State.RESUMED) {
- launchPaintStatusBarAfterTransition()
- luanchConfigureMenuOnDeviceChanges()
- launchVersionInfoSubscription()
+ }
}
}
- private fun CoroutineScope.launchPaintStatusBarAfterTransition() = launch {
- transitionFinishedFlow.collect {
- paintStatusBar(ContextCompat.getColor(requireContext(), R.color.darkBlue))
+ private fun openFragment(fragment: Fragment) {
+ parentFragmentManager.beginTransaction().apply {
+ setCustomAnimations(
+ R.anim.fragment_enter_from_right,
+ R.anim.fragment_exit_to_left,
+ R.anim.fragment_half_enter_from_left,
+ R.anim.fragment_exit_to_right
+ )
+ replace(R.id.main_fragment, fragment)
+ addToBackStack(null)
+ commitAllowingStateLoss()
}
}
- private fun CoroutineScope.luanchConfigureMenuOnDeviceChanges() = launch {
- deviceRepository.deviceState
- .debounce { it.addDebounceForUnknownState(UNKNOWN_STATE_DEBOUNCE_DELAY_MILLISECONDS) }
- .collect { device -> updateLoggedInStatus(device is DeviceState.LoggedIn) }
- }
-
- private fun CoroutineScope.launchVersionInfoSubscription() = launch {
- serviceConnectionManager.connectionState
- .flatMapLatest { state ->
- if (state is ServiceConnectionState.ConnectedReady) {
- state.container.appVersionInfoCache.appVersionCallbackFlow()
- } else {
- emptyFlow()
- }
- }
- .collect { versionInfo -> updateVersionInfo(versionInfo) }
+ private fun openVpnSettingsFragment() {
+ openFragment(VpnSettingsFragment())
}
- private fun updateLoggedInStatus(loggedIn: Boolean) {
- val visibility =
- if (loggedIn) {
- View.VISIBLE
- } else {
- View.GONE
- }
-
- vpnSettingsMenu.visibility = visibility
- splitTunnelingMenu.visibility = visibility
+ private fun openSplitTunnelingFragment() {
+ openFragment(SplitTunnelingFragment())
}
- private fun updateVersionInfo(versionInfo: VersionInfo) {
- appVersionMenu.updateAvailable = versionInfo.isOutdated || !versionInfo.isSupported
+ private fun openReportProblemFragment() {
+ openFragment(ProblemReportFragment())
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/AccountExpiryNotification.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/AccountExpiryNotification.kt
index 38c8f8ed90..2e674ebc45 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/AccountExpiryNotification.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/AccountExpiryNotification.kt
@@ -2,14 +2,12 @@ package net.mullvad.mullvadvpn.ui.notification
import android.content.Context
import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.util.TimeLeftFormatter
+import net.mullvad.mullvadvpn.compose.extensions.getExpiryQuantityString
import org.joda.time.DateTime
class AccountExpiryNotification(
- context: Context,
+ val context: Context,
) : InAppNotification() {
- private val timeLeftFormatter = TimeLeftFormatter(context.resources)
-
init {
status = StatusLevel.Error
title = context.getString(R.string.account_credit_expires_soon)
@@ -19,7 +17,7 @@ class AccountExpiryNotification(
val threeDaysFromNow = DateTime.now().plusDays(3)
if (expiry != null && expiry.isBefore(threeDaysFromNow)) {
- message = timeLeftFormatter.format(expiry)
+ message = context.resources.getExpiryQuantityString(expiry)
shouldShow = true
} else {
shouldShow = false
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/TimeLeftFormatter.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/TimeLeftFormatter.kt
deleted file mode 100644
index c3a6aaa1cb..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/TimeLeftFormatter.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package net.mullvad.mullvadvpn.util
-
-import android.content.res.Resources
-import net.mullvad.mullvadvpn.R
-import org.joda.time.DateTime
-import org.joda.time.Duration
-import org.joda.time.PeriodType
-
-class TimeLeftFormatter(val resources: Resources) {
- fun format(accountExpiry: DateTime): String {
- val remainingTime = Duration(DateTime.now(), accountExpiry)
-
- return format(accountExpiry, remainingTime)
- }
-
- fun format(accountExpiry: DateTime, remainingTime: Duration): String {
- if (remainingTime.isShorterThan(Duration.ZERO)) {
- return resources.getString(R.string.out_of_time)
- } else {
- val remainingTimeInfo =
- remainingTime.toPeriodTo(accountExpiry, PeriodType.yearMonthDayTime())
-
- if (remainingTimeInfo.years > 0) {
- return getRemainingText(R.plurals.years_left, remainingTimeInfo.years)
- } else if (remainingTimeInfo.months >= 3) {
- return getRemainingText(R.plurals.months_left, remainingTimeInfo.months)
- } else if (remainingTimeInfo.months > 0 || remainingTimeInfo.days >= 1) {
- return getRemainingText(R.plurals.days_left, remainingTime.standardDays.toInt())
- } else {
- return resources.getString(R.string.less_than_a_day_left)
- }
- }
- }
-
- private fun getRemainingText(pluralId: Int, quantity: Int): String {
- return resources.getQuantityString(pluralId, quantity, quantity)
- }
-}
diff --git a/android/app/src/main/res/layout/settings.xml b/android/app/src/main/res/layout/settings.xml
deleted file mode 100644
index fe4d75ba99..0000000000
--- a/android/app/src/main/res/layout/settings.xml
+++ /dev/null
@@ -1,81 +0,0 @@
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:mullvad="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/darkBlue"
- android:gravity="left">
- <TextView android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/settings"
- style="@style/SettingsCollapsedHeader" />
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <FrameLayout android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <ImageButton android:id="@+id/close"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="12dp"
- android:background="?android:attr/selectableItemBackground"
- android:src="@drawable/icon_close" />
- <TextView android:id="@+id/collapsed_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginHorizontal="4dp"
- android:layout_gravity="center"
- android:text="@string/settings"
- style="@style/SettingsCollapsedHeader" />
- </FrameLayout>
- <net.mullvad.mullvadvpn.ui.widget.ListenableScrollView android:id="@+id/scroll_area"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <TextView android:id="@+id/expanded_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="4dp"
- android:layout_marginLeft="@dimen/side_margin"
- android:lines="1"
- android:text="@string/settings"
- style="@style/SettingsExpandedHeader" />
- <net.mullvad.mullvadvpn.ui.widget.NavigateCell android:id="@+id/vpn_settings"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/vertical_space"
- mullvad:text="@string/settings_vpn" />
- <net.mullvad.mullvadvpn.ui.widget.NavigateCell android:id="@+id/split_tunneling"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/vertical_space"
- mullvad:text="@string/split_tunneling" />
- <net.mullvad.mullvadvpn.ui.widget.AppVersionCell android:id="@+id/app_version"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/vertical_space"
- mullvad:text="@string/app_version"
- mullvad:footer="@string/update_available_footer" />
- <net.mullvad.mullvadvpn.ui.widget.NavigateCell android:id="@+id/report_a_problem"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/vertical_space"
- mullvad:text="@string/report_a_problem" />
- <net.mullvad.mullvadvpn.ui.widget.UrlCell android:id="@+id/faqs_and_guides"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="1dp"
- mullvad:text="@string/faqs_and_guides"
- mullvad:url="@string/faqs_and_guides_url" />
- <net.mullvad.mullvadvpn.ui.widget.UrlCell android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="1dp"
- mullvad:text="@string/privacy_policy_label"
- mullvad:url="@string/privacy_policy_url" />
- </LinearLayout>
- </net.mullvad.mullvadvpn.ui.widget.ListenableScrollView>
- </LinearLayout>
-</FrameLayout>