Skip to content

Commit

Permalink
Merge pull request #127 from Semper-Viventem/master
Browse files Browse the repository at this point in the history
Release 0.24.1-beta
  • Loading branch information
Semper-Viventem authored Mar 14, 2024
2 parents fe9efaf + f40a1bd commit 6778d64
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 18 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ android {
minSdk = 29
targetSdk = 34

versionCode = 1708536350
versionName = "0.24.0-beta"
versionCode = 1708536351
versionName = "0.24.1-beta"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,16 @@ object DeviceListScreen {
}
}

if (viewModel.enjoyTheAppState != DeviceListViewModel.EnjoyTheAppState.None) {
item(contentType = ListContentType.ENJOY_THE_APP, key = "enjoy_the_app") {
Spacer(modifier = Modifier.height(8.dp))
EnjoyTheApp(Modifier.animateItemPlacement(), viewModel, viewModel.enjoyTheAppState)
item(contentType = ListContentType.ENJOY_THE_APP, key = "enjoy_the_app") {
Spacer(modifier = Modifier.height(8.dp))
AnimatedVisibility(
modifier = Modifier
.animateItemPlacement(),
visible = viewModel.enjoyTheAppState != DeviceListViewModel.EnjoyTheAppState.None,
enter = fadeIn(),
exit = fadeOut(),
) {
EnjoyTheApp(viewModel, viewModel.enjoyTheAppState)
}
}

Expand Down Expand Up @@ -216,7 +222,8 @@ object DeviceListScreen {
Column(
Modifier
.fillMaxWidth()
.animateContentSize()) {
.animateContentSize()
) {
CurrentBatchList(viewModel)
}
}
Expand Down Expand Up @@ -350,8 +357,8 @@ object DeviceListScreen {
}

@Composable
private fun EnjoyTheApp(modifier: Modifier, viewModel: DeviceListViewModel, enjoyTheAppState: DeviceListViewModel.EnjoyTheAppState) {
RoundedBox(modifier) {
private fun EnjoyTheApp(viewModel: DeviceListViewModel, enjoyTheAppState: DeviceListViewModel.EnjoyTheAppState) {
RoundedBox(Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {
when (enjoyTheAppState) {
is DeviceListViewModel.EnjoyTheAppState.Question -> EnjoyTheAppQuestion(viewModel)
is DeviceListViewModel.EnjoyTheAppState.Like -> EnjoyTheAppLike(enjoyTheAppState, viewModel)
Expand Down
74 changes: 65 additions & 9 deletions app/src/main/java/f/cking/software/ui/main/MainScreen.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package f.cking.software.ui.main

import android.annotation.SuppressLint
import android.view.MotionEvent
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
Expand All @@ -22,12 +23,21 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInParent
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
Expand All @@ -38,9 +48,12 @@ import com.vanpra.composematerialdialogs.MaterialDialogState
import com.vanpra.composematerialdialogs.rememberMaterialDialogState
import f.cking.software.R
import f.cking.software.ui.GlobalUiState
import f.cking.software.utils.graphic.DropEffectState
import f.cking.software.utils.graphic.GlassBottomNavBar
import f.cking.software.utils.graphic.SystemNavbarSpacer
import f.cking.software.utils.graphic.ThemedDialog
import f.cking.software.utils.graphic.rememberDropEffectState
import f.cking.software.utils.graphic.withDropEffect
import org.koin.androidx.compose.koinViewModel

@OptIn(ExperimentalMaterial3Api::class)
Expand All @@ -50,8 +63,11 @@ object MainScreen {
@Composable
fun Screen() {
val viewModel: MainViewModel = koinViewModel()
val dropEffectState = rememberDropEffectState()
Scaffold(
modifier = Modifier.fillMaxSize(),
modifier = Modifier
.fillMaxSize()
.withDropEffect(dropEffectState),
topBar = {
TopBar(viewModel)
},
Expand All @@ -70,7 +86,7 @@ object MainScreen {
BottomNavigationBar(Modifier, viewModel)
},
floatingActionButton = {
ScanFab(viewModel)
ScanFab(viewModel, dropEffectState)
},
)
LocationDisabledDialog(viewModel)
Expand Down Expand Up @@ -163,11 +179,13 @@ object MainScreen {
}
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun ScanFab(viewModel: MainViewModel) {
private fun ScanFab(viewModel: MainViewModel, dropEffectState: DropEffectState) {
val text: String
val icon: Int

var geometry = remember { Rect(0f, 0f, 0f, 0f) }
if (viewModel.bgServiceIsActive) {
text = stringResource(R.string.stop)
icon = R.drawable.ic_cancel
Expand All @@ -186,17 +204,55 @@ object MainScreen {
Toast.makeText(context, "The scanner cannot work without these permissions", Toast.LENGTH_SHORT).show()
}
)
val haptic = LocalHapticFeedback.current
var observeEvent = remember { true }

ExtendedFloatingActionButton(
containerColor = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier.onGloballyPositioned { GlobalUiState.setBottomOffset(fabOffset = it.size.height.toFloat()) },
modifier = Modifier
.onGloballyPositioned {
GlobalUiState.setBottomOffset(fabOffset = it.size.height.toFloat())
geometry = Rect(Offset(it.positionInParent().x, it.positionInParent().y), Size(it.size.width.toFloat(), it.size.height.toFloat()))
}
.pointerInteropFilter { event ->
val touchX = geometry.left + event.x
val touchY = geometry.top + event.y
when {
event.action == MotionEvent.ACTION_DOWN -> {
dropEffectState.drop(type = DropEffectState.DropEvent.Type.TOUCH, touchX, touchY)
observeEvent = true
true
}

observeEvent && event.action == MotionEvent.ACTION_UP -> {
if (viewModel.needToShowPermissionsIntro()) {
permissionsIntro.show()
dropEffectState.drop(type = DropEffectState.DropEvent.Type.RELEASE_SOFT, touchX, touchY)
} else {
viewModel.runBackgroundScanning()
dropEffectState.drop(type = DropEffectState.DropEvent.Type.RELEASE_HARD, touchX, touchY)
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}
true
}

observeEvent && event.action == MotionEvent.ACTION_MOVE -> {
if (geometry.contains(Offset(geometry.left + event.x, geometry.top + event.y))) {
dropEffectState.move(touchX, touchY)
true
} else {
dropEffectState.drop(type = DropEffectState.DropEvent.Type.RELEASE_SOFT, touchX, touchY)
observeEvent = false
false
}
}

else -> false
}
},
text = { Text(text = text, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onPrimaryContainer) },
onClick = {
if (viewModel.needToShowPermissionsIntro()) {
permissionsIntro.show()
} else {
viewModel.runBackgroundScanning()
}
// ignore
},
icon = {
Image(
Expand Down
110 changes: 110 additions & 0 deletions app/src/main/java/f/cking/software/utils/graphic/DropEffectState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package f.cking.software.utils.graphic

import android.graphics.RenderEffect
import android.graphics.RuntimeShader
import android.os.Build
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.asComposeRenderEffect
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onSizeChanged

@Composable
fun Modifier.withDropEffect(dropEffectState: DropEffectState): Modifier = composed {

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
return@composed this
}

val shader = remember { RuntimeShader(Shaders.WATER_DROP) }
val factor = remember { Animatable(0f) }

val dropEvent = dropEffectState.dropEvent
if (dropEvent != null) {
LaunchedEffect(dropEffectState.dropEvent) {
dropEvent.type.animSpecs.forEach { animationSpec ->
factor.animateTo(
targetValue = animationSpec.to,
animationSpec = tween(durationMillis = animationSpec.duration, easing = animationSpec.easing)
)
}
dropEvent.type.postFactor?.let { factor.snapTo(it) }
}
}

shader.setFloatUniform(
"dropPosition",
dropEffectState.center[0],
dropEffectState.center[1],
)

shader.setFloatUniform("factor", factor.value)

this
.onSizeChanged {
shader.setFloatUniform(
"iResolution",
it.width.toFloat(),
it.height.toFloat(),
)
shader.setFloatUniform(
"dropPosition",
it.width.toFloat() / 2,
it.height.toFloat() / 2,
)
}
.then(
graphicsLayer {
renderEffect = RenderEffect
.createRuntimeShaderEffect(shader, "content")
.asComposeRenderEffect()
}
)
}

class DropEffectState {

var dropEvent: DropEvent? by mutableStateOf(null)
private set

var center: FloatArray by mutableStateOf(floatArrayOf(0f, 0f))

fun drop(type: DropEvent.Type, centerX: Float, centerY: Float) {
move(centerX, centerY)
drop(type)
}

fun drop(type: DropEvent.Type) {
dropEvent = DropEvent(System.currentTimeMillis(), type)
}

fun move(centerX: Float, centerY: Float) {
center = floatArrayOf(centerX, centerY)
}

data class DropEvent(val time: Long, val type: Type) {
enum class Type(val animSpecs: List<AnimationSpec>, val postFactor: Float? = null) {
TOUCH(animSpecs = listOf(AnimationSpec(to = -1.5f, duration = 200))),
RELEASE_SOFT(animSpecs = listOf(AnimationSpec(to = 0f, duration = 100))),
RELEASE_HARD(animSpecs = listOf(AnimationSpec(to = 0f, duration = 50), AnimationSpec(to = 2f, duration = 1000, easing = CubicBezierEasing(0f, 0.5f, 0.75f, 1f))), postFactor = 0f),
}

data class AnimationSpec(val to: Float, val duration: Int, val easing: Easing = LinearEasing)
}
}

@Composable
fun rememberDropEffectState(): DropEffectState {
return remember { DropEffectState() }
}
50 changes: 50 additions & 0 deletions app/src/main/java/f/cking/software/utils/graphic/Shaders.kt
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,54 @@ object Shaders {
return min(color + colorModificator, white);
}
"""


@Language("AGSL")
val WATER_DROP = """
uniform shader content;
uniform float factor;
uniform float2 iResolution;
uniform float2 dropPosition;
const float PI = 3.14159265359;
const float3 eps = float3(0.01, 0.0, 0.0);
float genWave(float len)
{
float wave = exp(-pow((len - factor + 0.35) * 8.0, 2.0))-(exp(-pow((len - factor + 0.5) * 16.0, 2.0) / 2.0)) - exp(-pow((len - factor - 3.2), 2.0));
return wave;
}
float scene(float len)
{
return genWave(len);
}
float2 normal(float len)
{
float tg = (scene(len + eps.x) - scene(len)) / eps.x;
return normalize(float2(-tg, 1.0));
}
float4 main(float2 fragCoord)
{
if (factor == 0.0) {
//return content.eval(fragCoord);
}
float2 uv = fragCoord.xy / iResolution.xy;
float2 so = dropPosition.xy / iResolution.xy;
float2 pos2 = float2(uv - so); //wave origin
float2 pos2n = normalize(pos2);
float len = length(pos2);
float wave = scene(len);
float2 uvR = -pos2n * wave/(1.0 + 5.0 * len + 0.2);
float2 uvG = -pos2n * wave/(1.0 + 5.0 * len + 0.1);
float2 uvB = -pos2n * wave/(1.0 + 5.0 * len);
return float4(content.eval((uv + uvR) * iResolution.xy).r, content.eval((uv + uvG) * iResolution.xy).g, content.eval((uv + uvB) * iResolution.xy).b, content.eval(fragCoord).a);
}
"""
}

0 comments on commit 6778d64

Please sign in to comment.