Implement new client deprecation UI.

This commit is contained in:
Greyson Parrelli 2020-09-09 10:22:22 -04:00 committed by GitHub
parent d8a489971c
commit 75d567e555
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 341 additions and 69 deletions

View File

@ -523,6 +523,11 @@
<activity android:name=".groups.ui.chooseadmin.ChooseNewAdminActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"/>
<activity android:name=".megaphone.ClientDeprecatedActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:launchMode="singleTask" />
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>

View File

@ -198,7 +198,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
public void checkBuildExpiration() {
if (Util.getTimeUntilBuildExpiry() <= 0 && !SignalStore.misc().isClientDeprecated()) {
Log.w(TAG, "Build expired!");
SignalStore.misc().markDeprecated();
SignalStore.misc().markClientDeprecated();
}
}

View File

@ -2,16 +2,25 @@ package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.PlayStoreUtil;
import java.util.List;
/**
* Showed when a build has fully expired (either via the compile-time constant, or remote
* deprecation).
*/
public class ExpiredBuildReminder extends Reminder {
public ExpiredBuildReminder(final Context context) {
super(context.getString(R.string.reminder_header_expired_build),
context.getString(R.string.reminder_header_expired_build_details));
super(null, context.getString(R.string.ExpiredBuildReminder_this_version_of_signal_has_expired));
setOkListener(v -> PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(context));
addAction(new Action(context.getString(R.string.ExpiredBuildReminder_update_now), R.id.reminder_action_update_now));
}
@Override
@ -19,8 +28,17 @@ public class ExpiredBuildReminder extends Reminder {
return false;
}
@Override
public List<Action> getActions() {
return super.getActions();
}
@Override
public @NonNull Importance getImportance() {
return Importance.TERMINAL;
}
public static boolean isEligible() {
return SignalStore.misc().isClientDeprecated();
}
}

View File

@ -8,20 +8,22 @@ import org.thoughtcrime.securesms.util.Util;
import java.util.concurrent.TimeUnit;
/**
* Reminder that is shown when a build is getting close to expiry (either because of the
* compile-time constant, or remote deprecation).
*/
public class OutdatedBuildReminder extends Reminder {
public OutdatedBuildReminder(final Context context) {
super(context.getString(R.string.reminder_header_outdated_build),
getPluralsText(context));
super(null, getPluralsText(context));
setOkListener(v -> PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(context));
addAction(new Action(context.getString(R.string.OutdatedBuildReminder_update_now), R.id.reminder_action_update_now));
}
private static CharSequence getPluralsText(final Context context) {
int days = getDaysUntilExpiry() - 1;
if (days == 0) {
return context.getString(R.string.reminder_header_outdated_build_details_today);
}
return context.getResources().getQuantityString(R.plurals.reminder_header_outdated_build_details, days, days);
return context.getResources().getQuantityString(R.plurals.OutdatedBuildReminder_your_version_of_signal_will_expire_in_n_days, days, days);
}
@Override

View File

@ -58,7 +58,7 @@ public abstract class Reminder {
return Importance.NORMAL;
}
public void addAction(@NonNull Action action) {
protected void addAction(@NonNull Action action) {
actions.add(action);
}
@ -71,7 +71,7 @@ public abstract class Reminder {
}
public enum Importance {
NORMAL, ERROR
NORMAL, ERROR, TERMINAL
}
public final class Action {

View File

@ -1,8 +1,6 @@
package org.thoughtcrime.securesms.components.reminder;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION_CODES;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@ -19,7 +17,6 @@ import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.List;
@ -48,7 +45,6 @@ public final class ReminderView extends FrameLayout {
initialize();
}
@TargetApi(VERSION_CODES.HONEYCOMB)
public ReminderView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
@ -56,14 +52,14 @@ public final class ReminderView extends FrameLayout {
private void initialize() {
LayoutInflater.from(getContext()).inflate(R.layout.reminder_header, this, true);
progressBar = ViewUtil.findById(this, R.id.reminder_progress);
progressText = ViewUtil.findById(this, R.id.reminder_progress_text);
container = ViewUtil.findById(this, R.id.container);
closeButton = ViewUtil.findById(this, R.id.cancel);
title = ViewUtil.findById(this, R.id.reminder_title);
text = ViewUtil.findById(this, R.id.reminder_text);
space = ViewUtil.findById(this, R.id.reminder_space);
actionsRecycler = ViewUtil.findById(this, R.id.reminder_actions);
progressBar = findViewById(R.id.reminder_progress);
progressText = findViewById(R.id.reminder_progress_text);
container = findViewById(R.id.container);
closeButton = findViewById(R.id.cancel);
title = findViewById(R.id.reminder_title);
text = findViewById(R.id.reminder_text);
space = findViewById(R.id.reminder_space);
actionsRecycler = findViewById(R.id.reminder_actions);
}
public void showReminder(final Reminder reminder) {
@ -76,9 +72,26 @@ public final class ReminderView extends FrameLayout {
title.setVisibility(GONE);
space.setVisibility(VISIBLE);
}
if (!reminder.isDismissable()) {
space.setVisibility(GONE);
}
text.setText(reminder.getText());
container.setBackgroundResource(reminder.getImportance() == Reminder.Importance.ERROR ? R.drawable.reminder_background_error
: R.drawable.reminder_background_normal);
switch (reminder.getImportance()) {
case NORMAL:
container.setBackgroundResource(R.drawable.reminder_background_normal);
break;
case ERROR:
container.setBackgroundResource(R.drawable.reminder_background_error);
break;
case TERMINAL:
container.setBackgroundResource(R.drawable.reminder_background_terminal);
break;
default:
throw new IllegalStateException();
}
setOnClickListener(reminder.getOkListener());

View File

@ -240,6 +240,7 @@ import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.MessageUtil;
import org.thoughtcrime.securesms.util.PlayStoreUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.TextSecurePreferences.MediaKeyboardMode;
@ -1685,6 +1686,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
reminderView.get().showReminder(new UnauthorizedReminder(this));
} else if (ExpiredBuildReminder.isEligible()) {
reminderView.get().showReminder(new ExpiredBuildReminder(this));
reminderView.get().setOnActionClickListener(this::handleReminderAction);
} else if (ServiceOutageReminder.isEligible(this)) {
ApplicationDependencies.getJobManager().add(new ServiceOutageDetectionJob());
reminderView.get().showReminder(new ServiceOutageReminder(this));
@ -1710,6 +1712,9 @@ public class ConversationActivity extends PassphraseRequiredActivity
case R.id.reminder_action_view_insights:
InsightsLauncher.showInsightsDashboard(getSupportFragmentManager());
break;
case R.id.reminder_action_update_now:
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(this);
break;
default:
throw new IllegalArgumentException("Unknown ID: " + reminderActionId);
}

View File

@ -56,6 +56,7 @@ import androidx.appcompat.widget.TooltipCompat;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.view.ViewCompat;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import androidx.lifecycle.ViewModelProviders;
@ -117,6 +118,7 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.AvatarUtil;
import org.thoughtcrime.securesms.util.PlayStoreUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.SnapToTopDataObserver;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
@ -176,6 +178,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
private ViewGroup megaphoneContainer;
private SnapToTopDataObserver snapToTopDataObserver;
private Drawable archiveDrawable;
private LifecycleObserver visibilityLifecycleObserver;
public static ConversationListFragment newInstance() {
return new ConversationListFragment();
@ -214,6 +217,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
cameraFab.show();
reminderView.setOnDismissListener(this::updateReminders);
reminderView.setOnActionClickListener(this::onReminderAction);
list.setLayoutManager(new LinearLayoutManager(requireActivity()));
list.setItemAnimator(new DeleteItemAnimator());
@ -272,6 +276,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
public void onStart() {
super.onStart();
ConversationFragment.prepare(requireContext());
ProcessLifecycleOwner.get().getLifecycle().addObserver(visibilityLifecycleObserver);
}
@Override
@ -283,6 +288,12 @@ public class ConversationListFragment extends MainFragment implements ActionMode
EventBus.getDefault().unregister(this);
}
@Override
public void onStop() {
super.onStop();
ProcessLifecycleOwner.get().getLifecycle().removeObserver(visibilityLifecycleObserver);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
menu.clear();
@ -412,6 +423,12 @@ public class ConversationListFragment extends MainFragment implements ActionMode
viewModel.onMegaphoneCompleted(event);
}
private void onReminderAction(@IdRes int reminderActionId) {
if (reminderActionId == R.id.reminder_action_update_now) {
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext());
}
}
private void hideKeyboard() {
InputMethodManager imm = ServiceUtil.getInputMethodManager(requireContext());
imm.hideSoftInputFromWindow(requireView().getWindowToken(), 0);
@ -508,12 +525,12 @@ public class ConversationListFragment extends MainFragment implements ActionMode
viewModel.getConversationList().observe(getViewLifecycleOwner(), this::onSubmitList);
viewModel.hasNoConversations().observe(getViewLifecycleOwner(), this::updateEmptyState);
ProcessLifecycleOwner.get().getLifecycle().addObserver(new DefaultLifecycleObserver() {
visibilityLifecycleObserver = new DefaultLifecycleObserver() {
@Override
public void onStart(@NonNull LifecycleOwner owner) {
viewModel.onVisible();
}
});
};
}
private void onSearchResultChanged(@Nullable SearchResult result) {

View File

@ -51,7 +51,11 @@ public final class MiscellaneousValues extends SignalStoreValues {
return getBoolean(CLIENT_DEPRECATED, false);
}
public void markDeprecated() {
public void markClientDeprecated() {
putBoolean(CLIENT_DEPRECATED, true);
}
public void clearClientDeprecated() {
putBoolean(CLIENT_DEPRECATED, false);
}
}

View File

@ -0,0 +1,62 @@
package org.thoughtcrime.securesms.megaphone;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.PlayStoreUtil;
import org.thoughtcrime.securesms.util.Util;
/**
* Shown when a users build fully expires. Controlled by {@link Megaphones.Event#CLIENT_DEPRECATED}.
*/
public class ClientDeprecatedActivity extends PassphraseRequiredActivity {
private final DynamicTheme theme = new DynamicNoActionBarTheme();
@Override
protected void onCreate(Bundle savedInstanceState, boolean ready) {
setContentView(R.layout.client_deprecated_activity);
findViewById(R.id.client_deprecated_update_button).setOnClickListener(v -> onUpdateClicked());
findViewById(R.id.client_deprecated_dont_update_button).setOnClickListener(v -> onDontUpdateClicked());
}
@Override
protected void onPreCreate() {
theme.onCreate(this);
}
@Override
protected void onResume() {
super.onResume();
theme.onResume(this);
}
@Override
public void onBackPressed() {
// Disabled
}
private void onUpdateClicked() {
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(this);
}
private void onDontUpdateClicked() {
new AlertDialog.Builder(this)
.setTitle(R.string.ClientDeprecatedActivity_warning)
.setMessage(R.string.ClientDeprecatedActivity_your_version_of_signal_has_expired_you_can_view_your_message_history)
.setPositiveButton(R.string.ClientDeprecatedActivity_dont_update, (dialog, which) -> {
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.CLIENT_DEPRECATED, () -> {
Util.runOnMain(this::finish);
});
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
.show();
}
}

View File

@ -20,7 +20,7 @@ public class Megaphone {
private final Event event;
private final Style style;
private final boolean mandatory;
private final Priority priority;
private final boolean canSnooze;
private final int titleRes;
private final int bodyRes;
@ -33,7 +33,7 @@ public class Megaphone {
private Megaphone(@NonNull Builder builder) {
this.event = builder.event;
this.style = builder.style;
this.mandatory = builder.mandatory;
this.priority = builder.priority;
this.canSnooze = builder.canSnooze;
this.titleRes = builder.titleRes;
this.bodyRes = builder.bodyRes;
@ -48,8 +48,8 @@ public class Megaphone {
return event;
}
public boolean isMandatory() {
return mandatory;
public @NonNull Priority getPriority() {
return priority;
}
public boolean canSnooze() {
@ -97,7 +97,7 @@ public class Megaphone {
private final Event event;
private final Style style;
private boolean mandatory;
private Priority priority;
private boolean canSnooze;
private int titleRes;
private int bodyRes;
@ -111,13 +111,14 @@ public class Megaphone {
public Builder(@NonNull Event event, @NonNull Style style) {
this.event = event;
this.style = style;
this.priority = Priority.DEFAULT;
}
/**
* Prioritizes this megaphone over others that do not set this flag.
*/
public @NonNull Builder setMandatory(boolean mandatory) {
this.mandatory = mandatory;
public @NonNull Builder setPriority(@NonNull Priority priority) {
this.priority = priority;
return this;
}
@ -192,6 +193,20 @@ public class Megaphone {
POPUP
}
enum Priority {
DEFAULT(0), HIGH(1), CLIENT_EXPIRATION(1000);
int priorityValue;
Priority(int priorityValue) {
this.priorityValue = priorityValue;
}
public int getPriorityValue() {
return priorityValue;
}
}
public interface EventListener {
void onEvent(@NonNull Megaphone megaphone, @NonNull MegaphoneActionController listener);
}

View File

@ -5,6 +5,7 @@ import android.content.Context;
import androidx.annotation.AnyThread;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.annimon.stream.Collectors;
@ -100,6 +101,11 @@ public class MegaphoneRepository {
@AnyThread
public void markFinished(@NonNull Event event) {
markFinished(event, null);
}
@AnyThread
public void markFinished(@NonNull Event event, @Nullable Runnable onComplete) {
executor.execute(() -> {
MegaphoneRecord record = databaseCache.get(event);
if (record != null && record.isFinished()) {
@ -108,6 +114,10 @@ public class MegaphoneRepository {
database.markFinished(event);
resetDatabaseCache();
if (onComplete != null) {
onComplete.run();
}
});
}

View File

@ -34,12 +34,12 @@ import java.util.Objects;
* Creating a new megaphone:
* - Add an enum to {@link Event}
* - Return a megaphone in {@link #forRecord(Context, MegaphoneRecord)}
* - Include the event in {@link #buildDisplayOrder()}
* - Include the event in {@link #buildDisplayOrder(Context)}
*
* Common patterns:
* - For events that have a snooze-able recurring display schedule, use a {@link RecurringSchedule}.
* - For events guarded by feature flags, set a {@link ForeverSchedule} with false in
* {@link #buildDisplayOrder()}.
* {@link #buildDisplayOrder(Context)}.
* - For events that change, return different megaphones in {@link #forRecord(Context, MegaphoneRecord)}
* based on whatever properties you're interested in.
*/
@ -65,15 +65,9 @@ public final class Megaphones {
.map(Map.Entry::getKey)
.map(records::get)
.map(record -> Megaphones.forRecord(context, record))
.sortBy(m -> -m.getPriority().getPriorityValue())
.toList();
boolean hasOptional = Stream.of(megaphones).anyMatch(m -> !m.isMandatory());
boolean hasMandatory = Stream.of(megaphones).anyMatch(Megaphone::isMandatory);
if (hasOptional && hasMandatory) {
megaphones = Stream.of(megaphones).filter(Megaphone::isMandatory).toList();
}
if (megaphones.size() > 0) {
return megaphones.get(0);
} else {
@ -93,6 +87,7 @@ public final class Megaphones {
put(Event.MESSAGE_REQUESTS, shouldShowMessageRequestsMegaphone() ? ALWAYS : NEVER);
put(Event.MENTIONS, shouldShowMentionsMegaphone() ? ALWAYS : NEVER);
put(Event.LINK_PREVIEWS, shouldShowLinkPreviewsMegaphone(context) ? ALWAYS : NEVER);
put(Event.CLIENT_DEPRECATED, SignalStore.misc().isClientDeprecated() ? ALWAYS : NEVER);
}};
}
@ -110,6 +105,8 @@ public final class Megaphones {
return buildMentionsMegaphone();
case LINK_PREVIEWS:
return buildLinkPreviewsMegaphone();
case CLIENT_DEPRECATED:
return buildClientDeprecatedMegaphone(context);
default:
throw new IllegalArgumentException("Event not handled!");
}
@ -117,14 +114,14 @@ public final class Megaphones {
private static @NonNull Megaphone buildReactionsMegaphone() {
return new Megaphone.Builder(Event.REACTIONS, Megaphone.Style.REACTIONS)
.setMandatory(false)
.setPriority(Megaphone.Priority.DEFAULT)
.build();
}
private static @NonNull Megaphone buildPinsForAllMegaphone(@NonNull MegaphoneRecord record) {
if (PinsForAllSchedule.shouldDisplayFullScreen(record.getFirstVisible(), System.currentTimeMillis())) {
return new Megaphone.Builder(Event.PINS_FOR_ALL, Megaphone.Style.FULLSCREEN)
.setMandatory(true)
.setPriority(Megaphone.Priority.HIGH)
.enableSnooze(null)
.setOnVisibleListener((megaphone, listener) -> {
if (new NetworkConstraint.Factory(ApplicationDependencies.getApplication()).create().isMet()) {
@ -134,7 +131,7 @@ public final class Megaphones {
.build();
} else {
return new Megaphone.Builder(Event.PINS_FOR_ALL, Megaphone.Style.BASIC)
.setMandatory(true)
.setPriority(Megaphone.Priority.HIGH)
.setImage(R.drawable.kbs_pin_megaphone)
.setTitle(R.string.KbsMegaphone__create_a_pin)
.setBody(R.string.KbsMegaphone__pins_keep_information_thats_stored_with_signal_encrytped)
@ -184,7 +181,7 @@ public final class Megaphones {
private static @NonNull Megaphone buildMessageRequestsMegaphone(@NonNull Context context) {
return new Megaphone.Builder(Event.MESSAGE_REQUESTS, Megaphone.Style.FULLSCREEN)
.disableSnooze()
.setMandatory(true)
.setPriority(Megaphone.Priority.HIGH)
.setOnVisibleListener(((megaphone, listener) -> {
listener.onMegaphoneNavigationRequested(new Intent(context, MessageRequestMegaphoneActivity.class),
ConversationListFragment.MESSAGE_REQUESTS_REQUEST_CODE_CREATE_NAME);
@ -202,7 +199,17 @@ public final class Megaphones {
private static @NonNull Megaphone buildLinkPreviewsMegaphone() {
return new Megaphone.Builder(Event.LINK_PREVIEWS, Megaphone.Style.LINK_PREVIEWS)
.setMandatory(true)
.setPriority(Megaphone.Priority.HIGH)
.build();
}
private static @NonNull Megaphone buildClientDeprecatedMegaphone(@NonNull Context context) {
return new Megaphone.Builder(Event.CLIENT_DEPRECATED, Megaphone.Style.FULLSCREEN)
.disableSnooze()
.setPriority(Megaphone.Priority.HIGH)
.setOnVisibleListener((megaphone, listener) -> {
listener.onMegaphoneNavigationRequested(new Intent(context, ClientDeprecatedActivity.class));
})
.build();
}
@ -224,7 +231,8 @@ public final class Megaphones {
PIN_REMINDER("pin_reminder"),
MESSAGE_REQUESTS("message_requests"),
MENTIONS("mentions"),
LINK_PREVIEWS("link_previews");
LINK_PREVIEWS("link_previews"),
CLIENT_DEPRECATED("client_deprecated");
private final String key;

View File

@ -75,6 +75,7 @@ public class ApplicationMigrations {
if (!isUpdate(context)) {
Log.d(TAG, "Not an update. Skipping.");
VersionTracker.updateLastSeenVersion(context);
return;
}

View File

@ -23,7 +23,7 @@ public final class RemoteDeprecationDetectorInterceptor implements Interceptor {
if (response.code() == 499 && !SignalStore.misc().isClientDeprecated()) {
Log.w(TAG, "Received 499. Client version is deprecated.");
SignalStore.misc().markDeprecated();
SignalStore.misc().markClientDeprecated();
}
return response;

View File

@ -3,10 +3,15 @@ package org.thoughtcrime.securesms.util;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.logging.Log;
import java.io.IOException;
public class VersionTracker {
private static final String TAG = Log.tag(VersionTracker.class);
public static int getLastSeenVersion(@NonNull Context context) {
return TextSecurePreferences.getLastVersionCode(context);
}
@ -14,7 +19,13 @@ public class VersionTracker {
public static void updateLastSeenVersion(@NonNull Context context) {
try {
int currentVersionCode = Util.getCanonicalVersionCode();
TextSecurePreferences.setLastVersionCode(context, currentVersionCode);
int lastVersionCode = TextSecurePreferences.getLastVersionCode(context);
if (currentVersionCode != lastVersionCode) {
Log.i(TAG, "Upgraded from " + lastVersionCode + " to " + currentVersionCode);
SignalStore.misc().clearClientDeprecated();
TextSecurePreferences.setLastVersionCode(context, currentVersionCode);
}
} catch (IOException ioe) {
throw new AssertionError(ioe);
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="106dp"
android:height="106dp"
android:viewportWidth="106"
android:viewportHeight="106">
<path
android:pathData="M40.28,1.549L41.472,6.368C36.781,7.522 32.292,9.379 28.156,11.875L25.606,7.619C30.163,4.867 35.111,2.821 40.28,1.549ZM65.72,1.549L64.527,6.368C69.219,7.522 73.708,9.379 77.844,11.875L80.411,7.619C75.848,4.866 70.895,2.819 65.72,1.549ZM7.619,25.606C4.867,30.163 2.821,35.111 1.549,40.28L6.368,41.472C7.522,36.782 9.379,32.292 11.875,28.156L7.619,25.606ZM4.969,53C4.967,50.591 5.147,48.186 5.507,45.804L0.596,45.058C-0.199,50.323 -0.199,55.677 0.596,60.942L5.507,60.196C5.147,57.815 4.967,55.409 4.969,53ZM80.394,98.381L77.844,94.125C73.713,96.62 69.229,98.477 64.544,99.632L65.737,104.451C70.9,103.178 75.842,101.131 80.394,98.381ZM101.031,53C101.033,55.409 100.853,57.815 100.493,60.196L105.404,60.942C106.199,55.677 106.199,50.323 105.404,45.058L100.493,45.804C100.853,48.186 101.033,50.591 101.031,53ZM104.451,65.72L99.632,64.527C98.478,69.219 96.621,73.708 94.125,77.844L98.381,80.411C101.134,75.848 103.181,70.895 104.451,65.72ZM60.196,100.501C55.426,101.219 50.574,101.219 45.804,100.501L45.058,105.412C50.323,106.207 55.677,106.207 60.942,105.412L60.196,100.501ZM91.665,81.496C88.8,85.376 85.37,88.804 81.488,91.665L84.436,95.665C88.718,92.516 92.504,88.742 95.665,84.469L91.665,81.496ZM81.488,14.335C85.37,17.199 88.801,20.63 91.665,24.513L95.665,21.531C92.514,17.26 88.74,13.486 84.469,10.335L81.488,14.335ZM14.335,24.513C17.199,20.63 20.63,17.199 24.513,14.335L21.531,10.335C17.26,13.486 13.486,17.26 10.335,21.531L14.335,24.513ZM98.381,25.606L94.125,28.156C96.62,32.287 98.477,36.771 99.632,41.456L104.451,40.264C103.178,35.1 101.131,30.158 98.381,25.606ZM45.804,5.507C50.574,4.789 55.426,4.789 60.196,5.507L60.942,0.596C55.677,-0.199 50.323,-0.199 45.058,0.596L45.804,5.507ZM16.885,96.982L6.625,99.375L9.018,89.115L4.182,87.98L1.789,98.241C1.596,99.064 1.616,99.922 1.848,100.735C2.08,101.549 2.515,102.289 3.113,102.887C3.711,103.485 4.451,103.92 5.265,104.152C6.078,104.384 6.936,104.404 7.76,104.211L18.012,101.859L16.885,96.982ZM5.217,83.55L10.053,84.676L11.71,77.562C9.296,73.505 7.496,69.112 6.368,64.527L1.549,65.72C2.629,70.111 4.271,74.345 6.435,78.316L5.217,83.55ZM28.405,94.315L21.291,95.971L22.417,100.808L27.651,99.59C31.622,101.754 35.856,103.396 40.247,104.476L41.439,99.657C36.864,98.516 32.483,96.705 28.438,94.282L28.405,94.315ZM53,9.938C45.306,9.942 37.753,12.007 31.128,15.919C24.503,19.831 19.046,25.446 15.327,32.181C11.607,38.917 9.76,46.526 9.977,54.216C10.194,61.908 12.468,69.4 16.563,75.914L12.422,93.578L30.086,89.438C35.735,92.996 42.135,95.192 48.779,95.852C55.423,96.512 62.129,95.618 68.368,93.24C74.607,90.863 80.208,87.067 84.729,82.154C89.249,77.24 92.564,71.342 94.413,64.926C96.263,58.511 96.595,51.753 95.383,45.187C94.172,38.621 91.451,32.427 87.434,27.094C83.417,21.76 78.215,17.434 72.239,14.457C66.263,11.479 59.677,9.932 53,9.938Z"
android:fillColor="#3A76F0"/>
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@color/core_grey_85" />
<item android:state_focused="true" android:drawable="@color/core_black" />
<item android:drawable="@color/core_black" />
</selector>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="32dp"
android:paddingEnd="32dp"
android:paddingTop="20dp"
android:paddingBottom="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/client_deprecated_update_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_signal_logo_large" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="27dp"
android:text="@string/ClientDeprecatedActivity_update_signal"
style="@style/Signal.Text.Headline"
android:textStyle="bold"
android:gravity="center" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/ClientDeprecatedActivity_this_version_of_the_app_is_no_longer_supported"
style="@style/Signal.Text.Body"
android:gravity="center" />
</LinearLayout>
<Button
android:id="@+id/client_deprecated_update_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/ClientDeprecatedActivity_update"
style="@style/Button.Primary"
app:layout_constraintBottom_toTopOf="@id/client_deprecated_dont_update_button"/>
<Button
android:id="@+id/client_deprecated_dont_update_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/ClientDeprecatedActivity_dont_update"
style="@style/Button.Borderless.LowPriority"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -5,7 +5,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="@color/white"
android:textSize="16sp"
android:textSize="14sp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:layout_marginStart="3dp"

View File

@ -44,12 +44,12 @@
android:id="@+id/reminder_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="14dp"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="6dp"
android:textColor="@color/white"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="sans-serif-medium"
app:layout_constraintBottom_toTopOf="@id/reminder_text"
app:layout_constraintStart_toEndOf="@id/reminder_progress"
app:layout_constraintTop_toTopOf="parent"
@ -62,17 +62,17 @@
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
style="@style/Signal.Text.Preview"
android:textColor="@color/white"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@id/reminder_actions"
app:layout_constraintEnd_toStartOf="@id/reminder_space"
app:layout_constraintStart_toEndOf="@id/reminder_progress"
app:layout_constraintTop_toBottomOf="@id/reminder_title"
app:layout_goneMarginBottom="12dp"
app:layout_goneMarginBottom="16dp"
app:layout_goneMarginEnd="16dp"
app:layout_goneMarginStart="16dp"
app:layout_goneMarginTop="15dp"
tools:text="Take your conversation with Jules Bonnot to the next level." />
app:layout_goneMarginTop="16dp"
tools:text="Take your conversation with Otto Octavius to the next level." />
<Space
android:id="@+id/reminder_space"
@ -103,6 +103,7 @@
android:orientation="horizontal"
android:overScrollMode="never"
android:visibility="gone"
tools:visibility="visible"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View File

@ -158,6 +158,8 @@
<attr name="conversation_scroll_to_bottom_background" format="reference" />
<attr name="conversation_scroll_to_bottom_foreground_color" format="color" />
<attr name="low_priority_button_text_color" format="color|reference" />
<attr name="safety_number_change_dialog_button_background" format="reference|color" />
<attr name="safety_number_change_dialog_button_text_color" format="color" />

View File

@ -6,4 +6,5 @@
<item name="reminder_action_view_insights" type="id" />
<item name="reminder_action_invite" type="id" />
<item name="reminder_action_update_now" type="id" />
</resources>

View File

@ -151,6 +151,14 @@
<string name="ClearProfileActivity_remove_profile_photo">Remove profile photo?</string>
<string name="ClearProfileActivity_remove_group_photo">Remove group photo?</string>
<!-- ClientDeprecatedActivity -->
<string name="ClientDeprecatedActivity_update_signal">Update Signal</string>
<string name="ClientDeprecatedActivity_this_version_of_the_app_is_no_longer_supported">This version of the app is no longer supported. To continue sending and receiving messages, update to the latest version.</string>
<string name="ClientDeprecatedActivity_update">Update</string>
<string name="ClientDeprecatedActivity_dont_update">Don\'t Update</string>
<string name="ClientDeprecatedActivity_warning">Warning</string>
<string name="ClientDeprecatedActivity_your_version_of_signal_has_expired_you_can_view_your_message_history">Your version of Signal has expired. You can view your message history but you won\'t be able to send or receive messages until you update.</string>
<!-- CommunicationActions -->
<string name="CommunicationActions_no_browser_found">No web browser found.</string>
<string name="CommunicationActions_no_email_app_found">No email app found.</string>
@ -428,6 +436,10 @@
<string name="DozeReminder_optimize_for_missing_play_services">Optimize for missing Play Services</string>
<string name="DozeReminder_this_device_does_not_support_play_services_tap_to_disable_system_battery">This device does not support Play Services. Tap to disable system battery optimizations that prevent Signal from retrieving messages while inactive.</string>
<!-- ExpiredBuildReminder -->
<string name="ExpiredBuildReminder_this_version_of_signal_has_expired">This version of Signal has expired. Update now to send and receive messages.</string>
<string name="ExpiredBuildReminder_update_now">Update now</string>
<!-- ShareActivity -->
<string name="ShareActivity_share_with">Share with</string>
<string name="ShareActivity_multiple_attachments_are_only_supported">Multiple attachments are only supported for images and videos</string>
@ -1087,6 +1099,14 @@
<string name="ExpirationDialog_your_messages_will_not_expire">Your messages will not expire.</string>
<string name="ExpirationDialog_your_messages_will_disappear_s_after_they_have_been_seen">Messages sent and received in this conversation will disappear %s after they have been seen.</string>
<!-- OutdatedBuildReminder -->
<string name="OutdatedBuildReminder_update_now">Update now</string>
<plurals name="OutdatedBuildReminder_your_version_of_signal_will_expire_in_n_days">
<item quantity="zero">This version of Signal will expire today. Update to the most recent version.</item>
<item quantity="one">This version of Signal will expire tomorrow. Update to the most recent version.</item>
<item quantity="other">This version of Signal will expire in %d days. Update to the most recent version.</item>
</plurals>
<!-- PassphrasePromptActivity -->
<string name="PassphrasePromptActivity_enter_passphrase">Enter passphrase</string>
<string name="PassphrasePromptActivity_watermark_content_description">Signal icon</string>
@ -2289,14 +2309,6 @@
<string name="verify_display_fragment_context_menu__compare_with_clipboard">Compare with clipboard</string>
<!-- reminder_header -->
<string name="reminder_header_outdated_build">Your version of Signal is outdated</string>
<plurals name="reminder_header_outdated_build_details">
<item quantity="one">Your version of Signal will expire in %d day. Tap to update to the most recent version.</item>
<item quantity="other">Your version of Signal will expire in %d days. Tap to update to the most recent version.</item>
</plurals>
<string name="reminder_header_outdated_build_details_today">Your version of Signal will expire today. Tap to update to the most recent version.</string>
<string name="reminder_header_expired_build">Your version of Signal has expired!</string>
<string name="reminder_header_expired_build_details">Messages will no longer send successfully. Tap to update to the most recent version.</string>
<string name="reminder_header_sms_default_title">Use as default SMS app</string>
<string name="reminder_header_sms_default_text">Tap to make Signal your default SMS app.</string>
<string name="reminder_header_sms_import_title">Import system SMS</string>

View File

@ -262,6 +262,10 @@
<item name="android:textColor">?attr/colorAccent</item>
</style>
<style name="Button.Borderless.LowPriority" parent="Button.Borderless">
<item name="android:textColor">?attr/low_priority_button_text_color</item>
</style>
<!-- RedPhone -->
<!-- Buttons in the main "button row" of the in-call onscreen touch UI. -->

View File

@ -266,6 +266,8 @@
<item name="conversation_subtitle_color">@color/transparent_white_90</item>
<item name="conversation_mention_background_color">@color/core_grey_20</item>
<item name="low_priority_button_text_color">@color/core_grey_70</item>
<item name="mention_picker_background_color">@color/core_white</item>
<item name="safety_number_change_dialog_button_background">@color/core_grey_05</item>
@ -648,6 +650,8 @@
<item name="conversation_scroll_to_bottom_background">@drawable/scroll_to_bottom_background_dark</item>
<item name="conversation_scroll_to_bottom_foreground_color">@color/core_white</item>
<item name="low_priority_button_text_color">@color/core_grey_50</item>
<item name="mention_picker_background_color">@color/core_grey_90</item>
<item name="reactions_overlay_toolbar_icon_tint">@color/core_white</item>