session-android/app/src/main/java/org/thoughtcrime/securesms/home/PathActivity.kt

268 lines
13 KiB
Kotlin
Raw Normal View History

2021-07-09 03:14:21 +02:00
package org.thoughtcrime.securesms.home
2020-05-28 08:43:37 +02:00
2020-05-29 03:16:52 +02:00
import android.content.BroadcastReceiver
2020-05-28 08:43:37 +02:00
import android.content.Context
import android.content.Intent
2020-05-29 03:16:52 +02:00
import android.content.IntentFilter
import android.net.Uri
2020-05-28 08:43:37 +02:00
import android.os.Bundle
2020-05-28 10:16:53 +02:00
import android.os.Handler
2020-05-28 08:43:37 +02:00
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.Toast
2020-09-08 08:36:24 +02:00
import androidx.annotation.ColorRes
import androidx.localbroadcastmanager.content.LocalBroadcastManager
2020-05-28 08:43:37 +02:00
import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityPathBinding
2021-04-23 08:09:47 +02:00
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsignal.utilities.Snode
2020-05-28 08:43:37 +02:00
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
2021-07-09 03:14:21 +02:00
import org.thoughtcrime.securesms.util.GlowViewUtilities
2021-07-09 05:18:48 +02:00
import org.thoughtcrime.securesms.util.IP2Country
2021-07-09 03:14:21 +02:00
import org.thoughtcrime.securesms.util.PathDotView
import org.thoughtcrime.securesms.util.UiModeUtilities
import org.thoughtcrime.securesms.util.animateSizeChange
import org.thoughtcrime.securesms.util.disableClipping
import org.thoughtcrime.securesms.util.fadeIn
import org.thoughtcrime.securesms.util.fadeOut
import org.thoughtcrime.securesms.util.getColorWithID
2020-05-28 08:43:37 +02:00
class PathActivity : PassphraseRequiredActionBarActivity() {
private lateinit var binding: ActivityPathBinding
2020-05-29 03:16:52 +02:00
private val broadcastReceivers = mutableListOf<BroadcastReceiver>()
2020-05-28 08:43:37 +02:00
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
binding = ActivityPathBinding.inflate(layoutInflater)
setContentView(binding.root)
2020-05-28 08:43:37 +02:00
supportActionBar!!.title = resources.getString(R.string.activity_path_title)
binding.pathRowsContainer.disableClipping()
binding.learnMoreButton.setOnClickListener { learnMore() }
2020-05-29 03:16:52 +02:00
update(false)
registerObservers()
}
private fun registerObservers() {
val buildingPathsReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
handleBuildingPathsEvent()
}
}
2020-05-29 03:16:52 +02:00
broadcastReceivers.add(buildingPathsReceiver)
LocalBroadcastManager.getInstance(this).registerReceiver(buildingPathsReceiver, IntentFilter("buildingPaths"))
val pathsBuiltReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
handlePathsBuiltEvent()
}
}
2020-05-29 03:16:52 +02:00
broadcastReceivers.add(pathsBuiltReceiver)
LocalBroadcastManager.getInstance(this).registerReceiver(pathsBuiltReceiver, IntentFilter("pathsBuilt"))
val onionRequestPathCountriesLoadedReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
handleOnionRequestPathCountriesLoaded()
}
}
broadcastReceivers.add(onionRequestPathCountriesLoadedReceiver)
LocalBroadcastManager.getInstance(this).registerReceiver(onionRequestPathCountriesLoadedReceiver, IntentFilter("onionRequestPathCountriesLoaded"))
2020-05-28 08:43:37 +02:00
}
2020-05-29 03:16:52 +02:00
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 handleOnionRequestPathCountriesLoaded() { update(false) }
2020-05-29 03:16:52 +02:00
private fun update(isAnimated: Boolean) {
binding.pathRowsContainer.removeAllViews()
2020-10-09 04:57:52 +02:00
if (OnionRequestAPI.paths.isNotEmpty()) {
2020-05-29 03:16:52 +02:00
val path = OnionRequestAPI.paths.firstOrNull() ?: return finish()
2020-05-29 04:01:43 +02:00
val dotAnimationRepeatInterval = path.count().toLong() * 1000 + 1000
2020-05-29 03:16:52 +02:00
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(resources.getString(R.string.activity_path_device_row_title), null, LineView.Location.Top, 1000, dotAnimationRepeatInterval)
val destinationRow = getPathRow(resources.getString(R.string.activity_path_destination_row_title), null, LineView.Location.Bottom, path.count().toLong() * 1000 + 2000, dotAnimationRepeatInterval)
2020-05-29 03:16:52 +02:00
val rows = listOf( youRow ) + pathRows + listOf( destinationRow )
for (row in rows) {
binding.pathRowsContainer.addView(row)
2020-05-29 03:16:52 +02:00
}
if (isAnimated) {
binding.spinner.fadeOut()
2020-05-29 03:16:52 +02:00
} else {
binding.spinner.alpha = 0.0f
2020-05-29 03:16:52 +02:00
}
} else {
if (isAnimated) {
binding.spinner.fadeIn()
2020-05-29 03:16:52 +02:00
} else {
binding.spinner.alpha = 1.0f
2020-05-29 03:16:52 +02:00
}
}
}
2020-05-28 08:43:37 +02:00
// endregion
// region General
2020-05-28 10:16:53 +02:00
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
2020-09-08 08:36:24 +02:00
mainContainer.disableClipping()
val mainContainerLayoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
mainContainer.layoutParams = mainContainerLayoutParams
2020-05-28 08:43:37 +02:00
val lineView = LineView(this, location, dotAnimationStartDelay, dotAnimationRepeatInterval)
2020-05-28 10:16:53 +02:00
val lineViewLayoutParams = LinearLayout.LayoutParams(resources.getDimensionPixelSize(R.dimen.path_row_expanded_dot_size), resources.getDimensionPixelSize(R.dimen.path_row_height))
2020-05-28 08:43:37 +02:00
lineView.layoutParams = lineViewLayoutParams
mainContainer.addView(lineView)
2020-05-28 08:43:37 +02:00
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
2020-07-17 04:10:25 +02:00
titleTextView.textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
2020-05-28 08:43:37 +02:00
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)
2020-05-28 08:43:37 +02:00
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
2020-07-17 04:10:25 +02:00
subtitleTextView.textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
2020-05-28 08:43:37 +02:00
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)
2020-08-19 04:15:02 +02:00
val subtitle = if (IP2Country.isInitialized) {
IP2Country.shared.countryNamesCache[snode.ip] ?: resources.getString(R.string.activity_path_resolving_progress)
2020-08-19 04:15:02 +02:00
} else {
resources.getString(R.string.activity_path_resolving_progress)
2020-08-19 04:15:02 +02:00
}
return getPathRow(title, subtitle, location, dotAnimationStartDelay, dotAnimationRepeatInterval)
}
// endregion
// region Interaction
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()
}
}
2020-05-28 08:43:37 +02:00
// endregion
// region Line View
private class LineView : RelativeLayout {
private lateinit var location: Location
2020-05-28 10:16:53 +02:00
private var dotAnimationStartDelay: Long = 0
private var dotAnimationRepeatInterval: Long = 0
private val dotView by lazy {
2020-09-08 08:36:24 +02:00
val result = PathDotView(context)
2020-05-28 10:16:53 +02:00
result.setBackgroundResource(R.drawable.accent_dot)
2020-09-08 08:36:24 +02:00
result.mainColor = resources.getColorWithID(R.color.accent, context.theme)
2020-05-28 10:16:53 +02:00
result
}
2020-05-28 08:43:37 +02:00
enum class Location {
Top, Middle, Bottom
}
2020-05-28 10:16:53 +02:00
constructor(context: Context, location: Location, dotAnimationStartDelay: Long, dotAnimationRepeatInterval: Long) : super(context) {
2020-05-28 08:43:37 +02:00
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() {
2020-09-08 08:36:24 +02:00
disableClipping()
2020-05-28 08:43:37 +02:00
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)
2020-05-28 10:16:53 +02:00
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)
2020-09-08 08:36:24 +02:00
@ColorRes val startColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
GlowViewUtilities.animateShadowColorChange(context, dotView, startColorID, R.color.accent)
2020-05-28 10:16:53 +02:00
}
private fun collapse() {
dotView.animateSizeChange(R.dimen.path_row_expanded_dot_size, R.dimen.path_row_dot_size)
2020-09-08 08:36:24 +02:00
@ColorRes val endColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
GlowViewUtilities.animateShadowColorChange(context, dotView, R.color.accent, endColorID)
2020-05-28 08:43:37 +02:00
}
}
// endregion
}