commit
f3ef282bfa
Binary file not shown.
@ -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/accent" />
|
||||
|
||||
</shape>
|
@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="496dp"
|
||||
android:height="496dp"
|
||||
android:viewportWidth="496"
|
||||
android:viewportHeight="496">
|
||||
<path
|
||||
android:pathData="M248,0C111.043,0 0,111.083 0,248C0,384.997 111.043,496 248,496C384.957,496 496,384.997 496,248C496,111.083 384.957,0 248,0ZM248.5,467C127.744,467 30,369.297 30,248.5C30,127.784 127.748,30 248.5,30C369.211,30 467,127.747 467,248.5C467,369.254 369.297,467 248.5,467ZM270.703,285.663L270.703,292C270.703,298.627 268.33,304 261.703,304L238.056,304C231.429,304 226.056,298.627 226.056,292L226.056,283.341C226.056,247.596 250.224,244.566 270.703,233.084C288.264,223.239 314.235,222.608 314.235,185.76C314.235,164.861 286.202,147.355 253.674,147.355C230.485,147.355 204.281,169.53 189.233,188.522C185.176,193.642 177.773,194.593 172.567,190.646L155.743,185.548C150.636,181.676 149.492,174.482 153.099,169.185C176.726,134.491 206.82,104 253.674,104C302.745,104 355.124,142.304 355.124,192.8C355.124,267.221 270.703,260.884 270.703,285.663ZM282,373C282,388.991 268.991,402 253,402C237.009,402 224,388.991 224,373C224,357.009 237.009,344 253,344C268.991,344 282,357.009 282,373Z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="@color/text"
|
||||
android:fillType="nonZero"
|
||||
android:strokeColor="@color/text"/>
|
||||
</vector>
|
@ -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/paths_building" />
|
||||
|
||||
</shape>
|
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="@drawable/default_session_background"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/large_spacing"
|
||||
android:layout_marginTop="@dimen/large_spacing"
|
||||
android:layout_marginRight="@dimen/large_spacing"
|
||||
android:textSize="@dimen/small_font_size"
|
||||
android:textColor="@color/text"
|
||||
android:alpha="0.6"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/activity_path_explanation" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_margin="@dimen/large_spacing">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/pathRowsContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_centerInParent="true" />
|
||||
|
||||
<com.github.ybq.android.spinkit.SpinKitView
|
||||
style="@style/SpinKitView.Large.ThreeBounce"
|
||||
android:id="@+id/spinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
app:SpinKit_Color="@color/text" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<Button
|
||||
style="@style/MediumProminentOutlineButton"
|
||||
android:id="@+id/rebuildPathButton"
|
||||
android:layout_width="196dp"
|
||||
android:layout_height="@dimen/medium_button_height"
|
||||
android:layout_marginBottom="@dimen/medium_spacing"
|
||||
android:text="@string/activity_path_rebuild_path_button_title" />
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/learnMoreButton"
|
||||
android:icon="@drawable/ic_question_mark"
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
@ -0,0 +1,261 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.support.v4.content.LocalBroadcastManager
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import kotlinx.android.synthetic.main.activity_path.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.utilities.animateSizeChange
|
||||
import org.thoughtcrime.securesms.loki.utilities.fadeIn
|
||||
import org.thoughtcrime.securesms.loki.utilities.fadeOut
|
||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||
import org.whispersystems.signalservice.loki.api.onionrequests.OnionRequestAPI
|
||||
import org.whispersystems.signalservice.loki.api.onionrequests.Snode
|
||||
|
||||
class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||
private val broadcastReceivers = mutableListOf<BroadcastReceiver>()
|
||||
|
||||
// region Lifecycle
|
||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
||||
super.onCreate(savedInstanceState, isReady)
|
||||
setContentView(R.layout.activity_path)
|
||||
supportActionBar!!.title = resources.getString(R.string.activity_path_title)
|
||||
rebuildPathButton.setOnClickListener { rebuildPath() }
|
||||
update(false)
|
||||
registerObservers()
|
||||
}
|
||||
|
||||
private fun registerObservers() {
|
||||
val buildingPathsReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
handleBuildingPathsEvent()
|
||||
}
|
||||
}
|
||||
broadcastReceivers.add(buildingPathsReceiver)
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(buildingPathsReceiver, IntentFilter("buildingPaths"))
|
||||
val pathsBuiltReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
handlePathsBuiltEvent()
|
||||
}
|
||||
}
|
||||
broadcastReceivers.add(pathsBuiltReceiver)
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(pathsBuiltReceiver, IntentFilter("pathsBuilt"))
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_path, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
for (receiver in broadcastReceivers) {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Updating
|
||||
private fun handleBuildingPathsEvent() { update(false) }
|
||||
private fun handlePathsBuiltEvent() { update(false) }
|
||||
|
||||
private fun update(isAnimated: Boolean) {
|
||||
pathRowsContainer.removeAllViews()
|
||||
if (OnionRequestAPI.paths.count() >= OnionRequestAPI.pathCount) {
|
||||
val path = OnionRequestAPI.paths.firstOrNull() ?: return finish()
|
||||
val dotAnimationRepeatInterval = path.count().toLong() * 1000 + 1000
|
||||
val pathRows = path.mapIndexed { index, snode ->
|
||||
val isGuardSnode = (OnionRequestAPI.guardSnodes.contains(snode))
|
||||
getPathRow(snode, LineView.Location.Middle, index.toLong() * 1000 + 2000, dotAnimationRepeatInterval, isGuardSnode)
|
||||
}
|
||||
val youRow = getPathRow("You", null, LineView.Location.Top, 1000, dotAnimationRepeatInterval)
|
||||
val destinationRow = getPathRow("Destination", null, LineView.Location.Bottom, path.count().toLong() * 1000 + 2000, dotAnimationRepeatInterval)
|
||||
val rows = listOf( youRow ) + pathRows + listOf( destinationRow )
|
||||
for (row in rows) {
|
||||
pathRowsContainer.addView(row)
|
||||
}
|
||||
if (isAnimated) {
|
||||
spinner.fadeOut()
|
||||
} else {
|
||||
spinner.alpha = 0.0f
|
||||
}
|
||||
} else {
|
||||
if (isAnimated) {
|
||||
spinner.fadeIn()
|
||||
} else {
|
||||
spinner.alpha = 1.0f
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region General
|
||||
private fun getPathRow(title: String, subtitle: String?, location: LineView.Location, dotAnimationStartDelay: Long, dotAnimationRepeatInterval: Long): LinearLayout {
|
||||
val mainContainer = LinearLayout(this)
|
||||
mainContainer.orientation = LinearLayout.HORIZONTAL
|
||||
mainContainer.gravity = Gravity.CENTER_VERTICAL
|
||||
val mainContainerLayoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
|
||||
mainContainer.layoutParams = mainContainerLayoutParams
|
||||
val lineView = LineView(this, location, dotAnimationStartDelay, dotAnimationRepeatInterval)
|
||||
val lineViewLayoutParams = LinearLayout.LayoutParams(resources.getDimensionPixelSize(R.dimen.path_row_expanded_dot_size), resources.getDimensionPixelSize(R.dimen.path_row_height))
|
||||
lineView.layoutParams = lineViewLayoutParams
|
||||
mainContainer.addView(lineView)
|
||||
val titleTextView = TextView(this)
|
||||
titleTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
|
||||
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.medium_font_size))
|
||||
titleTextView.text = title
|
||||
val titleContainer = LinearLayout(this)
|
||||
titleContainer.orientation = LinearLayout.VERTICAL
|
||||
titleContainer.addView(titleTextView)
|
||||
val titleContainerLayoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
|
||||
titleContainerLayoutParams.marginStart = resources.getDimensionPixelSize(R.dimen.large_spacing)
|
||||
titleContainer.layoutParams = titleContainerLayoutParams
|
||||
mainContainer.addView(titleContainer)
|
||||
if (subtitle != null) {
|
||||
val subtitleTextView = TextView(this)
|
||||
subtitleTextView.setTextColor(resources.getColorWithID(R.color.text, theme))
|
||||
subtitleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.small_font_size))
|
||||
subtitleTextView.text = subtitle
|
||||
titleContainer.addView(subtitleTextView)
|
||||
}
|
||||
return mainContainer
|
||||
}
|
||||
|
||||
private fun getPathRow(snode: Snode, location: LineView.Location, dotAnimationStartDelay: Long, dotAnimationRepeatInterval: Long, isGuardSnode: Boolean): LinearLayout {
|
||||
val title = if (isGuardSnode) resources.getString(R.string.activity_path_guard_node_row_title) else resources.getString(R.string.activity_path_service_node_row_title)
|
||||
val subtitle = snode.toString().removePrefix("https://").substringBefore(":")
|
||||
return getPathRow(title, subtitle, location, dotAnimationStartDelay, dotAnimationRepeatInterval)
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Interaction
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
val id = item.itemId
|
||||
when(id) {
|
||||
R.id.learnMoreButton -> learnMore()
|
||||
else -> { /* Do nothing */ }
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun learnMore() {
|
||||
try {
|
||||
val url = "https://getsession.org/faq/#onion-routing"
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(this, R.string.invalid_url, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun rebuildPath() {
|
||||
DatabaseFactory.getLokiAPIDatabase(this).clearPaths()
|
||||
OnionRequestAPI.guardSnodes = setOf()
|
||||
OnionRequestAPI.paths = listOf()
|
||||
OnionRequestAPI.buildPaths()
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Line View
|
||||
private class LineView : RelativeLayout {
|
||||
private lateinit var location: Location
|
||||
private var dotAnimationStartDelay: Long = 0
|
||||
private var dotAnimationRepeatInterval: Long = 0
|
||||
|
||||
private val dotView by lazy {
|
||||
val result = View(context)
|
||||
result.setBackgroundResource(R.drawable.accent_dot)
|
||||
result
|
||||
}
|
||||
|
||||
enum class Location {
|
||||
Top, Middle, Bottom
|
||||
}
|
||||
|
||||
constructor(context: Context, location: Location, dotAnimationStartDelay: Long, dotAnimationRepeatInterval: Long) : super(context) {
|
||||
this.location = location
|
||||
this.dotAnimationStartDelay = dotAnimationStartDelay
|
||||
this.dotAnimationRepeatInterval = dotAnimationRepeatInterval
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
throw Exception("Use LineView(context:location:dotAnimationStartDelay:dotAnimationRepeatInterval:) instead.")
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
throw Exception("Use LineView(context:location:dotAnimationStartDelay:dotAnimationRepeatInterval:) instead.")
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
throw Exception("Use LineView(context:location:dotAnimationStartDelay:dotAnimationRepeatInterval:) instead.")
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
|
||||
throw Exception("Use LineView(context:location:dotAnimationStartDelay:dotAnimationRepeatInterval:) instead.")
|
||||
}
|
||||
|
||||
private fun setUpViewHierarchy() {
|
||||
val lineView = View(context)
|
||||
lineView.setBackgroundColor(resources.getColorWithID(R.color.text, context.theme))
|
||||
val lineViewHeight = when (location) {
|
||||
Location.Top, Location.Bottom -> resources.getDimensionPixelSize(R.dimen.path_row_height) / 2
|
||||
Location.Middle -> resources.getDimensionPixelSize(R.dimen.path_row_height)
|
||||
}
|
||||
val lineViewLayoutParams = LayoutParams(1, lineViewHeight)
|
||||
when (location) {
|
||||
Location.Top -> lineViewLayoutParams.addRule(ALIGN_PARENT_BOTTOM)
|
||||
Location.Middle, Location.Bottom -> lineViewLayoutParams.addRule(ALIGN_PARENT_TOP)
|
||||
}
|
||||
lineViewLayoutParams.addRule(CENTER_HORIZONTAL)
|
||||
lineView.layoutParams = lineViewLayoutParams
|
||||
addView(lineView)
|
||||
val dotViewSize = resources.getDimensionPixelSize(R.dimen.path_row_dot_size)
|
||||
val dotViewLayoutParams = LayoutParams(dotViewSize, dotViewSize)
|
||||
dotViewLayoutParams.addRule(CENTER_IN_PARENT)
|
||||
dotView.layoutParams = dotViewLayoutParams
|
||||
addView(dotView)
|
||||
Handler().postDelayed({
|
||||
performAnimation()
|
||||
}, dotAnimationStartDelay)
|
||||
}
|
||||
|
||||
private fun performAnimation() {
|
||||
expand()
|
||||
Handler().postDelayed({
|
||||
collapse()
|
||||
Handler().postDelayed({
|
||||
performAnimation()
|
||||
}, dotAnimationRepeatInterval)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
private fun expand() {
|
||||
dotView.animateSizeChange(R.dimen.path_row_dot_size, R.dimen.path_row_expanded_dot_size)
|
||||
}
|
||||
|
||||
private fun collapse() {
|
||||
dotView.animateSizeChange(R.dimen.path_row_expanded_dot_size, R.dimen.path_row_dot_size)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.support.v4.content.LocalBroadcastManager
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import network.loki.messenger.R
|
||||
import org.whispersystems.signalservice.loki.api.onionrequests.OnionRequestAPI
|
||||
|
||||
class PathStatusView : View {
|
||||
private val broadcastReceivers = mutableListOf<BroadcastReceiver>()
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
initialize()
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
initialize()
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
initialize()
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
|
||||
initialize()
|
||||
}
|
||||
|
||||
private fun initialize() {
|
||||
update()
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
registerObservers()
|
||||
}
|
||||
|
||||
private fun registerObservers() {
|
||||
val buildingPathsReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
handleBuildingPathsEvent()
|
||||
}
|
||||
}
|
||||
broadcastReceivers.add(buildingPathsReceiver)
|
||||
LocalBroadcastManager.getInstance(context).registerReceiver(buildingPathsReceiver, IntentFilter("buildingPaths"))
|
||||
val pathsBuiltReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
handlePathsBuiltEvent()
|
||||
}
|
||||
}
|
||||
broadcastReceivers.add(pathsBuiltReceiver)
|
||||
LocalBroadcastManager.getInstance(context).registerReceiver(pathsBuiltReceiver, IntentFilter("pathsBuilt"))
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
for (receiver in broadcastReceivers) {
|
||||
LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver)
|
||||
}
|
||||
super.onDetachedFromWindow()
|
||||
}
|
||||
|
||||
private fun handleBuildingPathsEvent() { update() }
|
||||
private fun handlePathsBuiltEvent() { update() }
|
||||
|
||||
private fun update() {
|
||||
if (OnionRequestAPI.paths.count() >= OnionRequestAPI.pathCount) {
|
||||
setBackgroundResource(R.drawable.accent_dot)
|
||||
} else {
|
||||
setBackgroundResource(R.drawable.paths_building_dot)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue