Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Settings Copy & Paste #11

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions enigma-machine-android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ android {
applicationId "com.smitpatel.enigmamachine"
minSdk 21
targetSdk 33
versionCode 10
versionName "2.0"
versionCode 12
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

Expand Down Expand Up @@ -57,8 +57,12 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'

testImplementation 'junit:junit:4.13.2'
testImplementation "io.mockk:mockk-android:1.13.8"

androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
package com.smitpatel.enigmamachine

import android.content.Context
import com.smitpatel.enigmamachine.models.Reflector
import com.smitpatel.enigmamachine.models.Rotor

internal fun Char.letterToNumber() = this.code - 65

internal fun Int.numberToLetter() = Char(this + 65)

internal fun Rotor.RotorOption.toRotorLabelText(context: Context): String {
val rotorOptions = context.resources.getStringArray(R.array.rotor_options)
return when (this) {
Rotor.RotorOption.ROTOR_ONE -> rotorOptions[0]
Rotor.RotorOption.ROTOR_TWO -> rotorOptions[1]
Rotor.RotorOption.ROTOR_THREE -> rotorOptions[2]
Rotor.RotorOption.ROTOR_FOUR -> rotorOptions[3]
Rotor.RotorOption.ROTOR_FIVE -> rotorOptions[4]
}
}

internal fun Reflector.toReflectorLabelText(context: Context) = when(this) {
Reflector.REFLECTOR_UKW_A -> context.getString(R.string.reflector_a)
Reflector.REFLECTOR_UKW_B -> context.getString(R.string.reflector_b)
Reflector.REFLECTOR_UKW_C -> context.getString(R.string.reflector_c)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.smitpatel.enigmamachine

import android.content.Context
import com.smitpatel.enigmamachine.models.EnigmaHistoryItem
import com.smitpatel.enigmamachine.models.Reflector
import com.smitpatel.enigmamachine.models.Rotor
import org.json.JSONArray
import org.json.JSONObject
import kotlin.Exception

class DeserializeException : Exception()

object Serializer {

private const val rotorOptionsKey: String = "rotorOptions"
private const val rotorPositionsKey: String = "rotorPositions"
private const val ringPositionsKey: String = "ringPositions"
private const val reflectorKey: String = "reflector"
private const val plugboardPairsKey: String = "plugboardPairs"


fun serializeJson(context: Context, settings: EnigmaHistoryItem): String {
val json = JSONObject()

json.put(rotorOptionsKey, JSONArray(arrayOf(
settings.rotorOneOption.toRotorLabelText(context),
settings.rotorTwoOption.toRotorLabelText(context),
settings.rotorThreeOption.toRotorLabelText(context),
)))

json.put(rotorPositionsKey, JSONArray(arrayOf(
settings.rotorOnePosition.numberToLetter(),
settings.rotorTwoPosition.numberToLetter(),
settings.rotorThreePosition.numberToLetter(),
)))

json.put(ringPositionsKey, JSONArray(arrayOf(
settings.ringOneOption.numberToLetter(),
settings.ringTwoOption.numberToLetter(),
settings.ringThreeOption.numberToLetter(),
)))

json.put(reflectorKey, settings.reflectorOption.toReflectorLabelText(context))

json.put(plugboardPairsKey, JSONArray(settings.plugboardPairs.map {
JSONArray(arrayOf(it.first.numberToLetter(), it.second.numberToLetter()))
}))

return json.toString()
}

@Throws(DeserializeException::class)
fun deserializeJson(context: Context, settings: String): EnigmaHistoryItem {
try {
val json = JSONObject(settings)

val jsonRotorOptions = json.getJSONArray(rotorOptionsKey)
val rotorOptions = (0 until 3).map {
jsonRotorOptions.getString(it).toRotorOption(context)
}

val jsonRotorPositions = json.getJSONArray(rotorPositionsKey)
val rotorPositions = (0 until 3).map {
jsonRotorPositions.getString(it).toNumber()
}

val jsonRingPosition = json.getJSONArray(ringPositionsKey)
val ringPositions = (0 until 3).map {
jsonRingPosition.getString(it).toNumber()
}

val reflector = json.getString(reflectorKey).toReflector(context)

val jsonPlugboardPairs = json.getJSONArray(plugboardPairsKey)
val plugboardPairs = mutableSetOf<Pair<Int, Int>>()
for (i in 0 until jsonPlugboardPairs.length()) {
val jsonPair = jsonPlugboardPairs.getJSONArray(i)
plugboardPairs.add(
Pair(
first = jsonPair.getString(0).toNumber(),
second = jsonPair.getString(1).toNumber(),
)
)
}

return EnigmaHistoryItem(
rotorOneOption = rotorOptions[0],
rotorTwoOption = rotorOptions[1],
rotorThreeOption = rotorOptions[2],
rotorOnePosition = rotorPositions[0],
rotorTwoPosition = rotorPositions[1],
rotorThreePosition = rotorPositions[2],
ringOneOption = ringPositions[0],
ringTwoOption = ringPositions[1],
ringThreeOption = ringPositions[2],
reflectorOption = reflector,
plugboardPairs = plugboardPairs,
)
} catch (exception: Exception) {
throw DeserializeException()
}
}

@Throws(IllegalArgumentException::class, NoSuchElementException::class)
internal fun String.toNumber(): Int {
val char = this.trim().single().uppercaseChar()
if (!char.isLetter()) {
throw IllegalArgumentException("Input must be a letter: $this")
}
return char.letterToNumber()
}

@Throws(IllegalArgumentException::class)
internal fun String.toRotorOption(context: Context): Rotor.RotorOption {
val rotorOptions = context.resources.getStringArray(R.array.rotor_options)
return when (this.uppercase().trim()) {
rotorOptions[0] -> Rotor.RotorOption.ROTOR_ONE
rotorOptions[1] -> Rotor.RotorOption.ROTOR_TWO
rotorOptions[2] -> Rotor.RotorOption.ROTOR_THREE
rotorOptions[3] -> Rotor.RotorOption.ROTOR_FOUR
rotorOptions[4] -> Rotor.RotorOption.ROTOR_FIVE
else -> throw IllegalArgumentException("Invalid Roman numeral for RotorOption: $this")
}
}

@Throws(IllegalArgumentException::class)
internal fun String.toReflector(context: Context) = when (this.uppercase().trim()) {
context.getString(R.string.reflector_a) -> Reflector.REFLECTOR_UKW_A
context.getString(R.string.reflector_b) -> Reflector.REFLECTOR_UKW_B
context.getString(R.string.reflector_c) -> Reflector.REFLECTOR_UKW_C
else -> throw IllegalArgumentException("Invalid reflector string: $this")
}
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.smitpatel.enigmamachine.events

import com.smitpatel.enigmamachine.models.EnigmaHistoryItem
import com.smitpatel.enigmamachine.ui.RotorPosition

/**
Expand All @@ -11,6 +12,7 @@ sealed class EnigmaEvent {
data class RotorStartPositionChanged(val rotorPosition: RotorPosition, val start: Int): EnigmaEvent()
data class SettingMenuClosed(val didSettingsChanged : Boolean) : EnigmaEvent()
data class PasteRawText(val rawText: String) : EnigmaEvent()
data class PasteEnigmaSettings(val enigmaSettings: EnigmaHistoryItem?): EnigmaEvent()
object InputSpacePressed : EnigmaEvent()
object InputDeletePressed : EnigmaEvent()
object InputLongDeletePressed : EnigmaEvent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ import android.view.View
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.smitpatel.enigmamachine.DeserializeException
import com.smitpatel.enigmamachine.R
import com.smitpatel.enigmamachine.Serializer
import com.smitpatel.enigmamachine.databinding.ActivityEnigmaMainBinding
import com.smitpatel.enigmamachine.ui.EnigmaSounds
import com.smitpatel.enigmamachine.ui.SoundEffects
import com.smitpatel.enigmamachine.ui.setting.SettingsFragment
import com.smitpatel.enigmamachine.events.EnigmaEvent
import com.smitpatel.enigmamachine.letterToNumber
import com.smitpatel.enigmamachine.models.Reflector
import com.smitpatel.enigmamachine.models.Rotor
import com.smitpatel.enigmamachine.numberToLetter
import com.smitpatel.enigmamachine.toRotorLabelText
import com.smitpatel.enigmamachine.ui.RotorPosition
import com.smitpatel.enigmamachine.ui.paste_error.PasteErrorFragment
import com.smitpatel.enigmamachine.viewmodels.EnigmaViewModel
Expand All @@ -33,6 +33,8 @@ class EnigmaMainActivity : AppCompatActivity() {

private val sounds : SoundEffects = SoundEffects

private val serializer : Serializer = Serializer

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityEnigmaMainBinding.inflate(layoutInflater)
Expand All @@ -54,16 +56,6 @@ class EnigmaMainActivity : AppCompatActivity() {
}

private fun setupRenderer() {
fun getRotorLabelText(rotor: Rotor.RotorOption): String {
val rotorOptions = resources.getStringArray(R.array.rotor_options)
return when (rotor) {
Rotor.RotorOption.ROTOR_ONE -> rotorOptions[0]
Rotor.RotorOption.ROTOR_TWO -> rotorOptions[1]
Rotor.RotorOption.ROTOR_THREE -> rotorOptions[2]
Rotor.RotorOption.ROTOR_FOUR -> rotorOptions[3]
Rotor.RotorOption.ROTOR_FIVE -> rotorOptions[4]
}
}

fun showToast(text: String) {
Toast.makeText(applicationContext, text, Toast.LENGTH_SHORT).show()
Expand All @@ -75,9 +67,9 @@ class EnigmaMainActivity : AppCompatActivity() {
binding.rotors.rotor2.value = it.rotorTwoPosition + 1
binding.rotors.rotor3.value = it.rotorThreePosition + 1

binding.rotors.rotor1Label.text = getRotorLabelText(it.rotorOneLabel)
binding.rotors.rotor2Label.text = getRotorLabelText(it.rotorTwoLabel)
binding.rotors.rotor3Label.text = getRotorLabelText(it.rotorThreeLabel)
binding.rotors.rotor1Label.text = it.rotorOneLabel.toRotorLabelText(applicationContext)
binding.rotors.rotor2Label.text = it.rotorTwoLabel.toRotorLabelText(applicationContext)
binding.rotors.rotor3Label.text = it.rotorThreeLabel.toRotorLabelText(applicationContext)

binding.textboxes.textRaw.setText(it.rawMessage)
binding.textboxes.textCode.setText(it.encodedMessage)
Expand Down Expand Up @@ -113,53 +105,18 @@ class EnigmaMainActivity : AppCompatActivity() {
)
}

if (it.showSettingsErrorToast) {
showToast(text = resources.getString(R.string.settings_not_changed_toast_message))
viewModel.handleEvent(
event = EnigmaEvent.ToastMessageDisplayed
)
}

if (it.clipboardCopyState != null) {
val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
when (val settingsState = it.clipboardCopyState.settingsState) {
null -> {
clipboardManager.setPrimaryClip(ClipData.newPlainText(
"",
it.clipboardCopyState.text,
))
}
else -> {

fun getReflectorLabelText(reflector: Reflector) = when (reflector) {
Reflector.REFLECTOR_UKW_A -> getString(R.string.reflector_a)
Reflector.REFLECTOR_UKW_B -> getString(R.string.reflector_b)
Reflector.REFLECTOR_UKW_C -> getString(R.string.reflector_c)
}

val rotorOptions = "${getRotorLabelText(settingsState.rotorOneLabel)} " +
"${getRotorLabelText(settingsState.rotorTwoLabel)} " +
getRotorLabelText(settingsState.rotorThreeLabel)

val rotorPositions = "${settingsState.rotorOnePosition.numberToLetter()} " +
"${settingsState.rotorTwoPosition.numberToLetter()} " +
settingsState.rotorThreePosition.numberToLetter()

val ringPositions = "${settingsState.rotorOneRing.numberToLetter()} " +
"${settingsState.rotorTwoRing.numberToLetter()} " +
settingsState.rotorThreeRing.numberToLetter()

val plugboardPairs = settingsState.plugboardPairs.map { plugboardPair ->
Pair(
first = plugboardPair.first.numberToLetter(),
second = plugboardPair.second.numberToLetter()
)
}

clipboardManager.setPrimaryClip(ClipData.newPlainText(
"",
getString(R.string.copy_settings_text,
rotorOptions,
rotorPositions,
ringPositions,
getReflectorLabelText(settingsState.reflector),
plugboardPairs,
)
))
}
null -> clipboardManager.setPrimaryClip(ClipData.newPlainText("", it.clipboardCopyState.text))
else -> clipboardManager.setPrimaryClip(ClipData.newPlainText("", serializer.serializeJson(applicationContext, settingsState)))
}

if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
Expand Down Expand Up @@ -328,6 +285,19 @@ class EnigmaMainActivity : AppCompatActivity() {
}
true
}
R.id.paste_enigma_settings -> {
val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clipboardText = clipboardManager.primaryClip?.getItemAt(0)?.text
if (!clipboardText.isNullOrEmpty()) {
try {
val enigmaSettings = serializer.deserializeJson(applicationContext, clipboardText.toString())
viewModel.handleEvent(EnigmaEvent.PasteEnigmaSettings(enigmaSettings))
} catch (error: DeserializeException) {
viewModel.handleEvent(EnigmaEvent.PasteEnigmaSettings(enigmaSettings = null))
}
}
true
}
else -> super.onContextItemSelected(item)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.smitpatel.enigmamachine.ui.main

import com.smitpatel.enigmamachine.models.Reflector
import com.smitpatel.enigmamachine.models.EnigmaHistoryItem
import com.smitpatel.enigmamachine.models.Rotor

data class EnigmaUiState(
Expand All @@ -15,24 +15,11 @@ data class EnigmaUiState(
val encodedMessage: String,
val clipboardCopyState: ClipboardCopyState?,
val showSettingsChangedToast: Boolean,
val showSettingsErrorToast: Boolean,
val pasteError: String?,
)

data class ClipboardCopyState(
val text: String,
val settingsState: SettingsCopyState?,
) {
data class SettingsCopyState(
val rotorOneLabel: Rotor.RotorOption,
val rotorTwoLabel: Rotor.RotorOption,
val rotorThreeLabel: Rotor.RotorOption,
val rotorOnePosition: Int,
val rotorTwoPosition: Int,
val rotorThreePosition: Int,
val rotorOneRing: Int,
val rotorTwoRing: Int,
val rotorThreeRing: Int,
val reflector: Reflector,
val plugboardPairs: Set<Pair<Int, Int>>,
)
}
val settingsState: EnigmaHistoryItem?,
)
Loading
Loading