Create an initial intro screen on first launch

This commit is contained in:
Jakob Nixdorf 2018-02-27 16:43:02 +01:00
parent b610afdf93
commit 324f84b36c
No known key found for this signature in database
GPG key ID: BE99BF86574A7DBC
9 changed files with 222 additions and 4 deletions

View file

@ -25,6 +25,9 @@ android {
lintOptions { lintOptions {
abortOnError false abortOnError false
} }
dataBinding {
enabled = true
}
} }
ext { ext {
@ -41,6 +44,7 @@ dependencies {
compile "com.android.support:recyclerview-v7:$supportLibVersion" compile "com.android.support:recyclerview-v7:$supportLibVersion"
compile "com.android.support.constraint:constraint-layout:1.0.2" compile "com.android.support.constraint:constraint-layout:1.0.2"
compile "com.github.aakira:expandable-layout:1.6.0" compile "com.github.aakira:expandable-layout:1.6.0"
compile "com.heinrichreimersoftware:material-intro:1.6.2"
compile "com.journeyapps:zxing-android-embedded:3.5.0" compile "com.journeyapps:zxing-android-embedded:3.5.0"
compile "com.vanniktech:vntnumberpickerpreference:1.0.0" compile "com.vanniktech:vntnumberpickerpreference:1.0.0"
compile "de.psdev.licensesdialog:licensesdialog:1.8.3" compile "de.psdev.licensesdialog:licensesdialog:1.8.3"

View file

@ -35,6 +35,10 @@
android:name=".Activities.BackupActivity" android:name=".Activities.BackupActivity"
android:parentActivityName=".Activities.MainActivity" android:parentActivityName=".Activities.MainActivity"
android:theme="@style/AppTheme.NoActionBar" /> android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".Activities.MainIntroActivity"
android:parentActivityName=".Activities.MainActivity"
android:theme="@style/Theme.Intro" />
<activity <activity
android:name=".Activities.SettingsActivity" android:name=".Activities.SettingsActivity"
android:parentActivityName=".Activities.MainActivity" android:parentActivityName=".Activities.MainActivity"

View file

@ -103,7 +103,7 @@ public class MainActivity extends BaseActivity
.initiateScan(); .initiateScan();
} }
private void showFirstTimeWarning() { /*private void showFirstTimeWarning() {
ViewGroup container = findViewById(R.id.main_content); ViewGroup container = findViewById(R.id.main_content);
View msgView = getLayoutInflater().inflate(R.layout.dialog_database_encryption, container, false); View msgView = getLayoutInflater().inflate(R.layout.dialog_database_encryption, container, false);
@ -135,6 +135,11 @@ public class MainActivity extends BaseActivity
}) })
.create() .create()
.show(); .show();
}*/
private void showFirstTimeWarning() {
Intent introIntent = new Intent(this, MainIntroActivity.class);
startActivityForResult(introIntent, Constants.INTENT_MAIN_INTRO);
} }
public void authenticate(int messageId) { public void authenticate(int messageId) {

View file

@ -0,0 +1,140 @@
package org.shadowice.flocke.andotp.Activities;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Spinner;
import android.widget.TextView;
import com.heinrichreimersoftware.materialintro.app.IntroActivity;
import com.heinrichreimersoftware.materialintro.app.SlideFragment;
import com.heinrichreimersoftware.materialintro.slide.FragmentSlide;
import com.heinrichreimersoftware.materialintro.slide.SimpleSlide;
import org.shadowice.flocke.andotp.R;
import org.shadowice.flocke.andotp.Utilities.Constants;
import org.shadowice.flocke.andotp.Utilities.Settings;
public class MainIntroActivity extends IntroActivity {
private Settings settings;
private EncryptionFragment encryptionFragment;
@Override protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
settings = new Settings(this);
encryptionFragment = new EncryptionFragment();
setButtonBackFunction(BUTTON_BACK_FUNCTION_BACK);
addSlide(new SimpleSlide.Builder()
.title(R.string.intro_slide1_title)
.description(R.string.intro_slide1_desc)
.image(R.mipmap.ic_launcher)
.background(R.color.colorPrimary)
.backgroundDark(R.color.colorPrimaryDark)
.canGoBackward(false)
.scrollable(false)
.build()
);
addSlide(new FragmentSlide.Builder()
.background(R.color.colorPrimary)
.backgroundDark(R.color.colorPrimaryDark)
.fragment(encryptionFragment)
.build()
);
// TODO: Set authentication
addSlide(new SimpleSlide.Builder()
.title(R.string.intro_slide1_title)
.description(R.string.intro_slide1_desc)
.image(R.mipmap.ic_launcher)
.background(R.color.colorPrimary)
.backgroundDark(R.color.colorPrimaryDark)
.scrollable(false)
.build()
);
addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
if (position == 2) {
// TODO: Inter-page communication
settings.setEncryption(encryptionFragment.getSelectedEncryption());
Log.d("INTRO", "Encryption saved");
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
@Override
public void onBackPressed() {
if (getCurrentSlidePosition() != 0)
super.onBackPressed();
}
public static class EncryptionFragment extends SlideFragment {
private Spinner selection;
private TextView desc;
private SparseArray<Constants.EncryptionType> selectionMapping;
public EncryptionFragment() {
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.component_intro_encryption, container, false);
selection = root.findViewById(R.id.introEncryptionSelection);
desc = root.findViewById(R.id.introEncryptionDesc);
final String[] encValues = getResources().getStringArray(R.array.settings_values_encryption);
selectionMapping = new SparseArray<>();
for (int i = 0; i < encValues.length; i++)
selectionMapping.put(i, Constants.EncryptionType.valueOf(encValues[i].toUpperCase()));
selection.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
Constants.EncryptionType encryptionType = selectionMapping.get(i);
if (encryptionType == Constants.EncryptionType.PASSWORD)
desc.setText(R.string.intro_slide2_desc_password);
else if (encryptionType == Constants.EncryptionType.KEYSTORE)
desc.setText(R.string.intro_slide2_desc_keystore);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
return root;
}
private Constants.EncryptionType getSelectedEncryption() {
return selectionMapping.get(selection.getSelectedItemPosition());
}
}
}

View file

@ -52,6 +52,7 @@ public class Constants {
public final static int INTENT_MAIN_AUTHENTICATE = 100; public final static int INTENT_MAIN_AUTHENTICATE = 100;
public final static int INTENT_MAIN_SETTINGS = 101; public final static int INTENT_MAIN_SETTINGS = 101;
public final static int INTENT_MAIN_BACKUP = 102; public final static int INTENT_MAIN_BACKUP = 102;
public final static int INTENT_MAIN_INTRO = 103;
public final static int INTENT_BACKUP_OPEN_DOCUMENT_PLAIN = 200; public final static int INTENT_BACKUP_OPEN_DOCUMENT_PLAIN = 200;
public final static int INTENT_BACKUP_SAVE_DOCUMENT_PLAIN = 201; public final static int INTENT_BACKUP_SAVE_DOCUMENT_PLAIN = 201;

View file

@ -290,6 +290,10 @@ public class Settings {
return EncryptionType.valueOf(encType.toUpperCase()); return EncryptionType.valueOf(encType.toUpperCase());
} }
public void setEncryption(EncryptionType encryptionType) {
setEncryption(encryptionType.name().toLowerCase());
}
public void setEncryption(String encryption) { public void setEncryption(String encryption) {
setString(R.string.settings_key_encryption, encryption); setString(R.string.settings_key_encryption, encryption);
} }

View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:padding="@dimen/activity_margin_large" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
android:textStyle="bold"
android:text="@string/settings_title_encryption" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_margin"
android:text="@string/intro_slide2_desc"/>
<Spinner
android:id="@+id/introEncryptionSelection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_margin_large"
android:layout_marginStart="@dimen/activity_margin"
android:layout_marginEnd="@dimen/activity_margin"
android:entries="@array/settings_entries_encryption" />
<TextView
android:id="@+id/introEncryptionDesc"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/activity_margin_large"
android:text="@string/intro_slide2_desc_keystore"/>
</LinearLayout>

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="intro_slide1_title">Welcome to andOTP</string>
<string name="intro_slide1_desc">This wizard will guide you through the initial setup.</string>
<string name="intro_slide2_desc">To ensure the security of your accounts andOTP only stores them
in encrypted data files. Here you can choose which method of encryption will be used:</string>
<string name="intro_slide2_desc_keystore">The KeyStore is a system component of Android for
securely storing cryptographic keys. The advantage of this method is that the keys are
stored separated from the data files and can be backed by hardware cryptography (if your
device supports it). However, as the keys are not stored with the apps data <b>this method
prevents external backup solutions (like Titanium) from working</b>. If you choose this
method you will have to rely on the internal backup functions provided by andOTP.
\n\n<b>Warning</b>: The KeyStore is known to cause problems on some custom ROMs (and a few
stock ones as well). If you don\'t mind entering a password / PIN every time you start
andOTP it is highly recommended to use the password-based encryption.</string>
<string name="intro_slide2_desc_password">This method will encrypt your data with a key
generated from a password or PIN. The main advantage here is that this will work with
external backup solutions (like Titanium). However, you will have to enter your credentials
every time you start andOTP.</string>
</resources>

View file

@ -18,9 +18,8 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
jcenter() jcenter()
maven { maven { url "https://maven.google.com" }
url "https://maven.google.com" maven { url "https://jitpack.io" }
}
} }
} }