Unlock UI
This commit is contained in:
parent
3981e525eb
commit
2733834df0
17
.idea/deploymentTargetDropDown.xml
Normal file
17
.idea/deploymentTargetDropDown.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetDropDown">
|
||||||
|
<runningDeviceTargetSelectedWithDropDown>
|
||||||
|
<Target>
|
||||||
|
<type value="RUNNING_DEVICE_TARGET" />
|
||||||
|
<deviceKey>
|
||||||
|
<Key>
|
||||||
|
<type value="SERIAL_NUMBER" />
|
||||||
|
<value value="R38N50464FV" />
|
||||||
|
</Key>
|
||||||
|
</deviceKey>
|
||||||
|
</Target>
|
||||||
|
</runningDeviceTargetSelectedWithDropDown>
|
||||||
|
<timeTargetWasSelectedWithDropDown value="2023-09-18T13:23:48.152301762Z" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -36,8 +36,13 @@
|
|||||||
android:theme="@style/Theme.CringeAuthenticator.None"
|
android:theme="@style/Theme.CringeAuthenticator.None"
|
||||||
android:configChanges="orientation|screenSize">
|
android:configChanges="orientation|screenSize">
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name=".unlock.UnlockActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Theme.CringeAuthenticator.None"
|
||||||
|
android:configChanges="orientation|screenSize">
|
||||||
|
</activity>
|
||||||
<activity android:name=".scanner.QRScannerActivity"
|
<activity android:name=".scanner.QRScannerActivity"
|
||||||
android:exported="true"
|
android:exported="false"
|
||||||
android:theme="@style/Theme.CringeAuthenticator.None"
|
android:theme="@style/Theme.CringeAuthenticator.None"
|
||||||
android:configChanges="orientation|screenSize">
|
android:configChanges="orientation|screenSize">
|
||||||
</activity>
|
</activity>
|
||||||
@ -47,7 +52,7 @@
|
|||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="otpauth" />
|
<data android:scheme="otpauth"/>
|
||||||
<data android:scheme="otpauth-migration" />
|
<data android:scheme="otpauth-migration" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.cringe_studios.cringe_authenticator.unlock.UnlockContract;
|
||||||
|
|
||||||
|
public class BaseActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private ActivityResultLauncher<Void> startUnlockActivity;
|
||||||
|
|
||||||
|
private Runnable unlockSuccess, unlockFailure;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
registerCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerCallbacks() {
|
||||||
|
startUnlockActivity = registerForActivityResult(new UnlockContract(), success -> {
|
||||||
|
if(success && unlockSuccess != null) unlockSuccess.run();
|
||||||
|
if(!success && unlockFailure != null) unlockFailure.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void promptUnlock(Runnable success, Runnable failure) {
|
||||||
|
unlockSuccess = success;
|
||||||
|
unlockFailure = failure;
|
||||||
|
startUnlockActivity.launch(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -10,6 +10,7 @@ import android.widget.Toast;
|
|||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
import com.cringe_studios.cringe_authenticator.databinding.ActivityIntroBinding;
|
import com.cringe_studios.cringe_authenticator.databinding.ActivityIntroBinding;
|
||||||
|
import com.cringe_studios.cringe_authenticator.unlock.UnlockActivity;
|
||||||
import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
|
import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
|
||||||
|
|
||||||
public class IntroActivity extends AppCompatActivity {
|
public class IntroActivity extends AppCompatActivity {
|
||||||
@ -50,7 +51,7 @@ public class IntroActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void openMainActivity() {
|
public void openMainActivity() {
|
||||||
Intent m = new Intent(getApplicationContext(), MainActivity.class);
|
Intent m = new Intent(getApplicationContext(), UnlockActivity.class);
|
||||||
m.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
m.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||||
startActivity(m);
|
startActivity(m);
|
||||||
finish();
|
finish();
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
package com.cringe_studios.cringe_authenticator;
|
package com.cringe_studios.cringe_authenticator;
|
||||||
|
|
||||||
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
|
||||||
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
|
||||||
|
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.security.keystore.KeyProtection;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -19,13 +14,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.biometric.BiometricManager;
|
|
||||||
import androidx.biometric.BiometricPrompt;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import com.cringe_studios.cringe_authenticator.crypto.Crypto;
|
|
||||||
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
|
||||||
import com.cringe_studios.cringe_authenticator.databinding.ActivityMainBinding;
|
import com.cringe_studios.cringe_authenticator.databinding.ActivityMainBinding;
|
||||||
import com.cringe_studios.cringe_authenticator.databinding.DialogInputCodeChoiceBinding;
|
import com.cringe_studios.cringe_authenticator.databinding.DialogInputCodeChoiceBinding;
|
||||||
import com.cringe_studios.cringe_authenticator.fragment.AboutFragment;
|
import com.cringe_studios.cringe_authenticator.fragment.AboutFragment;
|
||||||
@ -44,25 +34,9 @@ import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder;
|
|||||||
import com.cringe_studios.cringe_authenticator.util.ThemeUtil;
|
import com.cringe_studios.cringe_authenticator.util.ThemeUtil;
|
||||||
import com.cringe_studios.cringe_authenticator_library.OTPType;
|
import com.cringe_studios.cringe_authenticator_library.OTPType;
|
||||||
|
|
||||||
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
|
|
||||||
import org.bouncycastle.crypto.params.Argon2Parameters;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
public class MainActivity extends BaseActivity {
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
private static final long LOCK_TIMEOUT = 10000;
|
|
||||||
|
|
||||||
private ActivityMainBinding binding;
|
private ActivityMainBinding binding;
|
||||||
|
|
||||||
@ -95,37 +69,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
setLocale(SettingsUtil.getLocale(this));
|
setLocale(SettingsUtil.getLocale(this));
|
||||||
|
|
||||||
OTPDatabase.promptLoadDatabase(this, () -> {
|
OTPDatabase.promptLoadDatabase(this, this::launchApp, this::finishAffinity);
|
||||||
launchApp();
|
|
||||||
}, () -> finishAffinity());
|
|
||||||
|
|
||||||
/*Executor executor = ContextCompat.getMainExecutor(this);
|
|
||||||
BiometricPrompt prompt = new BiometricPrompt(this, executor, new BiometricPrompt.AuthenticationCallback() {
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
|
||||||
finishAffinity();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
|
||||||
launchApp();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
boolean supportsBiometricAuth = BiometricManager.from(this).canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS;
|
|
||||||
boolean recentlyUnlocked = savedInstanceState != null && (System.currentTimeMillis() - savedInstanceState.getLong("pauseTime", 0L) < LOCK_TIMEOUT);
|
|
||||||
|
|
||||||
if(!recentlyUnlocked && SettingsUtil.isBiometricLock(this) && supportsBiometricAuth) {
|
|
||||||
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
|
|
||||||
.setTitle(getString(R.string.app_name))
|
|
||||||
.setSubtitle(getString(R.string.biometric_lock_subtitle))
|
|
||||||
.setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
prompt.authenticate(info);
|
|
||||||
}else {
|
|
||||||
launchApp();
|
|
||||||
}*/
|
|
||||||
|
|
||||||
startQRCodeScan = registerForActivityResult(new QRScannerContract(), obj -> {
|
startQRCodeScan = registerForActivityResult(new QRScannerContract(), obj -> {
|
||||||
if(obj == null) return; // Cancelled
|
if(obj == null) return; // Cancelled
|
||||||
@ -160,7 +104,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
binding.fabMenu.setOnClickListener(view -> NavigationUtil.navigate(this, MenuFragment.class, null));
|
binding.fabMenu.setOnClickListener(view -> NavigationUtil.navigate(this, MenuFragment.class, null));
|
||||||
binding.fabScan.setOnClickListener(view -> scanCode());
|
binding.fabScan.setOnClickListener(view -> scanCode());
|
||||||
binding.fabScanImage.setOnClickListener(view -> scanCode());
|
//binding.fabScanImage.setOnClickListener(view -> scanCode()); TODO: scan image
|
||||||
binding.fabInput.setOnClickListener(view -> inputCode());
|
binding.fabInput.setOnClickListener(view -> inputCode());
|
||||||
|
|
||||||
Fragment fragment = NavigationUtil.getCurrentFragment(this);
|
Fragment fragment = NavigationUtil.getCurrentFragment(this);
|
||||||
|
@ -93,11 +93,16 @@ public class GroupFragment extends NamedFragment {
|
|||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
DialogUtil.showChooseGroupDialog(requireContext(), group -> {
|
DialogUtil.showChooseGroupDialog(requireContext(), group -> {
|
||||||
OTPDatabase.promptLoadDatabase(requireContext(), () -> {
|
OTPDatabase.promptLoadDatabase(requireActivity(), () -> {
|
||||||
OTPDatabase.getLoadedDatabase().addOTP(group, data);
|
try {
|
||||||
// TODO: save
|
OTPDatabase.getLoadedDatabase().addOTP(group, data);
|
||||||
otpListAdapter.remove(data);
|
OTPDatabase.saveDatabase(requireContext(), SettingsUtil.getCryptoParameters(requireContext()));
|
||||||
}, () -> DialogUtil.showErrorDialog(requireContext(), "Failed to add OTP"));
|
otpListAdapter.remove(data);
|
||||||
|
saveOTPs();
|
||||||
|
} catch (OTPDatabaseException | CryptoException e) {
|
||||||
|
DialogUtil.showErrorDialog(requireContext(), e.toString());
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
saveOTPs();
|
saveOTPs();
|
||||||
}, null);
|
}, null);
|
||||||
break;
|
break;
|
||||||
@ -119,33 +124,37 @@ public class GroupFragment extends NamedFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void saveOTPs() {
|
private void saveOTPs() {
|
||||||
//SettingsUtil.updateOTPs(requireContext(), groupID, otpListAdapter.getItems());
|
OTPDatabase.promptLoadDatabase(requireActivity(), () -> {
|
||||||
if(OTPDatabase.getLoadedDatabase() == null) {
|
try {
|
||||||
// TODO: prompt user
|
OTPDatabase.getLoadedDatabase().updateOTPs(groupID, otpListAdapter.getItems());
|
||||||
return;
|
OTPDatabase.saveDatabase(requireContext(), SettingsUtil.getCryptoParameters(requireContext()));
|
||||||
}
|
refreshCodes();
|
||||||
|
} catch (OTPDatabaseException | CryptoException e) {
|
||||||
OTPDatabase.getLoadedDatabase().updateOTPs(groupID, otpListAdapter.getItems());
|
DialogUtil.showErrorDialog(requireContext(), e.toString());
|
||||||
try {
|
}
|
||||||
OTPDatabase.saveDatabase(requireContext(), SettingsUtil.getCryptoParameters(requireContext()));
|
}, null);
|
||||||
} catch (OTPDatabaseException | CryptoException e) {
|
|
||||||
DialogUtil.showErrorDialog(requireContext(), e.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshCodes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadOTPs() {
|
private void loadOTPs() {
|
||||||
/*List<OTPData> data = SettingsUtil.getOTPs(requireContext(), groupID); TODO
|
OTPDatabase.promptLoadDatabase(requireActivity(), () -> {
|
||||||
|
List<OTPData> data = OTPDatabase.getLoadedDatabase().getOTPs(groupID);
|
||||||
|
|
||||||
for(OTPData otp : data) {
|
for(OTPData otp : data) {
|
||||||
otpListAdapter.add(otp);
|
otpListAdapter.add(otp);
|
||||||
}*/
|
}
|
||||||
|
}, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addOTP(OTPData data) {
|
public void addOTP(OTPData data) {
|
||||||
//SettingsUtil.addOTP(requireContext(), groupID, data); TODO
|
OTPDatabase.promptLoadDatabase(requireActivity(), () -> {
|
||||||
otpListAdapter.add(data);
|
try {
|
||||||
|
OTPDatabase.getLoadedDatabase().addOTP(groupID, data);
|
||||||
|
OTPDatabase.saveDatabase(requireContext(), SettingsUtil.getCryptoParameters(requireContext()));
|
||||||
|
otpListAdapter.add(data);
|
||||||
|
} catch (OTPDatabaseException | CryptoException e) {
|
||||||
|
DialogUtil.showErrorDialog(requireContext(), "Failed to save database: " + e);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshCodes() {
|
public void refreshCodes() {
|
||||||
|
@ -71,6 +71,7 @@ public class SettingsFragment extends NamedFragment {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
binding.settingsEnableEncryption.setChecked(SettingsUtil.isDatabaseEncrypted(requireContext()));
|
||||||
binding.settingsEnableEncryption.setOnCheckedChangeListener((view, checked) -> {
|
binding.settingsEnableEncryption.setOnCheckedChangeListener((view, checked) -> {
|
||||||
if(!OTPDatabase.isDatabaseLoaded()) {
|
if(!OTPDatabase.isDatabaseLoaded()) {
|
||||||
// TODO: prompt user
|
// TODO: prompt user
|
||||||
|
@ -3,6 +3,7 @@ package com.cringe_studios.cringe_authenticator.otplist;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -58,6 +59,7 @@ public class OTPListAdapter extends RecyclerView.Adapter<OTPListItem> {
|
|||||||
holder.getBinding().progress.setVisibility(data.getType() == OTPType.TOTP ? View.VISIBLE : View.INVISIBLE);
|
holder.getBinding().progress.setVisibility(data.getType() == OTPType.TOTP ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
|
||||||
holder.getBinding().getRoot().setOnClickListener(view -> {
|
holder.getBinding().getRoot().setOnClickListener(view -> {
|
||||||
|
Log.i("CLICKED", "CLICKED: " + view.isClickable());
|
||||||
if(data.getType() != OTPType.HOTP) return;
|
if(data.getType() != OTPType.HOTP) return;
|
||||||
|
|
||||||
// Click delay for HOTP
|
// Click delay for HOTP
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.unlock;
|
||||||
|
|
||||||
|
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
||||||
|
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.biometric.BiometricManager;
|
||||||
|
import androidx.biometric.BiometricPrompt;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import com.cringe_studios.cringe_authenticator.MainActivity;
|
||||||
|
import com.cringe_studios.cringe_authenticator.R;
|
||||||
|
import com.cringe_studios.cringe_authenticator.crypto.Crypto;
|
||||||
|
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
||||||
|
import com.cringe_studios.cringe_authenticator.databinding.ActivityUnlockBinding;
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.DialogUtil;
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.OTPDatabase;
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.OTPDatabaseException;
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.ThemeUtil;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
|
public class UnlockActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private static final long LOCK_TIMEOUT = 10000;
|
||||||
|
|
||||||
|
private ActivityUnlockBinding binding;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
ThemeUtil.loadTheme(this);
|
||||||
|
|
||||||
|
if(!SettingsUtil.isDatabaseEncrypted(this)) {
|
||||||
|
launchApp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
binding = ActivityUnlockBinding.inflate(getLayoutInflater());
|
||||||
|
setContentView(binding.getRoot());
|
||||||
|
|
||||||
|
if(SettingsUtil.isBiometricLock(this)) {
|
||||||
|
Executor executor = ContextCompat.getMainExecutor(this);
|
||||||
|
BiometricPrompt prompt = new BiometricPrompt(this, executor, new BiometricPrompt.AuthenticationCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
||||||
|
//finishAffinity();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||||
|
launchApp();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
boolean supportsBiometricAuth = BiometricManager.from(this).canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS;
|
||||||
|
boolean recentlyUnlocked = savedInstanceState != null && (System.currentTimeMillis() - savedInstanceState.getLong("pauseTime", 0L) < LOCK_TIMEOUT);
|
||||||
|
|
||||||
|
if(!recentlyUnlocked && SettingsUtil.isBiometricLock(this) && supportsBiometricAuth) {
|
||||||
|
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setTitle(getString(R.string.app_name))
|
||||||
|
.setSubtitle(getString(R.string.biometric_lock_subtitle))
|
||||||
|
.setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
//prompt.authenticate(info);
|
||||||
|
|
||||||
|
binding.unlockBiometrics.setOnClickListener(view -> {
|
||||||
|
//prompt.authenticate(info);
|
||||||
|
});
|
||||||
|
}else {
|
||||||
|
launchApp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.unlockButton.setOnClickListener(view -> {
|
||||||
|
if(binding.unlockPassword.getText().length() == 0) {
|
||||||
|
DialogUtil.showErrorDialog(this, "You need to enter a password");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String password = binding.unlockPassword.getText().toString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
SecretKey key = Crypto.generateKey(SettingsUtil.getCryptoParameters(this), password);
|
||||||
|
OTPDatabase.loadDatabase(this, key);
|
||||||
|
launchApp();
|
||||||
|
}catch(CryptoException e) {
|
||||||
|
DialogUtil.showErrorDialog(this, "Failed to load database: Invalid password or database corrupted", this::failure);
|
||||||
|
} catch (OTPDatabaseException e) {
|
||||||
|
DialogUtil.showErrorDialog(this, "Failed to load database: " + e, this::failure);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void launchApp() {
|
||||||
|
if(getIntent() != null && getIntent().hasExtra("contract")) {
|
||||||
|
setResult(RESULT_OK);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent m = new Intent(getApplicationContext(), MainActivity.class);
|
||||||
|
m.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||||
|
startActivity(m);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void failure() {
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.unlock;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import androidx.activity.result.contract.ActivityResultContract;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.cringe_studios.cringe_authenticator.scanner.QRScannerActivity;
|
||||||
|
import com.cringe_studios.cringe_authenticator.scanner.ScannerResult;
|
||||||
|
|
||||||
|
public class UnlockContract extends ActivityResultContract<Void, Boolean> {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Intent createIntent(@NonNull Context context, Void unused) {
|
||||||
|
return new Intent(context, UnlockActivity.class).putExtra("contract", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean parseResult(int i, @Nullable Intent intent) {
|
||||||
|
return i == Activity.RESULT_OK;
|
||||||
|
}
|
||||||
|
}
|
@ -3,15 +3,18 @@ package com.cringe_studios.cringe_authenticator.urihandler;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.cringe_studios.cringe_authenticator.BaseActivity;
|
||||||
import com.cringe_studios.cringe_authenticator.R;
|
import com.cringe_studios.cringe_authenticator.R;
|
||||||
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
||||||
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
||||||
|
import com.cringe_studios.cringe_authenticator.unlock.UnlockContract;
|
||||||
import com.cringe_studios.cringe_authenticator.util.DialogUtil;
|
import com.cringe_studios.cringe_authenticator.util.DialogUtil;
|
||||||
import com.cringe_studios.cringe_authenticator.util.OTPDatabase;
|
import com.cringe_studios.cringe_authenticator.util.OTPDatabase;
|
||||||
import com.cringe_studios.cringe_authenticator.util.OTPDatabaseException;
|
import com.cringe_studios.cringe_authenticator.util.OTPDatabaseException;
|
||||||
@ -20,7 +23,7 @@ import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
|
|||||||
import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder;
|
import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder;
|
||||||
import com.cringe_studios.cringe_authenticator.util.ThemeUtil;
|
import com.cringe_studios.cringe_authenticator.util.ThemeUtil;
|
||||||
|
|
||||||
public class URIHandlerActivity extends AppCompatActivity {
|
public class URIHandlerActivity extends BaseActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
@ -63,19 +66,19 @@ public class URIHandlerActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void importCodes(OTPData... data) {
|
private void importCodes(OTPData... data) {
|
||||||
DialogUtil.showChooseGroupDialog(this, group -> {
|
OTPDatabase.promptLoadDatabase(this, () -> {
|
||||||
for(OTPData d : data) {
|
DialogUtil.showChooseGroupDialog(this, group -> {
|
||||||
OTPDatabase.promptLoadDatabase(this, () -> {
|
for(OTPData d : data) {
|
||||||
OTPDatabase.getLoadedDatabase().addOTP(group, d);
|
OTPDatabase.getLoadedDatabase().addOTP(group, d);
|
||||||
try {
|
try {
|
||||||
OTPDatabase.saveDatabase(this, SettingsUtil.getCryptoParameters(this));
|
OTPDatabase.saveDatabase(this, SettingsUtil.getCryptoParameters(this));
|
||||||
} catch (OTPDatabaseException | CryptoException e) {
|
} catch (OTPDatabaseException | CryptoException e) {
|
||||||
DialogUtil.showErrorDialog(this, e.toString());
|
DialogUtil.showErrorDialog(this, e.toString());
|
||||||
}
|
}
|
||||||
}, () -> finishAffinity());
|
}
|
||||||
}
|
Toast.makeText(this, R.string.uri_handler_code_added, Toast.LENGTH_SHORT).show();
|
||||||
Toast.makeText(this, R.string.uri_handler_code_added, Toast.LENGTH_SHORT).show();
|
}, this::finishAndRemoveTask);
|
||||||
}, this::finishAndRemoveTask);
|
}, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,18 +9,18 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|||||||
public class FabUtil {
|
public class FabUtil {
|
||||||
|
|
||||||
public static void showFabs(Activity activity) {
|
public static void showFabs(Activity activity) {
|
||||||
FloatingActionButton fabScan = activity.findViewById(R.id.fab_scan);
|
|
||||||
if(fabScan != null) {
|
|
||||||
fabScan.setVisibility(View.VISIBLE);
|
|
||||||
fabScan.setClickable(true);
|
|
||||||
fabScan.animate().translationX(-activity.getResources().getDimension(R.dimen.fab1_offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
FloatingActionButton fabScanImage = activity.findViewById(R.id.fab_scan_image);
|
FloatingActionButton fabScanImage = activity.findViewById(R.id.fab_scan_image);
|
||||||
if(fabScanImage != null) {
|
if(fabScanImage != null) {
|
||||||
fabScanImage.setVisibility(View.VISIBLE);
|
fabScanImage.setVisibility(View.VISIBLE);
|
||||||
fabScanImage.setClickable(true);
|
fabScanImage.setClickable(true);
|
||||||
fabScanImage.animate().translationX(-activity.getResources().getDimension(R.dimen.fab2_offset));
|
fabScanImage.animate().translationX(-activity.getResources().getDimension(R.dimen.fab1_offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatingActionButton fabScan = activity.findViewById(R.id.fab_scan);
|
||||||
|
if(fabScan != null) {
|
||||||
|
fabScan.setVisibility(View.VISIBLE);
|
||||||
|
fabScan.setClickable(true);
|
||||||
|
fabScan.animate().translationX(-activity.getResources().getDimension(R.dimen.fab2_offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatingActionButton fabInput = activity.findViewById(R.id.fab_input);
|
FloatingActionButton fabInput = activity.findViewById(R.id.fab_input);
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
package com.cringe_studios.cringe_authenticator.util;
|
package com.cringe_studios.cringe_authenticator.util;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.util.Consumer;
|
import androidx.core.util.Consumer;
|
||||||
|
|
||||||
|
import com.cringe_studios.cringe_authenticator.BaseActivity;
|
||||||
import com.cringe_studios.cringe_authenticator.crypto.Crypto;
|
import com.cringe_studios.cringe_authenticator.crypto.Crypto;
|
||||||
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
||||||
import com.cringe_studios.cringe_authenticator.crypto.CryptoParameters;
|
import com.cringe_studios.cringe_authenticator.crypto.CryptoParameters;
|
||||||
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
||||||
|
import com.cringe_studios.cringe_authenticator.unlock.UnlockContract;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
@ -60,12 +65,16 @@ public class OTPDatabase {
|
|||||||
otps.remove(groupId);
|
otps.remove(groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void promptLoadDatabase(Context ctx, Runnable success, Runnable failure) {
|
public static void promptLoadDatabase(Activity ctx, Runnable success, Runnable failure) {
|
||||||
if(isDatabaseLoaded()) {
|
if(isDatabaseLoaded()) {
|
||||||
success.run();
|
success.run();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!(ctx instanceof BaseActivity)) {
|
||||||
|
throw new RuntimeException("NOT BASEACTIVITY");
|
||||||
|
}
|
||||||
|
|
||||||
if(!SettingsUtil.isDatabaseEncrypted(ctx)) {
|
if(!SettingsUtil.isDatabaseEncrypted(ctx)) {
|
||||||
try {
|
try {
|
||||||
loadDatabase(ctx, null);
|
loadDatabase(ctx, null);
|
||||||
@ -76,7 +85,7 @@ public class OTPDatabase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DialogUtil.showInputPasswordDialog(ctx, password -> {
|
/*DialogUtil.showInputPasswordDialog(ctx, password -> {
|
||||||
try {
|
try {
|
||||||
SecretKey key = Crypto.generateKey(SettingsUtil.getCryptoParameters(ctx), password);
|
SecretKey key = Crypto.generateKey(SettingsUtil.getCryptoParameters(ctx), password);
|
||||||
loadDatabase(ctx, key);
|
loadDatabase(ctx, key);
|
||||||
@ -86,7 +95,8 @@ public class OTPDatabase {
|
|||||||
} catch (OTPDatabaseException e) {
|
} catch (OTPDatabaseException e) {
|
||||||
DialogUtil.showErrorDialog(ctx, "Failed to load database: " + e, failure);
|
DialogUtil.showErrorDialog(ctx, "Failed to load database: " + e, failure);
|
||||||
}
|
}
|
||||||
}, failure);
|
}, failure);*/
|
||||||
|
((BaseActivity) ctx).promptUnlock(success, failure);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OTPDatabase loadDatabase(Context context, SecretKey key) throws OTPDatabaseException, CryptoException {
|
public static OTPDatabase loadDatabase(Context context, SecretKey key) throws OTPDatabaseException, CryptoException {
|
||||||
|
9
app/src/main/res/drawable/fingerprint.xml
Normal file
9
app/src/main/res/drawable/fingerprint.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M17.81,4.47C17.73,4.47 17.65,4.45 17.58,4.41C15.66,3.42 14,3 12,3C10.03,3 8.15,3.47 6.44,4.41C6.2,4.54 5.9,4.45 5.76,4.21C5.63,3.97 5.72,3.66 5.96,3.53C7.82,2.5 9.86,2 12,2C14.14,2 16,2.47 18.04,3.5C18.29,3.65 18.38,3.95 18.25,4.19C18.16,4.37 18,4.47 17.81,4.47M3.5,9.72C3.4,9.72 3.3,9.69 3.21,9.63C3,9.47 2.93,9.16 3.09,8.93C4.08,7.53 5.34,6.43 6.84,5.66C10,4.04 14,4.03 17.15,5.65C18.65,6.42 19.91,7.5 20.9,8.9C21.06,9.12 21,9.44 20.78,9.6C20.55,9.76 20.24,9.71 20.08,9.5C19.18,8.22 18.04,7.23 16.69,6.54C13.82,5.07 10.15,5.07 7.29,6.55C5.93,7.25 4.79,8.25 3.89,9.5C3.81,9.65 3.66,9.72 3.5,9.72M9.75,21.79C9.62,21.79 9.5,21.74 9.4,21.64C8.53,20.77 8.06,20.21 7.39,19C6.7,17.77 6.34,16.27 6.34,14.66C6.34,11.69 8.88,9.27 12,9.27C15.12,9.27 17.66,11.69 17.66,14.66A0.5,0.5 0,0 1,17.16 15.16A0.5,0.5 0,0 1,16.66 14.66C16.66,12.24 14.57,10.27 12,10.27C9.43,10.27 7.34,12.24 7.34,14.66C7.34,16.1 7.66,17.43 8.27,18.5C8.91,19.66 9.35,20.15 10.12,20.93C10.31,21.13 10.31,21.44 10.12,21.64C10,21.74 9.88,21.79 9.75,21.79M16.92,19.94C15.73,19.94 14.68,19.64 13.82,19.05C12.33,18.04 11.44,16.4 11.44,14.66A0.5,0.5 0,0 1,11.94 14.16A0.5,0.5 0,0 1,12.44 14.66C12.44,16.07 13.16,17.4 14.38,18.22C15.09,18.7 15.92,18.93 16.92,18.93C17.16,18.93 17.56,18.9 17.96,18.83C18.23,18.78 18.5,18.96 18.54,19.24C18.59,19.5 18.41,19.77 18.13,19.82C17.56,19.93 17.06,19.94 16.92,19.94M14.91,22C14.87,22 14.82,22 14.78,22C13.19,21.54 12.15,20.95 11.06,19.88C9.66,18.5 8.89,16.64 8.89,14.66C8.89,13.04 10.27,11.72 11.97,11.72C13.67,11.72 15.05,13.04 15.05,14.66C15.05,15.73 16,16.6 17.13,16.6C18.28,16.6 19.21,15.73 19.21,14.66C19.21,10.89 15.96,7.83 11.96,7.83C9.12,7.83 6.5,9.41 5.35,11.86C4.96,12.67 4.76,13.62 4.76,14.66C4.76,15.44 4.83,16.67 5.43,18.27C5.53,18.53 5.4,18.82 5.14,18.91C4.88,19 4.59,18.87 4.5,18.62C4,17.31 3.77,16 3.77,14.66C3.77,13.46 4,12.37 4.45,11.42C5.78,8.63 8.73,6.82 11.96,6.82C16.5,6.82 20.21,10.33 20.21,14.65C20.21,16.27 18.83,17.59 17.13,17.59C15.43,17.59 14.05,16.27 14.05,14.65C14.05,13.58 13.12,12.71 11.97,12.71C10.82,12.71 9.89,13.58 9.89,14.65C9.89,16.36 10.55,17.96 11.76,19.16C12.71,20.1 13.62,20.62 15.03,21C15.3,21.08 15.45,21.36 15.38,21.62C15.33,21.85 15.12,22 14.91,22Z"/>
|
||||||
|
</vector>
|
@ -54,7 +54,7 @@
|
|||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:backgroundTint="?attr/colorTheme1"
|
android:backgroundTint="?attr/colorTheme1"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:srcCompat="@drawable/baseline_qr_code_scanner_24"
|
app:srcCompat="@drawable/baseline_settings_24"
|
||||||
app:tint="@color/white" />
|
app:tint="@color/white" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
65
app/src/main/res/layout/activity_unlock.xml
Normal file
65
app/src/main/res/layout/activity_unlock.xml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/unlock_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Unlock Cringe Authenticator"
|
||||||
|
android:textSize="21sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/unlock_password_text"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="19dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:text="Password"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/unlock_title" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/unlock_password"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="45dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:ems="10"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/unlock_password_text" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/unlock_button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:background="@drawable/button_themed"
|
||||||
|
android:text="Unlock"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/unlock_password" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/unlock_biometrics"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:background="@drawable/button_themed"
|
||||||
|
android:text="Unlock using biometrics"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/unlock_button" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -3,6 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
tools:context=".fragment.HomeFragment">
|
tools:context=".fragment.HomeFragment">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@ -37,7 +38,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:src="@drawable/ic_hamburger" />
|
android:src="@drawable/ic_hamburger"
|
||||||
|
app:tint="?android:attr/textColorPrimary" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/about_cringe_studios"
|
android:id="@+id/about_cringe_studios"
|
||||||
@ -45,7 +47,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:src="@drawable/ic_hamburger" />
|
android:src="@drawable/ic_hamburger"
|
||||||
|
app:tint="?android:attr/textColorPrimary" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -21,5 +21,12 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="65dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/itemList" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
@ -12,7 +12,7 @@
|
|||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textview_first"
|
android:id="@+id/unlock_password_text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="This is the home fragment"
|
android:text="This is the home fragment"
|
||||||
|
Loading…
Reference in New Issue
Block a user