Unverified Commit 8ae3f5ce authored by Max Lv's avatar Max Lv Committed by GitHub

Merge pull request #1903 from Mygod/bottomappbar

Is this a Material Design 2?
parents dc72fc00 2ea09791
...@@ -63,9 +63,7 @@ dependencies { ...@@ -63,9 +63,7 @@ dependencies {
implementation project(':core') implementation project(':core')
implementation "android.arch.work:work-runtime-ktx:$workVersion" implementation "android.arch.work:work-runtime-ktx:$workVersion"
implementation "androidx.browser:browser:$androidxVersion" implementation "androidx.browser:browser:$androidxVersion"
implementation "androidx.gridlayout:gridlayout:$androidxVersion"
implementation "androidx.room:room-runtime:$roomVersion" implementation "androidx.room:room-runtime:$roomVersion"
implementation "com.google.android.material:material:$androidxVersion"
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.4' implementation 'com.crashlytics.sdk.android:crashlytics:2.9.4'
implementation 'com.google.android.gms:play-services-vision:15.0.2' implementation 'com.google.android.gms:play-services-vision:15.0.2'
implementation 'com.google.firebase:firebase-ads:15.0.1' implementation 'com.google.firebase:firebase-ads:15.0.1'
......
...@@ -31,12 +31,9 @@ import android.net.VpnService ...@@ -31,12 +31,9 @@ import android.net.VpnService
import android.nfc.NdefMessage import android.nfc.NdefMessage
import android.nfc.NfcAdapter import android.nfc.NfcAdapter
import android.os.Bundle import android.os.Bundle
import android.os.SystemClock
import android.text.format.Formatter
import android.util.Log import android.util.Log
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
...@@ -49,7 +46,6 @@ import androidx.drawerlayout.widget.DrawerLayout ...@@ -49,7 +46,6 @@ import androidx.drawerlayout.widget.DrawerLayout
import androidx.preference.PreferenceDataStore import androidx.preference.PreferenceDataStore
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.acl.CustomRulesFragment import com.github.shadowsocks.acl.CustomRulesFragment
import com.github.shadowsocks.aidl.IShadowsocksService import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
...@@ -60,16 +56,10 @@ import com.github.shadowsocks.database.ProfileManager ...@@ -60,16 +56,10 @@ import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.responseLength
import com.github.shadowsocks.utils.thread
import com.github.shadowsocks.widget.ServiceButton import com.github.shadowsocks.widget.ServiceButton
import com.github.shadowsocks.widget.StatsBar
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import java.io.IOException
import java.net.HttpURLConnection
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.URL
import java.util.* import java.util.*
class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPreferenceDataStoreChangeListener, class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPreferenceDataStoreChangeListener,
...@@ -86,15 +76,10 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre ...@@ -86,15 +76,10 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre
// UI // UI
private lateinit var fab: ServiceButton private lateinit var fab: ServiceButton
private lateinit var stats: StatsBar
internal lateinit var drawer: DrawerLayout internal lateinit var drawer: DrawerLayout
private lateinit var navigation: NavigationView private lateinit var navigation: NavigationView
private var testCount = 0
private lateinit var statusText: TextView
private lateinit var txText: TextView
private lateinit var rxText: TextView
private lateinit var txRateText: TextView
private lateinit var rxRateText: TextView
val snackbar by lazy { findViewById<View>(R.id.snackbar) } val snackbar by lazy { findViewById<View>(R.id.snackbar) }
fun snackbar(text: CharSequence = "") = Snackbar.make(snackbar, text, Snackbar.LENGTH_LONG) fun snackbar(text: CharSequence = "") = Snackbar.make(snackbar, text, Snackbar.LENGTH_LONG)
...@@ -115,7 +100,12 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre ...@@ -115,7 +100,12 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre
app.handler.post { changeState(state, msg, true) } app.handler.post { changeState(state, msg, true) }
} }
override fun trafficUpdated(profileId: Long, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { override fun trafficUpdated(profileId: Long, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
app.handler.post { updateTraffic(profileId, txRate, rxRate, txTotal, rxTotal) } app.handler.post {
stats.updateTraffic(txRate, rxRate, txTotal, rxTotal)
val child = supportFragmentManager.findFragmentById(R.id.fragment_holder) as ToolbarFragment?
if (state != BaseService.STOPPING)
child?.onTrafficUpdated(profileId, txRate, rxRate, txTotal, rxTotal)
}
} }
override fun trafficPersisted(profileId: Long) { override fun trafficPersisted(profileId: Long) {
app.handler.post { ProfilesFragment.instance?.onTrafficPersisted(profileId) } app.handler.post { ProfilesFragment.instance?.onTrafficPersisted(profileId) }
...@@ -123,69 +113,14 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre ...@@ -123,69 +113,14 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre
} }
} }
fun changeState(state: Int, msg: String? = null, animate: Boolean = false) { private fun changeState(state: Int, msg: String? = null, animate: Boolean = false) {
fab.changeState(state, animate) fab.changeState(state, animate)
when (state) { stats.changeState(state)
BaseService.CONNECTING -> statusText.setText(R.string.connecting) if (msg != null) snackbar(getString(R.string.vpn_error).format(Locale.ENGLISH, msg)).show()
BaseService.CONNECTED -> statusText.setText(R.string.vpn_connected)
BaseService.STOPPING -> statusText.setText(R.string.stopping)
else -> {
if (msg != null) snackbar(getString(R.string.vpn_error).format(Locale.ENGLISH, msg)).show()
statusText.setText(R.string.not_connected)
}
}
this.state = state this.state = state
if (state != BaseService.CONNECTED) {
updateTraffic(-1, 0, 0, 0, 0)
testCount += 1 // suppress previous test messages
}
ProfilesFragment.instance?.profilesAdapter?.notifyDataSetChanged() // refresh button enabled state ProfilesFragment.instance?.profilesAdapter?.notifyDataSetChanged() // refresh button enabled state
stateListener?.invoke(state) stateListener?.invoke(state)
} }
fun updateTraffic(profileId: Long, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
txText.text = Formatter.formatFileSize(this, txTotal)
rxText.text = Formatter.formatFileSize(this, rxTotal)
txRateText.text = getString(R.string.speed, Formatter.formatFileSize(this, txRate))
rxRateText.text = getString(R.string.speed, Formatter.formatFileSize(this, rxRate))
val child = supportFragmentManager.findFragmentById(R.id.fragment_holder) as ToolbarFragment?
if (state != BaseService.STOPPING)
child?.onTrafficUpdated(profileId, txRate, rxRate, txTotal, rxTotal)
}
/**
* Based on: https://android.googlesource.com/platform/frameworks/base/+/97bfd27/services/core/java/com/android/server/connectivity/NetworkMonitor.java#879
*/
private fun testConnection(id: Int) {
val url = URL("https", when (app.currentProfile!!.route) {
Acl.CHINALIST -> "www.qualcomm.cn"
else -> "www.google.com"
}, "/generate_204")
val conn = (if (BaseService.usingVpnMode) url.openConnection() else
url.openConnection(Proxy(Proxy.Type.SOCKS,
InetSocketAddress("127.0.0.1", DataStore.portProxy))))
as HttpURLConnection
conn.setRequestProperty("Connection", "close")
conn.instanceFollowRedirects = false
conn.useCaches = false
val (success, result) = try {
val start = SystemClock.elapsedRealtime()
val code = conn.responseCode
val elapsed = SystemClock.elapsedRealtime() - start
if (code == 204 || code == 200 && conn.responseLength == 0L)
Pair(true, getString(R.string.connection_test_available, elapsed))
else throw IOException(getString(R.string.connection_test_error_status_code, code))
} catch (e: IOException) {
Pair(false, getString(R.string.connection_test_error, e.message))
} finally {
conn.disconnect()
}
if (testCount == id) app.handler.post {
if (success) statusText.text = result else {
statusText.setText(R.string.connection_test_fail)
snackbar(result).show()
}
}
}
override val listenForDeath: Boolean get() = true override val listenForDeath: Boolean get() = true
override fun onServiceConnected(service: IShadowsocksService) = changeState(service.state) override fun onServiceConnected(service: IShadowsocksService) = changeState(service.state)
...@@ -210,6 +145,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre ...@@ -210,6 +145,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.layout_main) setContentView(R.layout.layout_main)
stats = findViewById(R.id.stats)
stats.setOnClickListener { if (state == BaseService.CONNECTED) stats.testConnection() }
drawer = findViewById(R.id.drawer) drawer = findViewById(R.id.drawer)
navigation = findViewById(R.id.navigation) navigation = findViewById(R.id.navigation)
navigation.setNavigationItemSelectedListener(this) navigation.setNavigationItemSelectedListener(this)
...@@ -218,20 +155,6 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre ...@@ -218,20 +155,6 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre
displayFragment(ProfilesFragment()) displayFragment(ProfilesFragment())
} }
statusText = findViewById(R.id.status)
txText = findViewById(R.id.tx)
txRateText = findViewById(R.id.txRate)
rxText = findViewById(R.id.rx)
rxRateText = findViewById(R.id.rxRate)
findViewById<View>(R.id.stat).setOnClickListener {
if (state == BaseService.CONNECTED) {
++testCount
statusText.setText(R.string.connection_test_testing)
val id = testCount // it would change by other code
thread("ConnectionTest") { testConnection(id) }
}
}
fab = findViewById(R.id.fab) fab = findViewById(R.id.fab)
fab.setOnClickListener { fab.setOnClickListener {
when { when {
......
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program 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. *
* *
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.widget
import android.content.Context
import android.util.AttributeSet
import androidx.gridlayout.widget.GridLayout
import com.github.shadowsocks.R
/**
* Based on: http://stackoverflow.com/a/6212120/2245107
*/
class BoundedGridLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0,
defStyleRes: Int = 0) : GridLayout(context, attrs, defStyleAttr) {
private val boundedWidth: Int
private val boundedHeight: Int
init {
val arr = context.obtainStyledAttributes(attrs, R.styleable.BoundedGridLayout, defStyleAttr, defStyleRes)
boundedWidth = arr.getDimensionPixelSize(R.styleable.BoundedGridLayout_bounded_width, 0)
boundedHeight = arr.getDimensionPixelSize(R.styleable.BoundedGridLayout_bounded_height, 0)
arr.recycle()
}
override fun onMeasure(widthSpec: Int, heightSpec: Int) = super.onMeasure(
if (boundedWidth <= 0 || boundedWidth >= MeasureSpec.getSize(widthSpec)) widthSpec
else MeasureSpec.makeMeasureSpec(boundedWidth, MeasureSpec.getMode(widthSpec)),
if (boundedHeight <= 0 || boundedHeight >= MeasureSpec.getSize(heightSpec)) heightSpec
else MeasureSpec.makeMeasureSpec(boundedHeight, MeasureSpec.getMode(heightSpec)))
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program 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. *
* *
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.widget
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.accessibility.AccessibilityManager
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.getSystemService
import com.google.android.material.animation.AnimationUtils
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.SnackbarConsts
/**
* Full credits go to: https://stackoverflow.com/a/35904421/2245107
*/
class ShrinkUpwardBehavior(context: Context, attrs: AttributeSet?) : CoordinatorLayout.Behavior<View>(context, attrs) {
private val accessibility = context.getSystemService<AccessibilityManager>()!!
override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean =
dependency is Snackbar.SnackbarLayout
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
child.layoutParams.height = dependency.y.toInt()
child.requestLayout()
return true
}
/**
* Based on: https://android.googlesource.com/platform/frameworks/support.git/+/fa0f82f/design/src/android/support/design/widget/BaseTransientBottomBar.java#558
*/
override fun onDependentViewRemoved(parent: CoordinatorLayout, child: View, dependency: View) {
if (accessibility.isEnabled) child.layoutParams.height = parent.height else {
val animator = ValueAnimator()
val start = child.height
animator.setIntValues(start, parent.height)
animator.interpolator = AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR
animator.duration = SnackbarConsts.ANIMATION_DURATION
animator.addUpdateListener {
child.layoutParams.height = it.animatedValue as Int
child.requestLayout()
}
animator.start()
}
}
}
/*******************************************************************************
* *
* Copyright (C) 2018 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2018 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program 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. *
* *
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.widget
import android.content.Context
import android.os.SystemClock
import android.text.format.Formatter
import android.util.AttributeSet
import android.view.View
import android.widget.TextView
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.MainActivity
import com.github.shadowsocks.R
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.responseLength
import com.github.shadowsocks.utils.thread
import com.google.android.material.bottomappbar.BottomAppBar
import java.io.IOException
import java.net.HttpURLConnection
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.URL
class StatsBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.bottomAppBarStyle) :
BottomAppBar(context, attrs, defStyleAttr) {
private var testCount = 0
private lateinit var statusText: TextView
private lateinit var txText: TextView
private lateinit var rxText: TextView
private lateinit var txRateText: TextView
private lateinit var rxRateText: TextView
override fun getBehavior() = object : Behavior() {
val threshold = context.resources.getDimensionPixelSize(R.dimen.stats_bar_scroll_threshold)
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: BottomAppBar, target: View,
dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int) {
val dy = dyConsumed + dyUnconsumed
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, if (Math.abs(dy) >= threshold) dy else 0,
dxUnconsumed, 0, type)
}
}
override fun setOnClickListener(l: OnClickListener?) {
statusText = findViewById(R.id.status)
txText = findViewById(R.id.tx)
txRateText = findViewById(R.id.txRate)
rxText = findViewById(R.id.rx)
rxRateText = findViewById(R.id.rxRate)
super.setOnClickListener(l)
}
fun changeState(state: Int) {
statusText.setText(when (state) {
BaseService.CONNECTING -> R.string.connecting
BaseService.CONNECTED -> R.string.vpn_connected
BaseService.STOPPING -> R.string.stopping
else -> R.string.not_connected
})
if (state != BaseService.CONNECTED) {
updateTraffic(0, 0, 0, 0)
testCount += 1 // suppress previous test messages
}
}
fun updateTraffic(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
txText.text = "▲ ${Formatter.formatFileSize(context, txTotal)}"
rxText.text = "▼ ${Formatter.formatFileSize(context, rxTotal)}"
txRateText.text = context.getString(R.string.speed, Formatter.formatFileSize(context, txRate))
rxRateText.text = context.getString(R.string.speed, Formatter.formatFileSize(context, rxRate))
}
/**
* Based on: https://android.googlesource.com/platform/frameworks/base/+/b19a838/services/core/java/com/android/server/connectivity/NetworkMonitor.java#1071
*/
fun testConnection() {
++testCount
statusText.setText(R.string.connection_test_testing)
val id = testCount // it would change by other code
thread("ConnectionTest") {
val url = URL("https", when (app.currentProfile!!.route) {
Acl.CHINALIST -> "www.qualcomm.cn"
else -> "www.google.com"
}, "/generate_204")
val conn = (if (BaseService.usingVpnMode) url.openConnection() else
url.openConnection(Proxy(Proxy.Type.SOCKS,
InetSocketAddress("127.0.0.1", DataStore.portProxy))))
as HttpURLConnection
conn.setRequestProperty("Connection", "close")
conn.instanceFollowRedirects = false
conn.useCaches = false
val context = context as MainActivity
val (success, result) = try {
val start = SystemClock.elapsedRealtime()
val code = conn.responseCode
val elapsed = SystemClock.elapsedRealtime() - start
if (code == 204 || code == 200 && conn.responseLength == 0L)
Pair(true, context.getString(R.string.connection_test_available, elapsed))
else throw IOException(context.getString(R.string.connection_test_error_status_code, code))
} catch (e: IOException) {
Pair(false, context.getString(R.string.connection_test_error, e.message))
} finally {
conn.disconnect()
}
if (testCount == id) app.handler.post {
if (success) statusText.text = result else {
statusText.setText(R.string.connection_test_fail)
context.snackbar(result).show()
}
}
}
}
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program 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. *
* *
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.google.android.material.snackbar
object SnackbarConsts {
const val ANIMATION_DURATION = BaseTransientBottomBar.ANIMATION_DURATION.toLong()
}
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="@color/background_stat"/>
</item>
<item android:drawable="?android:attr/selectableItemBackground"/>
</layer-list>
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:paddingBottom="32dp" android:paddingBottom="88dp"
android:scrollbars="vertical" android:scrollbars="vertical"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false"/> android:clipToPadding="false"/>
......
This diff is collapsed.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BoundedGridLayout">
<attr name="bounded_width" format="dimension" />
<attr name="bounded_height" format="dimension" />
</declare-styleable>
</resources>
...@@ -3,4 +3,5 @@ ...@@ -3,4 +3,5 @@
<dimen name="qr_code_size">250dp</dimen> <dimen name="qr_code_size">250dp</dimen>
<dimen name="profile_padding">8dp</dimen> <dimen name="profile_padding">8dp</dimen>
<dimen name="bottom_sheet_padding">8dp</dimen> <dimen name="bottom_sheet_padding">8dp</dimen>
<dimen name="stats_bar_scroll_threshold">4dp</dimen>
</resources> </resources>
...@@ -126,8 +126,6 @@ ...@@ -126,8 +126,6 @@
<string name="profile_default">Use the current profile</string> <string name="profile_default">Use the current profile</string>
<!-- status --> <!-- status -->
<string name="sent">Sent:</string>
<string name="received">Received:</string>
<string name="connecting">Connecting…</string> <string name="connecting">Connecting…</string>
<string name="vpn_connected">Connected, tap to check connection</string> <string name="vpn_connected">Connected, tap to check connection</string>
<string name="not_connected">Not connected</string> <string name="not_connected">Not connected</string>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment