mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-03 03:42:24 +00:00
Merge branch 'dev' into profile-pictures
This commit is contained in:
@@ -16,18 +16,17 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.Application;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
import androidx.multidex.MultiDexApplication;
|
||||
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
|
||||
@@ -116,10 +115,10 @@ import org.whispersystems.signalservice.loki.protocol.closedgroups.SharedSenderK
|
||||
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
|
||||
import org.whispersystems.signalservice.loki.protocol.meta.TTLUtilities;
|
||||
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol;
|
||||
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocolDelegate;
|
||||
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink;
|
||||
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
|
||||
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol;
|
||||
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocolDelegate;
|
||||
import org.whispersystems.signalservice.loki.protocol.shelved.syncmessages.SyncMessagesProtocol;
|
||||
|
||||
import java.io.File;
|
||||
@@ -145,10 +144,11 @@ import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant;
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class ApplicationContext extends Application implements DependencyInjector, DefaultLifecycleObserver, LokiP2PAPIDelegate,
|
||||
public class ApplicationContext extends MultiDexApplication implements DependencyInjector, DefaultLifecycleObserver, LokiP2PAPIDelegate,
|
||||
SessionManagementProtocolDelegate, SharedSenderKeysImplementationDelegate {
|
||||
|
||||
private static final String TAG = ApplicationContext.class.getSimpleName();
|
||||
private final static int OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||
|
||||
private ExpiringMessageManager expiringMessageManager;
|
||||
private TypingStatusRepository typingStatusRepository;
|
||||
|
||||
@@ -23,9 +23,6 @@ import android.content.Intent;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import androidx.core.os.CancellationSignal;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.SpannableString;
|
||||
@@ -49,6 +46,9 @@ import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import androidx.core.os.CancellationSignal;
|
||||
|
||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
import org.thoughtcrime.securesms.components.AnimatingToggle;
|
||||
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
|
||||
@@ -91,8 +91,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Log.i(TAG, "onCreate()");
|
||||
dynamicLanguage.onCreate(this);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.prompt_passphrase_activity);
|
||||
@@ -204,7 +202,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
private void initializeResources() {
|
||||
|
||||
ImageButton okButton = findViewById(R.id.ok_button);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
|
||||
showButton = findViewById(R.id.passphrase_visibility);
|
||||
hideButton = findViewById(R.id.passphrase_visibility_off);
|
||||
@@ -217,9 +214,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
fingerprintCancellationSignal = new CancellationSignal();
|
||||
fingerprintListener = new FingerprintListener();
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setTitle("");
|
||||
|
||||
SpannableString hint = new SpannableString(" " + getString(R.string.PassphrasePromptActivity_enter_passphrase));
|
||||
hint.setSpan(new RelativeSizeSpan(0.9f), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
hint.setSpan(new TypefaceSpan("sans-serif"), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
@@ -229,8 +223,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
showButton.setOnClickListener(new ShowButtonOnClickListener());
|
||||
hideButton.setOnClickListener(new HideButtonOnClickListener());
|
||||
passphraseText.setOnEditorActionListener(new PassphraseActionListener());
|
||||
passphraseText.setImeActionLabel(getString(R.string.prompt_passphrase_activity__unlock),
|
||||
EditorInfo.IME_ACTION_DONE);
|
||||
passphraseText.setImeActionLabel(getString(R.string.prompt_passphrase_activity__unlock), EditorInfo.IME_ACTION_DONE);
|
||||
|
||||
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
|
||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
|
||||
|
||||
@@ -6,9 +6,6 @@ import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
@@ -17,11 +14,16 @@ import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
@@ -209,7 +211,8 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener
|
||||
authorView.setText(isOwnNumber ? getContext().getString(R.string.QuoteView_you) : quoteeDisplayName);
|
||||
|
||||
// We use the raw color resource because Android 4.x was struggling with tints here
|
||||
quoteBarView.setImageResource(R.color.accent);
|
||||
int colorID = UiModeUtilities.isDayUiMode(getContext()) ? R.color.black : R.color.accent;
|
||||
quoteBarView.setImageResource(colorID);
|
||||
mainView.setBackgroundColor(ThemeUtil.getThemedColor(getContext(),
|
||||
outgoing ? R.attr.message_received_background_color : R.attr.message_sent_background_color));
|
||||
}
|
||||
|
||||
@@ -160,6 +160,7 @@ import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
|
||||
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
|
||||
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
|
||||
import org.thoughtcrime.securesms.loki.views.MentionCandidateSelectionView;
|
||||
import org.thoughtcrime.securesms.loki.views.ProfilePictureView;
|
||||
import org.thoughtcrime.securesms.loki.views.SessionRestoreBannerView;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
|
||||
@@ -234,6 +235,7 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -300,6 +302,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private AnimatingToggle buttonToggle;
|
||||
private SendButton sendButton;
|
||||
private ImageButton attachButton;
|
||||
private ProfilePictureView profilePictureView;
|
||||
private TextView titleTextView;
|
||||
private TextView charactersLeft;
|
||||
private ConversationFragment fragment;
|
||||
@@ -527,6 +530,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
composeText.setTransport(sendButton.getSelectedTransport());
|
||||
|
||||
updateTitleTextView(recipient);
|
||||
updateProfilePicture();
|
||||
updateSubtitleTextView();
|
||||
setActionBarColor(recipient.getColor());
|
||||
updateInputUI(recipient, isSecureText, isDefaultSms);
|
||||
@@ -621,6 +625,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
recipient = Recipient.from(this, data.getParcelableExtra(GroupCreateActivity.GROUP_ADDRESS_EXTRA), true);
|
||||
recipient.addListener(this);
|
||||
updateTitleTextView(recipient);
|
||||
updateProfilePicture();
|
||||
updateSubtitleTextView();
|
||||
NotificationChannels.updateContactChannelName(this, recipient);
|
||||
updateInputUI(recipient, isSecureText, isDefaultSms);
|
||||
@@ -1654,6 +1659,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void initializeViews() {
|
||||
profilePictureView = findViewById(R.id.profilePictureView);
|
||||
titleTextView = findViewById(R.id.titleTextView);
|
||||
buttonToggle = ViewUtil.findById(this, R.id.button_toggle);
|
||||
sendButton = ViewUtil.findById(this, R.id.send_button);
|
||||
@@ -1872,6 +1878,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
Util.runOnMain(() -> {
|
||||
Log.i(TAG, "onModifiedRun(): " + recipient.getRegistered());
|
||||
updateTitleTextView(recipient);
|
||||
updateProfilePicture();
|
||||
updateSubtitleTextView();
|
||||
// titleView.setVerified(identityRecords.isVerified());
|
||||
updateInputUI(recipient, isSecureText, isDefaultSms);
|
||||
@@ -3100,18 +3107,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void updateProfilePicture() {
|
||||
profilePictureView.glide = GlideApp.with(this);
|
||||
profilePictureView.update(recipient, threadId);
|
||||
}
|
||||
|
||||
private void updateSubtitleTextView() {
|
||||
muteIndicatorImageView.setVisibility(View.GONE);
|
||||
subtitleTextView.setVisibility(View.VISIBLE);
|
||||
if (messageStatus != null) {
|
||||
switch (messageStatus) {
|
||||
case "calculatingPoW": subtitleTextView.setText("Encrypting message"); break;
|
||||
case "contactingNetwork": subtitleTextView.setText("Tracing a path"); break;
|
||||
case "sendingMessage": subtitleTextView.setText("Sending message"); break;
|
||||
case "messageSent": subtitleTextView.setText("Message sent securely"); break;
|
||||
case "messageFailed": subtitleTextView.setText("Message failed to send"); break;
|
||||
}
|
||||
} else if (recipient.isMuted()) {
|
||||
if (recipient.isMuted()) {
|
||||
muteIndicatorImageView.setVisibility(View.VISIBLE);
|
||||
subtitleTextView.setText("Muted until " + DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault()));
|
||||
} else if (recipient.isGroupRecipient() && recipient.getName() != null && !recipient.getName().equals("Session Updates") && !recipient.getName().equals("Loki News")) {
|
||||
@@ -3125,10 +3129,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
} else {
|
||||
subtitleTextView.setVisibility(View.GONE);
|
||||
}
|
||||
} else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) {
|
||||
String ourMasterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this);
|
||||
String hexEncodedPublicKey = (recipient.isLocalNumber() && ourMasterHexEncodedPublicKey != null) ? ourMasterHexEncodedPublicKey : recipient.getAddress().toPhoneString();
|
||||
subtitleTextView.setText(hexEncodedPublicKey);
|
||||
} else {
|
||||
subtitleTextView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@ public class ThreadDatabase extends Database {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
||||
} else {
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
|
||||
return createThreadForRecipient(recipient.getAddress(), recipient.isGroupRecipient(), distributionType);
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob
|
||||
import org.thoughtcrime.securesms.loki.dialogs.ConversationOptionsBottomSheet
|
||||
import org.thoughtcrime.securesms.loki.dialogs.LightThemeFeatureIntroBottomSheet
|
||||
import org.thoughtcrime.securesms.loki.dialogs.MultiDeviceRemovalBottomSheet
|
||||
import org.thoughtcrime.securesms.loki.dialogs.UserDetailsBottomSheet
|
||||
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol
|
||||
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
@@ -100,6 +101,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
||||
profileButton.displayName = TextSecurePreferences.getProfileName(this)
|
||||
profileButton.update()
|
||||
profileButton.setOnClickListener { openSettings() }
|
||||
pathStatusViewContainer.disableClipping()
|
||||
pathStatusViewContainer.setOnClickListener { showPath() }
|
||||
// Set up seed reminder view
|
||||
val isMasterDevice = (TextSecurePreferences.getMasterHexEncodedPublicKey(this) == null)
|
||||
@@ -202,7 +204,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
||||
seedReminderView.visibility = View.GONE
|
||||
}
|
||||
|
||||
// Multiple device removal notification
|
||||
// Multi device removal sheet
|
||||
if (!TextSecurePreferences.getHasSeenMultiDeviceRemovalSheet(this)) {
|
||||
TextSecurePreferences.setHasSeenMultiDeviceRemovalSheet(this)
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||
@@ -223,7 +225,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
||||
}
|
||||
}
|
||||
|
||||
// Light theme introduction
|
||||
// Light theme introduction sheet
|
||||
if (!TextSecurePreferences.hasSeenLightThemeIntroSheet(this) &&
|
||||
UiModeUtilities.isDayUiMode(this)) {
|
||||
TextSecurePreferences.setHasSeenLightThemeIntroSheet(this)
|
||||
@@ -271,6 +273,14 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
||||
val thread = view.thread ?: return
|
||||
val bottomSheet = ConversationOptionsBottomSheet()
|
||||
bottomSheet.recipient = thread.recipient
|
||||
bottomSheet.onViewDetailsTapped = {
|
||||
bottomSheet.dismiss()
|
||||
val userDetailsBottomSheet = UserDetailsBottomSheet()
|
||||
val bundle = Bundle()
|
||||
bundle.putString("publicKey", thread.recipient.address.toPhoneString())
|
||||
userDetailsBottomSheet.arguments = bundle
|
||||
userDetailsBottomSheet.show(supportFragmentManager, userDetailsBottomSheet.tag)
|
||||
}
|
||||
bottomSheet.onBlockTapped = {
|
||||
bottomSheet.dismiss()
|
||||
if (!thread.recipient.isBlocked) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
|
||||
import android.animation.ArgbEvaluator
|
||||
import android.animation.ValueAnimator
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.TransitionDrawable
|
||||
@@ -9,27 +11,36 @@ import androidx.annotation.DrawableRes
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.ColorRes
|
||||
import kotlinx.android.synthetic.main.activity_display_name.registerButton
|
||||
import kotlinx.android.synthetic.main.activity_pn_mode.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
import org.thoughtcrime.securesms.loki.utilities.disableClipping
|
||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
|
||||
import org.thoughtcrime.securesms.loki.utilities.show
|
||||
import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
|
||||
import org.thoughtcrime.securesms.loki.views.PNModeView
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
class PNModeActivity : BaseActionBarActivity() {
|
||||
private var selectedOptionView: LinearLayout? = null
|
||||
private var selectedOptionView: PNModeView? = null
|
||||
|
||||
// region Lifecycle
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setUpActionBarSessionLogo()
|
||||
setContentView(R.layout.activity_pn_mode)
|
||||
contentView.disableClipping()
|
||||
fcmOptionView.setOnClickListener { toggleFCM() }
|
||||
fcmOptionView.mainColor = resources.getColorWithID(R.color.pn_option_background, theme)
|
||||
fcmOptionView.strokeColor = resources.getColorWithID(R.color.pn_option_border, theme)
|
||||
backgroundPollingOptionView.setOnClickListener { toggleBackgroundPolling() }
|
||||
backgroundPollingOptionView.mainColor = resources.getColorWithID(R.color.pn_option_background, theme)
|
||||
backgroundPollingOptionView.strokeColor = resources.getColorWithID(R.color.pn_option_border, theme)
|
||||
registerButton.setOnClickListener { register() }
|
||||
}
|
||||
|
||||
@@ -71,15 +82,23 @@ class PNModeActivity : BaseActionBarActivity() {
|
||||
when (selectedOptionView) {
|
||||
null -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this, fcmOptionView, R.color.transparent, R.color.accent)
|
||||
animateStrokeColorChange(fcmOptionView, R.color.pn_option_border, R.color.accent)
|
||||
selectedOptionView = fcmOptionView
|
||||
}
|
||||
fcmOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this, fcmOptionView, R.color.accent, R.color.transparent)
|
||||
animateStrokeColorChange(fcmOptionView, R.color.accent, R.color.pn_option_border)
|
||||
selectedOptionView = null
|
||||
}
|
||||
backgroundPollingOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this, fcmOptionView, R.color.transparent, R.color.accent)
|
||||
animateStrokeColorChange(fcmOptionView, R.color.pn_option_border, R.color.accent)
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this, backgroundPollingOptionView, R.color.accent, R.color.transparent)
|
||||
animateStrokeColorChange(backgroundPollingOptionView, R.color.accent, R.color.pn_option_border)
|
||||
selectedOptionView = fcmOptionView
|
||||
}
|
||||
}
|
||||
@@ -89,20 +108,40 @@ class PNModeActivity : BaseActionBarActivity() {
|
||||
when (selectedOptionView) {
|
||||
null -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this, backgroundPollingOptionView, R.color.transparent, R.color.accent)
|
||||
animateStrokeColorChange(backgroundPollingOptionView, R.color.pn_option_border, R.color.accent)
|
||||
selectedOptionView = backgroundPollingOptionView
|
||||
}
|
||||
backgroundPollingOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this, backgroundPollingOptionView, R.color.accent, R.color.transparent)
|
||||
animateStrokeColorChange(backgroundPollingOptionView, R.color.accent, R.color.pn_option_border)
|
||||
selectedOptionView = null
|
||||
}
|
||||
fcmOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this, backgroundPollingOptionView, R.color.transparent, R.color.accent)
|
||||
animateStrokeColorChange(backgroundPollingOptionView, R.color.pn_option_border, R.color.accent)
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
|
||||
GlowViewUtilities.animateShadowColorChange(this, fcmOptionView, R.color.accent, R.color.transparent)
|
||||
animateStrokeColorChange(fcmOptionView, R.color.accent, R.color.pn_option_border)
|
||||
selectedOptionView = backgroundPollingOptionView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun animateStrokeColorChange(bubble: PNModeView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
|
||||
val startColor = resources.getColorWithID(startColorID, theme)
|
||||
val endColor = resources.getColorWithID(endColorID, theme)
|
||||
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
|
||||
animation.duration = 250
|
||||
animation.addUpdateListener { animator ->
|
||||
val color = animator.animatedValue as Int
|
||||
bubble.strokeColor = color
|
||||
}
|
||||
animation.start()
|
||||
}
|
||||
|
||||
private fun register() {
|
||||
if (selectedOptionView == null) {
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
@@ -111,7 +150,6 @@ class PNModeActivity : BaseActionBarActivity() {
|
||||
dialog.create().show()
|
||||
return
|
||||
}
|
||||
val displayName = TextSecurePreferences.getProfileName(this)
|
||||
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
|
||||
TextSecurePreferences.setPromptedPushRegistration(this, true)
|
||||
TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == fcmOptionView))
|
||||
|
||||
@@ -16,10 +16,13 @@ import android.widget.LinearLayout
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.ColorRes
|
||||
import kotlinx.android.synthetic.main.activity_path.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
|
||||
import org.thoughtcrime.securesms.loki.views.PathDotView
|
||||
import org.whispersystems.signalservice.loki.api.Snode
|
||||
import org.whispersystems.signalservice.loki.api.onionrequests.OnionRequestAPI
|
||||
|
||||
@@ -31,6 +34,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||
super.onCreate(savedInstanceState, isReady)
|
||||
setContentView(R.layout.activity_path)
|
||||
supportActionBar!!.title = resources.getString(R.string.activity_path_title)
|
||||
pathRowsContainer.disableClipping()
|
||||
learnMoreButton.setOnClickListener { learnMore() }
|
||||
update(false)
|
||||
registerObservers()
|
||||
@@ -111,6 +115,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||
val mainContainer = LinearLayout(this)
|
||||
mainContainer.orientation = LinearLayout.HORIZONTAL
|
||||
mainContainer.gravity = Gravity.CENTER_VERTICAL
|
||||
mainContainer.disableClipping()
|
||||
val mainContainerLayoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
|
||||
mainContainer.layoutParams = mainContainerLayoutParams
|
||||
val lineView = LineView(this, location, dotAnimationStartDelay, dotAnimationRepeatInterval)
|
||||
@@ -170,8 +175,9 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||
private var dotAnimationRepeatInterval: Long = 0
|
||||
|
||||
private val dotView by lazy {
|
||||
val result = View(context)
|
||||
val result = PathDotView(context)
|
||||
result.setBackgroundResource(R.drawable.accent_dot)
|
||||
result.mainColor = resources.getColorWithID(R.color.accent, context.theme)
|
||||
result
|
||||
}
|
||||
|
||||
@@ -203,6 +209,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||
}
|
||||
|
||||
private fun setUpViewHierarchy() {
|
||||
disableClipping()
|
||||
val lineView = View(context)
|
||||
lineView.setBackgroundColor(resources.getColorWithID(R.color.text, context.theme))
|
||||
val lineViewHeight = when (location) {
|
||||
@@ -239,10 +246,14 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||
|
||||
private fun expand() {
|
||||
dotView.animateSizeChange(R.dimen.path_row_dot_size, R.dimen.path_row_expanded_dot_size)
|
||||
@ColorRes val startColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
|
||||
GlowViewUtilities.animateShadowColorChange(context, dotView, startColorID, R.color.accent)
|
||||
}
|
||||
|
||||
private fun collapse() {
|
||||
dotView.animateSizeChange(R.dimen.path_row_expanded_dot_size, R.dimen.path_row_dot_size)
|
||||
@ColorRes val endColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
|
||||
GlowViewUtilities.animateShadowColorChange(context, dotView, R.color.accent, endColorID)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
@@ -33,8 +33,14 @@ class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database
|
||||
return TextSecurePreferences.getProfileName(context)
|
||||
} else {
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.get(displayNameTable, "${Companion.publicKey} = ?", arrayOf( publicKey )) { cursor ->
|
||||
val result = database.get(displayNameTable, "${Companion.publicKey} = ?", arrayOf( publicKey )) { cursor ->
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(displayName))
|
||||
} ?: return null
|
||||
val suffix = " (...${publicKey.substring(publicKey.count() - 8)})"
|
||||
if (result.endsWith(suffix)) {
|
||||
return result.substring(0..(result.count() - suffix.count()))
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ public class ConversationOptionsBottomSheet : BottomSheetDialogFragment() {
|
||||
// if we want to use dialog fragments properly.
|
||||
lateinit var recipient: Recipient
|
||||
|
||||
var onViewDetailsTapped: (() -> Unit?)? = null
|
||||
var onBlockTapped: (() -> Unit)? = null
|
||||
var onUnblockTapped: (() -> Unit)? = null
|
||||
var onDeleteTapped: (() -> Unit)? = null
|
||||
@@ -30,18 +31,16 @@ public class ConversationOptionsBottomSheet : BottomSheetDialogFragment() {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
if (!this::recipient.isInitialized) {
|
||||
dismiss()
|
||||
return
|
||||
}
|
||||
|
||||
if (!this::recipient.isInitialized) { return dismiss() }
|
||||
if (!recipient.isGroupRecipient && !recipient.isLocalNumber) {
|
||||
detailsTextView.visibility = View.VISIBLE
|
||||
unblockTextView.visibility = if (recipient.isBlocked) View.VISIBLE else View.GONE
|
||||
blockTextView.visibility = if (recipient.isBlocked) View.GONE else View.VISIBLE
|
||||
|
||||
detailsTextView.setOnClickListener { onViewDetailsTapped?.invoke() }
|
||||
blockTextView.setOnClickListener { onBlockTapped?.invoke() }
|
||||
unblockTextView.setOnClickListener { onUnblockTapped?.invoke() }
|
||||
} else {
|
||||
detailsTextView.visibility = View.GONE
|
||||
}
|
||||
deleteTextView.setOnClickListener { onDeleteTapped?.invoke() }
|
||||
}
|
||||
|
||||
@@ -7,17 +7,18 @@ import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.LayoutInflater
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import kotlinx.android.synthetic.main.dialog_seed.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
|
||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
||||
import org.whispersystems.signalservice.loki.utilities.hexEncodedPrivateKey
|
||||
import java.io.File
|
||||
|
||||
|
||||
class SeedDialog : DialogFragment() {
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.thoughtcrime.securesms.loki.dialogs
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import kotlinx.android.synthetic.main.fragment_user_details_bottom_sheet.*
|
||||
import kotlinx.android.synthetic.main.view_conversation.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
|
||||
public class UserDetailsBottomSheet : BottomSheetDialogFragment() {
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_user_details_bottom_sheet, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val publicKey = arguments?.getString("publicKey") ?: return dismiss()
|
||||
profilePictureView.publicKey = publicKey
|
||||
profilePictureView.glide = GlideApp.with(this)
|
||||
profilePictureView.isLarge = true
|
||||
profilePictureView.update()
|
||||
nameTextView.text = DatabaseFactory.getLokiUserDatabase(requireContext()).getDisplayName(publicKey) ?: "Anonymous"
|
||||
publicKeyTextView.text = publicKey
|
||||
copyButton.setOnClickListener {
|
||||
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("Session ID", publicKey)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
Toast.makeText(requireContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol
|
||||
import java.security.MessageDigest
|
||||
|
||||
object SessionMetaProtocol {
|
||||
@@ -37,24 +36,16 @@ object SessionMetaProtocol {
|
||||
|
||||
@JvmStatic
|
||||
fun handleProfileUpdateIfNeeded(context: Context, content: SignalServiceContent) {
|
||||
val rawDisplayName = content.senderDisplayName.orNull() ?: return
|
||||
if (rawDisplayName.isBlank()) { return }
|
||||
val displayName = content.senderDisplayName.orNull() ?: return
|
||||
if (displayName.isBlank()) { return }
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val userMasterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||
val sender = content.sender.toLowerCase()
|
||||
if (userMasterPublicKey == sender) {
|
||||
// Update the user's local name if the message came from their master device
|
||||
TextSecurePreferences.setProfileName(context, rawDisplayName)
|
||||
}
|
||||
// Don't overwrite if the message came from a linked device; the device name is
|
||||
// stored as a user name
|
||||
val allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey)
|
||||
if (!allUserDevices.contains(sender)) {
|
||||
val displayName = rawDisplayName + " (..." + sender.substring(sender.length - 8) + ")"
|
||||
DatabaseFactory.getLokiUserDatabase(context).setDisplayName(sender, displayName)
|
||||
} else {
|
||||
DatabaseFactory.getLokiUserDatabase(context).setDisplayName(sender, rawDisplayName)
|
||||
TextSecurePreferences.setProfileName(context, displayName)
|
||||
}
|
||||
DatabaseFactory.getLokiUserDatabase(context).setDisplayName(sender, displayName)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
||||
@@ -53,7 +53,9 @@ object MentionUtilities {
|
||||
}
|
||||
val result = SpannableString(text)
|
||||
for (mention in mentions) {
|
||||
result.setSpan(ForegroundColorSpan(context.resources.getColorWithID(R.color.accent, context.theme)), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
val isLightMode = UiModeUtilities.isDayUiMode(context)
|
||||
val colorID = if (isLightMode && isOutgoingMessage) R.color.black else R.color.accent
|
||||
result.setSpan(ForegroundColorSpan(context.resources.getColorWithID(colorID, context.theme)), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
result.setSpan(StyleSpan(Typeface.BOLD), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -57,37 +57,8 @@ class ConversationView : LinearLayout {
|
||||
accentView.setBackgroundResource(R.color.accent)
|
||||
accentView.visibility = if (thread.unreadCount > 0) View.VISIBLE else View.INVISIBLE
|
||||
}
|
||||
if (thread.recipient.isGroupRecipient) {
|
||||
if ("Session Public Chat" == thread.recipient.name) {
|
||||
profilePictureView.publicKey = ""
|
||||
profilePictureView.displayName = ""
|
||||
profilePictureView.additionalPublicKey = null
|
||||
profilePictureView.isRSSFeed = true
|
||||
} else {
|
||||
val userKeys = MentionsManager.shared.userPublicKeyCache[thread.threadId]?.toMutableList() ?: mutableListOf()
|
||||
userKeys.remove(TextSecurePreferences.getLocalNumber(context))
|
||||
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||
if (masterPublicKey != null) {
|
||||
userKeys.remove(masterPublicKey)
|
||||
}
|
||||
|
||||
val sortedUserKeys = userKeys.sorted() // Sort to provide a level of stability
|
||||
val userKey0 = sortedUserKeys.getOrNull(0) ?: ""
|
||||
val userKey1 = sortedUserKeys.getOrNull(1) ?: ""
|
||||
profilePictureView.publicKey = userKey0
|
||||
profilePictureView.displayName = getUserDisplayName(userKey0)
|
||||
profilePictureView.additionalPublicKey = userKey1
|
||||
profilePictureView.additionalDisplayName = getUserDisplayName(userKey1)
|
||||
profilePictureView.isRSSFeed = thread.recipient.name == "Loki News" || thread.recipient.name == "Session Updates"
|
||||
}
|
||||
} else {
|
||||
profilePictureView.publicKey = thread.recipient.address.toString()
|
||||
profilePictureView.displayName = thread.recipient.name
|
||||
profilePictureView.additionalPublicKey = null
|
||||
profilePictureView.isRSSFeed = false
|
||||
}
|
||||
profilePictureView.glide = glide
|
||||
profilePictureView.update()
|
||||
profilePictureView.update(thread.recipient, thread.threadId)
|
||||
val senderDisplayName = if (thread.recipient.isLocalNumber) context.getString(R.string.note_to_self) else if (!thread.recipient.name.isNullOrEmpty()) thread.recipient.name else thread.recipient.address.toString()
|
||||
btnGroupNameDisplay.text = senderDisplayName
|
||||
timestampTextView.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), thread.date)
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
|
||||
import android.animation.FloatEvaluator
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.content.Context.LAYOUT_INFLATER_SERVICE
|
||||
import android.os.Handler
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import kotlinx.android.synthetic.main.view_fake_chat.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.loki.utilities.disableClipping
|
||||
|
||||
class FakeChatView : ScrollView {
|
||||
|
||||
@@ -38,7 +42,8 @@ class FakeChatView : ScrollView {
|
||||
|
||||
private fun setUpViewHierarchy() {
|
||||
val inflater = context.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
val contentView = inflater.inflate(R.layout.view_fake_chat, null)
|
||||
val contentView = inflater.inflate(R.layout.view_fake_chat, null) as LinearLayout
|
||||
contentView.disableClipping()
|
||||
addView(contentView)
|
||||
isVerticalScrollBarEnabled = false
|
||||
}
|
||||
@@ -47,8 +52,13 @@ class FakeChatView : ScrollView {
|
||||
// region Animation
|
||||
fun startAnimating() {
|
||||
listOf( bubble1, bubble2, bubble3, bubble4, bubble5 ).forEach { it.alpha = 0.0f }
|
||||
fun show(view: View) {
|
||||
view.animate().alpha(1.0f).setDuration(animationDuration).start()
|
||||
fun show(bubble: View) {
|
||||
val animation = ValueAnimator.ofObject(FloatEvaluator(), 0.0f, 1.0f)
|
||||
animation.duration = animationDuration
|
||||
animation.addUpdateListener { animator ->
|
||||
bubble.alpha = animator.animatedValue as Float
|
||||
}
|
||||
animation.start()
|
||||
}
|
||||
Handler().postDelayed({
|
||||
show(bubble1)
|
||||
|
||||
158
src/org/thoughtcrime/securesms/loki/views/GlowView.kt
Normal file
158
src/org/thoughtcrime/securesms/loki/views/GlowView.kt
Normal file
@@ -0,0 +1,158 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
|
||||
import android.animation.ArgbEvaluator
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewOutlineProvider
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
|
||||
interface GlowView {
|
||||
var mainColor: Int
|
||||
var sessionShadowColor: Int
|
||||
}
|
||||
|
||||
object GlowViewUtilities {
|
||||
|
||||
fun animateColorChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
|
||||
val startColor = context.resources.getColorWithID(startColorID, context.theme)
|
||||
val endColor = context.resources.getColorWithID(endColorID, context.theme)
|
||||
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
|
||||
animation.duration = 250
|
||||
animation.addUpdateListener { animator ->
|
||||
val color = animator.animatedValue as Int
|
||||
view.mainColor = color
|
||||
}
|
||||
animation.start()
|
||||
}
|
||||
|
||||
fun animateShadowColorChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
|
||||
val startColor = context.resources.getColorWithID(startColorID, context.theme)
|
||||
val endColor = context.resources.getColorWithID(endColorID, context.theme)
|
||||
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
|
||||
animation.duration = 250
|
||||
animation.addUpdateListener { animator ->
|
||||
val color = animator.animatedValue as Int
|
||||
view.sessionShadowColor = color
|
||||
}
|
||||
animation.start()
|
||||
}
|
||||
}
|
||||
|
||||
class PNModeView : LinearLayout, GlowView {
|
||||
@ColorInt override var mainColor: Int = 0
|
||||
set(newValue) { field = newValue; paint.color = newValue }
|
||||
@ColorInt var strokeColor: Int = 0
|
||||
set(newValue) { field = newValue; strokePaint.color = newValue }
|
||||
@ColorInt override var sessionShadowColor: Int = 0
|
||||
set(newValue) { field = newValue; paint.setShadowLayer(toPx(4, resources).toFloat(), 0.0f, 0.0f, newValue) }
|
||||
|
||||
private val paint: Paint by lazy {
|
||||
val result = Paint()
|
||||
result.style = Paint.Style.FILL
|
||||
result.isAntiAlias = true
|
||||
result
|
||||
}
|
||||
|
||||
private val strokePaint: Paint by lazy {
|
||||
val result = Paint()
|
||||
result.style = Paint.Style.STROKE
|
||||
result.isAntiAlias = true
|
||||
result.strokeWidth = toPx(1, resources).toFloat()
|
||||
result
|
||||
}
|
||||
|
||||
// region Lifecycle
|
||||
constructor(context: Context) : super(context) { }
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { }
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { }
|
||||
|
||||
init {
|
||||
setWillNotDraw(false)
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Updating
|
||||
override fun onDraw(c: Canvas) {
|
||||
val w = width.toFloat()
|
||||
val h = height.toFloat()
|
||||
val r = resources.getDimension(R.dimen.pn_option_corner_radius)
|
||||
c.drawRoundRect(0.0f, 0.0f, w, h, r, r, paint)
|
||||
c.drawRoundRect(0.0f, 0.0f, w, h, r, r, strokePaint)
|
||||
super.onDraw(c)
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
||||
class NewConversationButtonImageView : androidx.appcompat.widget.AppCompatImageView, GlowView {
|
||||
@ColorInt override var mainColor: Int = 0
|
||||
set(newValue) { field = newValue; paint.color = newValue }
|
||||
@ColorInt override var sessionShadowColor: Int = 0
|
||||
set(newValue) { field = newValue; paint.setShadowLayer(toPx(6, resources).toFloat(), 0.0f, 0.0f, newValue) }
|
||||
|
||||
private val paint: Paint by lazy {
|
||||
val result = Paint()
|
||||
result.style = Paint.Style.FILL
|
||||
result.isAntiAlias = true
|
||||
result
|
||||
}
|
||||
|
||||
// region Lifecycle
|
||||
constructor(context: Context) : super(context) { }
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { }
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { }
|
||||
|
||||
init {
|
||||
setWillNotDraw(false)
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Updating
|
||||
override fun onDraw(c: Canvas) {
|
||||
val w = width.toFloat()
|
||||
val h = height.toFloat()
|
||||
c.drawCircle(w / 2, h / 2, w / 2, paint)
|
||||
super.onDraw(c)
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
||||
class PathDotView : View, GlowView {
|
||||
@ColorInt override var mainColor: Int = 0
|
||||
set(newValue) { field = newValue; paint.color = newValue }
|
||||
@ColorInt override var sessionShadowColor: Int = 0
|
||||
set(newValue) { field = newValue; paint.setShadowLayer(toPx(4, resources).toFloat(), 0.0f, 0.0f, newValue) }
|
||||
|
||||
private val paint: Paint by lazy {
|
||||
val result = Paint()
|
||||
result.style = Paint.Style.FILL
|
||||
result.isAntiAlias = true
|
||||
result
|
||||
}
|
||||
|
||||
// region Lifecycle
|
||||
constructor(context: Context) : super(context) { }
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { }
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { }
|
||||
|
||||
init {
|
||||
setWillNotDraw(false)
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Updating
|
||||
override fun onDraw(c: Canvas) {
|
||||
val w = width.toFloat()
|
||||
val h = height.toFloat()
|
||||
c.drawCircle(w / 2, h / 2, w / 2, paint)
|
||||
super.onDraw(c)
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class LabeledSeparatorView : RelativeLayout {
|
||||
|
||||
private val path = Path()
|
||||
|
||||
private val paint: Paint by lazy{
|
||||
private val paint: Paint by lazy {
|
||||
val result = Paint()
|
||||
result.style = Paint.Style.STROKE
|
||||
result.color = ThemeUtil.getThemedColor(context, R.attr.dividerHorizontal)
|
||||
|
||||
@@ -7,9 +7,7 @@ import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.content.Context.VIBRATOR_SERVICE
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.PointF
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Build
|
||||
import android.os.VibrationEffect
|
||||
import android.os.VibrationEffect.DEFAULT_AMPLITUDE
|
||||
@@ -66,24 +64,25 @@ class NewConversationButtonSetView : RelativeLayout {
|
||||
private val collapsedImageViewPosition by lazy { PointF((expandedSize - collapsedSize) / 2, (expandedSize - collapsedSize) / 2) }
|
||||
|
||||
private val imageView by lazy {
|
||||
val result = ImageView(context)
|
||||
val result = NewConversationButtonImageView(context)
|
||||
val size = collapsedSize.toInt()
|
||||
result.layoutParams = LayoutParams(size, size)
|
||||
result.setBackgroundResource(R.drawable.new_conversation_button_background)
|
||||
val background = result.background as GradientDrawable
|
||||
@ColorRes val backgroundColorID = if (isMain)
|
||||
R.color.accent else
|
||||
R.color.new_conversation_button_collapsed_background
|
||||
background.color = ColorStateList.valueOf(resources.getColorWithID(backgroundColorID, context.theme))
|
||||
@ColorRes val backgroundColorID = if (isMain) R.color.accent else R.color.new_conversation_button_collapsed_background
|
||||
@ColorRes val shadowColorID = if (isMain) {
|
||||
R.color.new_conversation_button_shadow
|
||||
} else {
|
||||
if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
|
||||
}
|
||||
result.mainColor = resources.getColorWithID(backgroundColorID, context.theme)
|
||||
result.sessionShadowColor = resources.getColorWithID(shadowColorID, context.theme)
|
||||
result.scaleType = ImageView.ScaleType.CENTER
|
||||
result.setImageResource(iconID)
|
||||
|
||||
result.imageTintList = if (isMain)
|
||||
// Always use white icon for the main button.
|
||||
result.imageTintList = if (isMain) {
|
||||
ColorStateList.valueOf(resources.getColorWithID(android.R.color.white, context.theme))
|
||||
else
|
||||
} else {
|
||||
ColorStateList.valueOf(resources.getColorWithID(R.color.text, context.theme))
|
||||
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
@@ -106,30 +105,21 @@ class NewConversationButtonSetView : RelativeLayout {
|
||||
}
|
||||
|
||||
fun expand() {
|
||||
animateImageViewColorChange(R.color.new_conversation_button_collapsed_background, R.color.accent)
|
||||
GlowViewUtilities.animateColorChange(context, imageView, R.color.new_conversation_button_collapsed_background, R.color.accent)
|
||||
@ColorRes val startShadowColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
|
||||
GlowViewUtilities.animateShadowColorChange(context, imageView, startShadowColorID, R.color.new_conversation_button_shadow)
|
||||
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)
|
||||
GlowViewUtilities.animateColorChange(context, imageView, R.color.accent, R.color.new_conversation_button_collapsed_background)
|
||||
@ColorRes val endShadowColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
|
||||
GlowViewUtilities.animateShadowColorChange(context, imageView, R.color.new_conversation_button_shadow, endShadowColorID)
|
||||
imageView.animateSizeChange(R.dimen.new_conversation_button_expanded_size, R.dimen.new_conversation_button_collapsed_size, animationDuration)
|
||||
animateImageViewPositionChange(expandedImageViewPosition, collapsedImageViewPosition)
|
||||
}
|
||||
|
||||
private fun animateImageViewColorChange(@ColorRes startColorID: Int, @ColorRes endColorID: Int) {
|
||||
val drawable = imageView.background as GradientDrawable
|
||||
val startColor = resources.getColorWithID(startColorID, context.theme)
|
||||
val endColor = resources.getColorWithID(endColorID, context.theme)
|
||||
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
|
||||
animation.duration = animationDuration
|
||||
animation.addUpdateListener { animator ->
|
||||
val color = animator.animatedValue as Int
|
||||
drawable.color = ColorStateList.valueOf(color)
|
||||
}
|
||||
animation.start()
|
||||
}
|
||||
|
||||
private fun animateImageViewPositionChange(startPosition: PointF, endPosition: PointF) {
|
||||
val animation = ValueAnimator.ofObject(PointFEvaluator(), startPosition, endPosition)
|
||||
animation.duration = animationDuration
|
||||
@@ -170,6 +160,7 @@ class NewConversationButtonSetView : RelativeLayout {
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { setUpViewHierarchy() }
|
||||
|
||||
private fun setUpViewHierarchy() {
|
||||
disableClipping()
|
||||
// Set up session button
|
||||
addView(sessionButton)
|
||||
sessionButton.alpha = 0.0f
|
||||
|
||||
@@ -4,14 +4,30 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.annotation.ColorInt
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.whispersystems.signalservice.loki.api.onionrequests.OnionRequestAPI
|
||||
|
||||
class PathStatusView : View {
|
||||
private val broadcastReceivers = mutableListOf<BroadcastReceiver>()
|
||||
@ColorInt var mainColor: Int = 0
|
||||
set(newValue) { field = newValue; paint.color = newValue }
|
||||
@ColorInt var sessionShadowColor: Int = 0
|
||||
set(newValue) { field = newValue; paint.setShadowLayer(toPx(8, resources).toFloat(), 0.0f, 0.0f, newValue) }
|
||||
|
||||
private val paint: Paint by lazy {
|
||||
val result = Paint()
|
||||
result.style = Paint.Style.FILL
|
||||
result.isAntiAlias = true
|
||||
result
|
||||
}
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
initialize()
|
||||
@@ -31,6 +47,7 @@ class PathStatusView : View {
|
||||
|
||||
private fun initialize() {
|
||||
update()
|
||||
setWillNotDraw(false)
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
@@ -70,8 +87,19 @@ class PathStatusView : View {
|
||||
private fun update() {
|
||||
if (OnionRequestAPI.paths.count() >= OnionRequestAPI.pathCount) {
|
||||
setBackgroundResource(R.drawable.accent_dot)
|
||||
mainColor = resources.getColorWithID(R.color.accent, context.theme)
|
||||
sessionShadowColor = resources.getColorWithID(R.color.accent, context.theme)
|
||||
} else {
|
||||
setBackgroundResource(R.drawable.paths_building_dot)
|
||||
mainColor = resources.getColorWithID(R.color.paths_building, context.theme)
|
||||
sessionShadowColor = resources.getColorWithID(R.color.paths_building, context.theme)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDraw(c: Canvas) {
|
||||
val w = width.toFloat()
|
||||
val h = height.toFloat()
|
||||
c.drawCircle(w / 2, h / 2, w / 2, paint)
|
||||
super.onDraw(c)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.loki.views
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -8,14 +9,17 @@ import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.annotation.DimenRes
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import kotlinx.android.synthetic.main.view_conversation.view.*
|
||||
import kotlinx.android.synthetic.main.view_profile_picture.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.todo.AvatarPlaceholderGenerator
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
|
||||
|
||||
// TODO: Look into a better way of handling different sizes. Maybe an enum (with associated values) encapsulating the different modes?
|
||||
|
||||
@@ -53,6 +57,43 @@ class ProfilePictureView : RelativeLayout {
|
||||
// endregion
|
||||
|
||||
// region Updating
|
||||
fun update(recipient: Recipient, threadID: Long) {
|
||||
fun getUserDisplayName(publicKey: String?): String? {
|
||||
if (publicKey == null || publicKey.isBlank()) {
|
||||
return null
|
||||
} else {
|
||||
return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey!!)
|
||||
}
|
||||
}
|
||||
if (recipient.isGroupRecipient) {
|
||||
if ("Session Public Chat" == recipient.name) {
|
||||
publicKey = ""
|
||||
displayName = ""
|
||||
additionalPublicKey = null
|
||||
isRSSFeed = true
|
||||
} else {
|
||||
val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toMutableList() ?: mutableListOf()
|
||||
users.remove(TextSecurePreferences.getLocalNumber(context))
|
||||
val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
||||
if (masterPublicKey != null) {
|
||||
users.remove(masterPublicKey)
|
||||
}
|
||||
val randomUsers = users.sorted() // Sort to provide a level of stability
|
||||
publicKey = randomUsers.getOrNull(0) ?: ""
|
||||
displayName = getUserDisplayName(randomUsers.getOrNull(0) ?: "")
|
||||
additionalPublicKey = randomUsers.getOrNull(1) ?: ""
|
||||
additionalDisplayName = getUserDisplayName(randomUsers.getOrNull(1) ?: "")
|
||||
isRSSFeed = recipient.name == "Loki News" || recipient.name == "Session Updates"
|
||||
}
|
||||
} else {
|
||||
publicKey = recipient.address.toString()
|
||||
displayName = recipient.name
|
||||
additionalPublicKey = null
|
||||
isRSSFeed = false
|
||||
}
|
||||
update()
|
||||
}
|
||||
|
||||
fun update() {
|
||||
val publicKey = publicKey ?: return
|
||||
val additionalPublicKey = additionalPublicKey
|
||||
|
||||
Reference in New Issue
Block a user