From 64616e3921970deb7379109920057b3ff1678481 Mon Sep 17 00:00:00 2001 From: m2049r Date: Sun, 13 Sep 2020 18:34:22 +0200 Subject: [PATCH] onboarding (#675) Co-authored-by: Stephan --- app/build.gradle | 12 +- app/src/main/AndroidManifest.xml | 21 +- .../com/m2049r/xmrwallet/MainActivity.java | 40 ++ .../onboarding/OnBoardingActivity.java | 110 +++++ .../onboarding/OnBoardingAdapter.java | 93 +++++ .../onboarding/OnBoardingManager.java | 51 +++ .../onboarding/OnBoardingScreen.java | 55 +++ .../m2049r/xmrwallet/util/KeyStoreHelper.java | 5 + app/src/main/res/drawable/dot_dark.xml | 12 + app/src/main/res/drawable/dot_light.xml | 12 + .../drawable/ic_onboarding_fingerprint.xml | 72 ++++ .../main/res/drawable/ic_onboarding_nodes.xml | 279 +++++++++++++ .../main/res/drawable/ic_onboarding_seed.xml | 393 ++++++++++++++++++ .../res/drawable/ic_onboarding_welcome.xml | 75 ++++ .../main/res/drawable/ic_onboarding_xmrto.xml | 138 ++++++ app/src/main/res/drawable/onboarding_dots.xml | 6 + .../main/res/layout/activity_on_boarding.xml | 57 +++ app/src/main/res/layout/view_onboarding.xml | 71 ++++ app/src/main/res/values/colors.xml | 2 +- app/src/main/res/values/strings.xml | 16 + build.gradle | 1 + 21 files changed, 1509 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/com/m2049r/xmrwallet/MainActivity.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingActivity.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingAdapter.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingManager.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingScreen.java create mode 100644 app/src/main/res/drawable/dot_dark.xml create mode 100644 app/src/main/res/drawable/dot_light.xml create mode 100644 app/src/main/res/drawable/ic_onboarding_fingerprint.xml create mode 100644 app/src/main/res/drawable/ic_onboarding_nodes.xml create mode 100644 app/src/main/res/drawable/ic_onboarding_seed.xml create mode 100644 app/src/main/res/drawable/ic_onboarding_welcome.xml create mode 100644 app/src/main/res/drawable/ic_onboarding_xmrto.xml create mode 100644 app/src/main/res/drawable/onboarding_dots.xml create mode 100644 app/src/main/res/layout/activity_on_boarding.xml create mode 100644 app/src/main/res/layout/view_onboarding.xml diff --git a/app/build.gradle b/app/build.gradle index e3b5fe21..7f32fe9d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,9 +7,8 @@ android { applicationId "com.m2049r.xmrwallet" minSdkVersion 21 targetSdkVersion 28 - versionCode 303 - versionName "1.13.3 'ReStart'" - + versionCode 400 + versionName "1.14 'On Board'" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { @@ -93,6 +92,11 @@ android { outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk" } } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { @@ -102,6 +106,7 @@ dependencies { implementation "com.android.support:recyclerview-v7:$rootProject.ext.supportVersion" implementation "com.android.support:cardview-v7:$rootProject.ext.supportVersion" implementation "com.android.support:swiperefreshlayout:$rootProject.ext.supportVersion" + implementation "com.android.support.constraint:constraint-layout:$rootProject.ext.constraintVersion" implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion" @@ -124,5 +129,4 @@ dependencies { testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion" testImplementation 'org.json:json:20180813' testImplementation 'net.jodah:concurrentunit:0.4.4' - } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 948520d2..7c28e383 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,24 +20,27 @@ android:supportsRtl="true" android:theme="@style/MyMaterialTheme" android:usesCleartextTraffic="true"> - + + + + + + - - - - - @@ -62,6 +65,10 @@ android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/usb_device_filter" /> + - \ No newline at end of file + diff --git a/app/src/main/java/com/m2049r/xmrwallet/MainActivity.java b/app/src/main/java/com/m2049r/xmrwallet/MainActivity.java new file mode 100644 index 00000000..cff572d8 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/MainActivity.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018-2020 EarlOfEgo, m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; + +import com.m2049r.xmrwallet.onboarding.OnBoardingActivity; +import com.m2049r.xmrwallet.onboarding.OnBoardingManager; + +import timber.log.Timber; + +public class MainActivity extends AppCompatActivity { + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (OnBoardingManager.shouldShowOnBoarding(getApplicationContext()) || BuildConfig.DEBUG) { + startActivity(new Intent(this, OnBoardingActivity.class)); + } else { + startActivity(new Intent(this, LoginActivity.class)); + } + finish(); + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingActivity.java b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingActivity.java new file mode 100644 index 00000000..6d17a5cc --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingActivity.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2018-2020 EarlOfEgo, m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.onboarding; + +import android.content.Intent; +import android.os.Bundle; +import android.support.design.widget.TabLayout; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatActivity; +import android.util.TypedValue; +import android.view.View; + +import com.m2049r.xmrwallet.LoginActivity; +import com.m2049r.xmrwallet.R; + +public class OnBoardingActivity extends AppCompatActivity implements OnBoardingAdapter.Listener { + + private ViewPager pager; + private OnBoardingAdapter pagerAdapter; + private int mustAgreePages = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_on_boarding); + + final View nextButton = findViewById(R.id.buttonNext); + + pager = findViewById(R.id.pager); + pagerAdapter = new OnBoardingAdapter(getApplicationContext(), this); + pager.setAdapter(pagerAdapter); + int pixels = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()); + pager.setPageMargin(pixels); + pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(int i) { + setButtonState(); + } + }); + + final TabLayout tabLayout = (TabLayout) findViewById(R.id.tabLayout); + if (pagerAdapter.getCount() > 1) { + tabLayout.setupWithViewPager(pager, true); + } else { + tabLayout.setVisibility(View.GONE); + } + + nextButton.setOnClickListener(v -> { + final int item = pager.getCurrentItem(); + if (item + 1 >= pagerAdapter.getCount()) { + finishOnboarding(); + } else { + pager.setCurrentItem(item + 1); + } + }); + + for (int i = 0; i < OnBoardingScreen.values().length; i++) { + if (OnBoardingScreen.values()[i].isMustAgree()) mustAgreePages++; + } + } + + private void finishOnboarding() { + OnBoardingManager.setOnBoardingShown(getApplicationContext()); + startActivity(new Intent(this, LoginActivity.class)); + finish(); + } + + int agreeCounter = 0; + boolean agreed[] = new boolean[OnBoardingScreen.values().length]; + + @Override + public void setAgreeClicked(int position, boolean isChecked) { + if (isChecked) { + agreeCounter++; + } else { + agreeCounter--; + } + agreed[position] = isChecked; + setButtonState(); + } + + @Override + public boolean isAgreeClicked(int position) { + return agreed[position]; + } + + @Override + public void setButtonState() { + if (pager.getCurrentItem() + 1 == pagerAdapter.getCount()) { // last page + findViewById(R.id.buttonNext).setEnabled(mustAgreePages == agreeCounter); + } else { + findViewById(R.id.buttonNext).setEnabled(true); + } + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingAdapter.java new file mode 100644 index 00000000..5ec1936f --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingAdapter.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018-2020 EarlOfEgo, m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.onboarding; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.support.v4.view.PagerAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.TextView; + +import com.m2049r.xmrwallet.R; + +import timber.log.Timber; + +public class OnBoardingAdapter extends PagerAdapter { + + interface Listener { + void setAgreeClicked(int position, boolean isChecked); + + boolean isAgreeClicked(int position); + + void setButtonState(); + } + + private final Context context; + private Listener listener; + + OnBoardingAdapter(final Context context, final Listener listener) { + this.context = context; + this.listener = listener; + } + + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup collection, int position) { + LayoutInflater inflater = LayoutInflater.from(context); + final View view = inflater.inflate(R.layout.view_onboarding, collection, false); + final OnBoardingScreen onBoardingScreen = OnBoardingScreen.values()[position]; + + final Drawable drawable = ContextCompat.getDrawable(context, onBoardingScreen.getDrawable()); + ((ImageView) view.findViewById(R.id.onboardingImage)).setImageDrawable(drawable); + ((TextView) view.findViewById(R.id.onboardingTitle)).setText(onBoardingScreen.getTitle()); + ((TextView) view.findViewById(R.id.onboardingInformation)).setText(onBoardingScreen.getInformation()); + if (onBoardingScreen.isMustAgree()) { + final CheckBox agree = ((CheckBox) view.findViewById(R.id.onboardingAgree)); + agree.setVisibility(View.VISIBLE); + agree.setChecked(listener.isAgreeClicked(position)); + agree.setOnClickListener(v -> { + listener.setAgreeClicked(position, ((CheckBox) v).isChecked()); + }); + listener.setButtonState(); + } + collection.addView(view); + Timber.d("add " + position); + return view; + } + + @Override + public int getCount() { + return OnBoardingScreen.values().length; + } + + @Override + public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) { + Timber.d("destroy " + position); + collection.removeView((View) view); + } + + @Override + public boolean isViewFromObject(@NonNull final View view, @NonNull final Object object) { + return view == object; + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingManager.java b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingManager.java new file mode 100644 index 00000000..7be6a033 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingManager.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018-2020 EarlOfEgo, m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.onboarding; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.m2049r.xmrwallet.util.KeyStoreHelper; + +import java.util.Date; + +import timber.log.Timber; + +public class OnBoardingManager { + + private static final String PREFS_ONBOARDING = "PREFS_ONBOARDING"; + private static final String ONBOARDING_SHOWN = "ONBOARDING_SHOWN"; + + public static boolean shouldShowOnBoarding(final Context context) { + return !getSharedPreferences(context).contains(ONBOARDING_SHOWN) && KeyStoreHelper.hasStoredPasswords(context); + } + + public static void setOnBoardingShown(final Context context) { + Timber.d("Set onboarding shown."); + SharedPreferences sharedPreferences = getSharedPreferences(context); + sharedPreferences.edit().putLong(ONBOARDING_SHOWN, new Date().getTime()).apply(); + } + + public static void clearOnBoardingShown(final Context context) { + SharedPreferences sharedPreferences = getSharedPreferences(context); + sharedPreferences.edit().remove(ONBOARDING_SHOWN).apply(); + } + + private static SharedPreferences getSharedPreferences(final Context context) { + return context.getSharedPreferences(PREFS_ONBOARDING, Context.MODE_PRIVATE); + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingScreen.java b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingScreen.java new file mode 100644 index 00000000..bbef7add --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingScreen.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018-2020 EarlOfEgo, m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.onboarding; + +import com.m2049r.xmrwallet.R; + +enum OnBoardingScreen { + WELCOME(R.string.onboarding_welcome_title, R.string.onboarding_welcome_information, R.drawable.ic_onboarding_welcome, false), + SEED(R.string.onboarding_seed_title, R.string.onboarding_seed_information, R.drawable.ic_onboarding_seed, true), + FPSEND(R.string.onboarding_fpsend_title, R.string.onboarding_fpsend_information, R.drawable.ic_onboarding_fingerprint, true), + XMRTO(R.string.onboarding_xmrto_title, R.string.onboarding_xmrto_information, R.drawable.ic_onboarding_xmrto, false), + NODES(R.string.onboarding_nodes_title, R.string.onboarding_nodes_information, R.drawable.ic_onboarding_nodes, false); + + private final int title; + private final int information; + private final int drawable; + private final boolean mustAgree; + + OnBoardingScreen(final int title, final int information, final int drawable, final boolean mustAgree) { + this.title = title; + this.information = information; + this.drawable = drawable; + this.mustAgree = mustAgree; + } + + public int getTitle() { + return title; + } + + public int getInformation() { + return information; + } + + public int getDrawable() { + return drawable; + } + + public boolean isMustAgree() { + return mustAgree; + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java index d00b3575..eb94e62a 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java @@ -140,6 +140,11 @@ public class KeyStoreHelper { } } + public static boolean hasStoredPasswords(@NonNull Context context) { + SharedPreferences prefs = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE); + return prefs.getAll().size() > 0; + } + public static String loadWalletUserPass(@NonNull Context context, String wallet) throws BrokenPasswordStoreException { String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet; String encoded = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE) diff --git a/app/src/main/res/drawable/dot_dark.xml b/app/src/main/res/drawable/dot_dark.xml new file mode 100644 index 00000000..c9dfb9ca --- /dev/null +++ b/app/src/main/res/drawable/dot_dark.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/dot_light.xml b/app/src/main/res/drawable/dot_light.xml new file mode 100644 index 00000000..e608d7ad --- /dev/null +++ b/app/src/main/res/drawable/dot_light.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_onboarding_fingerprint.xml b/app/src/main/res/drawable/ic_onboarding_fingerprint.xml new file mode 100644 index 00000000..9ec70a90 --- /dev/null +++ b/app/src/main/res/drawable/ic_onboarding_fingerprint.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_onboarding_nodes.xml b/app/src/main/res/drawable/ic_onboarding_nodes.xml new file mode 100644 index 00000000..391d3464 --- /dev/null +++ b/app/src/main/res/drawable/ic_onboarding_nodes.xml @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_onboarding_seed.xml b/app/src/main/res/drawable/ic_onboarding_seed.xml new file mode 100644 index 00000000..34811577 --- /dev/null +++ b/app/src/main/res/drawable/ic_onboarding_seed.xml @@ -0,0 +1,393 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_onboarding_welcome.xml b/app/src/main/res/drawable/ic_onboarding_welcome.xml new file mode 100644 index 00000000..0158b095 --- /dev/null +++ b/app/src/main/res/drawable/ic_onboarding_welcome.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_onboarding_xmrto.xml b/app/src/main/res/drawable/ic_onboarding_xmrto.xml new file mode 100644 index 00000000..6476a203 --- /dev/null +++ b/app/src/main/res/drawable/ic_onboarding_xmrto.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/onboarding_dots.xml b/app/src/main/res/drawable/onboarding_dots.xml new file mode 100644 index 00000000..d3889826 --- /dev/null +++ b/app/src/main/res/drawable/onboarding_dots.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_on_boarding.xml b/app/src/main/res/layout/activity_on_boarding.xml new file mode 100644 index 00000000..4f945885 --- /dev/null +++ b/app/src/main/res/layout/activity_on_boarding.xml @@ -0,0 +1,57 @@ + + + +