Migrate UI to Jetpack Compose

This commit is contained in:
Pavel Baksy 2026-03-03 23:30:57 +01:00
parent dff3db885d
commit ac70718bea
11 changed files with 435 additions and 449 deletions

View File

@ -33,15 +33,32 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
} }
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.10"
}
} }
dependencies { dependencies {
implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0") implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
val composeBom = platform("androidx.compose:compose-bom:2024.02.01")
implementation(composeBom)
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(composeBom)
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
} }

View File

@ -3,88 +3,296 @@ package cz.bugsy.passwordzebra
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.widget.Button import androidx.activity.ComponentActivity
import android.widget.TextView import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.border
private const val KEY_PASSWORD = "password" import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
class MainActivity : AppCompatActivity() { import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
private lateinit var generateButton: Button import androidx.compose.foundation.layout.Row
private lateinit var passwordTextView: TextView import androidx.compose.foundation.layout.Spacer
private lateinit var switchWithoutSpaces: com.google.android.material.switchmaterial.SwitchMaterial import androidx.compose.foundation.layout.fillMaxHeight
private lateinit var switchSpecialChars: com.google.android.material.switchmaterial.SwitchMaterial import androidx.compose.foundation.layout.fillMaxSize
private lateinit var passwordLengthPicker: android.widget.NumberPicker import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import cz.bugsy.passwordzebra.ui.components.NumberPickerView
import cz.bugsy.passwordzebra.ui.theme.PasswordZebraTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) enableEdgeToEdge()
setContent {
generateButton = findViewById(R.id.generateButton) PasswordZebraTheme {
passwordTextView = findViewById(R.id.passwordTextView) PasswordZebraApp()
switchWithoutSpaces = findViewById(R.id.switchWithoutSpaces) }
switchSpecialChars = findViewById(R.id.switchSpecialChars) }
passwordLengthPicker = findViewById(R.id.passwordLengthPicker) }
// Set the range of selectable values for password length (1 to 10)
val defaultPasswordLength = 4
passwordLengthPicker.minValue = 1
passwordLengthPicker.maxValue = 10
passwordLengthPicker.value = defaultPasswordLength
generateButton.setOnClickListener {
generatePassword()
} }
// Restore password after rotation, otherwise generate a new one @Composable
if (savedInstanceState != null) { private fun PasswordZebraApp() {
passwordTextView.text = savedInstanceState.getString(KEY_PASSWORD, "") var password by rememberSaveable { mutableStateOf("") }
var wordCount by rememberSaveable { mutableIntStateOf(4) }
var withoutSpaces by rememberSaveable { mutableStateOf(false) }
var addSpecialChars by rememberSaveable { mutableStateOf(false) }
val context = LocalContext.current
LaunchedEffect(Unit) {
if (password.isEmpty()) {
password = generatePassword(wordCount, addSpecialChars)
}
}
val onGenerate = { password = generatePassword(wordCount, addSpecialChars) }
val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
if (isLandscape) {
// Landscape: controls left (vertically centered), password + generate button right
Row(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
// Left: controls only, vertically centered
Column(
modifier = Modifier
.weight(1f)
.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
ControlsPanel(
wordCount = wordCount,
withoutSpaces = withoutSpaces,
addSpecialChars = addSpecialChars,
onWordCountChange = { wordCount = it },
onWithoutSpacesChange = { withoutSpaces = it },
onAddSpecialCharsChange = { addSpecialChars = it },
modifier = Modifier.fillMaxWidth(),
)
}
// Right: password box fills remaining height, generate button pinned to bottom
Column(
modifier = Modifier
.weight(1f)
.fillMaxHeight(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
PasswordBox(
password = password,
onClick = { copyToClipboard(context, password, withoutSpaces) },
modifier = Modifier
.fillMaxWidth()
.weight(1f),
)
Spacer(modifier = Modifier.height(12.dp))
GenerateButton(onClick = onGenerate)
}
}
} else { } else {
generatePassword() // Portrait: password box top, controls middle, generate button pinned to bottom
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Spacer(modifier = Modifier.weight(1f))
PasswordBox(
password = password,
onClick = { copyToClipboard(context, password, withoutSpaces) },
modifier = Modifier
.fillMaxWidth()
.height(150.dp),
)
Spacer(modifier = Modifier.weight(2f))
ControlsPanel(
wordCount = wordCount,
withoutSpaces = withoutSpaces,
addSpecialChars = addSpecialChars,
onWordCountChange = { wordCount = it },
onWithoutSpacesChange = { withoutSpaces = it },
onAddSpecialCharsChange = { addSpecialChars = it },
modifier = Modifier.fillMaxWidth(),
)
Spacer(modifier = Modifier.weight(3f))
GenerateButton(onClick = onGenerate)
Spacer(modifier = Modifier.height(16.dp))
}
} }
// Set click listener to copy password to clipboard
passwordTextView.setOnClickListener {
copyToClipboard(passwordTextView.text.toString(), getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
} }
} }
override fun onSaveInstanceState(outState: Bundle) { @Composable
super.onSaveInstanceState(outState) private fun PasswordBox(password: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
outState.putString(KEY_PASSWORD, passwordTextView.text.toString()) Box(
modifier = modifier
.border(
width = 2.dp,
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(12.dp),
)
.clickable { onClick() }
.padding(16.dp),
contentAlignment = Alignment.Center,
) {
Text(
text = password,
fontSize = 24.sp,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurface,
)
}
} }
private fun generatePassword() { @Composable
val passwordLength = passwordLengthPicker.value private fun ControlsPanel(
val password = generateRandomWords(passwordLength) wordCount: Int,
passwordTextView.text = password withoutSpaces: Boolean,
addSpecialChars: Boolean,
onWordCountChange: (Int) -> Unit,
onWithoutSpacesChange: (Boolean) -> Unit,
onAddSpecialCharsChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
Icon(
painter = painterResource(R.drawable.pw_straighten),
contentDescription = stringResource(R.string.image_length_desc),
tint = MaterialTheme.colorScheme.primary,
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(R.string.textview_password_length),
modifier = Modifier.weight(1f),
)
NumberPickerView(
value = wordCount,
minValue = 1,
maxValue = 10,
onValueChange = onWordCountChange,
modifier = Modifier.widthIn(min = 80.dp),
)
} }
private fun generateRandomWords(numWords: Int): String { Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
Icon(
painter = painterResource(R.drawable.pw_letter_spacing),
contentDescription = stringResource(R.string.image_wospaces),
tint = MaterialTheme.colorScheme.primary,
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(R.string.switch_without_spaces),
modifier = Modifier.weight(1f),
)
Switch(
checked = withoutSpaces,
onCheckedChange = onWithoutSpacesChange,
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
Icon(
painter = painterResource(R.drawable.pw_special_character),
contentDescription = stringResource(R.string.image_specialchars),
tint = MaterialTheme.colorScheme.primary,
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(R.string.switch_special_characters),
modifier = Modifier.weight(1f),
)
Switch(
checked = addSpecialChars,
onCheckedChange = onAddSpecialCharsChange,
)
}
}
}
@Composable
private fun GenerateButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
FilledIconButton(
onClick = onClick,
modifier = modifier.size(60.dp),
shape = RoundedCornerShape(16.dp),
) {
Icon(
painter = painterResource(R.drawable.pw_generate),
contentDescription = stringResource(R.string.button_generate_description),
)
}
}
private fun generatePassword(wordCount: Int, addSpecialChars: Boolean): String {
return buildString { return buildString {
repeat(numWords) { repeat(wordCount) {
append(PasswordGenerator.generateRandomWord()) append(PasswordGenerator.generateRandomWord())
append(" ") // Add space between words append(" ")
} }
}.let { password -> }.let { password ->
// Check if the switch for adding special characters is checked if (addSpecialChars) {
if (switchSpecialChars.isChecked) {
PasswordGenerator.addSpecialCharacters(password) PasswordGenerator.addSpecialCharacters(password)
} else { } else {
password password
} }
}.trim() // Remove trailing space }.trim()
} }
private fun copyToClipboard(text: String, clipboardManager: ClipboardManager) { private fun copyToClipboard(context: Context, text: String, withoutSpaces: Boolean) {
val clipData = if (switchWithoutSpaces.isChecked) { val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val textNoSpaces = text.filter { !it.isWhitespace() } val clipText = if (withoutSpaces) text.filter { !it.isWhitespace() } else text
ClipData.newPlainText("Password", textNoSpaces) val clipData = ClipData.newPlainText("Password", clipText)
} else {
ClipData.newPlainText("Password", text)
}
clipboardManager.setPrimaryClip(clipData) clipboardManager.setPrimaryClip(clipData)
} }
}

View File

@ -2,16 +2,10 @@ package cz.bugsy.passwordzebra
import android.app.Application import android.app.Application
import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColors
import com.google.android.material.color.DynamicColorsOptions
class PasswordZebra : Application() { class PasswordZebra : Application() {
override fun onCreate() { override fun onCreate() {
// Apply dynamic color super.onCreate()
DynamicColors.applyToActivitiesIfAvailable( DynamicColors.applyToActivitiesIfAvailable(this)
this,
DynamicColorsOptions.Builder()
.setThemeOverlay(R.style.AppTheme_Overlay)
.build()
)
} }
} }

View File

@ -0,0 +1,30 @@
package cz.bugsy.passwordzebra.ui.components
import android.widget.NumberPicker
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
@Composable
fun NumberPickerView(
value: Int,
minValue: Int,
maxValue: Int,
onValueChange: (Int) -> Unit,
modifier: Modifier = Modifier,
) {
AndroidView(
factory = { ctx ->
NumberPicker(ctx).apply {
this.minValue = minValue
this.maxValue = maxValue
this.value = value
setOnValueChangedListener { _, _, new -> onValueChange(new) }
}
},
update = { picker ->
if (picker.value != value) picker.value = value
},
modifier = modifier,
)
}

View File

@ -0,0 +1,92 @@
package cz.bugsy.passwordzebra.ui.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF286C2A),
onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFABF5A3),
onPrimaryContainer = Color(0xFF002203),
secondary = Color(0xFF00639A),
onSecondary = Color(0xFFFFFFFF),
secondaryContainer = Color(0xFFCEE5FF),
onSecondaryContainer = Color(0xFF001D32),
tertiary = Color(0xFF38656A),
onTertiary = Color(0xFFFFFFFF),
tertiaryContainer = Color(0xFFBCEBF0),
onTertiaryContainer = Color(0xFF002023),
error = Color(0xFFBA1A1A),
errorContainer = Color(0xFFFFDAD6),
onError = Color(0xFFFFFFFF),
onErrorContainer = Color(0xFF410002),
background = Color(0xFFFCFDF6),
onBackground = Color(0xFF1A1C19),
surface = Color(0xFFFCFDF6),
onSurface = Color(0xFF1A1C19),
surfaceVariant = Color(0xFFDEE5D8),
onSurfaceVariant = Color(0xFF424940),
outline = Color(0xFF72796F),
inverseOnSurface = Color(0xFFF1F1EB),
inverseSurface = Color(0xFF2F312D),
inversePrimary = Color(0xFF90D889),
surfaceTint = Color(0xFF286C2A),
outlineVariant = Color(0xFFC2C9BD),
scrim = Color(0xFF000000),
)
private val DarkColorScheme = darkColorScheme(
primary = Color(0xFF90D889),
onPrimary = Color(0xFF003909),
primaryContainer = Color(0xFF085314),
onPrimaryContainer = Color(0xFFABF5A3),
secondary = Color(0xFF96CCFF),
onSecondary = Color(0xFF003353),
secondaryContainer = Color(0xFF004A75),
onSecondaryContainer = Color(0xFFCEE5FF),
tertiary = Color(0xFFA0CFD4),
onTertiary = Color(0xFF00363B),
tertiaryContainer = Color(0xFF1E4D52),
onTertiaryContainer = Color(0xFFBCEBF0),
error = Color(0xFFFFB4AB),
errorContainer = Color(0xFF93000A),
onError = Color(0xFF690005),
onErrorContainer = Color(0xFFFFDAD6),
background = Color(0xFF1A1C19),
onBackground = Color(0xFFE2E3DD),
surface = Color(0xFF1A1C19),
onSurface = Color(0xFFE2E3DD),
surfaceVariant = Color(0xFF424940),
onSurfaceVariant = Color(0xFFC2C9BD),
outline = Color(0xFF8C9388),
inverseOnSurface = Color(0xFF1A1C19),
inverseSurface = Color(0xFFE2E3DD),
inversePrimary = Color(0xFF286C2A),
surfaceTint = Color(0xFF90D889),
outlineVariant = Color(0xFF424940),
scrim = Color(0xFF000000),
)
@Composable
fun PasswordZebraTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit,
) {
val colorScheme = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val ctx = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(ctx) else dynamicLightColorScheme(ctx)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(colorScheme = colorScheme, content = content)
}

View File

@ -1,6 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<stroke android:width="1dip" android:color="@color/material_dynamic_primary80"/>
<corners android:radius="8dp" />
</shape>

View File

@ -1,167 +0,0 @@
<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"
tools:context=".MainActivity"
>
<TextView
android:id="@+id/passwordTextView"
android:layout_width="335dp"
android:layout_height="149dp"
android:layout_centerHorizontal="false"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="20dp"
android:background="@drawable/password_background"
android:gravity="center"
android:minHeight="150dp"
android:padding="16dp"
android:text=""
android:textSize="24sp"
app:layout_constraintBottom_toTopOf="@+id/generateButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/passwordLengthPicker"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:id="@+id/passwordLengthLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:text="@string/textview_password_length"
app:layout_constraintBottom_toBottomOf="@+id/passwordLengthPicker"
app:layout_constraintStart_toStartOf="@id/guideline4"
app:layout_constraintTop_toTopOf="@+id/passwordLengthPicker" />
<NumberPicker
android:id="@+id/passwordLengthPicker"
android:layout_width="69dp"
android:layout_height="178dp"
android:layout_marginStart="81dp"
app:layout_constraintBottom_toTopOf="@+id/switchWithoutSpaces"
app:layout_constraintEnd_toEndOf="@id/guideline5"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@+id/passwordLengthLabel"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switchWithoutSpaces"
style="@style/Widget.Material3.CompoundButton.MaterialSwitch"
android:layout_width="299dp"
android:layout_height="48dp"
android:text="@string/switch_without_spaces"
app:layout_constraintBottom_toTopOf="@id/switchSpecialChars"
app:layout_constraintEnd_toStartOf="@+id/guideline5"
app:layout_constraintStart_toStartOf="@id/guideline4" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switchSpecialChars"
style="@style/Widget.Material3.CompoundButton.MaterialSwitch"
android:layout_width="299dp"
android:layout_height="48dp"
android:layout_marginBottom="36dp"
android:text="@string/switch_special_characters"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline5"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@id/guideline4" />
<Button
android:id="@+id/generateButton"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="end|bottom"
android:layout_marginBottom="48dp"
android:contentDescription="@string/button_generate_description"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:padding="0dp"
android:tooltipText="@string/button_generate_text"
app:backgroundColor="@color/design_default_color_primary"
app:cornerRadius="16dp"
app:icon="@drawable/pw_generate"
app:iconGravity="textTop"
app:iconPadding="0dp"
app:iconSize="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/passwordLengthPicker"
app:layout_constraintStart_toStartOf="@+id/guideline5" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="20dp" />
<ImageView
android:id="@+id/imageViewPwLength"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/image_length_desc"
app:layout_constraintBottom_toBottomOf="@+id/passwordLengthLabel"
app:layout_constraintStart_toStartOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/passwordLengthLabel"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@drawable/pw_straighten" />
<ImageView
android:id="@+id/imageViewWithoutSpaces"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:layout_marginBottom="9dp"
android:contentDescription="@string/image_wospaces"
app:layout_constraintBottom_toBottomOf="@+id/switchWithoutSpaces"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/switchWithoutSpaces"
app:layout_constraintVertical_bias="0.8"
app:srcCompat="@drawable/pw_letter_spacing" />
<ImageView
android:id="@+id/imageViewAddSpecialChars"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:layout_marginBottom="8dp"
android:contentDescription="@string/image_specialchars"
app:layout_constraintBottom_toBottomOf="@+id/switchSpecialChars"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/switchSpecialChars"
app:layout_constraintVertical_bias="0.625"
app:srcCompat="@drawable/pw_special_character" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="65dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="365dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,161 +0,0 @@
<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"
tools:context=".MainActivity"
>
<TextView
android:id="@+id/passwordTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="16dp"
android:background="@drawable/password_background"
android:gravity="center"
android:minHeight="150dp"
android:padding="16dp"
android:text=""
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/passwordLengthLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:text="@string/textview_password_length"
app:layout_constraintBottom_toBottomOf="@+id/passwordLengthPicker"
app:layout_constraintStart_toStartOf="@id/guideline4"
app:layout_constraintTop_toTopOf="@+id/passwordLengthPicker" />
<NumberPicker
android:id="@+id/passwordLengthPicker"
android:layout_width="69dp"
android:layout_height="178dp"
android:layout_marginStart="81dp"
android:layout_marginEnd="0dp"
app:layout_constraintBottom_toTopOf="@+id/switchWithoutSpaces"
app:layout_constraintEnd_toEndOf="@id/guideline5"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@+id/passwordLengthLabel"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switchWithoutSpaces"
android:layout_width="299dp"
android:layout_height="48dp"
android:text="@string/switch_without_spaces"
style="@style/Widget.Material3.CompoundButton.MaterialSwitch"
app:layout_constraintBottom_toTopOf="@id/switchSpecialChars"
app:layout_constraintStart_toStartOf="@id/guideline4" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switchSpecialChars"
android:layout_width="299dp"
android:layout_height="48dp"
android:layout_marginBottom="36dp"
android:text="@string/switch_special_characters"
style="@style/Widget.Material3.CompoundButton.MaterialSwitch"
app:layout_constraintBottom_toTopOf="@id/generateButton"
app:layout_constraintStart_toStartOf="@id/guideline4" />
<Button
android:id="@+id/generateButton"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="end|bottom"
app:iconGravity="textTop"
android:padding="0dp"
app:cornerRadius="16dp"
app:iconPadding="0dp"
android:insetTop="0dp"
android:insetBottom="0dp"
android:insetLeft="0dp"
android:insetRight="0dp"
android:layout_marginBottom="44dp"
android:tooltipText="@string/button_generate_text"
android:contentDescription="@string/button_generate_description"
app:icon="@drawable/pw_generate"
app:iconSize="0dp"
app:backgroundColor="@color/design_default_color_primary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="20dp" />
<ImageView
android:id="@+id/imageViewPwLength"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/passwordLengthLabel"
app:layout_constraintStart_toStartOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/passwordLengthLabel"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@drawable/pw_straighten"
android:contentDescription="@string/image_length_desc" />
<ImageView
android:id="@+id/imageViewWithoutSpaces"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:layout_marginBottom="9dp"
app:layout_constraintBottom_toBottomOf="@+id/switchWithoutSpaces"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/switchWithoutSpaces"
app:layout_constraintVertical_bias="0.8"
app:srcCompat="@drawable/pw_letter_spacing"
android:contentDescription="@string/image_wospaces" />
<ImageView
android:id="@+id/imageViewAddSpecialChars"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="@+id/switchSpecialChars"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/switchSpecialChars"
app:layout_constraintVertical_bias="0.625"
app:srcCompat="@drawable/pw_special_character"
android:contentDescription="@string/image_specialchars" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="65dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.92" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,18 +1,12 @@
<resources> <resources>
<!-- <style name="Theme.PasswordZebra" parent="Theme.AppCompat.DayNight.DarkActionBar"> --> <style name="Theme.PasswordZebra" parent="Theme.Material3.Dark.NoActionBar">
<style name="Theme.PasswordZebra" parent="Theme.Material3.DayNight">
<!-- <style name="Theme.PasswordZebra" parent="Theme.Material3.DayNight.NoActionBar"> -->
<item name="android:statusBarColor">@android:color/transparent</item> <item name="android:statusBarColor">@android:color/transparent</item>
<!-- status bar font color --> <!-- status bar font color -->
<item name="android:windowLightStatusBar">false</item> <item name="android:windowLightStatusBar">false</item>
<!-- true = status bar semi-transparent --> <!-- true = status bar semi-transparent -->
<item name="android:windowTranslucentStatus">false</item> <item name="android:windowTranslucentStatus">false</item>
<!-- action bar -->
<!-- defined in themes_overlays - item name="actionBarStyle" -->
<!-- navigation bar = bottom bar with navigation buttons --> <!-- navigation bar = bottom bar with navigation buttons -->
<item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightNavigationBar">false</item> <item name="android:windowLightNavigationBar">false</item>
@ -44,9 +38,4 @@
<item name="colorSurfaceInverse">@color/md_theme_dark_inverseSurface</item> <item name="colorSurfaceInverse">@color/md_theme_dark_inverseSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_dark_inversePrimary</item> <item name="colorPrimaryInverse">@color/md_theme_dark_inversePrimary</item>
</style> </style>
<style name="MyActionBar" parent="@style/Widget.AppCompat.ActionBar">
<item name="background">@android:color/transparent</item>
</style>
</resources> </resources>

View File

@ -1,8 +1,6 @@
<resources> <resources>
<style name="Theme.PasswordZebra" parent="Theme.Material3.Light"> <style name="Theme.PasswordZebra" parent="Theme.Material3.Light.NoActionBar">
<!-- <style name="Theme.PasswordZebra" parent="Theme.Material3.Light.NoActionBar"> -->
<!-- statusbar background color --> <!-- statusbar background color -->
<item name="android:statusBarColor">@android:color/transparent</item> <item name="android:statusBarColor">@android:color/transparent</item>
@ -17,9 +15,4 @@
<item name="android:windowLightNavigationBar">true</item> <item name="android:windowLightNavigationBar">true</item>
<item name="android:windowTranslucentNavigation">false</item> <item name="android:windowTranslucentNavigation">false</item>
</style> </style>
<style name="MyActionBar" parent="@style/Widget.AppCompat.ActionBar">
<item name="background">@android:color/transparent</item>
</style>
</resources> </resources>

View File

@ -1,6 +1,3 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<style name="AppTheme.Overlay" parent="ThemeOverlay.Material3.DynamicColors.DayNight"> <style name="AppTheme.Overlay" parent="ThemeOverlay.Material3.DynamicColors.DayNight" />
<item name="actionBarStyle">@style/MyActionBar</item>
<!-- <item name="actionBarStyle">@style/Widget.AppCompat.ActionBar</item> -->
</style>
</resources> </resources>