From c0bab85d63d66180400ae88d3a5bf5eda5e97ee5 Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Mon, 11 May 2026 15:29:59 +0200 Subject: [PATCH 1/2] Revert "Revert orientation tracking on the camera image" This reverts commit 4f8661f648ca80b5602eda6129d7ec72369ab425. --- .../no/naiv/tiltshift/camera/CameraManager.kt | 36 +++++ .../tiltshift/effect/TiltShiftRenderer.kt | 131 +++++++++++------- .../naiv/tiltshift/effect/TiltShiftShader.kt | 30 +++- .../java/no/naiv/tiltshift/ui/CameraScreen.kt | 8 ++ .../java/no/naiv/tiltshift/ui/ControlPanel.kt | 5 + app/src/main/res/raw/tiltshift_vertex.glsl | 16 ++- version.properties | 4 +- 7 files changed, 173 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/no/naiv/tiltshift/camera/CameraManager.kt b/app/src/main/java/no/naiv/tiltshift/camera/CameraManager.kt index 51d42ed..7f7bed5 100644 --- a/app/src/main/java/no/naiv/tiltshift/camera/CameraManager.kt +++ b/app/src/main/java/no/naiv/tiltshift/camera/CameraManager.kt @@ -2,8 +2,10 @@ package no.naiv.tiltshift.camera import android.content.Context import android.graphics.SurfaceTexture +import android.hardware.display.DisplayManager import android.util.Log import android.util.Size +import android.view.Display import android.view.Surface import androidx.camera.core.Camera import androidx.camera.core.CameraSelector @@ -70,6 +72,14 @@ class CameraManager(private val context: Context) { /** Weak reference to avoid preventing Activity GC across config changes. */ private var lifecycleOwnerRef: WeakReference? = null + /** + * Target rotation passed to CameraX use cases. Drives the SurfaceTexture + * transform matrix and the rotation metadata on captured images. + * Initialized to the display rotation when the camera binds; updated by + * [setTargetRotation] when the device orientation changes. + */ + private var targetRotation: Int = Surface.ROTATION_0 + /** * Starts the camera with the given lifecycle owner. * The surfaceTextureProvider should return the SurfaceTexture from the GL renderer. @@ -80,6 +90,13 @@ class CameraManager(private val context: Context) { ) { this.surfaceTextureProvider = surfaceTextureProvider this.lifecycleOwnerRef = WeakReference(lifecycleOwner) + // Capture initial display rotation so the very first frame is oriented correctly, + // before the OrientationEventListener has had a chance to fire. + // Note: Context.getDisplay() throws on Application contexts; DisplayManager works + // for any context type and returns the default display. + targetRotation = (context.getSystemService(Context.DISPLAY_SERVICE) as? DisplayManager) + ?.getDisplay(Display.DEFAULT_DISPLAY)?.rotation + ?: Surface.ROTATION_0 val cameraProviderFuture = ProcessCameraProvider.getInstance(context) cameraProviderFuture.addListener({ @@ -110,11 +127,13 @@ class CameraManager(private val context: Context) { preview = Preview.Builder() .setResolutionSelector(resolutionSelector) + .setTargetRotation(targetRotation) .build() // Image capture use case val captureBuilder = ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) + .setTargetRotation(targetRotation) imageCapture = captureBuilder.build() @@ -173,6 +192,23 @@ class CameraManager(private val context: Context) { } } + /** + * Updates the target rotation for Preview and ImageCapture use cases. + * + * Rebinds the use cases so CameraX issues a fresh SurfaceRequest with a + * resolution matching the new rotation and a corresponding texture transform + * matrix. Calling `preview.targetRotation = rotation` alone is insufficient + * for a custom SurfaceProvider — the new rotation only takes effect on + * subsequently bound streams, leaving the live SurfaceTexture matrix and + * buffer size stale (which made the preview appear locked to the original + * portrait orientation when the device was rotated to landscape). + */ + fun setTargetRotation(rotation: Int) { + if (targetRotation == rotation) return + targetRotation = rotation + lifecycleOwnerRef?.get()?.let { bindCameraUseCases(it) } + } + /** * Sets the zoom ratio. Updates UI state immediately so that rapid pinch-to-zoom * gestures accumulate correctly (each frame uses the latest ratio as its base). diff --git a/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftRenderer.kt b/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftRenderer.kt index d170d58..e8ff4bd 100644 --- a/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftRenderer.kt +++ b/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftRenderer.kt @@ -5,7 +5,9 @@ import android.graphics.SurfaceTexture import android.opengl.GLES11Ext import android.opengl.GLES20 import android.opengl.GLSurfaceView +import android.opengl.Matrix import android.util.Log +import android.view.Surface import java.nio.ByteBuffer import java.nio.ByteOrder import java.nio.FloatBuffer @@ -38,9 +40,8 @@ class TiltShiftRenderer( private var surfaceTexture: SurfaceTexture? = null private var cameraTextureId: Int = 0 - // Camera quad: crop-to-fill vertices + rotated texcoords (pass 1 only) + // Camera quad: crop-to-fill vertices, standard texcoords (rotation comes from texMatrix) private lateinit var cameraVertexBuffer: FloatBuffer - private lateinit var cameraTexCoordBuffer: FloatBuffer // Fullscreen quad for blur passes (no crop, standard texcoords) private lateinit var fullscreenVertexBuffer: FloatBuffer @@ -54,6 +55,11 @@ class TiltShiftRenderer( private var fboTexA: Int = 0 private var fboTexB: Int = 0 + // SurfaceTexture transform matrix, refreshed each frame on the GL thread. + private val texMatrix = FloatArray(16) + // Sensor-to-buffer matrix from SurfaceTexture before display-rotation correction. + private val sensorMatrix = FloatArray(16) + // Current effect parameters (updated from UI thread) @Volatile var blurParameters: BlurParameters = BlurParameters.DEFAULT @@ -66,33 +72,14 @@ class TiltShiftRenderer( private var cameraWidth: Int = 0 @Volatile private var cameraHeight: Int = 0 + + /** Display rotation as a Surface.ROTATION_* constant; affects effective aspect. */ + @Volatile + private var displayRotation: Int = Surface.ROTATION_0 + @Volatile private var vertexBufferDirty: Boolean = false - // Texture coordinates rotated 90° for portrait mode (back camera) - // (Camera sensors are landscape-oriented, we rotate to portrait) - private val texCoordsBack = floatArrayOf( - 1f, 1f, // Bottom left of screen -> bottom right of texture - 1f, 0f, // Bottom right of screen -> top right of texture - 0f, 1f, // Top left of screen -> bottom left of texture - 0f, 0f // Top right of screen -> top left of texture - ) - - // Texture coordinates for front camera (mirrored + rotated) - // Front camera needs horizontal mirror for natural selfie view - private val texCoordsFront = floatArrayOf( - 0f, 1f, // Bottom left of screen - 0f, 0f, // Bottom right of screen - 1f, 1f, // Top left of screen - 1f, 0f // Top right of screen - ) - - @Volatile - private var currentTexCoords = texCoordsBack - - @Volatile - private var updateTexCoordBuffer = false - override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) { GLES20.glClearColor(0f, 0f, 0f, 1f) @@ -104,12 +91,8 @@ class TiltShiftRenderer( cameraVertexBuffer.put(floatArrayOf(-1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f)) cameraVertexBuffer.position(0) - // Camera texcoord buffer (rotated for portrait) - cameraTexCoordBuffer = allocateFloatBuffer(8) - cameraTexCoordBuffer.put(currentTexCoords) - cameraTexCoordBuffer.position(0) - - // Fullscreen quad for blur passes (standard coords) + // Fullscreen quad for blur passes (standard coords). The same buffer is reused + // for the camera passthrough texcoords — rotation is applied via uTexMatrix. fullscreenVertexBuffer = allocateFloatBuffer(8) fullscreenVertexBuffer.put(floatArrayOf(-1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f)) fullscreenVertexBuffer.position(0) @@ -145,20 +128,19 @@ class TiltShiftRenderer( } override fun onDrawFrame(gl: GL10?) { - surfaceTexture?.updateTexImage() + val st = surfaceTexture + st?.updateTexImage() + // SurfaceTexture's transform matrix only handles the sensor-to-buffer + // orientation; for a custom SurfaceProvider it does NOT vary with + // Preview.targetRotation. Apply the display-rotation correction here. + st?.getTransformMatrix(sensorMatrix) + composeTexMatrix() if (vertexBufferDirty) { recomputeVertices() vertexBufferDirty = false } - if (updateTexCoordBuffer) { - cameraTexCoordBuffer.clear() - cameraTexCoordBuffer.put(currentTexCoords) - cameraTexCoordBuffer.position(0) - updateTexCoordBuffer = false - } - val params = blurParameters // --- Pass 1: Camera → FBO-A (passthrough with crop-to-fill) --- @@ -169,10 +151,10 @@ class TiltShiftRenderer( ) GLES20.glViewport(0, 0, surfaceWidth, surfaceHeight) GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) - shader.usePassthrough(cameraTextureId) + shader.usePassthrough(cameraTextureId, texMatrix, isFrontCamera) drawQuad( shader.passthroughPositionLoc, shader.passthroughTexCoordLoc, - cameraVertexBuffer, cameraTexCoordBuffer + cameraVertexBuffer, fullscreenTexCoordBuffer ) // --- Pass 2: FBO-A → FBO-B (horizontal blur) --- @@ -203,11 +185,7 @@ class TiltShiftRenderer( } fun setFrontCamera(front: Boolean) { - if (isFrontCamera != front) { - isFrontCamera = front - currentTexCoords = if (front) texCoordsFront else texCoordsBack - updateTexCoordBuffer = true - } + isFrontCamera = front } fun setCameraResolution(width: Int, height: Int) { @@ -218,6 +196,49 @@ class TiltShiftRenderer( } } + /** Updates the display rotation so crop-to-fill picks the right effective aspect. */ + fun setDisplayRotation(rotation: Int) { + if (displayRotation != rotation) { + displayRotation = rotation + vertexBufferDirty = true + } + } + + /** + * Combines the sensor-to-buffer matrix with a rotation that compensates for + * the activity's current rotation. The activity rotates with the device + * (screenOrientation="fullSensor"), so the GL clip-space "up" direction + * tracks the device rather than the world. To keep world-up at screen-up + * regardless of orientation, rotate the texcoord sampling pattern by the + * inverse of the activity rotation. Without this correction, the two + * landscape orientations would render the same matrix and one would appear + * upside-down on a real device. + */ + private fun composeTexMatrix() { + // Inverse of the activity rotation: rotating the sampling pattern by + // -activityAngle puts the world-aligned point originally at screen P + // at the same screen P after the activity has rotated. + val angle = when (displayRotation) { + Surface.ROTATION_90 -> -90f + Surface.ROTATION_180 -> 180f + Surface.ROTATION_270 -> 90f + else -> 0f + } + if (angle == 0f) { + System.arraycopy(sensorMatrix, 0, texMatrix, 0, 16) + return + } + // Build rotation around the (0.5, 0.5) texcoord center. + Matrix.setIdentityM(texMatrix, 0) + Matrix.translateM(texMatrix, 0, 0.5f, 0.5f, 0f) + Matrix.rotateM(texMatrix, 0, angle, 0f, 0f, 1f) + Matrix.translateM(texMatrix, 0, -0.5f, -0.5f, 0f) + // texMatrix = sensorMatrix * rotation (rotation is applied to the + // texcoord first, then the sensor-to-buffer transform). + val rot = texMatrix.copyOf() + Matrix.multiplyMM(texMatrix, 0, sensorMatrix, 0, rot, 0) + } + fun release() { shader.release() surfaceTexture?.release() @@ -254,16 +275,24 @@ class TiltShiftRenderer( /** * Recomputes camera vertex positions to achieve crop-to-fill. * - * The camera sensor is landscape; after the 90° rotation applied via texture coordinates, - * the effective portrait dimensions are (cameraHeight × cameraWidth). We scale the vertex - * quad so the camera frame fills the surface without stretching — the GPU clips the overflow. + * The camera buffer is the sensor's native landscape resolution. The texMatrix + * rotates it to match the display, so the *effective* displayed dimensions + * depend on the display rotation: in portrait the buffer is rotated 90° + * (effective width = cameraHeight), in landscape it is unrotated. + * We scale the vertex quad so the frame fills the surface without stretching — + * the GPU clips the overflow. */ private fun recomputeVertices() { var scaleX = 1f var scaleY = 1f if (cameraWidth > 0 && cameraHeight > 0 && surfaceWidth > 0 && surfaceHeight > 0) { - val cameraRatio = cameraHeight.toFloat() / cameraWidth + val isPortrait = displayRotation == Surface.ROTATION_0 || + displayRotation == Surface.ROTATION_180 + val effectiveW = if (isPortrait) cameraHeight else cameraWidth + val effectiveH = if (isPortrait) cameraWidth else cameraHeight + + val cameraRatio = effectiveW.toFloat() / effectiveH val screenRatio = surfaceWidth.toFloat() / surfaceHeight if (cameraRatio > screenRatio) { diff --git a/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftShader.kt b/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftShader.kt index cdafd8f..ebbfb18 100644 --- a/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftShader.kt +++ b/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftShader.kt @@ -20,6 +20,15 @@ import kotlin.math.sin */ class TiltShiftShader(private val context: Context) { + private companion object { + val IDENTITY_MATRIX = floatArrayOf( + 1f, 0f, 0f, 0f, + 0f, 1f, 0f, 0f, + 0f, 0f, 1f, 0f, + 0f, 0f, 0f, 1f + ) + } + // --- Passthrough program (camera → FBO) --- private var passthroughProgramId: Int = 0 @@ -29,6 +38,8 @@ class TiltShiftShader(private val context: Context) { var passthroughTexCoordLoc: Int = 0 private set private var passthroughTextureLoc: Int = 0 + private var passthroughTexMatrixLoc: Int = 0 + private var passthroughMirrorLoc: Int = 0 // --- Blur program (FBO → FBO/screen) --- @@ -39,6 +50,8 @@ class TiltShiftShader(private val context: Context) { var blurTexCoordLoc: Int = 0 private set private var blurTextureLoc: Int = 0 + private var blurTexMatrixLoc: Int = 0 + private var blurMirrorLoc: Int = 0 private var blurModeLoc: Int = 0 private var blurPositionXLoc: Int = 0 private var blurPositionYLoc: Int = 0 @@ -68,6 +81,8 @@ class TiltShiftShader(private val context: Context) { passthroughPositionLoc = GLES20.glGetAttribLocation(passthroughProgramId, "aPosition") passthroughTexCoordLoc = GLES20.glGetAttribLocation(passthroughProgramId, "aTexCoord") passthroughTextureLoc = GLES20.glGetUniformLocation(passthroughProgramId, "uTexture") + passthroughTexMatrixLoc = GLES20.glGetUniformLocation(passthroughProgramId, "uTexMatrix") + passthroughMirrorLoc = GLES20.glGetUniformLocation(passthroughProgramId, "uMirrorX") // Blur program val blurFragSource = loadShaderSource(R.raw.tiltshift_fragment) @@ -78,6 +93,8 @@ class TiltShiftShader(private val context: Context) { blurPositionLoc = GLES20.glGetAttribLocation(blurProgramId, "aPosition") blurTexCoordLoc = GLES20.glGetAttribLocation(blurProgramId, "aTexCoord") blurTextureLoc = GLES20.glGetUniformLocation(blurProgramId, "uTexture") + blurTexMatrixLoc = GLES20.glGetUniformLocation(blurProgramId, "uTexMatrix") + blurMirrorLoc = GLES20.glGetUniformLocation(blurProgramId, "uMirrorX") blurModeLoc = GLES20.glGetUniformLocation(blurProgramId, "uMode") blurPositionXLoc = GLES20.glGetUniformLocation(blurProgramId, "uPositionX") blurPositionYLoc = GLES20.glGetUniformLocation(blurProgramId, "uPositionY") @@ -96,12 +113,19 @@ class TiltShiftShader(private val context: Context) { /** * Activates the passthrough program and binds the camera texture. + * + * @param cameraTextureId The OES texture receiving camera frames. + * @param texMatrix 4x4 transform from SurfaceTexture.getTransformMatrix() — + * encodes sensor-to-display rotation and Y-flip. + * @param mirrorX true to horizontally mirror (front camera selfie view). */ - fun usePassthrough(cameraTextureId: Int) { + fun usePassthrough(cameraTextureId: Int, texMatrix: FloatArray, mirrorX: Boolean) { GLES20.glUseProgram(passthroughProgramId) GLES20.glActiveTexture(GLES20.GL_TEXTURE0) GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, cameraTextureId) GLES20.glUniform1i(passthroughTextureLoc, 0) + GLES20.glUniformMatrix4fv(passthroughTexMatrixLoc, 1, false, texMatrix, 0) + GLES20.glUniform1f(passthroughMirrorLoc, if (mirrorX) 1f else 0f) } /** @@ -128,6 +152,10 @@ class TiltShiftShader(private val context: Context) { GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fboTextureId) GLES20.glUniform1i(blurTextureLoc, 0) + // FBO content is already in display orientation — pass identity matrix and no mirror. + GLES20.glUniformMatrix4fv(blurTexMatrixLoc, 1, false, IDENTITY_MATRIX, 0) + GLES20.glUniform1f(blurMirrorLoc, 0f) + GLES20.glUniform1i(blurModeLoc, if (params.mode == BlurMode.RADIAL) 1 else 0) GLES20.glUniform1f(blurPositionXLoc, params.positionX) GLES20.glUniform1f(blurPositionYLoc, params.positionY) diff --git a/app/src/main/java/no/naiv/tiltshift/ui/CameraScreen.kt b/app/src/main/java/no/naiv/tiltshift/ui/CameraScreen.kt index bbb706d..167b376 100644 --- a/app/src/main/java/no/naiv/tiltshift/ui/CameraScreen.kt +++ b/app/src/main/java/no/naiv/tiltshift/ui/CameraScreen.kt @@ -112,6 +112,7 @@ fun CameraScreen( val isFrontCamera by viewModel.cameraManager.isFrontCamera.collectAsState() val previewResolution by viewModel.cameraManager.previewResolution.collectAsState() val cameraError by viewModel.cameraManager.error.collectAsState() + val currentRotation by viewModel.currentRotation.collectAsState() // Gallery picker val galleryLauncher = rememberLauncherForActivityResult( @@ -164,6 +165,13 @@ fun CameraScreen( } } + // Forward device rotation to renderer (aspect math) and CameraX (target rotation) + LaunchedEffect(currentRotation, renderer) { + renderer?.setDisplayRotation(currentRotation) + viewModel.cameraManager.setTargetRotation(currentRotation) + glSurfaceView?.requestRender() + } + // Start camera when surface texture is available LaunchedEffect(surfaceTexture) { surfaceTexture?.let { diff --git a/app/src/main/java/no/naiv/tiltshift/ui/ControlPanel.kt b/app/src/main/java/no/naiv/tiltshift/ui/ControlPanel.kt index ad94ad0..dbc5112 100644 --- a/app/src/main/java/no/naiv/tiltshift/ui/ControlPanel.kt +++ b/app/src/main/java/no/naiv/tiltshift/ui/ControlPanel.kt @@ -12,7 +12,10 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.RestartAlt import androidx.compose.material3.Icon @@ -110,8 +113,10 @@ fun ControlPanel( Column( modifier = modifier .width(200.dp) + .wrapContentHeight() .clip(RoundedCornerShape(16.dp)) .background(AppColors.OverlayDarker) + .verticalScroll(rememberScrollState()) .padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { diff --git a/app/src/main/res/raw/tiltshift_vertex.glsl b/app/src/main/res/raw/tiltshift_vertex.glsl index e009424..ca79319 100644 --- a/app/src/main/res/raw/tiltshift_vertex.glsl +++ b/app/src/main/res/raw/tiltshift_vertex.glsl @@ -1,12 +1,22 @@ -// Vertex shader for tilt-shift effect -// Passes through position and calculates texture coordinates +// Vertex shader for tilt-shift effect. +// +// uTexMatrix: applied to texcoords. For the passthrough pass it carries the +// SurfaceTexture transform (sensor → display rotation, plus Y-flip). For the +// blur passes it is identity. +// uMirrorX: 1.0 to horizontally mirror texcoords (front-camera selfie view), +// 0.0 otherwise. Applied AFTER uTexMatrix. attribute vec4 aPosition; attribute vec2 aTexCoord; +uniform mat4 uTexMatrix; +uniform float uMirrorX; + varying vec2 vTexCoord; void main() { gl_Position = aPosition; - vTexCoord = aTexCoord; + vec2 tc = (uTexMatrix * vec4(aTexCoord, 0.0, 1.0)).xy; + if (uMirrorX > 0.5) tc.x = 1.0 - tc.x; + vTexCoord = tc; } diff --git a/version.properties b/version.properties index ba10bb5..34449a3 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ versionMajor=1 versionMinor=1 -versionPatch=10 -versionCode=12 +versionPatch=9 +versionCode=11 From 1cd2b0a57c7ad2afa3a555d8dfe549268c1896e8 Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Mon, 11 May 2026 15:46:31 +0200 Subject: [PATCH 2/2] Flip rotation-correction angles for both landscape orientations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bring back the v1.1.9 approach (apply an inverse rotation to the texcoord sampling pattern so the camera image stays world-aligned through device rotation), but with the right signs this time. The previous angles were 180° off for both landscape orientations and showed the camera content upside-down on a real device. Verified each Display.rotation against the emulator's virtual scene (sky-yellow → road-brown → buildings-dark): the sky/yellow band now sits at the top of the screen in all four orientations. Bump to 1.1.11. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../main/java/no/naiv/tiltshift/effect/TiltShiftRenderer.kt | 4 ++-- version.properties | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftRenderer.kt b/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftRenderer.kt index e8ff4bd..f7f247e 100644 --- a/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftRenderer.kt +++ b/app/src/main/java/no/naiv/tiltshift/effect/TiltShiftRenderer.kt @@ -219,9 +219,9 @@ class TiltShiftRenderer( // -activityAngle puts the world-aligned point originally at screen P // at the same screen P after the activity has rotated. val angle = when (displayRotation) { - Surface.ROTATION_90 -> -90f + Surface.ROTATION_90 -> 90f Surface.ROTATION_180 -> 180f - Surface.ROTATION_270 -> 90f + Surface.ROTATION_270 -> -90f else -> 0f } if (angle == 0f) { diff --git a/version.properties b/version.properties index 34449a3..6ca2659 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ versionMajor=1 versionMinor=1 -versionPatch=9 -versionCode=11 +versionPatch=11 +versionCode=13