[Jetpack Compose] パスワードジェネレータを作る

実装

@Composable
fun PasswordGeneratorView(modifier: Modifier = Modifier) {
	var password by remember { mutableStateOf("") }
	var passwordLength by remember { mutableStateOf(12) }
	var includeUppercase by remember { mutableStateOf(true) }
	var includeLowercase by remember { mutableStateOf(true) }
	var includeNumbers by remember { mutableStateOf(true) }
	var includeSymbols by remember { mutableStateOf(true) }
	val clipboardManager = LocalClipboardManager.current

	Column(
		modifier = Modifier
			.fillMaxSize()
			.padding(16.dp),
		verticalArrangement = Arrangement.spacedBy(8.dp)
	) {
		Text("Password Generator", style = MaterialTheme.typography.displaySmall)
		Row(verticalAlignment = Alignment.CenterVertically) {
			Text("Length: $passwordLength")
			Slider(
				value = passwordLength.toFloat(),
				onValueChange = { passwordLength = it.toInt() },
				valueRange = 8f..32f
			)
		}
		PasswordCheckBoxView(includeUppercase, includeLowercase, includeNumbers, includeSymbols)
		Button(onClick = {
			password = generatePassword(
				length = passwordLength,
				includeUppercase = includeUppercase,
				includeLowercase = includeLowercase,
				includeNumbers = includeNumbers,
				includeSymbols = includeSymbols
			)
		}) {
			Text("Generate Password")
		}
		if (password.isNotEmpty()) {
			GeneratedPasswordDetailView(password, clipboardManager)
		}
	}
}

@Composable
private fun GeneratedPasswordDetailView(
	password: String,
	clipboardManager: ClipboardManager
) {
	Row(verticalAlignment = Alignment.CenterVertically) {
		OutlinedTextField(
			value = password,
			onValueChange = {},
			readOnly = true,
			modifier = Modifier.weight(1f)
		)
		IconButton(onClick = {
			clipboardManager.setText(AnnotatedString(password))
		}) {
			Icon(
				imageVector = Icons.Filled.Share,
				contentDescription = null
			)
		}
	}
	Text("Strength: ${getPasswordStrength(password)}")
}

@Composable
private fun PasswordCheckBoxView(
	includeUppercase: Boolean,
	includeLowercase: Boolean,
	includeNumbers: Boolean,
	includeSymbols: Boolean
) {
	var includeUppercase1 = includeUppercase
	var includeLowercase1 = includeLowercase
	var includeNumbers1 = includeNumbers
	var includeSymbols1 = includeSymbols
	Row {
		Checkbox(
			checked = includeUppercase1,
			onCheckedChange = { includeUppercase1 = it }
		)
		Text(text = "Uppercase")
	}
	Row {
		Checkbox(
			checked = includeLowercase1,
			onCheckedChange = { includeLowercase1 = it }
		)
		Text(text = "Lowercase")
	}
	Row {
		Checkbox(
			checked = includeNumbers1,
			onCheckedChange = { includeNumbers1 = it }
		)
		Text(text = "Numbers")
	}
	Row {
		Checkbox(
			checked = includeSymbols1,
			onCheckedChange = { includeSymbols1 = it }
		)
		Text(text = "Symbols")
	}
}

private fun generatePassword(
	length: Int,
	includeUppercase: Boolean,
	includeLowercase: Boolean,
	includeNumbers: Boolean,
	includeSymbols: Boolean,
): String {
	val uppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	val lowercaseChars = "abcdefghijklmnopqrstuvwxyz"
	val numberChars = "0123456789"
	val symbolChars = "!@#$%^&*()-_=+[]{}|;:'\",.<>/?`~"

	var allowedChars = ""
	if (includeUppercase) allowedChars += uppercaseChars
	if (includeLowercase) allowedChars += lowercaseChars
	if (includeNumbers) allowedChars += numberChars
	if (includeSymbols) allowedChars += symbolChars

	// 使用可能な文字がない場合は空文字列を返す
	if (allowedChars.isEmpty()) return ""

	return (1..length) // 1からlengthまでの範囲でループ
		.map { allowedChars[Random.nextInt(allowedChars.length)] }
		.joinToString("") // 選択された文字を結合して文字列にする
}

private fun getPasswordStrength(password: String): String {
	val strength = when {
		password.length < 8 -> "Weak"
		password.length < 12 -> "Medium"
		else -> "Strong"
	}
	return strength
}