[android] Implemented sliding of onboarding screens on splash activity

This commit is contained in:
Александр Зацепин 2019-11-21 19:29:52 +03:00 committed by yoksnod
parent c52ab9a34e
commit d2531b27d8
3 changed files with 208 additions and 71 deletions

View file

@ -5,14 +5,14 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import com.mapswithme.maps.ads.Banner;
import com.mapswithme.maps.analytics.AdvertisingObserver;
import com.mapswithme.maps.analytics.ExternalLibrariesMediator;
@ -22,6 +22,7 @@ import com.mapswithme.maps.base.Detachable;
import com.mapswithme.maps.downloader.UpdaterDialogFragment;
import com.mapswithme.maps.editor.ViralFragment;
import com.mapswithme.maps.location.LocationHelper;
import com.mapswithme.maps.news.WelcomeScreenBindingType;
import com.mapswithme.maps.onboarding.BaseNewsFragment;
import com.mapswithme.maps.onboarding.NewsFragment;
import com.mapswithme.maps.onboarding.WelcomeDialogFragment;
@ -40,8 +41,10 @@ import com.my.tracker.MyTracker;
public class SplashActivity extends AppCompatActivity
implements BaseNewsFragment.NewsDialogListener, BaseActivity,
WelcomeDialogFragment.PolicyAgreementListener
WelcomeDialogFragment.PolicyAgreementListener,
WelcomeDialogFragment.OnboardingStepPassedListener
{
private static final String EXTRA_CURRENT_ONBOARDING_STEP = "extra_current_onboarding_step";
private static final Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC);
private static final String TAG = SplashActivity.class.getSimpleName();
private static final String EXTRA_ACTIVITY_TO_START = "extra_activity_to_start";
@ -59,7 +62,7 @@ public class SplashActivity extends AppCompatActivity
private boolean mWaitForAdvertisingInfo;
@NonNull
private final Runnable mWelcomeScreenDelayedTask = new Runnable()
private final Runnable mUserAgreementDelayedTask = new Runnable()
{
@Override
public void run()
@ -68,6 +71,23 @@ public class SplashActivity extends AppCompatActivity
}
};
@NonNull
private Runnable mOnboardingStepsTask = new Runnable()
{
@Override
public void run()
{
if (mCurrentOnboardingStep != null)
{
WelcomeDialogFragment.showOnboardinStepsStartWith(SplashActivity.this,
mCurrentOnboardingStep);
return;
}
WelcomeDialogFragment.showOnboardinSteps(SplashActivity.this);
}
};
@NonNull
private final Runnable mPermissionsDelayedTask = new Runnable()
{
@ -142,6 +162,8 @@ public class SplashActivity extends AppCompatActivity
@NonNull
private final AdvertisingInfoObserver mAdvertisingObserver = new AdvertisingInfoObserver();
@Nullable
private WelcomeScreenBindingType mCurrentOnboardingStep;
public static void start(@NonNull Context context,
@Nullable Class<? extends Activity> activityToStart,
@ -160,8 +182,10 @@ public class SplashActivity extends AppCompatActivity
{
super.onCreate(savedInstanceState);
mBaseDelegate.onCreate();
handleOnboardingStep(savedInstanceState);
handleUpdateMapsFragmentCorrectly(savedInstanceState);
UiThread.cancelDelayedTasks(mWelcomeScreenDelayedTask);
UiThread.cancelDelayedTasks(mUserAgreementDelayedTask);
UiThread.cancelDelayedTasks(mOnboardingStepsTask);
UiThread.cancelDelayedTasks(mPermissionsDelayedTask);
UiThread.cancelDelayedTasks(mInitCoreDelayedTask);
UiThread.cancelDelayedTasks(mFinalDelayedTask);
@ -169,6 +193,18 @@ public class SplashActivity extends AppCompatActivity
initView();
}
private void handleOnboardingStep(@Nullable Bundle savedInstanceState)
{
if (savedInstanceState == null)
return;
if (!savedInstanceState.containsKey(EXTRA_CURRENT_ONBOARDING_STEP))
return;
int step = savedInstanceState.getInt(EXTRA_CURRENT_ONBOARDING_STEP);
mCurrentOnboardingStep = WelcomeScreenBindingType.values()[step];
}
@Override
protected void onNewIntent(Intent intent)
{
@ -233,20 +269,40 @@ public class SplashActivity extends AppCompatActivity
Counters.setMigrationExecuted();
}
boolean isFirstLaunch = WelcomeDialogFragment.isFirstLaunch(this);
final boolean isFirstLaunch = WelcomeDialogFragment.isFirstLaunch(this);
if (isFirstLaunch)
MwmApplication.from(this).setFirstLaunch(true);
if (isFirstLaunch || WelcomeDialogFragment.recreate(this))
boolean isWelcomeFragmentOnScreen = false;
DialogFragment welcomeFragment = WelcomeDialogFragment.find(this);
if (welcomeFragment != null)
{
UiThread.runLater(mWelcomeScreenDelayedTask, FIRST_START_DELAY);
return;
isWelcomeFragmentOnScreen = true;
welcomeFragment.dismissAllowingStateLoss();
}
processPermissionGranting();
if (isFirstLaunch || isWelcomeFragmentOnScreen)
{
if (WelcomeDialogFragment.isAgreementDeclined(this))
{
UiThread.runLater(mUserAgreementDelayedTask, FIRST_START_DELAY);
return;
}
else
{
if (processPermissionGranting())
{
UiThread.runLater(mOnboardingStepsTask, DELAY);
return;
}
}
}
if (processPermissionGranting())
runInitCoreTask();
}
private void processPermissionGranting()
private boolean processPermissionGranting()
{
mPermissionsGranted = PermissionsUtils.isExternalStorageGranted();
DialogFragment storagePermissionsDialog = StoragePermissionsDialogFragment.find(this);
@ -259,13 +315,13 @@ public class SplashActivity extends AppCompatActivity
permissionsDialog.dismiss();
if (storagePermissionsDialog == null)
StoragePermissionsDialogFragment.show(this);
return;
return false;
}
permissionsDialog = PermissionsDialogFragment.find(this);
if (permissionsDialog == null)
UiThread.runLater(mPermissionsDelayedTask, DELAY);
return;
return false;
}
if (permissionsDialog != null)
@ -274,7 +330,7 @@ public class SplashActivity extends AppCompatActivity
if (storagePermissionsDialog != null)
storagePermissionsDialog.dismiss();
runInitCoreTask();
return true;
}
private void runInitCoreTask()
@ -288,7 +344,8 @@ public class SplashActivity extends AppCompatActivity
super.onPause();
mBaseDelegate.onPause();
mCanceled = true;
UiThread.cancelDelayedTasks(mWelcomeScreenDelayedTask);
UiThread.cancelDelayedTasks(mUserAgreementDelayedTask);
UiThread.cancelDelayedTasks(mOnboardingStepsTask);
UiThread.cancelDelayedTasks(mPermissionsDelayedTask);
UiThread.cancelDelayedTasks(mInitCoreDelayedTask);
UiThread.cancelDelayedTasks(mFinalDelayedTask);
@ -305,6 +362,14 @@ public class SplashActivity extends AppCompatActivity
mediator.removeAdvertisingObserver(mAdvertisingObserver);
}
@Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
if (mCurrentOnboardingStep != null)
outState.putInt(EXTRA_CURRENT_ONBOARDING_STEP, mCurrentOnboardingStep.ordinal());
}
@Override
protected void onDestroy()
{
@ -395,6 +460,18 @@ public class SplashActivity extends AppCompatActivity
processPermissionGranting();
}
@Override
public void onLastOnboardingStepPassed()
{
runInitCoreTask();
}
@Override
public void onOnboardingStepPassed(@NonNull WelcomeScreenBindingType step)
{
mCurrentOnboardingStep = step;
}
private void initView()
{
UiUtils.setupStatusBar(this);

View file

@ -4,7 +4,6 @@ import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
@ -14,12 +13,12 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.mapswithme.maps.BuildConfig;
import com.mapswithme.maps.Framework;
import com.mapswithme.maps.MwmApplication;
import com.mapswithme.maps.R;
import com.mapswithme.maps.base.BaseMwmDialogFragment;
import com.mapswithme.maps.news.WelcomeScreenBindingType;
@ -28,12 +27,21 @@ import com.mapswithme.util.SharedPropertiesUtils;
import com.mapswithme.util.ThemeUtils;
import com.mapswithme.util.UiUtils;
import java.util.Stack;
public class WelcomeDialogFragment extends BaseMwmDialogFragment implements View.OnClickListener
{
private static final String BUNDLE_WELCOME_SCREEN_TYPE = "welcome_screen_type";
private static final String ARG_HAS_SPECIFIC_STEP = "welcome_screen_type";
private static final String ARG_HAS_MANY_STEPS = "show_onboarding_steps";
@NonNull
private final Stack<WelcomeScreenBindingType> mOnboardingSteps = new Stack<>();
@Nullable
private PolicyAgreementListener mListener;
private PolicyAgreementListener mPolicyAgreementListener;
@Nullable
private OnboardingStepPassedListener mOnboardingStepPassedListener;
@Nullable
private WelcomeScreenBindingType mWelcomeScreenBindingType;
@ -68,8 +76,23 @@ public class WelcomeDialogFragment extends BaseMwmDialogFragment implements View
public static void show(@NonNull FragmentActivity activity)
{
create(activity);
Counters.setFirstStartDialogSeen();
create(activity, null);
}
public static void showOnboardinSteps(@NonNull FragmentActivity activity)
{
Bundle args = new Bundle();
args.putBoolean(ARG_HAS_MANY_STEPS, true);
create(activity, args);
}
public static void showOnboardinStepsStartWith(@NonNull FragmentActivity activity,
@NonNull WelcomeScreenBindingType startStep)
{
Bundle args = new Bundle();
args.putBoolean(ARG_HAS_MANY_STEPS, true);
args.putInt(ARG_HAS_SPECIFIC_STEP, startStep.ordinal());
create(activity, args);
}
public static boolean isFirstLaunch(@NonNull FragmentActivity activity)
@ -77,40 +100,32 @@ public class WelcomeDialogFragment extends BaseMwmDialogFragment implements View
if (Counters.getFirstInstallVersion() < BuildConfig.VERSION_CODE)
return false;
if (isAgreementDeclined(activity))
return true;
FragmentManager fm = activity.getSupportFragmentManager();
if (fm.isDestroyed())
return false;
return !Counters.isFirstStartDialogSeen();
return !Counters.isFirstStartDialogSeen(activity);
}
private static void create(@NonNull FragmentActivity activity)
private static void create(@NonNull FragmentActivity activity, @Nullable Bundle args)
{
final WelcomeDialogFragment fragment = new WelcomeDialogFragment();
fragment.setArguments(args);
activity.getSupportFragmentManager()
.beginTransaction()
.add(fragment, WelcomeDialogFragment.class.getName())
.commitAllowingStateLoss();
}
public static boolean recreate(@NonNull FragmentActivity activity)
@Nullable
public static DialogFragment find(@NonNull FragmentActivity activity)
{
FragmentManager fm = activity.getSupportFragmentManager();
Fragment f = fm.findFragmentByTag(WelcomeDialogFragment.class.getName());
if (f == null)
return false;
final FragmentManager fm = activity.getSupportFragmentManager();
if (fm.isDestroyed())
return null;
// If we're here, it means that the user has rotated the screen.
// We use different dialog themes for landscape and portrait modes on tablets,
// so the fragment should be recreated to be displayed correctly.
fm.beginTransaction()
.remove(f)
.commitAllowingStateLoss();
fm.executePendingTransactions();
return true;
Fragment f = fm.findFragmentByTag(WelcomeDialogFragment.class.getName());
return (DialogFragment) f;
}
@Override
@ -118,13 +133,15 @@ public class WelcomeDialogFragment extends BaseMwmDialogFragment implements View
{
super.onAttach(activity);
if (activity instanceof BaseNewsFragment.NewsDialogListener)
mListener = (PolicyAgreementListener) activity;
mPolicyAgreementListener = (PolicyAgreementListener) activity;
if (activity instanceof OnboardingStepPassedListener)
mOnboardingStepPassedListener = (OnboardingStepPassedListener) activity;
}
@Override
public void onDetach()
{
mListener = null;
mPolicyAgreementListener = null;
super.onDetach();
}
@ -143,9 +160,8 @@ public class WelcomeDialogFragment extends BaseMwmDialogFragment implements View
res.requestWindowFeature(Window.FEATURE_NO_TITLE);
res.setCancelable(false);
Bundle args = getArguments();
mWelcomeScreenBindingType = args != null && args.containsKey(BUNDLE_WELCOME_SCREEN_TYPE)
? makeWelcomeScreenType(args) : null;
hanldeOnboardingSteps();
mContentView = View.inflate(getActivity(), R.layout.fragment_welcome, null);
res.setContentView(mContentView);
mAcceptBtn = mContentView.findViewById(R.id.accept_btn);
@ -163,6 +179,40 @@ public class WelcomeDialogFragment extends BaseMwmDialogFragment implements View
return res;
}
private void hanldeOnboardingSteps()
{
Bundle args = getArguments();
if (args != null)
{
boolean hasManySteps = args.containsKey(ARG_HAS_MANY_STEPS);
if (hasManySteps)
{
mOnboardingSteps.push(WelcomeScreenBindingType.SHARE_EMOTIONS);
mOnboardingSteps.push(WelcomeScreenBindingType.EXPERIENCE);
mOnboardingSteps.push(WelcomeScreenBindingType.DREAM_AND_PLAN);
}
boolean hasSpecificStep = args.containsKey(ARG_HAS_SPECIFIC_STEP);
if (hasSpecificStep)
mWelcomeScreenBindingType =
WelcomeScreenBindingType.values()[args.getInt(ARG_HAS_SPECIFIC_STEP)];
if (hasManySteps && hasSpecificStep)
{
WelcomeScreenBindingType step = null;
while (!mWelcomeScreenBindingType.equals(step))
{
step = mOnboardingSteps.pop();
}
mWelcomeScreenBindingType = step;
return;
}
if (hasManySteps)
mWelcomeScreenBindingType = mOnboardingSteps.pop();
}
}
private void initUserAgreementViews()
{
mTermOfUseCheckbox = mContentView.findViewById(R.id.term_of_use_welcome_checkbox);
@ -205,9 +255,9 @@ public class WelcomeDialogFragment extends BaseMwmDialogFragment implements View
if (!isAgreementGranted)
return;
if (mListener != null)
mListener.onPolicyAgreementApplied();
dismiss();
if (mPolicyAgreementListener != null)
mPolicyAgreementListener.onPolicyAgreementApplied();
dismissAllowingStateLoss();
}
private void bindWelcomeScreenType()
@ -236,37 +286,41 @@ public class WelcomeDialogFragment extends BaseMwmDialogFragment implements View
mSubtitle.setText(mWelcomeScreenBindingType.getSubtitle());
}
@NonNull
private static WelcomeScreenBindingType makeWelcomeScreenType(@NonNull Bundle args)
{
return WelcomeScreenBindingType.values()[args.getInt(BUNDLE_WELCOME_SCREEN_TYPE)];
}
@Override
public void onClick(View v)
{
if (v.getId() != R.id.accept_btn)
{
Counters.setFirstStartDialogSeen(requireContext());
return;
}
if (mListener != null)
mListener.onPolicyAgreementApplied();
dismiss();
if (!mOnboardingSteps.isEmpty())
{
mWelcomeScreenBindingType = mOnboardingSteps.pop();
if (mOnboardingStepPassedListener != null)
mOnboardingStepPassedListener.onOnboardingStepPassed(mWelcomeScreenBindingType);
bindWelcomeScreenType();
return;
}
Counters.setFirstStartDialogSeen(requireContext());
dismissAllowingStateLoss();
if (mOnboardingStepPassedListener != null)
mOnboardingStepPassedListener.onLastOnboardingStepPassed();
}
@Override
public void onCancel(DialogInterface dialog)
{
super.onCancel(dialog);
if (!isAgreementDeclined(requireContext()))
Counters.setFirstStartDialogSeen(requireContext());
requireActivity().finish();
}
private void applyPreferenceChanges(@NonNull String key, boolean value)
{
SharedPreferences.Editor editor = MwmApplication.prefs(requireContext()).edit();
editor.putBoolean(key, value).apply();
}
private static boolean isAgreementDeclined(@NonNull Context context)
public static boolean isAgreementDeclined(@NonNull Context context)
{
return !SharedPropertiesUtils.isTermOfUseAgreementConfirmed(context)
|| !SharedPropertiesUtils.isPrivacyPolicyAgreementConfirmed(context);
@ -277,4 +331,10 @@ public class WelcomeDialogFragment extends BaseMwmDialogFragment implements View
{
void onPolicyAgreementApplied();
}
public interface OnboardingStepPassedListener
{
void onOnboardingStepPassed(@NonNull WelcomeScreenBindingType step);
void onLastOnboardingStepPassed();
}
}

View file

@ -39,14 +39,14 @@ public final class Counters
return MwmApplication.prefs().getInt(KEY_APP_FIRST_INSTALL_VERSION, 0);
}
public static boolean isFirstStartDialogSeen()
public static boolean isFirstStartDialogSeen(@NonNull Context context)
{
return MwmApplication.prefs().getBoolean(KEY_MISC_FIRST_START_DIALOG_SEEN, false);
return MwmApplication.prefs(context).getBoolean(KEY_MISC_FIRST_START_DIALOG_SEEN, false);
}
public static void setFirstStartDialogSeen()
public static void setFirstStartDialogSeen(@NonNull Context context)
{
MwmApplication.prefs()
MwmApplication.prefs(context)
.edit()
.putBoolean(KEY_MISC_FIRST_START_DIALOG_SEEN, true)
.apply();