mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-21 22:39:12 +00:00
Merge pull request #925 from alexbakker/intro-import
Add an import button to the intro
This commit is contained in:
commit
2dc01adc48
13 changed files with 277 additions and 24 deletions
|
@ -74,7 +74,7 @@ public abstract class AegisTest {
|
|||
private VaultRepository initVault(@Nullable VaultFileCredentials creds, @Nullable List<VaultEntry> entries) {
|
||||
VaultRepository vault;
|
||||
try {
|
||||
vault = _vaultManager.init(creds);
|
||||
vault = _vaultManager.initNew(creds);
|
||||
} catch (VaultRepositoryException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
|
|
@ -6,16 +6,25 @@ import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
|||
import static androidx.test.espresso.action.ViewActions.replaceText;
|
||||
import static androidx.test.espresso.action.ViewActions.typeText;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.intent.Intents.intending;
|
||||
import static androidx.test.espresso.intent.matcher.IntentMatchers.isInternal;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
import static junit.framework.TestCase.assertFalse;
|
||||
import static junit.framework.TestCase.assertNull;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Instrumentation;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.test.espresso.IdlingRegistry;
|
||||
import androidx.test.espresso.IdlingResource;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.espresso.intent.Intents;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
|
@ -23,6 +32,7 @@ import androidx.viewpager2.widget.ViewPager2;
|
|||
|
||||
import com.beemdevelopment.aegis.rules.ScreenshotTestRule;
|
||||
import com.beemdevelopment.aegis.ui.IntroActivity;
|
||||
import com.beemdevelopment.aegis.util.IOUtils;
|
||||
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
|
@ -36,6 +46,11 @@ import org.junit.rules.RuleChain;
|
|||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import dagger.hilt.android.testing.HiltAndroidTest;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
|
@ -51,6 +66,8 @@ public class IntroTest extends AegisTest {
|
|||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Intents.init();
|
||||
|
||||
_activityRule.getScenario().onActivity(activity -> {
|
||||
_viewPager2IdlingResource = new ViewPager2IdlingResource(activity.findViewById(R.id.pager), "viewPagerIdlingResource");
|
||||
IdlingRegistry.getInstance().register(_viewPager2IdlingResource);
|
||||
|
@ -59,6 +76,7 @@ public class IntroTest extends AegisTest {
|
|||
|
||||
@After
|
||||
public void tearDown() {
|
||||
Intents.release();
|
||||
IdlingRegistry.getInstance().unregister(_viewPager2IdlingResource);
|
||||
}
|
||||
|
||||
|
@ -113,6 +131,58 @@ public class IntroTest extends AegisTest {
|
|||
assertFalse(slots.has(BiometricSlot.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doIntro_Import_Plain() {
|
||||
Uri uri = getResourceUri("aegis_plain.json");
|
||||
Intent resultData = new Intent();
|
||||
resultData.setData(uri);
|
||||
|
||||
Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData);
|
||||
intending(not(isInternal())).respondWith(result);
|
||||
|
||||
ViewInteraction next = onView(withId(R.id.btnNext));
|
||||
onView(withId(R.id.btnImport)).perform(click());
|
||||
next.perform(click());
|
||||
|
||||
VaultRepository vault = _vaultManager.getVault();
|
||||
assertFalse(vault.isEncryptionEnabled());
|
||||
assertNull(vault.getCredentials());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doIntro_Import_Encrypted() {
|
||||
Uri uri = getResourceUri("aegis_encrypted.json");
|
||||
Intent resultData = new Intent();
|
||||
resultData.setData(uri);
|
||||
|
||||
Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData);
|
||||
intending(not(isInternal())).respondWith(result);
|
||||
|
||||
ViewInteraction next = onView(withId(R.id.btnNext));
|
||||
onView(withId(R.id.btnImport)).perform(click());
|
||||
onView(withId(R.id.text_input)).perform(typeText(VAULT_PASSWORD), closeSoftKeyboard());
|
||||
onView(withId(android.R.id.button1)).perform(click());
|
||||
next.perform(click());
|
||||
|
||||
VaultRepository vault = _vaultManager.getVault();
|
||||
SlotList slots = vault.getCredentials().getSlots();
|
||||
assertTrue(vault.isEncryptionEnabled());
|
||||
assertTrue(slots.has(PasswordSlot.class));
|
||||
assertFalse(slots.has(BiometricSlot.class));
|
||||
}
|
||||
|
||||
private Uri getResourceUri(String resourceName) {
|
||||
File targetFile = new File(getInstrumentation().getTargetContext().getExternalCacheDir(), resourceName);
|
||||
try (InputStream inStream = getClass().getResourceAsStream(resourceName);
|
||||
FileOutputStream outStream = new FileOutputStream(targetFile)) {
|
||||
IOUtils.copy(inStream, outStream);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return Uri.fromFile(targetFile);
|
||||
}
|
||||
|
||||
// Source: https://stackoverflow.com/a/32763454/12972657
|
||||
private static class ViewPager2IdlingResource implements IdlingResource {
|
||||
private final String _resName;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../../../../../test/resources/com/beemdevelopment/aegis/importers/aegis_encrypted.json
|
|
@ -0,0 +1 @@
|
|||
../../../../../test/resources/com/beemdevelopment/aegis/importers/aegis_plain.json
|
|
@ -3,6 +3,7 @@ package com.beemdevelopment.aegis.importers;
|
|||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
|
@ -72,7 +73,7 @@ public class AegisImporter extends DatabaseImporter {
|
|||
throw new DatabaseImporterException(e);
|
||||
}
|
||||
|
||||
return new DecryptedState(obj);
|
||||
return new DecryptedState(obj, creds);
|
||||
}
|
||||
|
||||
public State decrypt(char[] password) throws DatabaseImporterException {
|
||||
|
@ -109,10 +110,21 @@ public class AegisImporter extends DatabaseImporter {
|
|||
|
||||
public static class DecryptedState extends State {
|
||||
private JSONObject _obj;
|
||||
private VaultFileCredentials _creds;
|
||||
|
||||
private DecryptedState(JSONObject obj) {
|
||||
this(obj, null);
|
||||
}
|
||||
|
||||
private DecryptedState(JSONObject obj, VaultFileCredentials creds) {
|
||||
super(false);
|
||||
_obj = obj;
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public VaultFileCredentials getCredentials() {
|
||||
return _creds;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -6,6 +6,7 @@ import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE
|
|||
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_PASS;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
|
@ -51,6 +52,18 @@ public class IntroActivity extends IntroBaseActivity {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (oldSlide == WelcomeSlide.class
|
||||
&& newSlide == SecurityPickerSlide.class
|
||||
&& getState().getBoolean("imported")) {
|
||||
skipToSlide(DoneSlide.class);
|
||||
return true;
|
||||
}
|
||||
|
||||
// on the welcome page, we don't want the keyboard to push any views up
|
||||
getWindow().setSoftInputMode(newSlide == WelcomeSlide.class
|
||||
? WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
|
||||
: WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -58,8 +71,9 @@ public class IntroActivity extends IntroBaseActivity {
|
|||
protected void onDonePressed() {
|
||||
Bundle state = getState();
|
||||
|
||||
int cryptType = state.getInt("cryptType", CRYPT_TYPE_INVALID);
|
||||
VaultFileCredentials creds = (VaultFileCredentials) state.getSerializable("creds");
|
||||
if (!state.getBoolean("imported")) {
|
||||
int cryptType = state.getInt("cryptType", CRYPT_TYPE_INVALID);
|
||||
if (cryptType == CRYPT_TYPE_INVALID
|
||||
|| (cryptType == CRYPT_TYPE_NONE && creds != null)
|
||||
|| (cryptType == CRYPT_TYPE_PASS && (creds == null || !creds.getSlots().has(PasswordSlot.class)))
|
||||
|
@ -68,12 +82,21 @@ public class IntroActivity extends IntroBaseActivity {
|
|||
}
|
||||
|
||||
try {
|
||||
_vaultManager.init(creds);
|
||||
_vaultManager.initNew(creds);
|
||||
} catch (VaultRepositoryException e) {
|
||||
e.printStackTrace();
|
||||
Dialogs.showErrorDialog(this, R.string.vault_init_error, e);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
_vaultManager.load(creds);
|
||||
} catch (VaultRepositoryException e) {
|
||||
e.printStackTrace();
|
||||
Dialogs.showErrorDialog(this, R.string.vault_load_error, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// skip the intro from now on
|
||||
_prefs.setIntroDone(true);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package com.beemdevelopment.aegis.ui.intro;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
@ -13,7 +13,6 @@ import androidx.viewpager2.adapter.FragmentStateAdapter;
|
|||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.Theme;
|
||||
import com.beemdevelopment.aegis.ui.AegisActivity;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
@ -116,7 +115,7 @@ public abstract class IntroBaseActivity extends AegisActivity implements IntroAc
|
|||
* @param newSlide the next slide that will be shown.
|
||||
* @return whether to block the transition.
|
||||
*/
|
||||
protected boolean onBeforeSlideChanged(Class<? extends SlideFragment> oldSlide, Class<? extends SlideFragment> newSlide) {
|
||||
protected boolean onBeforeSlideChanged(@Nullable Class<? extends SlideFragment> oldSlide, @NonNull Class<? extends SlideFragment> newSlide) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -125,7 +124,7 @@ public abstract class IntroBaseActivity extends AegisActivity implements IntroAc
|
|||
* @param oldSlide the slide that was previously shown.
|
||||
* @param newSlide the slide that is now shown.
|
||||
*/
|
||||
protected void onAfterSlideChanged(Class<? extends SlideFragment> oldSlide, Class<? extends SlideFragment> newSlide) {
|
||||
protected void onAfterSlideChanged(@Nullable Class<? extends SlideFragment> oldSlide, @NonNull Class<? extends SlideFragment> newSlide) {
|
||||
|
||||
}
|
||||
|
||||
|
@ -178,6 +177,13 @@ public abstract class IntroBaseActivity extends AegisActivity implements IntroAc
|
|||
|
||||
_slides.add(type);
|
||||
_slideIndicator.setSlideCount(_slides.size());
|
||||
|
||||
// send 'slide changed' events for the first slide
|
||||
if (_slides.size() == 1) {
|
||||
Class<? extends SlideFragment> slide = _slides.get(0);
|
||||
onBeforeSlideChanged(null, slide);
|
||||
onAfterSlideChanged(null, slide);
|
||||
}
|
||||
}
|
||||
|
||||
private class ScreenSlidePagerAdapter extends FragmentStateAdapter {
|
||||
|
|
|
@ -1,16 +1,109 @@
|
|||
package com.beemdevelopment.aegis.ui.slides;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.importers.AegisImporter;
|
||||
import com.beemdevelopment.aegis.importers.DatabaseImporter;
|
||||
import com.beemdevelopment.aegis.importers.DatabaseImporterException;
|
||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||
import com.beemdevelopment.aegis.ui.intro.SlideFragment;
|
||||
import com.beemdevelopment.aegis.ui.tasks.ImportFileTask;
|
||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class WelcomeSlide extends SlideFragment {
|
||||
public static final int CODE_IMPORT_VAULT = 0;
|
||||
|
||||
private boolean _imported;
|
||||
private VaultFileCredentials _creds;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_welcome_slide, container, false);
|
||||
View view = inflater.inflate(R.layout.fragment_welcome_slide, container, false);
|
||||
view.findViewById(R.id.btnImport).setOnClickListener(v -> {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("*/*");
|
||||
startActivityForResult(intent, CODE_IMPORT_VAULT);
|
||||
});
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == CODE_IMPORT_VAULT && data != null && data.getData() != null) {
|
||||
startImportVault(data.getData());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveIntroState(@NonNull Bundle introState) {
|
||||
introState.putBoolean("imported", _imported);
|
||||
introState.putSerializable("creds", _creds);
|
||||
}
|
||||
|
||||
private void startImportVault(Uri uri) {
|
||||
ImportFileTask.Params params = new ImportFileTask.Params(uri, "intro-import", null);
|
||||
ImportFileTask task = new ImportFileTask(requireContext(), result -> {
|
||||
if (result.getException() != null) {
|
||||
Dialogs.showErrorDialog(requireContext(), R.string.reading_file_error, result.getException());
|
||||
return;
|
||||
}
|
||||
|
||||
try (FileInputStream inStream = new FileInputStream(result.getFile())) {
|
||||
AegisImporter importer = new AegisImporter(requireContext());
|
||||
DatabaseImporter.State state = importer.read(inStream, false);
|
||||
if (state.isEncrypted()) {
|
||||
state.decrypt(requireContext(), new DatabaseImporter.DecryptListener() {
|
||||
@Override
|
||||
protected void onStateDecrypted(DatabaseImporter.State state) {
|
||||
_creds = ((AegisImporter.DecryptedState) state).getCredentials();
|
||||
importVault(result.getFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onError(Exception e) {
|
||||
e.printStackTrace();
|
||||
Dialogs.showErrorDialog(requireContext(), R.string.decryption_error, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCanceled() {
|
||||
|
||||
}
|
||||
});
|
||||
} else {
|
||||
importVault(result.getFile());
|
||||
}
|
||||
} catch (DatabaseImporterException | IOException e) {
|
||||
e.printStackTrace();
|
||||
Dialogs.showErrorDialog(requireContext(), R.string.intro_import_error_title, e);
|
||||
}
|
||||
});
|
||||
task.execute(getLifecycle(), params);
|
||||
}
|
||||
|
||||
private void importVault(File file) {
|
||||
try (FileInputStream inStream = new FileInputStream(file)) {
|
||||
VaultRepository.writeToFile(requireContext(), inStream);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Dialogs.showErrorDialog(requireContext(), R.string.intro_import_error_title, e);
|
||||
return;
|
||||
}
|
||||
|
||||
_imported = true;
|
||||
goToNextSlide();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ public class VaultManager {
|
|||
|
||||
if (_vaultFile != null && !_vaultFile.isEncrypted()) {
|
||||
try {
|
||||
load(_vaultFile, null);
|
||||
loadFrom(_vaultFile, null);
|
||||
} catch (VaultRepositoryException e) {
|
||||
e.printStackTrace();
|
||||
_vaultFile = null;
|
||||
|
@ -76,7 +76,7 @@ public class VaultManager {
|
|||
* Calling this method removes the manager's internal reference to the raw vault file (if it had one).
|
||||
*/
|
||||
@NonNull
|
||||
public VaultRepository init(@Nullable VaultFileCredentials creds) throws VaultRepositoryException {
|
||||
public VaultRepository initNew(@Nullable VaultFileCredentials creds) throws VaultRepositoryException {
|
||||
if (isVaultLoaded()) {
|
||||
throw new IllegalStateException("Vault manager is already initialized");
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ public class VaultManager {
|
|||
* Calling this method removes the manager's internal reference to the raw vault file (if it had one).
|
||||
*/
|
||||
@NonNull
|
||||
public VaultRepository load(@NonNull VaultFile vaultFile, @Nullable VaultFileCredentials creds) throws VaultRepositoryException {
|
||||
public VaultRepository loadFrom(@NonNull VaultFile vaultFile, @Nullable VaultFileCredentials creds) throws VaultRepositoryException {
|
||||
if (isVaultLoaded()) {
|
||||
throw new IllegalStateException("Vault manager is already initialized");
|
||||
}
|
||||
|
@ -116,9 +116,30 @@ public class VaultManager {
|
|||
return getVault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the vault repository by loading and decrypting the vault file stored in
|
||||
* internal storage, with the given creds. It can only be called if isVaultLoaded()
|
||||
* returns false.
|
||||
*
|
||||
* Calling this method removes the manager's internal reference to the raw vault file (if it had one).
|
||||
*/
|
||||
@NonNull
|
||||
public VaultRepository load(@Nullable VaultFileCredentials creds) throws VaultRepositoryException {
|
||||
if (isVaultLoaded()) {
|
||||
throw new IllegalStateException("Vault manager is already initialized");
|
||||
}
|
||||
|
||||
loadVaultFile();
|
||||
if (isVaultLoaded()) {
|
||||
return _repo;
|
||||
}
|
||||
|
||||
return loadFrom(getVaultFile(), creds);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public VaultRepository unlock(@NonNull VaultFileCredentials creds) throws VaultRepositoryException {
|
||||
VaultRepository repo = load(getVaultFile(), creds);
|
||||
VaultRepository repo = loadFrom(getVaultFile(), creds);
|
||||
startNotificationService();
|
||||
return repo;
|
||||
}
|
||||
|
|
|
@ -28,13 +28,27 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/titleText"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnImport"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="@string/import_vault"
|
||||
android:textSize="11sp"
|
||||
style="?attr/introButtonStyle"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/descriptionText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/descriptionText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_description"
|
||||
android:textAlignment="center"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<attr name="iconColorInverted" format="color" />
|
||||
<attr name="dropdownStyle" format="reference" />
|
||||
<attr name="colorAppBar" format="color" />
|
||||
<attr name="introButtonStyle" format="reference" />
|
||||
|
||||
<declare-styleable name="SlideIndicator">
|
||||
<attr name="dot_radius" format="dimension" />
|
||||
|
|
|
@ -224,6 +224,7 @@
|
|||
<item quantity="one">Imported %d entry</item>
|
||||
<item quantity="other">Imported %d entries</item>
|
||||
</plurals>
|
||||
<string name="intro_import_error_title">An error occurred while importing the vault</string>
|
||||
<string name="import_error_title">One or more errors occurred during the import</string>
|
||||
<string name="exporting_vault_error">An error occurred while trying to export the vault</string>
|
||||
<string name="exported_vault">The vault has been exported</string>
|
||||
|
@ -392,6 +393,7 @@
|
|||
<string name="pref_panic_trigger_title">Delete vault on panic trigger</string>
|
||||
<string name="pref_panic_trigger_summary">Delete vault when a panic trigger is received from Ripple</string>
|
||||
|
||||
<string name="import_vault">Import vault</string>
|
||||
<string name="importer_help_2fas">Supply a 2FAS Authenticator backup file.</string>
|
||||
<string name="importer_help_aegis">Supply an Aegis export/backup file.</string>
|
||||
<string name="importer_help_authenticator_plus">Supply an Authenticator Plus export file obtained through <b>Settings -> Backup & Restore -> Export as Text and HTML</b>.</string>
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
<item name="alertDialogTheme">@style/Theme.Aegis.Dialog.Light</item>
|
||||
<item name="preferenceTheme">@style/Theme.Aegis.Preference.Light</item>
|
||||
<item name="textInputStyle">@style/Widget.Aegis.TextInput.Light</item>
|
||||
<item name="introButtonStyle">@style/Widget.Aegis.OutlinedButton.Light</item>
|
||||
<item name="dropdownStyle">@style/Widget.Aegis.Dropdown.Light</item>
|
||||
<item name="bottomSheetDialogTheme">@style/ThemeOverLay.Aegis.BottomSheetDialog.Rounded</item>
|
||||
|
||||
|
@ -74,6 +75,7 @@
|
|||
<item name="alertDialogTheme">@style/Theme.Aegis.Dialog.Dark</item>
|
||||
<item name="preferenceTheme">@style/Theme.Aegis.Preference.Dark</item>
|
||||
<item name="textInputStyle">@style/Widget.Aegis.TextInput.Dark</item>
|
||||
<item name="introButtonStyle">@style/Widget.Aegis.OutlinedButton.Dark</item>
|
||||
<item name="dropdownStyle">@style/Widget.Aegis.Dropdown.Dark</item>
|
||||
<item name="bottomSheetDialogTheme">@style/ThemeOverLay.Aegis.BottomSheetDialog.Rounded</item>
|
||||
|
||||
|
@ -108,6 +110,7 @@
|
|||
<item name="alertDialogTheme">@style/Theme.Aegis.Dialog.TrueDark</item>
|
||||
<item name="preferenceTheme">@style/Theme.Aegis.Preference.Dark</item>
|
||||
<item name="textInputStyle">@style/Widget.Aegis.TextInput.Dark</item>
|
||||
<item name="introButtonStyle">@style/Widget.Aegis.OutlinedButton.Dark</item>
|
||||
<item name="dropdownStyle">@style/Widget.Aegis.Dropdown.Dark</item>
|
||||
<item name="bottomSheetDialogTheme">@style/ThemeOverLay.Aegis.BottomSheetDialog.Rounded</item>
|
||||
|
||||
|
@ -224,6 +227,12 @@
|
|||
<item name="android:textColor">@color/dialog_button_color</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Aegis.OutlinedButton.Light" parent="Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||
<style name="Widget.Aegis.OutlinedButton.Dark" parent="Widget.MaterialComponents.Button.OutlinedButton">
|
||||
<item name="android:textColor">@color/secondary_text</item>
|
||||
<item name="rippleColor">@color/secondary_text</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Aegis.BottomSheet.Rounded" parent="Widget.MaterialComponents.BottomSheet">
|
||||
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Aegis.BottomSheetDialog.Rounded</item>
|
||||
<item name="android:background">@android:color/transparent</item>
|
||||
|
|
Loading…
Add table
Reference in a new issue