mirror of
https://github.com/oxen-io/session-android.git
synced 2025-03-25 22:20:51 +00:00
Merge pull request #206 from loki-project/onion-requests
Onion Requests 2.0
This commit is contained in:
commit
f3ef282bfa
@ -117,6 +117,9 @@
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.SettingsActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Session.DarkTheme.NoActionBar" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.PathActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name="org.thoughtcrime.securesms.loki.activities.QRCodeActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
|
BIN
captures/network.loki.messenger_2020.05.28_16.39.li
Normal file
BIN
captures/network.loki.messenger_2020.05.28_16.39.li
Normal file
Binary file not shown.
8
res/drawable/accent_dot.xml
Normal file
8
res/drawable/accent_dot.xml
Normal file
@ -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>
|
12
res/drawable/ic_question_mark.xml
Normal file
12
res/drawable/ic_question_mark.xml
Normal file
@ -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>
|
8
res/drawable/paths_building_dot.xml
Normal file
8
res/drawable/paths_building_dot.xml
Normal file
@ -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>
|
@ -40,6 +40,22 @@
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="64dp" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/pathStatusViewContainer"
|
||||
android:layout_width="@dimen/small_profile_picture_size"
|
||||
android:layout_height="@dimen/small_profile_picture_size"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true" >
|
||||
|
||||
<org.thoughtcrime.securesms.loki.views.PathStatusView
|
||||
android:layout_width="@dimen/path_status_view_size"
|
||||
android:layout_height="@dimen/path_status_view_size"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginRight="8dp" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</android.support.v7.widget.Toolbar>
|
||||
|
54
res/layout/activity_path.xml
Normal file
54
res/layout/activity_path.xml
Normal file
@ -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>
|
10
res/menu/menu_path.xml
Normal file
10
res/menu/menu_path.xml
Normal file
@ -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>
|
@ -5,6 +5,7 @@
|
||||
<dimen name="medium_button_height">44dp</dimen>
|
||||
<dimen name="tab_bar_height">48dp</dimen>
|
||||
<dimen name="setting_button_height">72dp</dimen>
|
||||
<dimen name="path_row_height">72dp</dimen>
|
||||
<!-- Session -->
|
||||
|
||||
<dimen name="album_total_width">300dp</dimen>
|
||||
|
@ -28,6 +28,7 @@
|
||||
<color name="new_conversation_button_collapsed_background">#1F1F1F</color>
|
||||
<color name="pn_option_background">#1B1B1B</color>
|
||||
<color name="pn_option_border">#212121</color>
|
||||
<color name="paths_building">#FFCE3A</color>
|
||||
<!-- Session -->
|
||||
|
||||
<!-- Loki -->
|
||||
|
@ -33,6 +33,10 @@
|
||||
<dimen name="dialog_corner_radius">8dp</dimen>
|
||||
<dimen name="dialog_button_corner_radius">4dp</dimen>
|
||||
<dimen name="pn_option_corner_radius">8dp</dimen>
|
||||
<dimen name="path_status_view_size">8dp</dimen>
|
||||
<dimen name="path_row_height">56dp</dimen>
|
||||
<dimen name="path_row_dot_size">8dp</dimen>
|
||||
<dimen name="path_row_expanded_dot_size">16dp</dimen>
|
||||
|
||||
<!-- Distances -->
|
||||
<dimen name="small_spacing">8dp</dimen>
|
||||
|
@ -1699,7 +1699,7 @@
|
||||
<string name="fragment_enter_session_id_edit_text_hint">Enter your Session ID</string>
|
||||
|
||||
<string name="activity_display_name_title_2">Pick your display name</string>
|
||||
<string name="activity_display_name_explanation">This will be your name when you use Session.</string>
|
||||
<string name="activity_display_name_explanation">This will be your name when you use Session. It can be your real name, an alias, or anything else you like.</string>
|
||||
<string name="activity_display_name_edit_text_hint">Enter a display name</string>
|
||||
<string name="activity_display_name_display_name_missing_error">Please pick a display name</string>
|
||||
<string name="activity_display_name_display_name_invalid_error">Please pick a display name that consists of only a-z, A-Z, 0-9 and _ characters</string>
|
||||
@ -1708,9 +1708,9 @@
|
||||
<string name="activity_pn_mode_title">Push Notifications</string>
|
||||
<string name="activity_pn_mode_explanation">There are two ways Session can handle push notifications. Make sure to read the descriptions carefully before you choose.</string>
|
||||
<string name="activity_pn_mode_fcm_option_title">Firebase Cloud Messaging</string>
|
||||
<string name="activity_pn_mode_fcm_option_explanation">Session will use the Firebase Cloud Messaging service to receive push notifications. You\’ll be notified of new messages reliably and immediately. Using FCM means that this device will communicate directly with Google\’s servers to retrieve push notifications, which will expose your IP address to Google. Your messages will still be onion-routed and end-to-end encrypted, so the contents of your messages will remain completely private.</string>
|
||||
<string name="activity_pn_mode_fcm_option_explanation">Session will use the Firebase Cloud Messaging service to receive push notifications. You\'ll be notified of new messages reliably and immediately. Using FCM means that your IP address and device token will be exposed to Google. If you use push notifications for other apps, this will already be the case. Your IP address and device token will also be exposed to Loki, but your messages will still be onion-routed and end-to-end encrypted, so the contents of your messages will remain completely private.</string>
|
||||
<string name="activity_pn_mode_background_polling_option_title">Background Polling</string>
|
||||
<string name="activity_pn_mode_background_polling_option_explanation">Session will occasionally check for new messages in the background. This guarantees full privacy protection, but message notifications may be significantly delayed.</string>
|
||||
<string name="activity_pn_mode_background_polling_option_explanation">Session will occasionally check for new messages in the background. This guarantees full metadata protection, but message notifications may be significantly delayed.</string>
|
||||
<string name="activity_pn_mode_recommended_option_tag">Recommended</string>
|
||||
<string name="activity_pn_mode_no_option_picked_dialog_title">Please Pick an Option</string>
|
||||
|
||||
@ -1724,9 +1724,9 @@
|
||||
<string name="sheet_pn_mode_title">Push Notifications</string>
|
||||
<string name="sheet_pn_mode_explanation">Session now features two ways to handle push notifications. Make sure to read the descriptions carefully before you choose.</string>
|
||||
<string name="sheet_pn_mode_fcm_option_title">Firebase Cloud Messaging</string>
|
||||
<string name="sheet_pn_mode_fcm_option_explanation">Session will use the Firebase Cloud Messaging service to receive push notifications. You\’ll be notified of new messages reliably and immediately. Using FCM means that this device will communicate directly with Google\’s servers to retrieve push notifications, which will expose your IP address to Google. Your messages will still be onion-routed and end-to-end encrypted, so the contents of your messages will remain completely private.</string>
|
||||
<string name="sheet_pn_mode_fcm_option_explanation">Session will use the Firebase Cloud Messaging service to receive push notifications. You\'ll be notified of new messages reliably and immediately. Using FCM means that your IP address and device token will be exposed to Google. If you use push notifications for other apps, this will already be the case. Your IP address and device token will also be exposed to Loki, but your messages will still be onion-routed and end-to-end encrypted, so the contents of your messages will remain completely private.</string>
|
||||
<string name="sheet_pn_mode_background_polling_option_title">Background Polling</string>
|
||||
<string name="sheet_pn_mode_background_polling_option_explanation">Session will occasionally check for new messages in the background. This guarantees full privacy protection, but message notifications may be significantly delayed.</string>
|
||||
<string name="sheet_pn_mode_background_polling_option_explanation">Session will occasionally check for new messages in the background. This guarantees full metadata protection, but message notifications may be significantly delayed.</string>
|
||||
<string name="sheet_pn_mode_recommended_option_tag">Recommended</string>
|
||||
<string name="sheet_pn_mode_no_option_picked_dialog_title">Please Pick an Option</string>
|
||||
<string name="sheet_pn_mode_confirm_button_title">Confirm</string>
|
||||
@ -1734,13 +1734,21 @@
|
||||
|
||||
<string name="activity_seed_title">Your Recovery Phrase</string>
|
||||
<string name="activity_seed_title_2">Meet your recovery phrase</string>
|
||||
<string name="activity_seed_explanation">Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don\’t give it to anyone. To restore your Session ID, launch Session and tap Continue your Session.</string>
|
||||
<string name="activity_seed_explanation">Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don\’t give it to anyone.</string>
|
||||
<string name="activity_seed_reveal_button_title">Hold to reveal</string>
|
||||
|
||||
<string name="view_seed_reminder_subtitle_1">Secure your account by saving your recovery phrase</string>
|
||||
<string name="view_seed_reminder_subtitle_2">Tap and hold the redacted words to reveal your recovery phrase, then store it safely to secure your Session ID.</string>
|
||||
<string name="view_seed_reminder_subtitle_3">Make sure to store your recovery phrase in a safe place</string>
|
||||
|
||||
<string name="activity_path_title">Path</string>
|
||||
<string name="activity_path_explanation">Session hides your IP by bouncing your messages through several Service Nodes in Session\'s decentralized network. These are the Service Nodes currently being used by your device:</string>
|
||||
<string name="activity_path_device_row_title">You</string>
|
||||
<string name="activity_path_guard_node_row_title">Guard Node</string>
|
||||
<string name="activity_path_service_node_row_title">Service Node</string>
|
||||
<string name="activity_path_destination_row_title">Destination</string>
|
||||
<string name="activity_path_rebuild_path_button_title">Rebuild Path</string>
|
||||
|
||||
<string name="activity_create_private_chat_title">New Session</string>
|
||||
<string name="activity_create_private_chat_enter_session_id_tab_title">Enter Session ID</string>
|
||||
<string name="activity_create_private_chat_scan_qr_code_tab_title">Scan QR Code</string>
|
||||
@ -1796,7 +1804,7 @@
|
||||
|
||||
<string name="preferences_notifications_strategy_category_title">Notification Strategy</string>
|
||||
<string name="preferences_notifications_use_fcm_option_title">Use FCM</string>
|
||||
<string name="preferences_notifications_use_fcm_option_explanation">Using Firebase Cloud Messaging allows for more reliable push notifications, but exposes your IP to Google.</string>
|
||||
<string name="preferences_notifications_use_fcm_option_explanation">Using Firebase Cloud Messaging allows for more reliable push notifications, but exposes your IP and device token to Google and Loki.</string>
|
||||
|
||||
<string name="dialog_link_device_slave_mode_title_1">Waiting for Authorization</string>
|
||||
<string name="dialog_link_device_slave_mode_title_2">Device Link Authorized</string>
|
||||
@ -1820,7 +1828,7 @@
|
||||
<string name="dialog_seed_explanation">This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device.</string>
|
||||
|
||||
<string name="dialog_clear_all_data_title">Clear All Data</string>
|
||||
<string name="dialog_clear_all_data_explanation">This will permanently delete your Session ID, including all messages, sessions, and contacts.</string>
|
||||
<string name="dialog_clear_all_data_explanation">This will permanently delete your messages, sessions, and contacts.</string>
|
||||
|
||||
<string name="activity_qr_code_title">QR Code</string>
|
||||
<string name="activity_qr_code_view_my_qr_code_tab_title">View My QR Code</string>
|
||||
|
@ -181,6 +181,8 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||
LokiSessionResetImplementation sessionResetImpl = new LokiSessionResetImplementation(this);
|
||||
if (userPublicKey != null) {
|
||||
LokiSwarmAPI.Companion.configureIfNeeded(apiDB);
|
||||
LokiAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
||||
FriendRequestProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
|
||||
MentionsManager.Companion.configureIfNeeded(userPublicKey, threadDB, userDB);
|
||||
SessionMetaProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
|
||||
|
@ -82,8 +82,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
private static final int lokiV6 = 27;
|
||||
private static final int lokiV7 = 28;
|
||||
private static final int lokiV8 = 29;
|
||||
private static final int lokiV9 = 30;
|
||||
|
||||
private static final int DATABASE_VERSION = lokiV8; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
||||
private static final int DATABASE_VERSION = lokiV9; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
||||
private static final String DATABASE_NAME = "signal.db";
|
||||
|
||||
private final Context context;
|
||||
@ -131,7 +132,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
db.execSQL(StickerDatabase.CREATE_TABLE);
|
||||
|
||||
db.execSQL(LokiAPIDatabase.getCreateSwarmCacheTableCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateSnodePoolCacheCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreatePathCacheCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateSwarmCacheCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateLastMessageHashValueTableCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateReceivedMessageHashValuesTableCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateGroupChatAuthTokenTableCommand());
|
||||
@ -582,6 +585,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(LokiAPIDatabase.getCreateSessionRequestTimestampTableCommand());
|
||||
}
|
||||
|
||||
if (oldVersion < lokiV9) {
|
||||
db.execSQL(LokiAPIDatabase.getCreateSnodePoolCacheCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreatePathCacheCommand());
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
@ -101,6 +101,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
||||
profileButton.hexEncodedPublicKey = hexEncodedPublicKey
|
||||
profileButton.update()
|
||||
profileButton.setOnClickListener { openSettings() }
|
||||
pathStatusViewContainer.setOnClickListener { showPath() }
|
||||
// Set up seed reminder view
|
||||
val isMasterDevice = (TextSecurePreferences.getMasterHexEncodedPublicKey(this) == null)
|
||||
val hasViewedSeed = TextSecurePreferences.getHasViewedSeed(this)
|
||||
@ -262,6 +263,11 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
||||
show(intent)
|
||||
}
|
||||
|
||||
private fun showPath() {
|
||||
val intent = Intent(this, PathActivity::class.java)
|
||||
show(intent)
|
||||
}
|
||||
|
||||
override fun createNewPrivateChat() {
|
||||
val intent = Intent(this, CreatePrivateChatActivity::class.java)
|
||||
show(intent)
|
||||
|
261
src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt
Normal file
261
src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt
Normal file
@ -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
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
@ -31,6 +29,8 @@ import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.dialogs.ClearAllDataDialog
|
||||
import org.thoughtcrime.securesms.loki.dialogs.SeedDialog
|
||||
import org.thoughtcrime.securesms.loki.utilities.fadeIn
|
||||
import org.thoughtcrime.securesms.loki.utilities.fadeOut
|
||||
import org.thoughtcrime.securesms.loki.utilities.push
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
@ -147,7 +147,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
}
|
||||
|
||||
private fun updateProfile(isUpdatingProfilePicture: Boolean) {
|
||||
showLoader()
|
||||
loader.fadeIn()
|
||||
val promises = mutableListOf<Promise<*, Exception>>()
|
||||
val displayName = displayNameToBeUploaded
|
||||
if (displayName != null) {
|
||||
@ -187,24 +187,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
profilePictureView.update()
|
||||
}
|
||||
profilePictureToBeUploaded = null
|
||||
hideLoader()
|
||||
loader.fadeOut()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLoader() {
|
||||
loader.visibility = View.VISIBLE
|
||||
loader.animate().setDuration(150).alpha(1.0f).start()
|
||||
}
|
||||
|
||||
private fun hideLoader() {
|
||||
loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() {
|
||||
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
super.onAnimationEnd(animation)
|
||||
loader.visibility = View.GONE
|
||||
}
|
||||
})
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Interaction
|
||||
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import org.thoughtcrime.securesms.database.Database
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
@ -16,11 +17,21 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
private val userPublicKey get() = TextSecurePreferences.getLocalNumber(context)
|
||||
|
||||
companion object {
|
||||
// Snode pool cache
|
||||
private val snodePoolCache = "loki_snode_pool_cache"
|
||||
private val dummyKey = "dummy_key"
|
||||
private val snodePoolKey = "snode_pool_key"
|
||||
@JvmStatic val createSnodePoolCacheCommand = "CREATE TABLE $snodePoolCache ($dummyKey TEXT PRIMARY KEY, $snodePoolKey TEXT);"
|
||||
// Path cache
|
||||
private val pathCache = "loki_path_cache"
|
||||
private val indexPath = "index_path"
|
||||
private val snode = "snode"
|
||||
@JvmStatic val createPathCacheCommand = "CREATE TABLE $pathCache ($indexPath TEXT PRIMARY KEY, $snode TEXT);"
|
||||
// Swarm cache
|
||||
private val swarmCache = "loki_api_swarm_cache"
|
||||
private val hexEncodedPublicKey = "hex_encoded_public_key"
|
||||
private val swarm = "swarm"
|
||||
@JvmStatic val createSwarmCacheTableCommand = "CREATE TABLE $swarmCache ($hexEncodedPublicKey TEXT PRIMARY KEY, $swarm TEXT);"
|
||||
@JvmStatic val createSwarmCacheCommand = "CREATE TABLE $swarmCache ($hexEncodedPublicKey TEXT PRIMARY KEY, $swarm TEXT);"
|
||||
// Last message hash value cache
|
||||
private val lastMessageHashValueCache = "loki_api_last_message_hash_value_cache"
|
||||
private val target = "target"
|
||||
@ -66,6 +77,90 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
@JvmStatic val createSessionRequestTimestampTableCommand = "CREATE TABLE $sessionRequestTimestampCache ($publicKey STRING PRIMARY KEY, $timestamp INTEGER DEFAULT 0);"
|
||||
}
|
||||
|
||||
override fun getSnodePool(): Set<LokiAPITarget> {
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.get(snodePoolCache, "${Companion.dummyKey} = ?", wrap("dummy_key")) { cursor ->
|
||||
val snodePoolAsString = cursor.getString(cursor.getColumnIndexOrThrow(snodePoolKey))
|
||||
snodePoolAsString.split(", ").mapNotNull { snodeAsString ->
|
||||
val components = snodeAsString.split("-")
|
||||
val address = components[0]
|
||||
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
|
||||
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
|
||||
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
|
||||
LokiAPITarget(address, port, LokiAPITarget.KeySet(ed25519Key, x25519Key))
|
||||
}
|
||||
}?.toSet() ?: setOf()
|
||||
}
|
||||
|
||||
override fun setSnodePool(newValue: Set<LokiAPITarget>) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val snodePoolAsString = newValue.joinToString(", ") { snode ->
|
||||
var string = "${snode.address}-${snode.port}"
|
||||
val keySet = snode.publicKeySet
|
||||
if (keySet != null) {
|
||||
string += "-${keySet.ed25519Key}-${keySet.x25519Key}"
|
||||
}
|
||||
string
|
||||
}
|
||||
val row = wrap(mapOf(Companion.dummyKey to "dummy_key", snodePoolKey to snodePoolAsString))
|
||||
database.insertOrUpdate(snodePoolCache, row, "${Companion.dummyKey} = ?", wrap("dummy_key"))
|
||||
}
|
||||
|
||||
override fun getPaths(): List<List<LokiAPITarget>> {
|
||||
val database = databaseHelper.readableDatabase
|
||||
fun get(indexPath: String): LokiAPITarget? {
|
||||
return database.get(pathCache, "${Companion.indexPath} = ?", wrap(indexPath)) { cursor ->
|
||||
val snodeAsString = cursor.getString(cursor.getColumnIndexOrThrow(snode))
|
||||
val components = snodeAsString.split("-")
|
||||
val address = components[0]
|
||||
val port = components.getOrNull(1)?.toIntOrNull()
|
||||
val ed25519Key = components.getOrNull(2)
|
||||
val x25519Key = components.getOrNull(3)
|
||||
if (port != null && ed25519Key != null && x25519Key != null) {
|
||||
LokiAPITarget(address, port, LokiAPITarget.KeySet(ed25519Key, x25519Key))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
val path0Snode0 = get("0-0") ?: return listOf(); val path0Snode1 = get("0-1") ?: return listOf()
|
||||
val path0Snode2 = get("0-2") ?: return listOf(); val path1Snode0 = get("1-0") ?: return listOf()
|
||||
val path1Snode1 = get("1-1") ?: return listOf(); val path1Snode2 = get("1-2") ?: return listOf()
|
||||
return listOf( listOf( path0Snode0, path0Snode1, path0Snode2 ), listOf( path1Snode0, path1Snode1, path1Snode2 ) )
|
||||
}
|
||||
|
||||
fun clearPaths() {
|
||||
val database = databaseHelper.writableDatabase
|
||||
fun delete(indexPath: String) {
|
||||
database.delete(pathCache, "${Companion.indexPath} = ?", wrap(indexPath))
|
||||
}
|
||||
delete("0-0"); delete("0-1")
|
||||
delete("0-2"); delete("1-0")
|
||||
delete("1-1"); delete("1-2")
|
||||
}
|
||||
|
||||
override fun setPaths(newValue: List<List<LokiAPITarget>>) {
|
||||
// FIXME: This is a bit of a dirty approach that assumes 2 paths of length 3 each. We should do better than this.
|
||||
if (newValue.count() != 2) { return }
|
||||
val path0 = newValue[0]
|
||||
val path1 = newValue[1]
|
||||
if (path0.count() != 3 || path1.count() != 3) { return }
|
||||
Log.d("Loki", "Persisting onion request paths to database.")
|
||||
val database = databaseHelper.writableDatabase
|
||||
fun set(indexPath: String ,snode: LokiAPITarget) {
|
||||
var snodeAsString = "${snode.address}-${snode.port}"
|
||||
val keySet = snode.publicKeySet
|
||||
if (keySet != null) {
|
||||
snodeAsString += "-${keySet.ed25519Key}-${keySet.x25519Key}"
|
||||
}
|
||||
val row = wrap(mapOf(Companion.indexPath to indexPath, Companion.snode to snodeAsString))
|
||||
database.insertOrUpdate(pathCache, row, "${Companion.indexPath} = ?", wrap(indexPath))
|
||||
}
|
||||
set("0-0", path0[0]); set("0-1", path0[1])
|
||||
set("0-2", path0[2]); set("1-0", path1[0])
|
||||
set("1-1", path1[1]); set("1-2", path1[2])
|
||||
}
|
||||
|
||||
override fun getSwarmCache(hexEncodedPublicKey: String): Set<LokiAPITarget>? {
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.get(swarmCache, "${Companion.hexEncodedPublicKey} = ?", wrap(hexEncodedPublicKey)) { cursor ->
|
||||
@ -75,7 +170,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
val address = components[0]
|
||||
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
|
||||
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
|
||||
val x25519Key = components.getOrNull(3)?: return@mapNotNull null
|
||||
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
|
||||
LokiAPITarget(address, port, LokiAPITarget.KeySet(ed25519Key, x25519Key))
|
||||
}
|
||||
}?.toSet()
|
||||
|
@ -1,7 +1,12 @@
|
||||
package org.thoughtcrime.securesms.loki.utilities
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.FloatEvaluator
|
||||
import android.animation.ValueAnimator
|
||||
import android.graphics.PointF
|
||||
import android.graphics.Rect
|
||||
import android.support.annotation.DimenRes
|
||||
import android.view.View
|
||||
|
||||
fun View.contains(point: PointF): Boolean {
|
||||
@ -13,4 +18,34 @@ val View.hitRect: Rect
|
||||
val rect = Rect()
|
||||
getHitRect(rect)
|
||||
return rect
|
||||
}
|
||||
}
|
||||
|
||||
fun View.animateSizeChange(@DimenRes startSizeID: Int, @DimenRes endSizeID: Int, animationDuration: Long = 250) {
|
||||
val layoutParams = this.layoutParams
|
||||
val startSize = resources.getDimension(startSizeID)
|
||||
val endSize = resources.getDimension(endSizeID)
|
||||
val animation = ValueAnimator.ofObject(FloatEvaluator(), startSize, endSize)
|
||||
animation.duration = animationDuration
|
||||
animation.addUpdateListener { animator ->
|
||||
val size = animator.animatedValue as Float
|
||||
layoutParams.width = size.toInt()
|
||||
layoutParams.height = size.toInt()
|
||||
this.layoutParams = layoutParams
|
||||
}
|
||||
animation.start()
|
||||
}
|
||||
|
||||
fun View.fadeIn(duration: Long = 150) {
|
||||
visibility = View.VISIBLE
|
||||
animate().setDuration(duration).alpha(1.0f).start()
|
||||
}
|
||||
|
||||
fun View.fadeOut(duration: Long = 150) {
|
||||
animate().setDuration(duration).alpha(0.0f).setListener(object : AnimatorListenerAdapter() {
|
||||
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
super.onAnimationEnd(animation)
|
||||
visibility = View.GONE
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -96,13 +96,13 @@ class NewConversationButtonSetView : RelativeLayout {
|
||||
|
||||
fun expand() {
|
||||
animateImageViewColorChange(R.color.new_conversation_button_collapsed_background, R.color.accent)
|
||||
animateImageViewSizeChange(R.dimen.new_conversation_button_collapsed_size, R.dimen.new_conversation_button_expanded_size)
|
||||
imageView.animateSizeChange(R.dimen.new_conversation_button_collapsed_size, R.dimen.new_conversation_button_expanded_size, animationDuration)
|
||||
animateImageViewPositionChange(collapsedImageViewPosition, expandedImageViewPosition)
|
||||
}
|
||||
|
||||
fun collapse() {
|
||||
animateImageViewColorChange(R.color.accent, R.color.new_conversation_button_collapsed_background)
|
||||
animateImageViewSizeChange(R.dimen.new_conversation_button_expanded_size, R.dimen.new_conversation_button_collapsed_size)
|
||||
imageView.animateSizeChange(R.dimen.new_conversation_button_expanded_size, R.dimen.new_conversation_button_collapsed_size, animationDuration)
|
||||
animateImageViewPositionChange(expandedImageViewPosition, collapsedImageViewPosition)
|
||||
}
|
||||
|
||||
@ -119,21 +119,6 @@ class NewConversationButtonSetView : RelativeLayout {
|
||||
animation.start()
|
||||
}
|
||||
|
||||
private fun animateImageViewSizeChange(@DimenRes startSizeID: Int, @DimenRes endSizeID: Int) {
|
||||
val layoutParams = imageView.layoutParams
|
||||
val startSize = resources.getDimension(startSizeID)
|
||||
val endSize = resources.getDimension(endSizeID)
|
||||
val animation = ValueAnimator.ofObject(FloatEvaluator(), startSize, endSize)
|
||||
animation.duration = animationDuration
|
||||
animation.addUpdateListener { animator ->
|
||||
val size = animator.animatedValue as Float
|
||||
layoutParams.width = size.toInt()
|
||||
layoutParams.height = size.toInt()
|
||||
imageView.layoutParams = layoutParams
|
||||
}
|
||||
animation.start()
|
||||
}
|
||||
|
||||
private fun animateImageViewPositionChange(startPosition: PointF, endPosition: PointF) {
|
||||
val animation = ValueAnimator.ofObject(PointFEvaluator(), startPosition, endPosition)
|
||||
animation.duration = animationDuration
|
||||
|
77
src/org/thoughtcrime/securesms/loki/views/PathStatusView.kt
Normal file
77
src/org/thoughtcrime/securesms/loki/views/PathStatusView.kt
Normal file
@ -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…
x
Reference in New Issue
Block a user