diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f1402f..250735b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,13 +4,22 @@ on: push: tags: - 'v*' - + pull_request: + types: [labeled, synchronize] # 'synchronize' triggers on new commits jobs: build: + # Runs if it's a tag push OR if the PR currently has the 'build-apk' label applied + # for a same-repository PR where signing secrets and PR write permissions are available + if: > + github.event_name == 'push' || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository && + contains(github.event.pull_request.labels.*.name, 'build-apk')) runs-on: ubuntu-latest permissions: contents: write + pull-requests: write # Required to post the comment in the PR history steps: - name: Checkout code @@ -29,12 +38,10 @@ jobs: - name: Decode Keystore env: KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} - KEYSTORE_PROPERTIES: ${{ secrets.KEYSTORE_PROPERTIES }} run: | echo "$KEYSTORE_BASE64" | base64 --decode > release.keystore # Create keystore.properties - # We point to ../release.keystore because this property is read by the :app module echo "storeFile=../release.keystore" > keystore.properties echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> keystore.properties echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> keystore.properties @@ -43,13 +50,52 @@ jobs: - name: Build Release APK run: ./gradlew assembleRelease + - name: Determine APK Name + id: apk-name + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "filename=notate-pr-${{ github.event.pull_request.number }}.apk" >> $GITHUB_OUTPUT + else + echo "filename=notate-${{ github.ref_name }}.apk" >> $GITHUB_OUTPUT + fi + - name: Rename APK - run: mv app/build/outputs/apk/release/app-release.apk notate-${{ github.ref_name }}.apk + run: mv app/build/outputs/apk/release/app-release.apk ${{ steps.apk-name.outputs.filename }} + # --------------------------------------------------------- + # PATH A: TAG PUSH -> Create GitHub Release + # --------------------------------------------------------- - name: Create Release + if: github.event_name == 'push' uses: softprops/action-gh-release@v1 with: - files: notate-${{ github.ref_name }}.apk + files: ${{ steps.apk-name.outputs.filename }} draft: true prerelease: false generate_release_notes: true + + # --------------------------------------------------------- + # PATH B: PULL REQUEST -> Upload Artifact & Comment + # --------------------------------------------------------- + - name: Upload APK as Artifact + if: github.event_name == 'pull_request' + id: upload-artifact + uses: actions/upload-artifact@v4 + with: + name: PR-Release-APK-${{ github.event.pull_request.number }} + path: ${{ steps.apk-name.outputs.filename }} + retention-days: 7 + + - name: Comment on PR with Artifact Link + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + env: + ARTIFACT_URL: ${{ steps.upload-artifact.outputs.artifact-url }} + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `✅ **APK Build Successful!**\n\nAn admin triggered a release build for this PR. You can download the generated APK here: [Download APK](${process.env.ARTIFACT_URL})` + }) diff --git a/app/src/main/java/com/alexdremov/notate/ui/OnyxCanvasView.kt b/app/src/main/java/com/alexdremov/notate/ui/OnyxCanvasView.kt index 28b99c5..42343bf 100644 --- a/app/src/main/java/com/alexdremov/notate/ui/OnyxCanvasView.kt +++ b/app/src/main/java/com/alexdremov/notate/ui/OnyxCanvasView.kt @@ -8,6 +8,7 @@ import android.graphics.Paint import android.graphics.Path import android.graphics.Rect import android.graphics.RectF +import android.hardware.display.DisplayManager import android.os.Looper import android.util.AttributeSet import android.view.Choreographer @@ -42,6 +43,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import java.util.LinkedList +import kotlin.math.abs class OnyxCanvasView @JvmOverloads @@ -737,13 +739,62 @@ class OnyxCanvasView } } + private fun getPhysicalDisplaySize(): Pair? { + val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as? DisplayManager ?: return null + val targetDisplayId = display?.displayId + val targetDisplay = targetDisplayId?.let { displayManager.getDisplay(it) } + val fallbackDisplay = displayManager.displays.firstOrNull() + val activeDisplay = targetDisplay ?: fallbackDisplay ?: return null + val mode = activeDisplay.mode ?: return null + return mode.physicalWidth to mode.physicalHeight + } + + private fun scaleRectForEpd(logicalRect: Rect): Rect { + val metrics = context.resources.displayMetrics + val physicalSize = getPhysicalDisplaySize() ?: return logicalRect + val (physicalWidth, physicalHeight) = physicalSize + val normalMapping = + physicalWidth / metrics.widthPixels.toFloat() to + physicalHeight / metrics.heightPixels.toFloat() + val transposedMapping = + physicalHeight / metrics.widthPixels.toFloat() to + physicalWidth / metrics.heightPixels.toFloat() + // Pick the axis mapping where X/Y scales are closest. With correct width↔width and + // height↔height pairing, both axes should need nearly the same scale factor. + val (scaleX, scaleY) = + if (abs(normalMapping.first - normalMapping.second) <= + abs(transposedMapping.first - transposedMapping.second) + ) { + normalMapping + } else { + transposedMapping + } + // Scales at or below 1 mean no hardware-space expansion is needed. + if (scaleX <= 1f && scaleY <= 1f) return logicalRect + + // Return the hardware-mapped coordinates, rounding outward to avoid shrinking + // the limit/exclusion regions after scaling. + return Rect( + kotlin.math.floor(logicalRect.left * scaleX.toDouble()).toInt(), + kotlin.math.floor(logicalRect.top * scaleY.toDouble()).toInt(), + kotlin.math.ceil(logicalRect.right * scaleX.toDouble()).toInt(), + kotlin.math.ceil(logicalRect.bottom * scaleY.toDouble()).toInt(), + ) + } + fun setExclusionRects(rects: List) { exclusionRects.clear() exclusionRects.addAll(rects) + + val limit = Rect() + getGlobalVisibleRect(limit) + + // Scale the bounds to raw hardware pixels + val hardwareLimit = scaleRectForEpd(limit) + val hardwareExclusions = exclusionRects.map { scaleRectForEpd(it) } + touchHelper?.let { - val limit = Rect() - getLocalVisibleRect(limit) - it.setLimitRect(limit, exclusionRects) + it.setLimitRect(hardwareLimit, hardwareExclusions) } } @@ -864,10 +915,10 @@ class OnyxCanvasView } com.alexdremov.notate.util.OnyxSystemHelper .ignoreSystemSideButton(this) - val limit = Rect() - getLocalVisibleRect(limit) + + setExclusionRects(exclusionRects.toList()) + touchHelper?.apply { - setLimitRect(limit, exclusionRects) openRawDrawing() setRawDrawingEnabled(true) setRawDrawingRenderEnabled(true)