Skip to content

Commit

Permalink
Merge branch 'main' of github.com:bumble-tech/puzzyx
Browse files Browse the repository at this point in the history
  • Loading branch information
mmartosdev committed Sep 23, 2023
2 parents 83e1f7d + 8ec7949 commit 05a4ecb
Show file tree
Hide file tree
Showing 193 changed files with 591 additions and 44 deletions.
2 changes: 1 addition & 1 deletion androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {

android {
namespace = "com.bumble.appyx.puzzyx.android"
compileSdk = 33
compileSdk = libs.versions.androidCompileSdk.get().toInt()
defaultConfig {
applicationId = "com.bumble.appyx.puzzyx.android"
minSdk = 30
Expand Down
8 changes: 5 additions & 3 deletions androidApp/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="false"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="DataExtractionRules">
<activity
android:name=".MainActivity"
android:name="com.bumble.puzzyx.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.bumble.appyx.puzzyx.android
package com.bumble.puzzyx

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
Expand Down
Binary file added bumble_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
[versions]
accompanist = "0.28.0"
agp = "8.1.1"
androidCompileSdk = "33"
androidCompileSdk = "34"
androidMinSdk = "21"
androidTargetSdk = "32"
androidx-lifecycle = "2.6.1"
androidx-navigation-compose = "2.5.1"
appyx = "2.0.0-alpha06"
coil = "2.2.1"
composePlugin = "1.4.0"
composePlugin = "1.4.3"
composeBom = "2023.05.01"
composeCompiler = "1.4.4"
image-loader-version = "1.6.4"
coroutines = "1.7.1"
dependencyAnalysis = "1.13.1"
detekt = "1.21.0"
Expand All @@ -21,8 +22,10 @@ jvmTarget = "17"
kotlin = "1.8.10"
ksp = "1.8.0-1.0.8"
mvicore = "1.2.6"
resources = "0.22.3"
ribs = "0.39.0"
serialization-json = "1.5.0"
ktor = "2.3.4"
uuid = "9.0.0"

[libraries]
Expand Down Expand Up @@ -59,6 +62,7 @@ compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
google-accompanist-systemui = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
google-accompanist-flow = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" }
google-material = "com.google.android.material:material:1.4.0"
compose-image-loader = { module = "io.github.qdsfdhvh:image-loader", version.ref = "image-loader-version"}
junit = "junit:junit:4.13.2"
kotlin-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
Expand Down
49 changes: 49 additions & 0 deletions puzzle_slicer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# This is a script to slice an image into a specified number of pieces
# and save them to a specified directory.

import os
import argparse
import numpy as np
from PIL import Image

def main():
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--image', type=str, required=True, help='Path to image to slice')
parser.add_argument('-o', '--output', type=str, help='Path to output directory')
parser.add_argument('-c', '--columns', type=int, required=True, help='Number of columns to slice image into')
parser.add_argument('-r', '--rows', type=int, required=True, help='Number of rows to slice image into')
args = parser.parse_args()

# If output directory is not provided, create one named after the image in the working directory
if not args.output:
image_name = os.path.splitext(os.path.basename(args.image))[0]
args.output = os.path.join(os.getcwd(), image_name)

# Create directory if it does not exist
if not os.path.exists(args.output):
os.makedirs(args.output)

# Load image
img = Image.open(args.image)
img = np.array(img)

# Get image dimensions
height, width, channels = img.shape

# Get slice dimensions
slice_height = height // args.rows
slice_width = width // args.columns

# Slice image
for y in range(args.rows):
for x in range(args.columns):
start_height = y * slice_height
end_height = start_height + slice_height
start_width = x * slice_width
end_width = start_width + slice_width
slice = img[start_height:end_height, start_width:end_width, :]
slice = Image.fromarray(slice)
slice.save(os.path.join(args.output, 'slice_{}_{}.png'.format(y, x)))

if __name__ == '__main__':
main()
6 changes: 5 additions & 1 deletion shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ kotlin {
implementation(kotlin("test"))
implementation(libs.appyx.navigation)
api(libs.appyx.components.backstack)
api(libs.compose.image.loader)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources)
}
}
val commonTest by getting {
Expand All @@ -40,7 +43,8 @@ kotlin {

android {
namespace = "com.bumble.appyx.puzzyx"
compileSdk = 33
sourceSets["main"].resources.srcDirs("src/commonMain/resources")
compileSdk = libs.versions.androidCompileSdk.get().toInt()
defaultConfig {
minSdk = 30
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.bumble.puzzyx.imageloader

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap

actual fun ByteArray.toImageBitmap(): ImageBitmap = toAndroidBitmap().asImageBitmap()

fun ByteArray.toAndroidBitmap(): Bitmap = BitmapFactory.decodeByteArray(this, 0, size)
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.bumble.puzzyx.component.backstackclipper

import androidx.compose.animation.core.SpringSpec
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Shape
import com.bumble.appyx.components.backstack.BackStackModel
import com.bumble.appyx.interactions.core.ui.context.UiContext
import com.bumble.appyx.interactions.core.ui.helper.DefaultAnimationSpec
import com.bumble.appyx.interactions.core.ui.property.impl.ZIndex
import com.bumble.appyx.interactions.core.ui.state.MatchedTargetUiState
import com.bumble.appyx.transitionmodel.BaseMotionController


/**
* With Appyx, we usually map model states (like a back stack element's state) to visual end
* states.
*
* To achieve the canvas clipping effect, instead of usual Alpha, Scale, Rotation etc. properties
* we'll animate the progress value related to a shape, and apply it as a clip mask on the outgoing
* element.
*
* @param shape Should return a Shape given a progress value in the range of 0..1f. The shape will
* be applied as a clip mask on the outgoing back stack element.
*/
class BackStackClipper<InteractionTarget : Any>(
uiContext: UiContext,
private val shape: @Composable (progress: Float) -> Shape,
defaultAnimationSpec: SpringSpec<Float> = DefaultAnimationSpec
) : BaseMotionController<InteractionTarget, BackStackModel.State<InteractionTarget>, MutableUiState, TargetUiState>(
uiContext = uiContext,
defaultAnimationSpec = defaultAnimationSpec,
) {

private val incoming = TargetUiState(
clipShapeProgress = ClipShapeProgress.Target(0f),
zIndex = ZIndex.Target(0f),
)

/**
* The Shape is animated towards 100% progress.
* zIndex ensures the outgoing element stays on top. As the clipping is applied to it,
* any elements behind it should start showing through.
*/
private val outgoing = TargetUiState(
clipShapeProgress = ClipShapeProgress.Target(1f),
zIndex = ZIndex.Target(1f),
)

override fun BackStackModel.State<InteractionTarget>.toUiTargets():
List<MatchedTargetUiState<InteractionTarget, TargetUiState>> =
(created + listOf(active)).map {
MatchedTargetUiState(it, incoming)
} + (stashed + destroyed).map {
MatchedTargetUiState(it, outgoing)
}

override fun mutableUiStateFor(uiContext: UiContext, targetUiState: TargetUiState): MutableUiState =
targetUiState.toMutableUiState(uiContext, shape)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.bumble.puzzyx.component.backstackclipper

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.Easing
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import com.bumble.appyx.interactions.core.ui.math.lerpFloat
import com.bumble.appyx.interactions.core.ui.property.Interpolatable
import com.bumble.appyx.interactions.core.ui.property.MotionProperty
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

/**
* With Appyx, we usually animate actual UI-related properties like Alpha, Rotation, etc.
*
* This class wraps an animatable Float that should represent an animation progress value
* in the 0..1f range. Using this animated value, a [Shape] is fetched as a function of progress
* and applied as clip mask with Modifier.clip(shape).
*/
class ClipShapeProgress(
coroutineScope: CoroutineScope,
target: Target,
displacement: StateFlow<Float> = MutableStateFlow(0f),
private val shape: @Composable (progress: Float) -> Shape = { RectangleShape },
) : MotionProperty<Float, AnimationVector1D>(
coroutineScope = coroutineScope,
animatable = Animatable(target.value),
displacement = displacement
), Interpolatable<ClipShapeProgress.Target> {

class Target(
val value: Float,
val easing: Easing? = null,
) : MotionProperty.Target

override fun calculateRenderValue(base: Float, displacement: Float): Float =
base - displacement

override val modifier: Modifier
get() = Modifier.composed {
val progress = renderValueFlow.collectAsState().value
if (progress == 0f) this
else this.clip(shape.invoke(progress))
}


override suspend fun lerpTo(start: Target, end: Target, fraction: Float) {
snapTo(
lerpFloat(
start = start.value,
end = end.value,
progress = easingTransform(end.easing, fraction)
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.bumble.puzzyx.component.backstackclipper

import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Shape
import com.bumble.appyx.interactions.core.ui.context.UiContext
import com.bumble.appyx.interactions.core.ui.property.impl.ZIndex
import com.bumble.appyx.interactions.core.ui.state.MutableUiStateSpecs

@Suppress("unused")
@MutableUiStateSpecs
class TargetUiState(
val clipShapeProgress: ClipShapeProgress.Target,
val zIndex: ZIndex.Target,
) {
fun toMutableUiState(uiContext: UiContext, shape: @Composable (progress: Float) -> Shape): MutableUiState =
MutableUiState(
uiContext = uiContext,
clipShapeProgress = ClipShapeProgress(
coroutineScope = uiContext.coroutineScope,
target = clipShapeProgress,
shape = shape
),
zIndex = ZIndex(
coroutineScope = uiContext.coroutineScope,
target = zIndex
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.bumble.puzzyx.component.gridpuzzle

import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.SpringSpec
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.times
import com.bumble.appyx.interactions.core.ui.context.UiContext
Expand Down Expand Up @@ -55,15 +56,24 @@ class GridPuzzleVisualisation(
position = Target(
alignment = alignment(i, j),
offset = DpOffset(
x = (i - (gridCols - 1) / 2f) * Random.nextInt(3,9) * 0.5f * transitionBounds.widthDp,
y = (j - (gridRows - 1) / 2f) * Random.nextInt(3,9) * 0.5f * transitionBounds.heightDp,
x = offset(i, gridCols, transitionBounds.widthDp),
y = offset(j, gridRows, transitionBounds.heightDp),
),
),
rotationZ = RotationZ.Target(
(if (Random.nextBoolean()) -1 else 1) * Random.nextInt(1, 4) * 360f
)
)

private fun offset(index: Int, maxIndex: Int, step: Dp): Dp {
var multiplier = (index - (maxIndex - 1) / 2f)
// if maxIndex is odd the middle one will always have 0 offsetX without this check
if (multiplier == 0f) {
multiplier = 1f
}
return multiplier * Random.nextInt(3, 9) * 0.5f * step
}

private fun State.assembled(i: Int, j: Int, idx: Int) = TargetUiState(
position = Target(
alignment = alignment(i, j),
Expand Down Expand Up @@ -93,7 +103,7 @@ class GridPuzzleVisualisation(
rotationZ = RotationZ.Target(
(if (Random.nextBoolean()) -1 else 1) * Random.nextInt(1, 3) * 360f
),
angularPosition = AngularPosition.Target(
angularPosition = AngularPosition.Target(
AngularPosition.Value(
// This should be the same as the prepared angle in the assembled state
// as it's not supposed to be animated
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.bumble.puzzyx.composable

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.bumble.puzzyx.ui.appyx_dark
import com.bumble.puzzyx.ui.appyx_yellow1

@Composable
fun CallToActionScreen(modifier: Modifier) {
Box(
modifier = modifier
.fillMaxSize()
.background(appyx_dark)
) {
Text(
text = "Join the challenge",
color = appyx_yellow1,
modifier = Modifier.align(Alignment.Center)
)
}
}
Loading

0 comments on commit 05a4ecb

Please sign in to comment.