From cdb58ee7e076f7827ee10feb7e9ee152aa42d4cc Mon Sep 17 00:00:00 2001 From: Jerboa <84378622+Jerboa-app@users.noreply.github.com> Date: Mon, 19 Aug 2024 20:37:38 +0100 Subject: [PATCH] Adds moveable menu widget (#63) * Adds moveable reactive menu --- .../main/java/app/jerboa/spp/MainActivity.kt | 66 ++++++++-------- .../java/app/jerboa/spp/composable/about.kt | 12 +-- .../app/jerboa/spp/composable/menuPrompt.kt | 77 +++++++++++++++++-- .../app/jerboa/spp/composable/musicMenu.kt | 17 +++- .../java/app/jerboa/spp/composable/newsLog.kt | 8 +- .../java/app/jerboa/spp/composable/screen.kt | 6 +- .../java/app/jerboa/spp/composable/socials.kt | 2 +- .../app/jerboa/spp/composable/verticalMenu.kt | 20 +++-- 8 files changed, 147 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/app/jerboa/spp/MainActivity.kt b/app/src/main/java/app/jerboa/spp/MainActivity.kt index a559e96..dc987f6 100644 --- a/app/src/main/java/app/jerboa/spp/MainActivity.kt +++ b/app/src/main/java/app/jerboa/spp/MainActivity.kt @@ -6,26 +6,33 @@ import android.app.ActivityManager.RunningAppProcessInfo import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent +import android.content.res.Resources +import android.graphics.Point +import android.graphics.Rect import android.media.MediaPlayer import android.net.Uri import android.os.Bundle -import android.util.DisplayMetrics import android.util.Log -import android.view.View +import android.view.WindowManager import android.widget.Toast import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.compose.ui.graphics.Color +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat import app.jerboa.spp.composable.NewsItem -import app.jerboa.spp.viewmodel.MUSIC -import app.jerboa.spp.viewmodel.REVIEW_RATE_LIMIT_MILLIS -import app.jerboa.spp.viewmodel.SPPViewModel -import app.jerboa.spp.viewmodel.SOCIAL import app.jerboa.spp.composable.screen import app.jerboa.spp.ui.theme.SPPTheme import app.jerboa.spp.viewmodel.AboutViewModel -import app.jerboa.spp.viewmodel.ToyMenuViewModel +import app.jerboa.spp.viewmodel.MUSIC import app.jerboa.spp.viewmodel.MenuPromptViewModel +import app.jerboa.spp.viewmodel.REVIEW_RATE_LIMIT_MILLIS +import app.jerboa.spp.viewmodel.SOCIAL +import app.jerboa.spp.viewmodel.SPPViewModel +import app.jerboa.spp.viewmodel.ToyMenuViewModel import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.games.AuthenticationResult @@ -40,7 +47,8 @@ import com.google.android.play.core.review.ReviewManager import com.google.android.play.core.review.ReviewManagerFactory import com.google.android.play.core.review.model.ReviewErrorCode import java.lang.Integer.min -import java.util.* +import java.util.Date + val news = "news-21-07-24" @@ -50,6 +58,7 @@ data class AppInfo( val versionString: String, val firstLaunch: Boolean, val density: Float, + val textScaling: Float, val heightDp: Float, val widthDp: Float ) @@ -573,12 +582,29 @@ class MainActivity : AppCompatActivity() { } ) + val news: List = listOf( + NewsItem(R.string.news1, R.drawable.__5_0), + NewsItem(R.string.news2, null), + NewsItem(R.string.news3, null), + NewsItem(R.string.news4, R.drawable.__5_3), + NewsItem(R.string.news5, null), + NewsItem(R.string.news6, R.drawable.__5_5), + NewsItem(R.string.news7, null), + NewsItem(R.string.news8, R.drawable.__6_0), + NewsItem(R.string.news9, null), + NewsItem(R.string.news10, R.drawable.news) + ).reversed() + + val height = resources.displayMetrics.heightPixels + val width = resources.displayMetrics.widthPixels + val displayInfo = resources.displayMetrics val dpHeight = displayInfo.heightPixels / displayInfo.density val dpWidth = displayInfo.widthPixels / displayInfo.density val appInfo = AppInfo( versionString, firstLaunch, + displayInfo.density, if (resources.getBoolean(R.bool.isTablet)) { displayInfo.density } else { @@ -588,30 +614,6 @@ class MainActivity : AppCompatActivity() { dpWidth ) - if (DEBUG) { - Log.d("density", appInfo.density.toString()) - } - - val news: List = listOf( - NewsItem(R.string.news1, R.drawable.__5_0), - NewsItem(R.string.news2, null), - NewsItem(R.string.news3, null), - NewsItem(R.string.news4, R.drawable.__5_3), - NewsItem(R.string.news5, null), - NewsItem(R.string.news6, R.drawable.__5_5), - NewsItem(R.string.news7, null), - NewsItem(R.string.news8, R.drawable.__6_0), - NewsItem(R.string.news9, null), - NewsItem(R.string.news10, R.drawable.news) - ).reversed() - - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN - actionBar?.hide() - - val displayMetrics = DisplayMetrics() - windowManager.defaultDisplay.getMetrics(displayMetrics) - val height = displayMetrics.heightPixels - val width = displayMetrics.widthPixels setContent { SPPTheme { screen( diff --git a/app/src/main/java/app/jerboa/spp/composable/about.kt b/app/src/main/java/app/jerboa/spp/composable/about.kt index 2aa2cd4..5b292bb 100644 --- a/app/src/main/java/app/jerboa/spp/composable/about.kt +++ b/app/src/main/java/app/jerboa/spp/composable/about.kt @@ -74,7 +74,7 @@ fun about( stringResource(id = R.string.tagline) + stringResource(id = R.string.description), textAlign = TextAlign.Center, maxLines = 4, - fontSize = MaterialTheme.typography.body1.fontSize * info.density, + fontSize = MaterialTheme.typography.body1.fontSize * info.textScaling, colour = Color.Black ) TextButton(onClick = { aboutViewModel.onRequestingLicenses() }) { @@ -82,20 +82,20 @@ fun about( stringResource(id = R.string.OSSprompt), textAlign = TextAlign.Center, modifier = Modifier.weight(1f), - fontSize = MaterialTheme.typography.body1.fontSize * info.density, + fontSize = MaterialTheme.typography.body1.fontSize * info.textScaling, color = MaterialTheme.colors.primary ) } adaptiveTextBox( stringResource(R.string.attrib), maxLines = 3, - fontSize = MaterialTheme.typography.overline.fontSize * info.density, + fontSize = MaterialTheme.typography.overline.fontSize * info.textScaling, textAlign = TextAlign.Center, colour = Color.Black ) Text( "version: " + info.versionString, modifier = Modifier.weight(1f), - fontSize = MaterialTheme.typography.overline.fontSize * info.density, + fontSize = MaterialTheme.typography.overline.fontSize * info.textScaling, textAlign = TextAlign.Center, color = Color.Black ) @@ -104,7 +104,7 @@ fun about( stringResource(id = R.string.resetTutorial), textAlign = TextAlign.Center, modifier = Modifier.weight(1f), - fontSize = MaterialTheme.typography.body1.fontSize * info.density, + fontSize = MaterialTheme.typography.body1.fontSize * info.textScaling, color = MaterialTheme.colors.primary ) } @@ -113,7 +113,7 @@ fun about( Spacer(modifier = Modifier.size(8.dp)) socials(images, info) { aboutViewModel.onRequestingSocial(it) } Spacer(modifier = Modifier.size(2.dp)) - newsLog(newsItems = newsItems, width = width75Percent*0.75, density = info.density) + newsLog(newsItems = newsItems, width = width75Percent*0.75) } } } \ No newline at end of file diff --git a/app/src/main/java/app/jerboa/spp/composable/menuPrompt.kt b/app/src/main/java/app/jerboa/spp/composable/menuPrompt.kt index ddec3ee..9cb364e 100644 --- a/app/src/main/java/app/jerboa/spp/composable/menuPrompt.kt +++ b/app/src/main/java/app/jerboa/spp/composable/menuPrompt.kt @@ -10,31 +10,46 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.Image import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.* import androidx.compose.material.IconButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import app.jerboa.spp.AppInfo import app.jerboa.spp.viewmodel.AboutViewModel import app.jerboa.spp.viewmodel.MenuPromptViewModel import app.jerboa.spp.viewmodel.SPPViewModel +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min -@OptIn(ExperimentalAnimationApi::class) @Composable fun menuPrompt( menuPromptViewModel: MenuPromptViewModel, aboutViewModel: AboutViewModel, sppViewModel: SPPViewModel, images: Map, - menuItemHeight: Double + menuItemHeight: Double, + info: AppInfo ){ val displayingMenu: Boolean by menuPromptViewModel.displayingMenu.observeAsState(initial = false) + val displayingAbout: Boolean by aboutViewModel.displayingAbout.observeAsState(initial = false) + val displayingSliders: Boolean by menuPromptViewModel.displayingToyMenu.observeAsState(initial = false) + val displayingSound: Boolean by menuPromptViewModel.displayingSound.observeAsState(initial = false) val paused: Boolean by menuPromptViewModel.paused.observeAsState(initial = false) val playSuccess: Boolean by sppViewModel.playSuccess.observeAsState(initial = false) @@ -47,6 +62,10 @@ fun menuPrompt( ), label = "alpha for fading the prompt" ) + val xmax = info.widthDp*info.density-1.7f*menuItemHeight.toFloat()*info.density + val ymax = info.heightDp*info.density-1.75f*menuItemHeight.toFloat()*info.density + var position by remember { mutableStateOf(Offset(0.0f,ymax)) } + Box(modifier = Modifier .fillMaxHeight() .fillMaxWidth() @@ -55,14 +74,32 @@ fun menuPrompt( modifier = Modifier .fillMaxHeight() .fillMaxWidth() - .align(alignment = Alignment.BottomStart) + .graphicsLayer { + if (displayingAbout || displayingSliders) { + if (abs(position.x-xmax) < abs(position.x)) { + translationX = xmax + } + else { + translationX = 0.0f + } + translationY = ymax + } else { + translationX = position.x + translationY = position.y + } + } ) { AnimatedVisibility( visible = displayingMenu, enter = fadeIn(tween(0)), exit = fadeOut(tween(0)) ) { - verticalMenu(modifier = Modifier, offset = Pair(0.dp, menuItemHeight.dp), contentPadding = 16.dp) { + verticalMenu( + modifier = Modifier, + offset = Pair(0.dp, 0.dp), + contentPadding = 16.dp, + headSpacePx = if (displayingAbout || displayingSliders) { ymax } else { position.y } + ) { Image( painter = painterResource(id = images["toyMenu"]!!), contentDescription = "Toy menu and sliders", @@ -80,7 +117,8 @@ fun menuPrompt( musicMenu( menuPromptViewModel, menuItemHeight, - images + images, + left = position.x > xmax/2.0f ) } else { @@ -164,7 +202,30 @@ fun menuPrompt( Box( modifier = Modifier .size(menuItemHeight.dp) - .align(alignment = Alignment.BottomStart) + .graphicsLayer { + if (displayingAbout || displayingSliders) { + if (abs(position.x-xmax) < abs(position.x)) { + translationX = xmax + } + else { + translationX = 0.0f + } + translationY = ymax + } else { + translationX = position.x + translationY = position.y + } + } + .conditional(!(displayingAbout || displayingSliders)){ Modifier.pointerInput(Unit) { + detectDragGestures { change, dragAmount -> + change.consume() + var x = max(0.0f, position.x+dragAmount.x) + x = min(x, xmax) + var y = max(0.0f, position.y+dragAmount.y) + y = min(y, ymax) + position = Offset(x, y) + } + }} ) { AnimatedVisibility( visible = !displayingMenu, @@ -194,7 +255,9 @@ fun menuPrompt( .size(menuItemHeight.dp) .clickable( onClick = { - menuPromptViewModel.onDisplayingMenuChanged(false) + if (!(displayingAbout || displayingSliders)) { + menuPromptViewModel.onDisplayingMenuChanged(false) + } aboutViewModel.onDisplayingAboutChanged(false) menuPromptViewModel.onDisplayingToyMenuChanged(false) menuPromptViewModel.onDisplayingMusicChanged(false) diff --git a/app/src/main/java/app/jerboa/spp/composable/musicMenu.kt b/app/src/main/java/app/jerboa/spp/composable/musicMenu.kt index e8ac7bf..b1e4102 100644 --- a/app/src/main/java/app/jerboa/spp/composable/musicMenu.kt +++ b/app/src/main/java/app/jerboa/spp/composable/musicMenu.kt @@ -4,6 +4,7 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -13,6 +14,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -20,7 +22,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import app.jerboa.spp.viewmodel.AboutViewModel import app.jerboa.spp.viewmodel.MUSIC @@ -31,7 +36,16 @@ fun musicMenu( menuPromptViewModel: MenuPromptViewModel, menuItemHeight: Double, images: Map, + left: Boolean = false ) { + val offsetX: Dp = if (left) + { + (-2*menuItemHeight).dp + } + else { + 0.dp + } + AnimatedVisibility( visible = true, enter = fadeIn(), @@ -40,7 +54,8 @@ fun musicMenu( Row( modifier = Modifier .fillMaxWidth() - .height(menuItemHeight.dp), + .height(menuItemHeight.dp) + .offset(offsetX, 0.dp), horizontalArrangement = Arrangement.spacedBy((0.01*menuItemHeight).dp), verticalAlignment = Alignment.CenterVertically ) { diff --git a/app/src/main/java/app/jerboa/spp/composable/newsLog.kt b/app/src/main/java/app/jerboa/spp/composable/newsLog.kt index 2b80494..6eb7ae5 100644 --- a/app/src/main/java/app/jerboa/spp/composable/newsLog.kt +++ b/app/src/main/java/app/jerboa/spp/composable/newsLog.kt @@ -33,8 +33,7 @@ data class NewsItem (val textResourceId: Int, val imageResourceId: Int?) @Composable fun newsItem( item: NewsItem, - width: Double, - density: Float + width: Double ) { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -64,8 +63,7 @@ fun newsItem( fun newsLog( newsItems: List, spacing: Dp = 2.dp, - width: Double, - density: Float + width: Double ) { val scrollState = rememberScrollState() @@ -94,7 +92,7 @@ fun newsLog( ) { for (item in newsItems) { - newsItem(item, width*0.9, density) + newsItem(item, width*0.9) Spacer(modifier = Modifier.size(spacing)) } } diff --git a/app/src/main/java/app/jerboa/spp/composable/screen.kt b/app/src/main/java/app/jerboa/spp/composable/screen.kt index f55f2ae..075062c 100644 --- a/app/src/main/java/app/jerboa/spp/composable/screen.kt +++ b/app/src/main/java/app/jerboa/spp/composable/screen.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.Scaffold import androidx.compose.material.SnackbarDuration @@ -72,7 +73,7 @@ fun screen( toyMenuViewModel.selectDefaultColourMap() Column( - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().fillMaxHeight() ) { // padding is unused is we are using the scaffold as // a hacky overlay on top of opengl @@ -173,7 +174,8 @@ fun screen( aboutViewModel, sppViewModel, images, - menuItemHeight + menuItemHeight, + info ) if (!dismissedTutorial && (info.firstLaunch || resetTutorial) ){ diff --git a/app/src/main/java/app/jerboa/spp/composable/socials.kt b/app/src/main/java/app/jerboa/spp/composable/socials.kt index 16f44e2..1f32c2b 100644 --- a/app/src/main/java/app/jerboa/spp/composable/socials.kt +++ b/app/src/main/java/app/jerboa/spp/composable/socials.kt @@ -87,7 +87,7 @@ fun socials( Text( text = stringResource(id = R.string.rate), color = Color(255,255,255,255), - fontSize = MaterialTheme.typography.body1.fontSize*info.density, + fontSize = MaterialTheme.typography.body1.fontSize*info.textScaling, textAlign = TextAlign.Center ) } diff --git a/app/src/main/java/app/jerboa/spp/composable/verticalMenu.kt b/app/src/main/java/app/jerboa/spp/composable/verticalMenu.kt index bd44087..b6f5a65 100644 --- a/app/src/main/java/app/jerboa/spp/composable/verticalMenu.kt +++ b/app/src/main/java/app/jerboa/spp/composable/verticalMenu.kt @@ -1,5 +1,6 @@ package app.jerboa.spp.composable +import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout @@ -11,27 +12,32 @@ import kotlin.math.max @Composable fun verticalMenu( modifier: Modifier, + headSpacePx: Float? = null, contentPadding: Dp = 0.dp, offset: Pair = Pair(0.dp, 0.dp), - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { val configuration = LocalConfiguration.current Layout(content = content, modifier = modifier) { children, constraints -> val screenWidth = configuration.screenWidthDp.dp.toPx() - val screenHeight = configuration.screenHeightDp.dp.toPx() val pad = contentPadding.toPx() val offsetPx = Pair(offset.first.toPx().toInt(), offset.second.toPx().toInt()) val items = children.map { it.measure(constraints) } - var maxHeight = 0 - items.map { maxHeight = max(maxHeight, it.height) } - + var height = 0 + items.map { maxHeight = max(maxHeight, it.height); height += it.height } + val sign = if (headSpacePx != null && headSpacePx < height+(maxHeight+offsetPx.second+pad.toInt())) { + 1 + } + else { + -1 + } layout(screenWidth.toInt(), screenWidth.toInt()) { var y = 0 for (i in items.indices) { - items[i].placeRelative(x=pad.toInt()+offsetPx.first, y=(screenHeight-y-maxHeight-offsetPx.second-pad.toInt()).toInt()) - y += items[i].height + items[i].placeRelative(x=pad.toInt()+offsetPx.first, y=(y+sign*(maxHeight+offsetPx.second+pad.toInt())).toInt()) + y += sign*items[i].height } } }