diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index c08052c43b..0466db7e71 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -3,12 +3,9 @@ You can also preview your report before submitting it. You may remove sections t
Before we begin, please note that this tracker is only for issues. It is not for questions, comments, or feature requests.
-If you would like to discuss a new feature or submit suggestions, please visit the community forum:
-https://community.signalusers.org
-If you are looking for support, please visit our support center:
-https://support.signal.org/
-or email support@signal.org
+If you are looking for support, please file an issue or:
+or email team@loki.network
Let's begin with a checklist: Replace the empty checkboxes [ ] below with checked ones [x] accordingly. -->
@@ -26,11 +23,11 @@ Describe here the issue that you are experiencing.
- list the steps
- that reproduce the bug
-**Actual result:**
+**Actual result:**
Describe here what happens after you run the steps above (i.e. the buggy behaviour)
-**Expected result:**
+**Expected result:**
Describe here what should happen after you run the steps above (i.e. what would be the correct behaviour)
@@ -48,6 +45,3 @@ Describe here what should happen after you run the steps above (i.e. what would
**Session version:** 0.0.0
### Link to debug log
-
-
-
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 543e006459..4e2e4b5310 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,12 +1,8 @@
### First time contributor checklist
-- [ ] I have read [how to contribute](https://github.com/signalapp/Signal-Android/blob/master/CONTRIBUTING.md) to this project
-- [ ] I have signed the [Contributor License Agreement](https://whispersystems.org/cla/)
-
### Contributor checklist
-- [ ] I am following the [Code Style Guidelines](https://github.com/signalapp/Signal-Android/wiki/Code-Style-Guidelines)
- [ ] I have tested my contribution on these devices:
* Device A, Android X.Y.Z
* Device B, Android Z.Y
diff --git a/.gitignore b/.gitignore
index a918936f20..191fbe6f50 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,4 @@ jni/libspeex/.deps/
pkcs11.password
fabric.properties
play
+google-services.json
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bfa2c456dc..df2bebd0b5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -47,6 +47,7 @@
+
@@ -112,6 +113,9 @@
android:name="org.thoughtcrime.securesms.loki.redesign.activities.DisplayNameActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" />
+
+
+
+
+
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index b48217186e..0000000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,89 +0,0 @@
-# Contributing to Signal Android
-
-Thank you for supporting Signal and looking for ways to help. Please note that some conventions here might be a bit different than what you are used to, even if you have contributed to other open source projects before. Reading this document will help you save time and work effectively with the developers and other contributors.
-
-
-## Development Ideology
-
-Truths which we believe to be self-evident:
-
-1. **The answer is not more options.** If you feel compelled to add a preference that's exposed to the user, it's very possible you've made a wrong turn somewhere.
-1. **The user doesn't know what a key is.** We need to minimize the points at which a user is exposed to this sort of terminology as extremely as possible.
-1. **There are no power users.** The idea that some users "understand" concepts better than others has proven to be, for the most part, false. If anything, "power users" are more dangerous than the rest, and we should avoid exposing dangerous functionality to them.
-1. **If it's "like PGP," it's wrong.** PGP is our guide for what not to do.
-1. **It's an asynchronous world.** Be wary of anything that is anti-asynchronous: ACKs, protocol confirmations, or any protocol-level "advisory" message.
-1. **There is no such thing as time.** Protocol ideas that require synchronized clocks are doomed to failure.
-
-
-## Translations
-
-Thanks to a dedicated community of volunteer translators, Signal is now available in more than one hundred languages. We use Transifex to manage our translation efforts, not GitHub. Any suggestions, corrections, or new translations should be submitted to the [Signal localization project for Android](https://www.transifex.com/signalapp/signal-android/).
-
-
-## Issues
-
-### Useful bug reports
-1. Please search both open and closed issues to make sure your bug report is not a duplicate.
-1. Read the [guide to submitting useful bug reports](https://github.com/signalapp/Signal-Android/wiki/Submitting-useful-bug-reports) before posting a bug.
-
-### The issue tracker is for bugs, not feature requests
-The GitHub issue tracker is not used for feature requests, but new ideas can be submitted and discussed on the [community forum](https://community.signalusers.org/c/feature-requests). The purpose of this issue tracker is to track bugs in the Android client. Bug reports should only be submitted for existing functionality that does not work as intended. Comments that are relevant and concise will help the developers solve issues more quickly.
-
-### Send support questions to support
-You can reach support by sending an email to support@signal.org or by visiting the [Signal Support Center](https://support.signal.org/) where you can also search for existing troubleshooting articles and find answers to frequently asked questions. Please do not post support questions on the GitHub issue tracker.
-
-### GitHub is not a generic discussion forum
-Conversations about open bug reports belong here. However, all other discussions should take place on the [community forum](https://community.signalusers.org). You can use the community forum to discuss anything that is related to Signal or to hang out with your fellow users in the "Off Topic" category.
-
-### Don't bump issues
-Every time someone comments on an issue, GitHub sends an email to [hundreds of people](https://github.com/signalapp/Signal-Android/watchers). Bumping issues with a "+1" (or asking for updates) generates a lot of unnecessary email notifications and does not help anyone solve the issue any faster. Please be respectful of everyone's time and only comment when you have new information to add.
-
-### Open issues
-
-#### If it's open, it's tracked
-The developers read every issue, but high-priority bugs or features can take precedence over others. Signal is an open source project, and everyone is encouraged to play an active role in diagnosing and fixing open issues.
-
-### Closed issues
-
-#### "My issue was closed without giving a reason!"
-Although we do our best, writing detailed explanations for every issue can be time consuming, and the topic also might have been covered previously in other related issues.
-
-
-## Pull requests
-
-### Smaller is better
-Big changes are significantly less likely to be accepted. Large features often require protocol modifications and necessitate a staged rollout process that is coordinated across millions of users on multiple platforms (Android, iOS, and Desktop).
-
-Try not to take on too much at once. As a first-time contributor, we recommend starting with small and simple PRs in order to become familiar with the codebase. Most of the work should go into discovering which three lines need to change rather than writing the code.
-
-### Sign the Contributor License Agreement (CLA)
-You will need to [sign our CLA](https://signal.org/cla/) before your pull request can be merged.
-
-### Follow the Code Style Guidelines
-Ensure that your code adheres to the [Code Style Guidelines](https://github.com/signalapp/Signal-Android/wiki/Code-Style-Guidelines) before submitting a pull request.
-
-### Submit finished and well-tested pull requests
-Please do not submit pull requests that are still a work in progress. Pull requests should be thoroughly tested and ready to merge before they are submitted.
-
-### Merging can sometimes take a while
-If your pull request follows all of the advice above but still has not been merged, this usually means that the developers haven't had time to review it yet. We understand that this might feel frustrating, and we apologize. The Signal team is still small, but [we are hiring](https://signal.org/workworkwork/).
-
-
-## How can I contribute?
-There are several other ways to get involved:
-* Help new users learn about Signal.
- * Redirect support questions to support@signal.org and the [Signal Support Center](https://support.signal.org/).
- * Redirect non-bug discussions to the [community forum](https://community.signalusers.org).
-* Improve documentation in the [wiki](https://github.com/signalapp/Signal-Android/wiki).
-* Join the community of volunteer translators on Transifex:
- * [Android](https://www.transifex.com/signalapp/signal-android/)
- * [iOS](https://www.transifex.com/signalapp/signal-ios/)
- * [Desktop](https://www.transifex.com/signalapp/signal-desktop/)
-* Find and mark duplicate issues.
-* Try to reproduce issues and help with troubleshooting.
-* Discover solutions to open issues and post any relevant findings.
-* Test other people's pull requests.
-* Contribute to Signal via the [Freedom of the Press Foundation's donation page](https://freedom.press/crowdfunding/signal/).
-* Share Signal with your friends and family.
-
-Signal is made for you. Thank you for your feedback and support.
diff --git a/build.gradle b/build.gradle
index 41e2ac5d37..392ad467f4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,6 +6,8 @@ buildscript {
ext.kovenant_version = "3.3.0"
ext.identicon_version = "v11"
ext.rss_parser_version = "2.0.4"
+ ext.google_services_version = "4.3.3"
+ ext.firebase_messaging_version = "18.0.0"
repositories {
mavenLocal()
@@ -16,6 +18,7 @@ buildscript {
classpath "com.android.tools.build:gradle:$gradle_version"
classpath files('libs/gradle-witness.jar')
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath "com.google.gms:google-services:$google_services_version"
}
}
@@ -24,6 +27,7 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'
apply plugin: 'witness'
apply plugin: 'kotlin-kapt'
+apply plugin: 'com.google.gms.google-services'
repositories {
mavenLocal()
@@ -87,6 +91,8 @@ dependencies {
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'android.arch.lifecycle:common-java8:1.1.1'
+ implementation "com.google.firebase:firebase-messaging:$firebase_messaging_version"
+
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
@@ -184,8 +190,8 @@ dependencies {
implementation "com.github.ybq:Android-SpinKit:1.4.0"
}
-def canonicalVersionCode = 48
-def canonicalVersionName = "1.0.11"
+def canonicalVersionCode = 49
+def canonicalVersionName = "1.1.0"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,
diff --git a/res/drawable/pn_option_background.xml b/res/drawable/pn_option_background.xml
new file mode 100644
index 0000000000..b43e8de341
--- /dev/null
+++ b/res/drawable/pn_option_background.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/pn_option_background_deselect_transition.xml b/res/drawable/pn_option_background_deselect_transition.xml
new file mode 100644
index 0000000000..7fcb8e1160
--- /dev/null
+++ b/res/drawable/pn_option_background_deselect_transition.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/pn_option_background_select_transition.xml b/res/drawable/pn_option_background_select_transition.xml
new file mode 100644
index 0000000000..21c58cf71d
--- /dev/null
+++ b/res/drawable/pn_option_background_select_transition.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/pn_option_background_selected.xml b/res/drawable/pn_option_background_selected.xml
new file mode 100644
index 0000000000..56d1ed983a
--- /dev/null
+++ b/res/drawable/pn_option_background_selected.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout-sw400dp/activity_pn_mode.xml b/res/layout-sw400dp/activity_pn_mode.xml
new file mode 100644
index 0000000000..8edc9267df
--- /dev/null
+++ b/res/layout-sw400dp/activity_pn_mode.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout-sw400dp/fragment_pn_mode_bottom_sheet.xml b/res/layout-sw400dp/fragment_pn_mode_bottom_sheet.xml
new file mode 100644
index 0000000000..0b01b12fdc
--- /dev/null
+++ b/res/layout-sw400dp/fragment_pn_mode_bottom_sheet.xml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/activity_home.xml b/res/layout/activity_home.xml
index ce436d7b31..c75bf7218b 100644
--- a/res/layout/activity_home.xml
+++ b/res/layout/activity_home.xml
@@ -73,6 +73,32 @@
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true" />
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/activity_linked_devices.xml b/res/layout/activity_linked_devices.xml
index 4fe783f514..028a7f82bf 100644
--- a/res/layout/activity_linked_devices.xml
+++ b/res/layout/activity_linked_devices.xml
@@ -31,7 +31,7 @@
android:layout_width="196dp"
android:layout_height="@dimen/medium_button_height"
android:layout_marginTop="@dimen/medium_spacing"
- android:text="Link a Device" />
+ android:text="Link a Device (Beta)" />
diff --git a/res/layout/activity_pn_mode.xml b/res/layout/activity_pn_mode.xml
new file mode 100644
index 0000000000..25f868d1f8
--- /dev/null
+++ b/res/layout/activity_pn_mode.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/fragment_pn_mode_bottom_sheet.xml b/res/layout/fragment_pn_mode_bottom_sheet.xml
new file mode 100644
index 0000000000..48fc5f3a31
--- /dev/null
+++ b/res/layout/fragment_pn_mode_bottom_sheet.xml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 958e2a8bd1..374020fd95 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -26,6 +26,8 @@
#3F4146#99FFFFFF#1F1F1F
+ #1B1B1B
+ #212121
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3ed8a9a187..16ed23c8fe 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -32,6 +32,7 @@
56dp8dp4dp
+ 8dp8dp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1f8e364b2b..480008a35a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1671,4 +1671,26 @@
ContactsClosed GroupsOpen Groups
+ Push Notifications
+ There are two ways Session can handle push notifications. Make sure to read the descriptions carefully before you choose.
+ Firebase Cloud Messaging
+ 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.
+ Background Polling
+ Session will occasionally check for new messages in the background. This guarantees full privacy protection, but message notifications may be significantly delayed.
+ Recommended
+ Please Pick an Option
+ Push Notifications
+ Session now features two ways to handle push notifications. Make sure to read the descriptions carefully before you choose.
+ Firebase Cloud Messaging
+ 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.
+ Background Polling
+ Session will occasionally check for new messages in the background. This guarantees full privacy protection, but message notifications may be significantly delayed.
+ Recommended
+ Please Pick an Option
+ Confirm
+ Skip
+ Notification Strategy
+ Use FCM
+ Using Firebase Cloud Messaging allows for more reliable push notifications, but exposes your IP to Google.
+
diff --git a/res/xml/preferences_notifications.xml b/res/xml/preferences_notifications.xml
index 12dafb675a..9a9c918179 100644
--- a/res/xml/preferences_notifications.xml
+++ b/res/xml/preferences_notifications.xml
@@ -21,6 +21,19 @@
+
+
+
+
+
+
+
+
{
+ if (!task.isSuccessful()) {
+ Log.w(TAG, "getInstanceId failed", task.getException());
+ return;
+ }
+ String token = task.getResult().getToken();
+ String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
+ if (userHexEncodedPublicKey == null) return;
+ if (TextSecurePreferences.isUsingFCM(this)) {
+ LokiPushNotificationManager.register(token, userHexEncodedPublicKey, context, force);
+ } else {
+ LokiPushNotificationManager.unregister(token, context);
+ }
+ });
+ }
+
@Override
public void ping(@NotNull String s) {
// TODO: Implement
@@ -509,8 +533,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
}
public void createRSSFeedsIfNeeded() {
+ return;
+ /*
ArrayList feeds = new ArrayList<>();
-// feeds.add(lokiNewsFeed());
+ feeds.add(lokiNewsFeed());
feeds.add(lokiMessengerUpdatesFeed());
for (LokiRSSFeed feed : feeds) {
boolean isFeedSetUp = TextSecurePreferences.isChatSetUp(this, feed.getId());
@@ -519,9 +545,12 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
TextSecurePreferences.markChatSetUp(this, feed.getId());
}
}
+ */
}
private void createRSSFeedPollersIfNeeded() {
+ return;
+ /*
// Only create the RSS feed pollers if their threads aren't deleted
LokiRSSFeed lokiNewsFeed = lokiNewsFeed();
long lokiNewsFeedThreadID = GroupManager.getRSSFeedThreadId(lokiNewsFeed.getId(), this);
@@ -537,6 +566,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
if (lokiMessengerUpdatesFeedPoller == null) {
lokiMessengerUpdatesFeedPoller = new LokiRSSFeedPoller(this, lokiMessengerUpdatesFeed());
}
+ */
}
private void setUpThreadDeletionListeners(long threadID, Runnable onDelete) {
@@ -561,9 +591,12 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
}
public void startRSSFeedPollersIfNeeded() {
+ return;
+ /*
createRSSFeedPollersIfNeeded();
if (lokiNewsFeedPoller != null) lokiNewsFeedPoller.startIfNeeded();
if (lokiMessengerUpdatesFeedPoller != null) lokiMessengerUpdatesFeedPoller.startIfNeeded();
+ */
}
private void resubmitProfilePictureIfNeeded() {
diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
index 1377386fe5..a7ce551c87 100644
--- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
@@ -317,7 +317,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private Button makeDefaultSmsButton;
private Button registerButton;
private InputAwareLayout container;
- private View composePanel;
protected Stub reminderView;
private Stub unverifiedBannerView;
private Stub groupShareProfileView;
@@ -552,7 +551,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateTitleTextView(recipient);
updateSubtitleTextView();
setActionBarColor(recipient.getColor());
- setBlockedUserState(recipient, isSecureText, isDefaultSms);
+ updateInputUI(recipient, isSecureText, isDefaultSms);
setGroupShareProfileReminder(recipient);
calculateCharactersRemaining();
@@ -645,7 +644,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateTitleTextView(recipient);
updateSubtitleTextView();
NotificationChannels.updateContactChannelName(this, recipient);
- setBlockedUserState(recipient, isSecureText, isDefaultSms);
+ updateInputUI(recipient, isSecureText, isDefaultSms);
supportInvalidateOptionsMenu();
break;
case TAKE_PHOTO:
@@ -858,6 +857,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
searchViewModel.onSearchClosed();
searchNav.setVisibility(View.GONE);
inputPanel.setVisibility(View.VISIBLE);
+ updateInputUI(recipient, isSecureText, isDefaultSms);
fragment.onSearchQueryUpdated(null);
invalidateOptionsMenu();
return true;
@@ -1343,7 +1343,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
calculateCharactersRemaining();
supportInvalidateOptionsMenu();
- setBlockedUserState(recipient, isSecureText, isDefaultSms);
+ updateInputUI(recipient, isSecureText, isDefaultSms);
}
///// Initializers
@@ -1627,7 +1627,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
unblockButton = ViewUtil.findById(this, R.id.unblock_button);
makeDefaultSmsButton = ViewUtil.findById(this, R.id.make_default_sms_button);
registerButton = ViewUtil.findById(this, R.id.register_button);
- composePanel = ViewUtil.findById(this, R.id.bottom_panel);
container = ViewUtil.findById(this, R.id.layout_container);
reminderView = ViewUtil.findStubById(this, R.id.reminder_stub);
unverifiedBannerView = ViewUtil.findStubById(this, R.id.unverified_banner_stub);
@@ -1835,7 +1834,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateTitleTextView(recipient);
updateSubtitleTextView();
// titleView.setVerified(identityRecords.isVerified());
- setBlockedUserState(recipient, isSecureText, isDefaultSms);
+ updateInputUI(recipient, isSecureText, isDefaultSms);
setActionBarColor(recipient.getColor());
setGroupShareProfileReminder(recipient);
updateReminders(recipient.hasSeenInviteReminder());
@@ -2043,29 +2042,30 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
setStatusBarColor(getResources().getColor(R.color.action_bar_background));
}
- private void setBlockedUserState(Recipient recipient, boolean isSecureText, boolean isDefaultSms) {
- if (recipient.isGroupRecipient() && recipient.getAddress().isRSSFeed()) {
+ // FIXME: This name is confusing because we also have updateInputPanel and setInputPanelEnabled
+ private void updateInputUI(Recipient recipient, boolean isSecureText, boolean isDefaultSms) {
+ if (recipient.isGroupRecipient() && !isActiveGroup()) {
unblockButton.setVisibility(View.GONE);
- composePanel.setVisibility(View.GONE);
+ inputPanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE);
registerButton.setVisibility(View.GONE);
} else if (recipient.isBlocked()) {
unblockButton.setVisibility(View.VISIBLE);
- composePanel.setVisibility(View.GONE);
+ inputPanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE);
registerButton.setVisibility(View.GONE);
} else if (!isSecureText && isPushGroupConversation()) {
unblockButton.setVisibility(View.GONE);
- composePanel.setVisibility(View.GONE);
+ inputPanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE);
registerButton.setVisibility(View.VISIBLE);
} else if (!isSecureText && !isDefaultSms) {
unblockButton.setVisibility(View.GONE);
- composePanel.setVisibility(View.GONE);
+ inputPanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.VISIBLE);
registerButton.setVisibility(View.GONE);
} else {
- composePanel.setVisibility(View.VISIBLE);
+ inputPanel.setVisibility(View.VISIBLE);
unblockButton.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE);
registerButton.setVisibility(View.GONE);
@@ -2125,7 +2125,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private boolean isActiveGroup() {
- if (!isGroupConversation()) return false;
+ if (!isGroupConversation() || recipient.getAddress().isRSSFeed()) return false;
Optional record = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getAddress().toGroupString());
return record.isPresent() && record.get().isActive();
@@ -2314,7 +2314,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
String hint = enabled ? "Message" : "Pending session request";
inputPanel.setHint(hint);
inputPanel.setEnabled(enabled);
- if (enabled) {
+ if (enabled && inputPanel.getVisibility() == View.VISIBLE) {
inputPanel.composeText.requestFocus();
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
inputMethodManager.showSoftInput(inputPanel.composeText, 0);
@@ -2939,6 +2939,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void handleReplyMessage(MessageRecord messageRecord) {
+ if (recipient.isGroupRecipient() && !isActiveGroup()) { return; }
+
Recipient author;
if (messageRecord.isOutgoing()) {
diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
index 916491ac71..3711ddb8b0 100644
--- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
+++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java
@@ -137,49 +137,57 @@ public class GroupMessageProcessor {
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
String id = GroupUtil.getEncodedId(group);
- // Only update group if admin sent the message
+ String ourHexEncodedPublicKey = getMasterHexEncodedPublicKey(context, TextSecurePreferences.getLocalNumber(context));
+
if (group.getGroupType() == SignalServiceGroup.GroupType.SIGNAL) {
+ // Only update group if the group admin sent the message
String hexEncodedPublicKey = getMasterHexEncodedPublicKey(context, content.getSender());
if (!groupRecord.getAdmins().contains(Address.fromSerialized(hexEncodedPublicKey))) {
Log.d("Loki - Group Message", "Received a group update message from a non-admin user for " + id +". Ignoring.");
return null;
}
+
+ // We should only process update messages if we're in the group
+ Address ourAddress = Address.fromSerialized(ourHexEncodedPublicKey);
+ if (!groupRecord.getMembers().contains(ourAddress) &&
+ !group.getMembers().or(Collections.emptyList()).contains(ourHexEncodedPublicKey)) {
+ Log.d("Loki - Group Message", "Received a group update message from a group we are not a member of: " + id + "; ignoring.");
+ database.setActive(id, false);
+ return null;
+ }
}
- Set recordMembers = new HashSet<>(groupRecord.getMembers());
- Set messageMembers = new HashSet<>();
+ Set currentMembers = new HashSet<>(groupRecord.getMembers());
+ Set newMembers = new HashSet<>();
for (String messageMember : group.getMembers().get()) {
- messageMembers.add(Address.fromExternal(context, messageMember));
+ newMembers.add(Address.fromExternal(context, messageMember));
}
- Set addedMembers = new HashSet<>(messageMembers);
- addedMembers.removeAll(recordMembers);
+ // Added members are the members who are present in newMembers but not in currentMembers
+ Set addedMembers = new HashSet<>(newMembers);
+ addedMembers.removeAll(currentMembers);
- Set missingMembers = new HashSet<>(recordMembers);
- missingMembers.removeAll(messageMembers);
+ // Kicked members are members who are present in currentMembers but not in newMembers
+ Set removedMembers = new HashSet<>(currentMembers);
+ removedMembers.removeAll(newMembers);
GroupContext.Builder builder = createGroupContext(group);
builder.setType(GroupContext.Type.UPDATE);
- if (addedMembers.size() > 0) {
- Set unionMembers = new HashSet<>(recordMembers);
- unionMembers.addAll(messageMembers);
- database.updateMembers(id, new LinkedList<>(unionMembers));
-
- builder.clearMembers();
-
- for (Address addedMember : addedMembers) {
- builder.addMembers(addedMember.serialize());
- }
- } else {
- builder.clearMembers();
+ // Update our group members if they're different
+ if (!currentMembers.equals(newMembers)) {
+ database.updateMembers(id, new LinkedList<>(newMembers));
}
- if (missingMembers.size() > 0) {
- for (Address removedMember : missingMembers) {
- builder.addMembers(removedMember.serialize());
- }
+ // We add any new or removed members to the group context
+ // This will allow us later to iterate over them to check if they left or were added for UI purposes
+ for (Address addedMember : addedMembers) {
+ builder.addNewMembers(addedMember.serialize());
+ }
+
+ for (Address removedMember : removedMembers) {
+ builder.addRemovedMembers(removedMember.serialize());
}
if (group.getName().isPresent() || group.getAvatar().isPresent()) {
@@ -191,10 +199,15 @@ public class GroupMessageProcessor {
builder.clearName();
}
- if (!groupRecord.isActive()) database.setActive(id, true);
+ // If we were removed then we need to disable the chat
+ if (removedMembers.contains(Address.fromSerialized(ourHexEncodedPublicKey))) {
+ database.setActive(id, false);
+ } else {
+ if (!groupRecord.isActive()) database.setActive(id, true);
- if (group.getMembers().isPresent()) {
- establishSessionsWithMembersIfNeeded(context, group.getMembers().get());
+ if (group.getMembers().isPresent()) {
+ establishSessionsWithMembersIfNeeded(context, group.getMembers().get());
+ }
}
return storeMessage(context, content, group, builder.build(), outgoing);
diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
index 0ecea52e29..f91b97f574 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
@@ -1895,7 +1895,28 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
boolean isGroupActive = groupId.isPresent() && groupDatabase.isActive(groupId.get());
boolean isLeaveMessage = message.getGroupInfo().isPresent() && message.getGroupInfo().get().getType() == SignalServiceGroup.Type.QUIT;
- return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage);
+ boolean isClosedGroup = conversation.getAddress().isSignalGroup();
+ boolean isGroupMember = true;
+
+ // Only allow messages from group members
+ if (isClosedGroup) {
+ String senderHexEncodedPublicKey = content.getSender();
+
+ try {
+ String masterHexEncodedPublicKey = PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(content.getSender()), 5000).get();
+ if (masterHexEncodedPublicKey != null) {
+ senderHexEncodedPublicKey = masterHexEncodedPublicKey;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ Recipient senderMasterAddress = Recipient.from(context, Address.fromSerialized(senderHexEncodedPublicKey), false);
+
+ isGroupMember = groupId.isPresent() && groupDatabase.getGroupMembers(groupId.get(), true).contains(senderMasterAddress);
+ }
+
+ return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage) || (isContentMessage && !isGroupMember);
} else {
return sender.isBlocked();
}
diff --git a/src/org/thoughtcrime/securesms/loki/LokiPushNotificationManager.kt b/src/org/thoughtcrime/securesms/loki/LokiPushNotificationManager.kt
new file mode 100644
index 0000000000..8b60462d81
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/LokiPushNotificationManager.kt
@@ -0,0 +1,82 @@
+package org.thoughtcrime.securesms.loki
+
+import android.content.Context
+import okhttp3.*
+import org.thoughtcrime.securesms.util.TextSecurePreferences
+import org.whispersystems.libsignal.logging.Log
+import org.whispersystems.signalservice.internal.util.JsonUtil
+import org.whispersystems.signalservice.loki.api.LokiPushNotificationAcknowledgement
+import java.io.IOException
+
+object LokiPushNotificationManager {
+ private val connection = OkHttpClient()
+
+ private val server by lazy {
+ LokiPushNotificationAcknowledgement.shared.server
+ }
+
+ private const val tokenExpirationInterval = 12 * 60 * 60 * 1000
+
+ @JvmStatic
+ fun unregister(token: String, context: Context?) {
+ val parameters = mapOf( "token" to token )
+ val url = "${server}/register"
+ val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
+ val request = Request.Builder().url(url).post(body).build()
+ connection.newCall(request).enqueue(object : Callback {
+
+ override fun onResponse(call: Call, response: Response) {
+ when (response.code()) {
+ 200 -> {
+ val bodyAsString = response.body()!!.string()
+ val json = JsonUtil.fromJson(bodyAsString, Map::class.java)
+ val code = json?.get("code") as? Int
+ if (code != null && code != 0) {
+ TextSecurePreferences.setIsUsingFCM(context, false)
+ } else {
+ Log.d("Loki", "Couldn't disable FCM due to error: ${json?.get("message") as? String ?: "null"}.")
+ }
+ }
+ }
+ }
+
+ override fun onFailure(call: Call, exception: IOException) {
+ Log.d("Loki", "Couldn't disable FCM.")
+ }
+ })
+ }
+
+ @JvmStatic
+ fun register(token: String, hexEncodedPublicKey: String, context: Context?, force: Boolean) {
+ val oldToken = TextSecurePreferences.getFCMToken(context)
+ val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context)
+ if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return }
+ val parameters = mapOf( "token" to token, "pubKey" to hexEncodedPublicKey )
+ val url = "${server}/register"
+ val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
+ val request = Request.Builder().url(url).post(body).build()
+ connection.newCall(request).enqueue(object : Callback {
+
+ override fun onResponse(call: Call, response: Response) {
+ when (response.code()) {
+ 200 -> {
+ val bodyAsString = response.body()!!.string()
+ val json = JsonUtil.fromJson(bodyAsString, Map::class.java)
+ val code = json?.get("code") as? Int
+ if (code != null && code != 0) {
+ TextSecurePreferences.setIsUsingFCM(context, true)
+ TextSecurePreferences.setFCMToken(context, token)
+ TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis())
+ } else {
+ Log.d("Loki", "Couldn't register for FCM due to error: ${json?.get("message") as? String ?: "null"}.")
+ }
+ }
+ }
+ }
+
+ override fun onFailure(call: Call, exception: IOException) {
+ Log.d("Loki", "Couldn't register for FCM.")
+ }
+ })
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/CreateClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/CreateClosedGroupActivity.kt
index d3fe0743ce..710aa98f66 100644
--- a/src/org/thoughtcrime/securesms/loki/redesign/activities/CreateClosedGroupActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/CreateClosedGroupActivity.kt
@@ -47,7 +47,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
setContentView(R.layout.activity_create_closed_group)
- supportActionBar!!.title = "New Closed Group"
+ supportActionBar!!.title = "New Closed Group (Beta)"
recyclerView.adapter = createClosedGroupAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() }
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/DisplayNameActivity.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/DisplayNameActivity.kt
index 2c9b06e47e..f3f1d3f2b4 100644
--- a/src/org/thoughtcrime/securesms/loki/redesign/activities/DisplayNameActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/DisplayNameActivity.kt
@@ -9,11 +9,9 @@ import android.widget.TextView.OnEditorActionListener
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_display_name.*
import network.loki.messenger.R
-import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity
-import org.thoughtcrime.securesms.database.DatabaseFactory
+import org.thoughtcrime.securesms.loki.redesign.utilities.push
import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo
-import org.thoughtcrime.securesms.loki.redesign.utilities.show
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.crypto.ProfileCipher
@@ -25,16 +23,14 @@ class DisplayNameActivity : BaseActionBarActivity() {
setContentView(R.layout.activity_display_name)
displayNameEditText.imeOptions = displayNameEditText.imeOptions or 16777216 // Always use incognito keyboard
displayNameEditText.setOnEditorActionListener(
- OnEditorActionListener { _, actionId, event ->
- // Handle validation from keyboard to trigger registration
- if (actionId == EditorInfo.IME_ACTION_SEARCH ||
- actionId == EditorInfo.IME_ACTION_DONE ||
- (event.action === KeyEvent.ACTION_DOWN
- && event.keyCode === KeyEvent.KEYCODE_ENTER)) {
- this.register();
+ OnEditorActionListener { _, actionID, event ->
+ if (actionID == EditorInfo.IME_ACTION_SEARCH ||
+ actionID == EditorInfo.IME_ACTION_DONE ||
+ (event.action == KeyEvent.ACTION_DOWN &&
+ event.keyCode == KeyEvent.KEYCODE_ENTER)) {
+ this.register()
return@OnEditorActionListener true
}
- // Return true if you have consumed the action, else false.
false
})
registerButton.setOnClickListener { register() }
@@ -54,20 +50,7 @@ class DisplayNameActivity : BaseActionBarActivity() {
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(displayNameEditText.windowToken, 0)
TextSecurePreferences.setProfileName(this, displayName)
- TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
- TextSecurePreferences.setPromptedPushRegistration(this, true)
- val application = ApplicationContext.getInstance(this)
- application.setUpStorageAPIIfNeeded()
- application.setUpP2PAPI()
- val publicChatAPI = ApplicationContext.getInstance(this).lokiPublicChatAPI
- if (publicChatAPI != null) {
- // TODO: This won't be necessary anymore when we don't auto-join the Loki Public Chat anymore
- application.createDefaultPublicChatsIfNeeded()
- val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
- servers.forEach { publicChatAPI.setDisplayName(displayName, it) }
- }
- val intent = Intent(this, HomeActivity::class.java)
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
- show(intent)
+ val intent = Intent(this, PNModeActivity::class.java)
+ push(intent)
}
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt
index 7e99a11ac7..a6e8c8d997 100644
--- a/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt
@@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.loki.getColorWithID
+import org.thoughtcrime.securesms.loki.redesign.dialogs.PNModeBottomSheet
import org.thoughtcrime.securesms.loki.redesign.utilities.push
import org.thoughtcrime.securesms.loki.redesign.utilities.show
import org.thoughtcrime.securesms.loki.redesign.views.ConversationView
@@ -111,6 +112,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
recyclerView.adapter = homeAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
ItemTouchHelper(SwipeCallback(this)).attachToRecyclerView(recyclerView)
+ // Set up empty state view
+ createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() }
// This is a workaround for the fact that CursorRecyclerViewAdapter doesn't actually auto-update (even though it says it will)
LoaderManager.getInstance(this).restartLoader(0, null, object : LoaderManager.LoaderCallbacks {
@@ -120,6 +123,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
override fun onLoadFinished(loader: Loader, cursor: Cursor?) {
homeAdapter.changeCursor(cursor)
+ updateEmptyState()
}
override fun onLoaderReset(cursor: Loader) {
@@ -156,32 +160,20 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
if (hasViewedSeed || !isMasterDevice) {
seedReminderView.visibility = View.GONE
}
-// if (!TextSecurePreferences.getHasSeenOpenGroupSuggestionSheet(this)) {
-// val bottomSheet = OpenGroupSuggestionBottomSheet()
-// bottomSheet.onJoinTapped = {
-// TextSecurePreferences.setHasSeenOpenGroupSuggestionSheet(this)
-// bottomSheet.dismiss()
-// // TODO: Duplication of the code in JoinPublicChatActivity
-// val application = ApplicationContext.getInstance(this)
-// val channel: Long = 1
-// val displayName = TextSecurePreferences.getProfileName(this)
-// val lokiPublicChatAPI = application.lokiPublicChatAPI!!
-// val url = "https://chat.getsession.org"
-// application.lokiPublicChatManager.addChat(url, channel).successUi {
-// lokiPublicChatAPI.getMessages(channel, url)
-// lokiPublicChatAPI.setDisplayName(displayName, url)
-// lokiPublicChatAPI.join(channel, url)
-// val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(this)
-// val profileUrl: String? = TextSecurePreferences.getProfileAvatarUrl(this)
-// lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl)
-// }
-// }
-// bottomSheet.onDismissTapped = {
-// TextSecurePreferences.setHasSeenOpenGroupSuggestionSheet(this)
-// bottomSheet.dismiss()
-// }
-// bottomSheet.show(supportFragmentManager, bottomSheet.tag)
-// }
+ if (!TextSecurePreferences.hasSeenPNModeSheet(this)) {
+ val bottomSheet = PNModeBottomSheet()
+ bottomSheet.onConfirmTapped = { isUsingFCM ->
+ TextSecurePreferences.setHasSeenPNModeSheet(this, true)
+ TextSecurePreferences.setIsUsingFCM(this, isUsingFCM)
+ ApplicationContext.getInstance(this).registerForFCMIfNeeded(true)
+ bottomSheet.dismiss()
+ }
+ bottomSheet.onSkipTapped = {
+ TextSecurePreferences.setHasSeenPNModeSheet(this, true)
+ bottomSheet.dismiss()
+ }
+ bottomSheet.show(supportFragmentManager, bottomSheet.tag)
+ }
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -192,6 +184,14 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
}
// endregion
+ // region Updating
+ private fun updateEmptyState() {
+ val threadCount = (recyclerView.adapter as HomeAdapter).itemCount
+ emptyStateContainer.visibility = if (threadCount == 0) View.VISIBLE else View.GONE
+ }
+ // endregion
+
+ // region Interaction
override fun handleSeedReminderViewContinueButtonTapped() {
val intent = Intent(this, SeedActivity::class.java)
show(intent)
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/PNModeActivity.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/PNModeActivity.kt
new file mode 100644
index 0000000000..2aa0a403dd
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/PNModeActivity.kt
@@ -0,0 +1,112 @@
+package org.thoughtcrime.securesms.loki.redesign.activities
+
+import android.app.AlertDialog
+import android.content.Intent
+import android.graphics.drawable.TransitionDrawable
+import android.os.Bundle
+import android.os.Handler
+import android.support.annotation.DrawableRes
+import android.view.View
+import android.widget.LinearLayout
+import android.widget.Toast
+import kotlinx.android.synthetic.main.activity_display_name.registerButton
+import kotlinx.android.synthetic.main.activity_home.*
+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.database.DatabaseFactory
+import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo
+import org.thoughtcrime.securesms.loki.redesign.utilities.show
+import org.thoughtcrime.securesms.util.GroupUtil
+import org.thoughtcrime.securesms.util.TextSecurePreferences
+
+class PNModeActivity : BaseActionBarActivity() {
+ private var selectedOptionView: LinearLayout? = null
+
+ // region Lifecycle
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setUpActionBarSessionLogo()
+ setContentView(R.layout.activity_pn_mode)
+ fcmOptionView.setOnClickListener { toggleFCM() }
+ backgroundPollingOptionView.setOnClickListener { toggleBackgroundPolling() }
+ registerButton.setOnClickListener { register() }
+ }
+ // endregion
+
+ // region Animation
+ private fun performTransition(@DrawableRes transitionID: Int, subject: View) {
+ val drawable = resources.getDrawable(transitionID, theme) as TransitionDrawable
+ subject.background = drawable
+ drawable.startTransition(250)
+ }
+ // endregion
+
+ // region Interaction
+ private fun toggleFCM() {
+ when (selectedOptionView) {
+ null -> {
+ performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
+ selectedOptionView = fcmOptionView
+ }
+ fcmOptionView -> {
+ performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
+ selectedOptionView = null
+ }
+ backgroundPollingOptionView -> {
+ performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
+ performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
+ selectedOptionView = fcmOptionView
+ }
+ }
+ }
+
+ private fun toggleBackgroundPolling() {
+ when (selectedOptionView) {
+ null -> {
+ performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
+ selectedOptionView = backgroundPollingOptionView
+ }
+ backgroundPollingOptionView -> {
+ performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
+ selectedOptionView = null
+ }
+ fcmOptionView -> {
+ performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
+ performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
+ selectedOptionView = backgroundPollingOptionView
+ }
+ }
+ }
+
+ private fun register() {
+ if (selectedOptionView == null) {
+ val dialog = AlertDialog.Builder(this)
+ dialog.setTitle(R.string.activity_pn_mode_no_option_picked_dialog_title)
+ dialog.setPositiveButton(R.string.ok) { _, _ -> }
+ dialog.create().show()
+ return
+ }
+ val displayName = TextSecurePreferences.getProfileName(this)
+ TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
+ TextSecurePreferences.setPromptedPushRegistration(this, true)
+ TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == fcmOptionView))
+ TextSecurePreferences.setHasSeenPNModeSheet(this, true) // Shouldn't be shown to users who've done the new onboarding
+ val application = ApplicationContext.getInstance(this)
+ application.setUpStorageAPIIfNeeded()
+ application.setUpP2PAPI()
+ val publicChatAPI = ApplicationContext.getInstance(this).lokiPublicChatAPI
+ if (publicChatAPI != null) {
+ // TODO: This won't be necessary anymore when we don't auto-join the Loki Public Chat anymore
+ application.createDefaultPublicChatsIfNeeded()
+ val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
+ servers.forEach { publicChatAPI.setDisplayName(displayName, it) }
+ }
+ application.registerForFCMIfNeeded(true)
+ val intent = Intent(this, HomeActivity::class.java)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ show(intent)
+ }
+ // endregion
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/dialogs/OpenGroupSuggestionBottomSheet.kt b/src/org/thoughtcrime/securesms/loki/redesign/dialogs/OpenGroupSuggestionBottomSheet.kt
index 274b36af74..18802dee97 100644
--- a/src/org/thoughtcrime/securesms/loki/redesign/dialogs/OpenGroupSuggestionBottomSheet.kt
+++ b/src/org/thoughtcrime/securesms/loki/redesign/dialogs/OpenGroupSuggestionBottomSheet.kt
@@ -8,7 +8,7 @@ import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_open_group_suggestion_bottom_sheet.*
import network.loki.messenger.R
-public class OpenGroupSuggestionBottomSheet : BottomSheetDialogFragment() {
+class OpenGroupSuggestionBottomSheet : BottomSheetDialogFragment() {
var onJoinTapped: (() -> Unit)? = null
var onDismissTapped: (() -> Unit)? = null
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/dialogs/PNModeBottomSheet.kt b/src/org/thoughtcrime/securesms/loki/redesign/dialogs/PNModeBottomSheet.kt
new file mode 100644
index 0000000000..749d9d5263
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/redesign/dialogs/PNModeBottomSheet.kt
@@ -0,0 +1,100 @@
+package org.thoughtcrime.securesms.loki.redesign.dialogs
+
+import android.app.AlertDialog
+import android.content.DialogInterface
+import android.graphics.drawable.TransitionDrawable
+import android.os.Bundle
+import android.support.annotation.DrawableRes
+import android.support.design.widget.BottomSheetDialogFragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import kotlinx.android.synthetic.main.fragment_pn_mode_bottom_sheet.*
+import network.loki.messenger.R
+import org.thoughtcrime.securesms.util.TextSecurePreferences
+
+class PNModeBottomSheet : BottomSheetDialogFragment() {
+ private var selectedOptionView: LinearLayout? = null
+ var onConfirmTapped: ((Boolean) -> Unit)? = null
+ var onSkipTapped: (() -> Unit)? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setStyle(STYLE_NORMAL, R.style.SessionBottomSheetDialogTheme)
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ return inflater.inflate(R.layout.fragment_pn_mode_bottom_sheet, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ fcmOptionView.setOnClickListener { toggleFCM() }
+ backgroundPollingOptionView.setOnClickListener { toggleBackgroundPolling() }
+ confirmButton.setOnClickListener { confirm() }
+ skipButton.setOnClickListener { onSkipTapped?.invoke() }
+ }
+
+ override fun onDismiss(dialog: DialogInterface?) {
+ TextSecurePreferences.setHasSeenPNModeSheet(context, true)
+ super.onDismiss(dialog)
+ }
+
+ // region Animation
+ private fun performTransition(@DrawableRes transitionID: Int, subject: View) {
+ val drawable = resources.getDrawable(transitionID, context!!.theme) as TransitionDrawable
+ subject.background = drawable
+ drawable.startTransition(250)
+ }
+ // endregion
+
+ // region Interaction
+ private fun toggleFCM() {
+ when (selectedOptionView) {
+ null -> {
+ performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
+ selectedOptionView = fcmOptionView
+ }
+ fcmOptionView -> {
+ performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
+ selectedOptionView = null
+ }
+ backgroundPollingOptionView -> {
+ performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
+ performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
+ selectedOptionView = fcmOptionView
+ }
+ }
+ }
+
+ private fun toggleBackgroundPolling() {
+ when (selectedOptionView) {
+ null -> {
+ performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
+ selectedOptionView = backgroundPollingOptionView
+ }
+ backgroundPollingOptionView -> {
+ performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
+ selectedOptionView = null
+ }
+ fcmOptionView -> {
+ performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
+ performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
+ selectedOptionView = backgroundPollingOptionView
+ }
+ }
+ }
+
+ private fun confirm() {
+ if (selectedOptionView == null) {
+ val dialog = AlertDialog.Builder(context)
+ dialog.setTitle(R.string.sheet_pn_mode_no_option_picked_dialog_title)
+ dialog.setPositiveButton(R.string.ok) { _, _ -> }
+ dialog.create().show()
+ return
+ }
+ onConfirmTapped?.invoke(selectedOptionView == fcmOptionView)
+ }
+ // endregion
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/messaging/BackgroundPollWorker.kt b/src/org/thoughtcrime/securesms/loki/redesign/messaging/BackgroundPollWorker.kt
index ca0cb7fd4b..46f0251afd 100644
--- a/src/org/thoughtcrime/securesms/loki/redesign/messaging/BackgroundPollWorker.kt
+++ b/src/org/thoughtcrime/securesms/loki/redesign/messaging/BackgroundPollWorker.kt
@@ -28,6 +28,7 @@ class BackgroundPollWorker : PersistentAlarmManagerListener() {
}
override fun onAlarm(context: Context, scheduledTime: Long): Long {
+ if (TextSecurePreferences.isUsingFCM(context)) { return 0L }
if (scheduledTime != 0L) {
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/messaging/LokiRSSFeedPoller.kt b/src/org/thoughtcrime/securesms/loki/redesign/shelved/LokiRSSFeedPoller.kt
similarity index 98%
rename from src/org/thoughtcrime/securesms/loki/redesign/messaging/LokiRSSFeedPoller.kt
rename to src/org/thoughtcrime/securesms/loki/redesign/shelved/LokiRSSFeedPoller.kt
index 27c08a958b..cebe06f21a 100644
--- a/src/org/thoughtcrime/securesms/loki/redesign/messaging/LokiRSSFeedPoller.kt
+++ b/src/org/thoughtcrime/securesms/loki/redesign/shelved/LokiRSSFeedPoller.kt
@@ -1,4 +1,4 @@
-package org.thoughtcrime.securesms.loki.redesign.messaging
+package org.thoughtcrime.securesms.loki.redesign.shelved
import android.content.Context
import android.os.Handler
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/views/NewConversationButtonSetView.kt b/src/org/thoughtcrime/securesms/loki/redesign/views/NewConversationButtonSetView.kt
index 3b1f5de593..1f3bf3973c 100644
--- a/src/org/thoughtcrime/securesms/loki/redesign/views/NewConversationButtonSetView.kt
+++ b/src/org/thoughtcrime/securesms/loki/redesign/views/NewConversationButtonSetView.kt
@@ -39,6 +39,7 @@ class NewConversationButtonSetView : RelativeLayout {
// endregion
// region Settings
+ private val minDragDistance by lazy { toPx(40, resources).toFloat() }
private val maxDragDistance by lazy { toPx(56, resources).toFloat() }
private val dragMargin by lazy { toPx(16, resources).toFloat() }
private val bottomMargin by lazy { resources.getDimension(R.dimen.new_conversation_button_bottom_offset) }
@@ -252,7 +253,8 @@ class NewConversationButtonSetView : RelativeLayout {
expandedButton?.collapse()
this.expandedButton = null
collapse()
- if (event.action == MotionEvent.ACTION_UP) {
+ val distanceFromRestPosition = touch.distanceTo(buttonRestPosition)
+ if (event.action == MotionEvent.ACTION_UP && distanceFromRestPosition > (minDragDistance + mainButton.collapsedSize / 2)) {
if (sessionButton.contains(touch) || touch.isAbove(sessionButton, dragMargin)) { delegate?.createNewPrivateChat() }
else if (closedGroupButton.contains(touch) || touch.isRightOf(closedGroupButton, dragMargin)) { delegate?.createNewClosedGroup() }
else if (openGroupButton.contains(touch) || touch.isLeftOf(openGroupButton, dragMargin)) { delegate?.joinOpenGroup() }
diff --git a/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java
index fd58d1d4b2..0ec4c0efdc 100644
--- a/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java
+++ b/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java
@@ -14,6 +14,7 @@ import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.text.TextUtils;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
@@ -32,6 +33,16 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
+ // Set up FCM toggle
+ String fcmKey = "pref_key_use_fcm";
+ ((SwitchPreferenceCompat)findPreference(fcmKey)).setChecked(TextSecurePreferences.isUsingFCM(getContext()));
+ this.findPreference(fcmKey)
+ .setOnPreferenceChangeListener((preference, newValue) -> {
+ TextSecurePreferences.setIsUsingFCM(getContext(), (boolean) newValue);
+ ApplicationContext.getInstance(getContext()).registerForFCMIfNeeded(true);
+ return true;
+ });
+
Preference ledBlinkPref = this.findPreference(TextSecurePreferences.LED_BLINK_PREF);
if (NotificationChannels.supported()) {
diff --git a/src/org/thoughtcrime/securesms/service/PushNotificationService.kt b/src/org/thoughtcrime/securesms/service/PushNotificationService.kt
new file mode 100644
index 0000000000..0fb0c14319
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/service/PushNotificationService.kt
@@ -0,0 +1,36 @@
+package org.thoughtcrime.securesms.service
+
+import com.google.firebase.messaging.FirebaseMessagingService
+import com.google.firebase.messaging.RemoteMessage
+import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
+import org.thoughtcrime.securesms.loki.LokiPushNotificationManager
+import org.thoughtcrime.securesms.util.TextSecurePreferences
+import org.whispersystems.libsignal.logging.Log
+import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
+import org.whispersystems.signalservice.internal.util.Base64
+import org.whispersystems.signalservice.loki.messaging.LokiMessageWrapper
+
+class PushNotificationService : FirebaseMessagingService() {
+
+ override fun onNewToken(token: String) {
+ super.onNewToken(token)
+ Log.d("Loki", "New FCM token: $token.")
+ val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return
+ LokiPushNotificationManager.register(token, userHexEncodedPublicKey, this, false)
+ }
+
+ override fun onMessageReceived(message: RemoteMessage) {
+ val base64EncodedData = message.data["ENCRYPTED_DATA"]
+ val data = base64EncodedData?.let { Base64.decode(it) }
+ if (data != null) {
+ try {
+ val envelope = LokiMessageWrapper.unwrap(data)
+ PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope))
+ } catch (e: Exception) {
+ Log.d("Loki", "Failed to unwrap data for message.")
+ }
+ } else {
+ Log.d("Loki", "Failed to decode data for message.")
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/util/GroupUtil.java b/src/org/thoughtcrime/securesms/util/GroupUtil.java
index 907a21217e..0029d5f6a4 100644
--- a/src/org/thoughtcrime/securesms/util/GroupUtil.java
+++ b/src/org/thoughtcrime/securesms/util/GroupUtil.java
@@ -10,7 +10,6 @@ import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
-import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -159,7 +158,7 @@ public class GroupUtil {
@NonNull private final Context context;
@Nullable private final GroupContext groupContext;
- private final List members;
+ private final List newMembers;
private final List removedMembers;
private boolean ourDeviceWasRemoved;
@@ -167,35 +166,35 @@ public class GroupUtil {
this.context = context.getApplicationContext();
this.groupContext = groupContext;
- this.members = new LinkedList<>();
+ this.newMembers = new LinkedList<>();
this.removedMembers = new LinkedList<>();
this.ourDeviceWasRemoved = false;
- if (groupContext != null && !groupContext.getMembersList().isEmpty()) {
- List memberList = groupContext.getMembersList();
- List currentMembers = getCurrentGroupMembers();
+ if (groupContext != null) {
+ List newMembers = groupContext.getNewMembersList();
+ for (String member : newMembers) {
+ this.newMembers.add(this.toRecipient(member));
+ }
- // Add them to the member or removed members lists
- for (String member : memberList) {
- Address address = Address.fromSerialized(member);
- Recipient recipient = Recipient.from(context, address, true);
- if (currentMembers == null || currentMembers.contains(address)) {
- this.members.add(recipient);
- } else {
- this.removedMembers.add(recipient);
- }
+ List removedMembers = groupContext.getRemovedMembersList();
+ for (String member : removedMembers) {
+ this.removedMembers.add(this.toRecipient(member));
}
// Check if our device was removed
if (!removedMembers.isEmpty()) {
String masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
String hexEncodedPublicKey = masterHexEncodedPublicKey != null ? masterHexEncodedPublicKey : TextSecurePreferences.getLocalNumber(context);
- Recipient self = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false);
- ourDeviceWasRemoved = removedMembers.contains(self);
+ ourDeviceWasRemoved = removedMembers.contains(hexEncodedPublicKey);
}
}
}
+ private Recipient toRecipient(String hexEncodedPublicKey) {
+ Address address = Address.fromSerialized(hexEncodedPublicKey);
+ return Recipient.from(context, address, false);
+ }
+
public String toString(Recipient sender) {
// Show the local removed message
if (ourDeviceWasRemoved) {
@@ -211,10 +210,10 @@ public class GroupUtil {
String title = groupContext.getName();
- if (!members.isEmpty()) {
+ if (!newMembers.isEmpty()) {
description.append("\n");
description.append(context.getResources().getQuantityString(R.plurals.GroupUtil_joined_the_group,
- members.size(), toString(members)));
+ newMembers.size(), toString(newMembers)));
}
if (!removedMembers.isEmpty()) {
@@ -224,8 +223,8 @@ public class GroupUtil {
}
if (title != null && !title.trim().isEmpty()) {
- if (!members.isEmpty()) description.append(" ");
- else description.append("\n");
+ String separator = (!newMembers.isEmpty() || !removedMembers.isEmpty()) ? " " : "\n";
+ description.append(separator);
description.append(context.getString(R.string.GroupUtil_group_name_is_now, title));
}
@@ -233,8 +232,8 @@ public class GroupUtil {
}
public void addListener(RecipientModifiedListener listener) {
- if (!this.members.isEmpty()) {
- for (Recipient member : this.members) {
+ if (!this.newMembers.isEmpty()) {
+ for (Recipient member : this.newMembers) {
member.addListener(listener);
}
}
@@ -252,23 +251,5 @@ public class GroupUtil {
return result;
}
-
- private List getCurrentGroupMembers() {
- if (groupContext == null) { return null; }
- GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
- byte[] decodedGroupId = groupContext.getId().toByteArray();
- String signalGroupId = getEncodedId(decodedGroupId, false);
- String publicChatId = getEncodedPublicChatId(decodedGroupId);
- String rssFeedId = getEncodedRSSFeedId(decodedGroupId);
- GroupRecord groupRecord = null;
- if (!groupDatabase.isUnknownGroup(signalGroupId)) {
- groupRecord = groupDatabase.getGroup(signalGroupId).orNull();
- } else if (!groupDatabase.isUnknownGroup(publicChatId)) {
- groupRecord = groupDatabase.getGroup(publicChatId).orNull();
- } else if (!groupDatabase.isUnknownGroup(rssFeedId)) {
- groupRecord = groupDatabase.getGroup(rssFeedId).orNull();
- }
- return (groupRecord != null) ? groupRecord.getMembers() : null;
- }
}
}
diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
index 42c204dfbb..d306ab259d 100644
--- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
+++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
@@ -185,6 +185,45 @@ public class TextSecurePreferences {
private static final String MEDIA_KEYBOARD_MODE = "pref_media_keyboard_mode";
+ // region FCM
+ private static final String IS_USING_FCM = "pref_is_using_fcm";
+ private static final String FCM_TOKEN = "pref_fcm_token";
+ private static final String LAST_FCM_TOKEN_UPLOAD_TIME = "pref_last_fcm_token_upload_time";
+ private static final String HAS_SEEN_PN_MODE_SHEET = "pref_has_seen_pn_mode_sheet";
+
+ public static boolean isUsingFCM(Context context) {
+ return getBooleanPreference(context, IS_USING_FCM, false);
+ }
+
+ public static void setIsUsingFCM(Context context, boolean value) {
+ setBooleanPreference(context, IS_USING_FCM, value);
+ }
+
+ public static String getFCMToken(Context context) {
+ return getStringPreference(context, FCM_TOKEN, "");
+ }
+
+ public static void setFCMToken(Context context, String value) {
+ setStringPreference(context, FCM_TOKEN, value);
+ }
+
+ public static long getLastFCMUploadTime(Context context) {
+ return getLongPreference(context, LAST_FCM_TOKEN_UPLOAD_TIME, 0);
+ }
+
+ public static void setLastFCMUploadTime(Context context, long value) {
+ setLongPreference(context, LAST_FCM_TOKEN_UPLOAD_TIME, value);
+ }
+
+ public static boolean hasSeenPNModeSheet(Context context) {
+ return getBooleanPreference(context, HAS_SEEN_PN_MODE_SHEET, false);
+ }
+
+ public static void setHasSeenPNModeSheet(Context context, boolean value) {
+ setBooleanPreference(context, HAS_SEEN_PN_MODE_SHEET, value);
+ }
+ // endregion
+
public static boolean isScreenLockEnabled(@NonNull Context context) {
return getBooleanPreference(context, SCREEN_LOCK, false);
}