Experience upgrade splash screen.

Behaves similarly to the DatabaseUpgradeActivity. You have a
static list of ExperienceUpgrade models that include a "trigger"
version, where when a user upgrades through it a notification
will appear, and there will be a splash explanation screen.

Right now the splash screens are basic and not too configurable,
but that can be reworked as upgrades demand.

Closes #4151
master
Jake McGinty 2015-09-29 13:14:22 -07:00 committed by Moxie Marlinspike
parent 0b20e99cd2
commit 3035dc4df9
16 changed files with 475 additions and 12 deletions

View File

@ -200,6 +200,11 @@
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ExperienceUpgradeActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphraseCreateActivity"
android:label="@string/AndroidManifest__create_passphrase"
android:windowSoftInputMode="stateUnchanged"
@ -435,5 +440,12 @@
</intent-filter>
</receiver>
<receiver android:name=".ExperienceUpgradeActivity$AppUpgradeReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED"/>
<data android:scheme="package" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -58,7 +58,7 @@ dependencies {
compile 'com.soundcloud.android:android-crop:0.9.10@aar'
compile 'com.android.support:appcompat-v7:22.1.1'
compile 'com.android.support:recyclerview-v7:21.0.3'
compile 'com.melnykov:floatingactionbutton:1.1.0'
compile 'com.melnykov:floatingactionbutton:1.3.0'
compile 'com.google.zxing:android-integration:3.1.0'
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
exclude module: 'support-v4'
@ -80,6 +80,7 @@ dependencies {
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
compile 'org.whispersystems:textsecure-android:1.8.1'
compile 'com.h6ah4i.android.compat:mulsellistprefcompat:1.0.0'
compile 'me.relex:circleindicator:1.0.0@aar'
testCompile 'junit:junit:4.12'
testCompile 'org.assertj:assertj-core:1.7.1'
@ -118,7 +119,7 @@ dependencyVerification {
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
'com.android.support:appcompat-v7:9a2355537c2f01cf0b95523605c18606b8d824017e6e94a05c77b0cfc8f21c96',
'com.android.support:recyclerview-v7:e525ad3f33c84bb12b73d2dc975b55364a53f0f2d0697e043efba59ba73e22d2',
'com.melnykov:floatingactionbutton:0679ad9f7d61eb7aeab91e8dc56358cdedd5b1c1b9c48464499ffa05c40d3985',
'com.melnykov:floatingactionbutton:15d58d4fac0f7a288d0e5301bbaf501a146f5b3f5921277811bf99bd3b397263',
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
'com.android.support:gridlayout-v7:a9b770cffca2c7c5cd83cba4dd12503365de5e8d9c79c479165adf18ab3bc25b',
@ -129,6 +130,7 @@ dependencyVerification {
'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2',
'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
'com.h6ah4i.android.compat:mulsellistprefcompat:47167c5cb796de1a854788e9ff318358e36c8fb88123baaa6e38fb78511dfabe',
'me.relex:circleindicator:996766d3dad51401331515e742948f2a10982d17c626edd976a246b9bf046aac',
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:background="#FF2090ea">
<TextView android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:id="@+id/blurb"
android:textSize="34sp"
android:textIsSelectable="false"
android:gravity="center_horizontal|bottom"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:fontFamily="sans-serif-light"
tools:text="@string/ExperienceUpgradeActivity_welcome_to_signal"
android:textColor="@android:color/white" />
<ImageView android:id="@+id/watermark"
android:layout_width="@dimen/onboarding_watermark_size"
android:layout_height="0dp"
android:layout_weight="2"
android:maxHeight="@dimen/onboarding_watermark_size"
android:scaleType="fitCenter"
tools:src="@drawable/splash_logo"
android:layout_gravity="center_horizontal"
android:layout_below="@id/blurb"
android:layout_marginBottom="20dp"
android:layout_marginTop="@dimen/onboarding_margin_vert" />
<TextView android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3"
android:id="@+id/subblurb"
android:textSize="20sp"
android:textIsSelectable="false"
android:gravity="center_horizontal"
android:layout_marginTop="20dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:fontFamily="sans-serif-light"
tools:text="@string/ExperienceUpgradeActivity_textsecure_is_now_called_signal"
android:textColor="@android:color/white" />
</LinearLayout>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fab="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button android:id="@+id/continue_button"
android:layout_width="140sp"
android:layout_height="wrap_content"
android:text="continue"
android:visibility="invisible"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />
<me.relex.circleindicator.CircleIndicator
android:id="@+id/indicator"
android:layout_gravity="bottom|center_horizontal"
android:layout_width="fill_parent"
android:layout_marginBottom="25dp"
android:clickable="false"
android:focusable="false"
android:layout_height="40dp" />
<com.melnykov.fab.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_margin="25dp"
android:src="@drawable/ic_arrow_forward_white_24dp"
android:focusable="true"
android:contentDescription="@string/conversation_list_fragment__fab_content_description"
fab:fab_shadow="false"
fab:fab_colorNormal="#33000000"
fab:fab_colorPressed="#66000000"
fab:fab_colorRipple="#66000000" />
</FrameLayout>

View File

@ -1,9 +1,12 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<color name="textsecure_primary">#ff2090ea</color>
<color name="textsecure_primary_dark">#ff1c7ac5</color>
<color name="signal_primary">#ff2090ea</color>
<color name="signal_primary_dark">#ff1c7ac5</color>
<color name="signal_primary_alpha33">#552090ea</color>
<color name="textsecure_primary_alpha33">#552090ea</color>
<color name="textsecure_primary">@color/signal_primary</color>
<color name="textsecure_primary_dark">@color/signal_primary_dark</color>
<color name="textsecure_primary_alpha33">@color/signal_primary_alpha33</color>
<color name="white">#ffffffff</color>
<color name="black">#ff000000</color>

View File

@ -55,4 +55,8 @@
holding the phone, *before* moving it up to your face and having
the prox sensor kick in.) -->
<dimen name="button_cluster_side_padding">20dp</dimen>
<dimen name="onboarding_margin_vert">35dp</dimen>
<dimen name="onboarding_margin_land">20dp</dimen>
<dimen name="onboarding_watermark_size">140dp</dimen>
</resources>

View File

@ -187,6 +187,12 @@
<!-- ShareActivity -->
<string name="ShareActivity_share_with">Share with</string>
<!-- ExperienceUpgradeActivity -->
<string name="ExperienceUpgradeActivity_welcome_to_signal_dgaf">Welcome to Signal.</string>
<string name="ExperienceUpgradeActivity_textsecure_is_now_called_signal">TextSecure and RedPhone are now one private messenger, for every situation: Signal.</string>
<string name="ExperienceUpgradeActivity_welcome_to_signal_excited">Welcome to Signal!</string>
<string name="ExperienceUpgradeActivity_textsecure_is_now_signal">TextSecure is now Signal.</string>
<string name="ExperienceUpgradeActivity_textsecure_is_now_signal_long">TextSecure and RedPhone are now one app: Signal. Tap to explore.</string>
<!-- ExportFragment -->
<string name="ExportFragment_export">Export</string>
<string name="ExportFragment_export_plaintext_to_sd_card">Export plaintext to SD card?</string>

View File

@ -0,0 +1,54 @@
package org.thoughtcrime.securesms;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
public class BasicIntroFragment extends Fragment {
private static final String ARG_DRAWABLE = "drawable";
private static final String ARG_TEXT = "text";
private static final String ARG_SUBTEXT = "subtext";
private int drawable;
private int text;
private int subtext;
public static BasicIntroFragment newInstance(int drawable, int text, int subtext) {
BasicIntroFragment fragment = new BasicIntroFragment();
Bundle args = new Bundle();
args.putInt(ARG_DRAWABLE, drawable);
args.putInt(ARG_TEXT, text);
args.putInt(ARG_SUBTEXT, subtext);
fragment.setArguments(args);
return fragment;
}
public BasicIntroFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
drawable = getArguments().getInt(ARG_DRAWABLE);
text = getArguments().getInt(ARG_TEXT );
subtext = getArguments().getInt(ARG_SUBTEXT );
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.color_fragment, container, false);
((ImageView)v.findViewById(R.id.watermark)).setImageResource(drawable);
((TextView)v.findViewById(R.id.blurb)).setText(text);
((TextView)v.findViewById(R.id.subblurb)).setText(subtext);
return v;
}
}

View File

@ -0,0 +1,224 @@
package org.thoughtcrime.securesms;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.v4.app.NotificationCompat;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import com.melnykov.fab.FloatingActionButton;
import com.nineoldandroids.animation.ArgbEvaluator;
import org.thoughtcrime.securesms.IntroPagerAdapter.IntroPage;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.Collections;
import java.util.List;
import me.relex.circleindicator.CircleIndicator;
public class ExperienceUpgradeActivity extends BaseActionBarActivity {
private static final String TAG = ExperienceUpgradeActivity.class.getSimpleName();
private static final int NOTIFICATION_ID = 1339;
private enum ExperienceUpgrade {
SIGNAL_REBRANDING(155,
new IntroPage(0xFF2090EA,
BasicIntroFragment.newInstance(R.drawable.splash_logo,
R.string.ExperienceUpgradeActivity_welcome_to_signal_dgaf,
R.string.ExperienceUpgradeActivity_textsecure_is_now_called_signal)),
R.string.ExperienceUpgradeActivity_welcome_to_signal_excited,
R.string.ExperienceUpgradeActivity_textsecure_is_now_signal,
R.string.ExperienceUpgradeActivity_textsecure_is_now_signal_long);
private int version;
private List<IntroPage> pages;
private @StringRes int notificationTitle;
private @StringRes int notificationText;
private @StringRes int notificationBigText;
ExperienceUpgrade(int version,
@NonNull List<IntroPage> pages,
@StringRes int notificationTitle,
@StringRes int notificationText,
@StringRes int notificationBigText)
{
this.version = version;
this.pages = pages;
this.notificationTitle = notificationTitle;
this.notificationText = notificationText;
this.notificationBigText = notificationBigText;
}
ExperienceUpgrade(int version,
@NonNull IntroPage page,
@StringRes int notificationTitle,
@StringRes int notificationText,
@StringRes int notificationBigText)
{
this(version, Collections.singletonList(page), notificationTitle, notificationText, notificationBigText);
}
public int getVersion() {
return version;
}
public List<IntroPage> getPages() {
return pages;
}
public IntroPage getPage(int i) {
return pages.get(i);
}
public int getNotificationTitle() {
return notificationTitle;
}
public int getNotificationText() {
return notificationText;
}
public int getNotificationBigText() {
return notificationBigText;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStatusBarColor(getResources().getColor(R.color.signal_primary_dark));
Optional<ExperienceUpgrade> upgrade = getExperienceUpgrade(this);
if (!upgrade.isPresent()) {
onContinue();
return;
}
setContentView(R.layout.experience_upgrade_activity);
final ViewPager pager = ViewUtil.findById(this, R.id.pager);
final CircleIndicator indicator = ViewUtil.findById(this, R.id.indicator);
final FloatingActionButton fab = ViewUtil.findById(this, R.id.fab);
pager.setAdapter(new IntroPagerAdapter(getSupportFragmentManager(), upgrade.get().getPages()));
if (upgrade.get().getPages().size() > 1) {
indicator.setViewPager(pager);
indicator.setOnPageChangeListener(new OnPageChangeListener(upgrade.get()));
} else {
indicator.setVisibility(View.GONE);
}
fab.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onContinue();
}
});
getWindow().setBackgroundDrawable(new ColorDrawable(upgrade.get().getPage(0).backgroundColor));
ServiceUtil.getNotificationManager(this).cancel(NOTIFICATION_ID);
}
@TargetApi(VERSION_CODES.LOLLIPOP)
private void setStatusBarColor(int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(color);
}
}
private void onContinue() {
TextSecurePreferences.setLastExperienceVersionCode(this, Util.getCurrentApkReleaseVersion(this));
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
finish();
}
public static boolean isUpdate(Context context) {
return getExperienceUpgrade(context).isPresent();
}
public static Optional<ExperienceUpgrade> getExperienceUpgrade(Context context) {
final int currentVersionCode = Util.getCurrentApkReleaseVersion(context);
final int lastSeenVersion = TextSecurePreferences.getLastExperienceVersionCode(context);
Log.w(TAG, "getExperienceUpgrade(" + lastSeenVersion + ")");
if (lastSeenVersion >= currentVersionCode || lastSeenVersion == 0) {
TextSecurePreferences.setLastExperienceVersionCode(context, currentVersionCode);
return Optional.absent();
}
Optional<ExperienceUpgrade> eligibleUpgrade = Optional.absent();
for (ExperienceUpgrade upgrade : ExperienceUpgrade.values()) {
if (lastSeenVersion < upgrade.getVersion()) eligibleUpgrade = Optional.of(upgrade);
}
return eligibleUpgrade;
}
private final class OnPageChangeListener implements ViewPager.OnPageChangeListener {
private final ArgbEvaluator evaluator = new ArgbEvaluator();
private final ExperienceUpgrade upgrade;
public OnPageChangeListener(ExperienceUpgrade upgrade) {
this.upgrade = upgrade;
}
@Override
public void onPageSelected(int position) {}
@Override
public void onPageScrollStateChanged(int state) {}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
final int nextPosition = (position + 1) % upgrade.getPages().size();
final int color = (Integer)evaluator.evaluate(positionOffset,
upgrade.getPage(position).backgroundColor,
upgrade.getPage(nextPosition).backgroundColor);
getWindow().setBackgroundDrawable(new ColorDrawable(color));
}
}
public static class AppUpgradeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction()) &&
intent.getData().getSchemeSpecificPart().equals(context.getPackageName()))
{
Optional<ExperienceUpgrade> experienceUpgrade = getExperienceUpgrade(context);
if (!experienceUpgrade.isPresent()) return;
Intent targetIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
Notification notification = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.icon_notification)
.setColor(context.getResources().getColor(R.color.signal_primary))
.setContentTitle(context.getString(experienceUpgrade.get().getNotificationTitle()))
.setContentText(context.getString(experienceUpgrade.get().getNotificationText()))
.setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(experienceUpgrade.get().getNotificationBigText())))
.setAutoCancel(true)
.setContentIntent(PendingIntent.getActivity(context, 0,
targetIntent,
PendingIntent.FLAG_UPDATE_CURRENT))
.build();
ServiceUtil.getNotificationManager(context).notify(NOTIFICATION_ID, notification);
}
}
}
}

View File

@ -0,0 +1,38 @@
package org.thoughtcrime.securesms;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import java.util.List;
public class IntroPagerAdapter extends FragmentStatePagerAdapter {
public static class IntroPage {
final int backgroundColor;
final Fragment fragment;
public IntroPage(int backgroundColor, Fragment fragment) {
this.backgroundColor = backgroundColor;
this.fragment = fragment;
}
}
private List<IntroPage> pages;
public IntroPagerAdapter(FragmentManager fm, List<IntroPage> pages) {
super(fm);
this.pages = pages;
}
@Override
public Fragment getItem(int i) {
IntroPage page = pages.get(i);
return page.fragment;
}
@Override
public int getCount() {
return pages.size();
}
}

View File

@ -31,6 +31,7 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
private static final int STATE_PROMPT_PASSPHRASE = 2;
private static final int STATE_UPGRADE_DATABASE = 3;
private static final int STATE_PROMPT_PUSH_REGISTRATION = 4;
private static final int STATE_EXPERIENCE_UPGRADE = 5;
private BroadcastReceiver clearKeyReceiver;
private boolean isVisible;
@ -128,17 +129,20 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
Log.w(TAG, "routeApplicationState(), state: " + state);
switch (state) {
case STATE_CREATE_PASSPHRASE: return getCreatePassphraseIntent();
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
case STATE_UPGRADE_DATABASE: return getUpgradeDatabaseIntent(masterSecret);
case STATE_PROMPT_PUSH_REGISTRATION: return getPushRegistrationIntent(masterSecret);
default: return null;
case STATE_CREATE_PASSPHRASE: return getCreatePassphraseIntent();
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
case STATE_UPGRADE_DATABASE: return getUpgradeDatabaseIntent(masterSecret);
case STATE_PROMPT_PUSH_REGISTRATION: return getPushRegistrationIntent(masterSecret);
case STATE_EXPERIENCE_UPGRADE: return getExperienceUpgradeIntent();
default: return null;
}
}
private int getApplicationState(MasterSecret masterSecret) {
if (!MasterSecretUtil.isPassphraseInitialized(this)) {
return STATE_CREATE_PASSPHRASE;
} else if (ExperienceUpgradeActivity.isUpdate(this)) {
return STATE_EXPERIENCE_UPGRADE;
} else if (masterSecret == null) {
return STATE_PROMPT_PASSPHRASE;
} else if (DatabaseUpgradeActivity.isUpdate(this)) {
@ -166,6 +170,10 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
masterSecret);
}
private Intent getExperienceUpgradeIntent() {
return getRoutedIntent(ExperienceUpgradeActivity.class, getIntent(), null);
}
private Intent getPushRegistrationIntent(MasterSecret masterSecret) {
return getRoutedIntent(RegistrationActivity.class, getConversationListIntent(), masterSecret);
}

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.util;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.Context;
import android.net.ConnectivityManager;
import android.view.WindowManager;
@ -18,4 +19,8 @@ public class ServiceUtil {
public static ConnectivityManager getConnectivityManager(Context context) {
return (ConnectivityManager) context.getSystemService(Activity.CONNECTIVITY_SERVICE);
}
public static NotificationManager getNotificationManager(Context context) {
return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
}
}

View File

@ -44,6 +44,7 @@ public class TextSecurePreferences {
public static final String ENABLE_MANUAL_MMS_PREF = "pref_enable_manual_mms";
private static final String LAST_VERSION_CODE_PREF = "last_version_code";
private static final String LAST_EXPERIENCE_VERSION_PREF = "last_experience_version_code";
public static final String RINGTONE_PREF = "pref_key_ringtone";
private static final String VIBRATE_PREF = "pref_key_vibrate";
private static final String NOTIFICATION_PREF = "pref_key_enable_notifications";
@ -342,7 +343,15 @@ public class TextSecurePreferences {
if (!setIntegerPrefrenceBlocking(context, LAST_VERSION_CODE_PREF, versionCode)) {
throw new IOException("couldn't write version code to sharedpreferences");
}
}
}
public static int getLastExperienceVersionCode(Context context) {
return getIntegerPreference(context, LAST_EXPERIENCE_VERSION_PREF, 0);
}
public static void setLastExperienceVersionCode(Context context, int versionCode) {
setIntegerPrefrence(context, LAST_EXPERIENCE_VERSION_PREF, versionCode);
}
public static String getTheme(Context context) {
return getStringPreference(context, THEME_PREF, "light");

View File

@ -16,7 +16,7 @@
*/
package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
@ -72,6 +72,11 @@ public class ViewUtil {
return (T) parent.findViewById(resId);
}
@SuppressWarnings("unchecked")
public static <T extends View> T findById(@NonNull Activity parent, @IdRes int resId) {
return (T) parent.findViewById(resId);
}
private static Animation getAlphaAnimation(float from, float to, int duration) {
final Animation anim = new AlphaAnimation(from, to);
anim.setInterpolator(new FastOutSlowInInterpolator());