diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/App.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/App.kt index 7f0061d45f5fe7402475986771b9ceb7aa20918a..03ff5bcc5ac18bd6be6dfe18f60202fdc335438f 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/App.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/App.kt @@ -6,10 +6,12 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CloseFullscreen import androidx.compose.material.icons.filled.OpenInFull import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier @@ -133,4 +135,32 @@ fun FullscreenIconButton( } } +@Composable +fun FullscreenDropdownMenuItem( + modifier: Modifier = Modifier, + onClicked: () -> Unit = {} +) { + val settings = App.settings() + val fullscreen = settings.value.fullscreen + DropdownMenuItem( + text = { + when (settings.value.fullscreen) { + false -> Text("Vollbild") + true -> Text("Vollbild verlassen") + } + }, + onClick = { + settings.tryUpdate { it.copy(fullscreen = !fullscreen) } + onClicked() + }, + modifier, + leadingIcon = { + when (settings.value.fullscreen) { + false -> Icon(Icons.Default.OpenInFull, "Vollbild") + true -> Icon(Icons.Default.CloseFullscreen, "Vollbild verlassen") + } + } + ) +} + diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/LoginDevice.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/LoginDevice.kt index a5431dd3538a006ac705e2d39dba9008b3448cb3..dc5c4536bd8af5eb98895a29cb7fe206fe3824fe 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/LoginDevice.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/LoginDevice.kt @@ -226,6 +226,110 @@ fun LogoutDeviceButton( ) } +@Composable +fun LogoutDeviceDropdownMenuItem( + modifier: Modifier = Modifier, + onClicked: () -> Unit = {} +) { + val state = findState() + var confirmLogout by remember { mutableStateOf(false) } + DropdownMenuItem( + text = { Text("Gerät ausloggen") }, + onClick = { confirmLogout = true }, + modifier, + leadingIcon = { Icon(Icons.Default.PhonelinkErase, "Gerät ausloggen") } + ) + if (confirmLogout) ConfirmLogoutDeviceDialog( + onDismiss = { confirmLogout = false }, + onConfirm = { + state.logout() + onClicked() + } + ) +} + +// TODO move to utilities +fun <T> Collection<T>.randomSubset( + random: Random = Random.Default, + minSize: Int = 0, + maxSize: Int = size +): Set<T> { + require(minSize >= 0) { "minSize must be non-negative" } + require(maxSize <= size) { "maxSize must be less than or equal to the size of the collection" } + require(minSize <= maxSize) { "minSize must be less than or equal to maxSize" } + val available = LinkedList(this) + val selected = mutableSetOf<T>() + val toSelect = random.nextInt(minSize, maxSize + 1) + repeat(toSelect) { + selected += available.removeAt(random.nextInt(available.size)) + } + return selected +} + +// TODO move to utilities +fun Iterable<Int>.product(): Int { + return reduceOrNull { acc, i -> acc * i } ?: 1 +} + +// TODO move to utilities +fun Int.primeFactors(): List<Int> { + val factors = mutableListOf<Int>() + var n = this + var i = 2 + while (i * i <= n) { + if (n % i == 0) { + factors += i + n /= i + } else { + i++ + } + } + if (n > 1) { + factors += n + } + return factors +} + +private fun randomEquation( + target: Int = 42, + minSolution: Int = -10, + maxSolution: Int = 10, + random: Random = Random.Default +): Pair<String, Int> { + val primeFactorsOfTarget = target.primeFactors() + + val b = primeFactorsOfTarget + .randomSubset(random = random, minSize = 1) + .product() + .let { + if (random.nextBoolean()) it else -it + } + val x = Random.nextInt(minSolution, maxSolution + 1) + val a = target / b - x + + return StringBuilder().apply { + append("(") + append("x") + if (a < 0) { + append(" - ") + } else { + append(" + ") + } + append(a.absoluteValue) + append(")") + append(" ⋅ ") + if (b < 0) { + append("(") + append(b) + append(")") + } else { + append(b) + } + append(" = ") + append(target) + }.toString() to x +} + @Composable private fun ConfirmLogoutDeviceDialog( onDismiss: () -> Unit, diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/UserSelection.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/UserSelection.kt index 7f8839e7c4926f5164e553f5b4e5ef2d545ccd5b..c1f0ae5b6a689aefbb50560bc14d2a5fbe8bf17e 100644 --- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/UserSelection.kt +++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/UserSelection.kt @@ -25,12 +25,15 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Password import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.DividerDefaults +import androidx.compose.material3.DropdownMenu import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedCard import androidx.compose.material3.OutlinedTextField @@ -87,7 +90,7 @@ import net.novagamestudios.kaffeekasse.repositories.LoginRepository import net.novagamestudios.kaffeekasse.repositories.RepositoryProvider import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository import net.novagamestudios.kaffeekasse.ui.AppInfoTopBarAction -import net.novagamestudios.kaffeekasse.ui.FullscreenIconButton +import net.novagamestudios.kaffeekasse.ui.FullscreenDropdownMenuItem import net.novagamestudios.kaffeekasse.ui.handleSession import net.novagamestudios.kaffeekasse.ui.navigation.AppScaffoldContentWithModel import net.novagamestudios.kaffeekasse.ui.navigation.LoginNavigation @@ -200,8 +203,7 @@ class UserSelectionContent(provider: ScreenModelProvider<UserSelectionScreenMode override fun TopAppBarActions(navigator: Navigator) { TopBarSearchAction(model.searchState, alwaysShow = true) AppInfoTopBarAction() - LogoutDeviceButton() - FullscreenIconButton() + UserSelectionDropdown() } @Composable @@ -540,3 +542,23 @@ private fun DeviceInfo( } } +@Composable +fun UserSelectionDropdown( + modifier: Modifier = Modifier +) = Box { + var expanded by remember { mutableStateOf(false) } + IconButton( + onClick = { expanded = true }, + modifier + ) { + Icon(Icons.Default.MoreVert, "Mehr") + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + FullscreenDropdownMenuItem(onClicked = { expanded = false }) + LogoutDeviceDropdownMenuItem(onClicked = { expanded = false }) + } +} +