Skip to content

Commit

Permalink
added random password generator
Browse files Browse the repository at this point in the history
  • Loading branch information
StellarSand committed Nov 23, 2023
1 parent 7161cc9 commit 9902134
Show file tree
Hide file tree
Showing 62 changed files with 1,040 additions and 105 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.



## v1.4.6
- Added random password generator.
- Fixed estimated time to crack not translating.
- Added Swedish and Turkish(by [mdksec](https://github.com/mdksec)) translations.



## v1.4.3
- Added support for Android 14.
- Added Spanish translations.
Expand All @@ -25,7 +32,7 @@ All notable changes to this project will be documented in this file.
- Select file support for Android 13 and above.
- Adaptive app icon.
- Included themed icons for Android 13 and above.
- Improved Italian(by [gdonisi](https://github.com/gdonisi)) and French(by [Amiralgaby](https://github.com/Amiralgaby)) translations.
- Improved Italian (by [gdonisi](https://github.com/gdonisi)) and French (by [Amiralgaby](https://github.com/Amiralgaby)) translations.



Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ Using this app you can determine whether the passwords are most commonly used on
- Fully open source
- Material design
- Completely offline
- Works on Android 6.0 and above devices
- Supports both light and dark theme
- No ads
- No collection of personal data
Expand All @@ -53,6 +52,8 @@ Using this app you can determine whether the passwords are most commonly used on

<img src="/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" width="200"/> <img src="/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" width="200"/>

<img src="/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png" width="200"/> <img src="/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png" width="200"/>



## Download
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ android {
applicationId = "com.iyps"
minSdk = 23
targetSdk = 34
versionCode = 144
versionName = "1.4.4"
versionCode = 146
versionName = "1.4.6"
}

buildTypes {
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/java/com/iyps/activities/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,17 @@ class MainActivity : AppCompatActivity() {

val actionsMap =
mapOf(Pair(R.id.fileFragment, R.id.nav_password) to R.id.action_fileFragment_to_passwordFragment,
Pair(R.id.generateFragment, R.id.nav_password) to R.id.action_generateFragment_to_passwordFragment,
Pair(R.id.settingsFragment, R.id.nav_password) to R.id.action_settingsFragment_to_passwordFragment,
Pair(R.id.passwordFragment, R.id.nav_file) to R.id.action_passwordFragment_to_fileFragment,
Pair(R.id.generateFragment, R.id.nav_file) to R.id.action_generateFragment_to_fileFragment,
Pair(R.id.settingsFragment, R.id.nav_file) to R.id.action_settingsFragment_to_fileFragment,
Pair(R.id.passwordFragment, R.id.nav_generate) to R.id.action_passwordFragment_to_generateFragment,
Pair(R.id.fileFragment, R.id.nav_generate) to R.id.action_fileFragment_to_generateFragment,
Pair(R.id.settingsFragment, R.id.nav_generate) to R.id.action_settingsFragment_to_generateFragment,
Pair(R.id.passwordFragment, R.id.nav_settings) to R.id.action_passwordFragment_to_settingsFragment,
Pair(R.id.fileFragment, R.id.nav_settings) to R.id.action_fileFragment_to_settingsFragment)
Pair(R.id.fileFragment, R.id.nav_settings) to R.id.action_fileFragment_to_settingsFragment,
Pair(R.id.generateFragment, R.id.nav_settings) to R.id.action_generateFragment_to_settingsFragment)

val action = actionsMap[Pair(currentFragment.id, clickedItem)] ?: 0

Expand Down
14 changes: 10 additions & 4 deletions app/src/main/java/com/iyps/fragments/details/DetailsFragment.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 StellarSand
* Copyright (c) 2022-present StellarSand
*
* This file is part of IYPS.
*
Expand Down Expand Up @@ -55,12 +55,19 @@ class DetailsFragment : Fragment() {
return fragmentBinding.root
}

@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

val zxcvbn = (requireContext().applicationContext as ApplicationManager).zxcvbn
passwordString = (requireActivity() as DetailsActivity).passwordLine

/*########################################################################################*/

fragmentBinding.lengthSubtitle.text = "\u2022 ${getString(R.string.length)}"
fragmentBinding.uppercaseText.text = "\u2022 ${getString(R.string.uppercase)}"
fragmentBinding.lowercaseSubtitle.text = "\u2022 ${getString(R.string.lowercase)}"
fragmentBinding.numbersSubtitle.text = "\u2022 ${getString(R.string.numbers)}"
fragmentBinding.specialCharsSubtitle.text = "\u2022 ${getString(R.string.special_char)}"

fragmentBinding.passwordText.apply {
setText(passwordString)
Expand Down Expand Up @@ -125,7 +132,6 @@ class DetailsFragment : Fragment() {
fragmentBinding.orderMagnSubtitle.text = strength.guessesLog10.formatToTwoDecimalPlaces()

// Entropy
@SuppressLint("SetTextI18n")
fragmentBinding.entropySubtitle.text =
"${log2(guesses).formatToTwoDecimalPlaces()} ${getString(R.string.bits)}"

Expand All @@ -135,8 +141,8 @@ class DetailsFragment : Fragment() {
// Statistics
val statsList = getStatisticsCounts(passwordString)
fragmentBinding.lengthText.text = statsList[0].toString()
fragmentBinding.upperCaseText.text = statsList[1].toString()
fragmentBinding.lowerCaseText.text = statsList[2].toString()
fragmentBinding.uppercaseText.text = statsList[1].toString()
fragmentBinding.lowercaseText.text = statsList[2].toString()
fragmentBinding.numbersText.text = statsList[3].toString()
fragmentBinding.specialCharsText.text = statsList[4].toString()
}
Expand Down
265 changes: 265 additions & 0 deletions app/src/main/java/com/iyps/fragments/main/GenerateFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/*
* Copyright (c) 2022-present StellarSand
*
* This file is part of IYPS.
*
* IYPS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* IYPS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with IYPS. If not, see <https://www.gnu.org/licenses/>.
*/

package com.iyps.fragments.main

import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context.CLIPBOARD_SERVICE
import android.content.Intent
import android.content.res.ColorStateList
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.materialswitch.MaterialSwitch
import com.google.android.material.slider.Slider
import com.google.android.material.slider.Slider.OnSliderTouchListener
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import com.iyps.R
import com.iyps.activities.DetailsActivity
import com.iyps.activities.MainActivity
import com.iyps.databinding.FragmentGenerateBinding
import com.iyps.preferences.PreferenceManager
import com.iyps.preferences.PreferenceManager.Companion.AMB_CHARS
import com.iyps.preferences.PreferenceManager.Companion.LOWERCASE
import com.iyps.preferences.PreferenceManager.Companion.NUMBERS
import com.iyps.preferences.PreferenceManager.Companion.PASS_LENGTH
import com.iyps.preferences.PreferenceManager.Companion.SPACES
import com.iyps.preferences.PreferenceManager.Companion.SPEC_CHARS
import com.iyps.preferences.PreferenceManager.Companion.UPPERCASE
import java.security.SecureRandom

class GenerateFragment : Fragment() {

private var _binding: FragmentGenerateBinding? = null
private val fragmentBinding get() = _binding!!
private lateinit var preferenceManager: PreferenceManager
private lateinit var uppercaseSwitch: MaterialSwitch
private lateinit var lowercaseSwitch: MaterialSwitch
private lateinit var numbersSwitch: MaterialSwitch
private lateinit var specialCharsSwitch: MaterialSwitch
private lateinit var avoidAmbCharsSwitch: MaterialSwitch
private lateinit var includeSpaceSwitch: MaterialSwitch
private lateinit var primarySwitchesList: List<MaterialSwitch>
private lateinit var primarySwitchesPrefMap: Map<MaterialSwitch, String>

companion object {
val uppercaseChars = ('A'..'Z').joinToString("")
val lowercaseChars = ('a'..'z').joinToString("")
val numbers = ('0'..'9').joinToString("")
const val specialChars = "!@#$%^&*-_=+<.>"
const val ambChars = "IiLlOo05S8B|"
}

override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
// Inflate the layout for this fragment
_binding = FragmentGenerateBinding.inflate(inflater, container, false)
return fragmentBinding.root
}

@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

val mainActivity = requireActivity() as MainActivity
preferenceManager = PreferenceManager(requireContext())
uppercaseSwitch = fragmentBinding.uppercaseSwitch
lowercaseSwitch = fragmentBinding.lowercaseSwitch
numbersSwitch = fragmentBinding.numbersSwitch
specialCharsSwitch = fragmentBinding.specialCharsSwitch
avoidAmbCharsSwitch = fragmentBinding.avoidAmbCharsSwitch
includeSpaceSwitch = fragmentBinding.includeSpacesSwitch

primarySwitchesList = listOf(uppercaseSwitch,
lowercaseSwitch,
numbersSwitch,
specialCharsSwitch)

primarySwitchesPrefMap = mapOf(uppercaseSwitch to UPPERCASE,
lowercaseSwitch to LOWERCASE,
numbersSwitch to NUMBERS,
specialCharsSwitch to SPEC_CHARS)

// Password length slider
fragmentBinding.passwordLengthSlider.apply {
value = preferenceManager.getFloat(PASS_LENGTH)
fragmentBinding.lengthText.text = "${getString(R.string.length)}: ${value.toInt()}"
setSliderThumbColor(this, value)

addOnSliderTouchListener(object : OnSliderTouchListener {
override fun onStartTrackingTouch(slider: Slider) {}

override fun onStopTrackingTouch(slider: Slider) {
generatePassword()
}

})

addOnChangeListener { slider, value, _ ->
fragmentBinding.lengthText.text = "${getString(R.string.length)}: ${slider.value.toInt()}"
setSliderThumbColor(slider, value)
}
}

// Switches
// At least one switch must be enabled at all times (excluding ambiguous chars switch)
primarySwitchesList.forEach { switch ->
primarySwitchesPrefMap[switch]?.let { preferenceKey ->
switch.isChecked = preferenceManager.getBoolean(preferenceKey)
}
switch.setOnCheckedChangeListener { _, _ ->
val otherSwitches = primarySwitchesList.filter { it != switch }
val otherSwitchesChecked = otherSwitches.any { it.isChecked }
if (otherSwitchesChecked) {
generatePassword()
}
else {
switch.isChecked = true
}
}
}
avoidAmbCharsSwitch.apply {
isChecked = preferenceManager.getBoolean(AMB_CHARS)
setOnCheckedChangeListener { _, _ ->
generatePassword()
}
}
includeSpaceSwitch.apply {
isChecked = preferenceManager.getBooleanDefValFalse(SPACES)
setOnCheckedChangeListener { _, _ ->
generatePassword()
}
}

generatePassword()

// Details
fragmentBinding.detailsBtn.setOnClickListener {
startActivity(Intent(requireActivity(), DetailsActivity::class.java)
.putExtra("PwdLine", fragmentBinding.generatedPasswordTextView.text))
}

// Copy
fragmentBinding.copyPasswordBtn.setOnClickListener {
val clipData = ClipData.newPlainText("Generated password",
fragmentBinding.generatedPasswordTextView.text)

(requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager)
.setPrimaryClip(clipData)

Snackbar.make(mainActivity.activityBinding.mainCoordLayout,
getString(R.string.copied_to_clipboard),
BaseTransientBottomBar.LENGTH_SHORT)
.setAnchorView(mainActivity.activityBinding.mainBottomNav)
.show()
}

// Regenerate
fragmentBinding.regenerateBtn.setOnClickListener {
generatePassword()
}

// Share
fragmentBinding.shareBtn.setOnClickListener {
startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND)
.setType("text/plain")
.putExtra(Intent.EXTRA_TEXT, fragmentBinding.generatedPasswordTextView.text),
getString(R.string.share)))
}

}

private fun setSliderThumbColor(slider: Slider, value: Float) {
val sliderThumbColor =
if (value == 5f) {
requireContext().getColor(R.color.color_primary)
}
else {
requireContext().getColor(R.color.color_onPrimary)
}

slider.thumbTintList = ColorStateList.valueOf(sliderThumbColor)
}

private fun generatePassword() {
val allChars = buildString {
if (uppercaseSwitch.isChecked) {
append(if (avoidAmbCharsSwitch.isChecked) uppercaseChars.filterNot { it in ambChars }
else uppercaseChars)
}
if (lowercaseSwitch.isChecked) {
append(if (avoidAmbCharsSwitch.isChecked) lowercaseChars.filterNot { it in ambChars }
else lowercaseChars)
}
if (numbersSwitch.isChecked) {
append(if (avoidAmbCharsSwitch.isChecked) numbers.filterNot { it in ambChars }
else numbers)
}
if (specialCharsSwitch.isChecked) {
append(if (avoidAmbCharsSwitch.isChecked) specialChars.filterNot { it in ambChars }
else specialChars)
}
}

val password = buildString {
val length = fragmentBinding.passwordLengthSlider.value.toInt()
val spacesToInsert =
if (includeSpaceSwitch.isChecked) SecureRandom().nextInt(length)
else 0

for (i in 0 until length) {
if (includeSpaceSwitch.isChecked
&& i > 0 // Avoid space at beginning
&& i < length - 1 // Avoid space at end
&& i < spacesToInsert) {
append(' ')
}
else {
val randomIndex = SecureRandom().nextInt(allChars.length)
append(allChars[randomIndex])
}
}
}

fragmentBinding.generatedPasswordTextView.text = password
}

override fun onPause() {
super.onPause()
preferenceManager.setFloat(PASS_LENGTH, fragmentBinding.passwordLengthSlider.value)
primarySwitchesList.forEach { switch ->
primarySwitchesPrefMap[switch]?.let { preferenceKey ->
preferenceManager.setBoolean(preferenceKey, switch.isChecked)
}
}
preferenceManager.setBoolean(AMB_CHARS, fragmentBinding.avoidAmbCharsSwitch.isChecked)
preferenceManager.setBoolean(SPACES, fragmentBinding.includeSpacesSwitch.isChecked)
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

}
Loading

0 comments on commit 9902134

Please sign in to comment.