Skip to content

Commit 63acde0

Browse files
authored
Merge pull request #78 from what3words/staging
Staging
2 parents 7194627 + 75d8489 commit 63acde0

File tree

94 files changed

+4377
-2133
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+4377
-2133
lines changed

.circleci/config.yml

Lines changed: 0 additions & 113 deletions
This file was deleted.

.github/workflows/BuildTest.yml

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
name: BuildTest
2+
3+
on:
4+
push:
5+
branches:
6+
- 'task/**'
7+
- 'bug/**'
8+
- 'fix/**'
9+
- 'epic/**'
10+
pull_request:
11+
branches:
12+
- 'staging'
13+
14+
jobs:
15+
build_test:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
21+
- name: Setup Java 17
22+
uses: actions/setup-java@v4
23+
with:
24+
distribution: 'temurin'
25+
java-version: '17'
26+
27+
- name: Restore gradle.properties
28+
env:
29+
MAPBOX_DOWNLOADS_TOKEN: ${{ secrets.MAPBOX_DOWNLOADS_TOKEN }}
30+
MAPBOX_ACCESS_TOKEN: ${{ secrets.MAPBOX_ACCESS_TOKEN }}
31+
MAPS_API_KEY: ${{ secrets.MAPS_API_KEY }}
32+
PROD_API_KEY: ${{ secrets.PROD_API_KEY }}
33+
shell: bash
34+
run: |
35+
mkdir -p ~/.gradle/
36+
echo "GRADLE_USER_HOME=${HOME}/.gradle" >> $GITHUB_ENV
37+
echo "MAPBOX_DOWNLOADS_TOKEN=${MAPBOX_DOWNLOADS_TOKEN}">> ~/.gradle/gradle.properties
38+
echo "MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN}">> ~/.gradle/gradle.properties
39+
echo "MAPS_API_KEY=${MAPS_API_KEY}">> ~/.gradle/gradle.properties
40+
echo "PROD_API_KEY=${PROD_API_KEY}">> ~/.gradle/gradle.properties
41+
cat ~/.gradle/gradle.properties
42+
43+
- name: Set Up Gradle Wrapper
44+
run: ./gradlew wrapper
45+
46+
- name: Assemble wrappers-sample
47+
run: ./gradlew wrappers-sample:assembleRelease
48+
49+
- name: Assemble ocr-sample
50+
run: ./gradlew ocr-sample:assembleRelease
51+
52+
- name: Assemble autosuggest-sample
53+
run: ./gradlew autosuggest-sample:assembleRelease
54+
55+
- name: Assemble autosuggest-sample-voice
56+
run: ./gradlew autosuggest-sample-voice:assembleRelease
57+
58+
- name: Assemble multi-component-sample
59+
run: ./gradlew multi-component-sample:assembleRelease
60+
61+
- name: Assemble maps-googlemaps-sample
62+
run: ./gradlew maps-googlemaps-sample:assembleRelease
63+
64+
- name: Assemble mapbox-sample
65+
run: ./gradlew mapbox-sample:assembleRelease
66+
67+
- name: Upload wrappers-sample APK
68+
uses: actions/upload-artifact@v4
69+
with:
70+
name: wrappers-sample-release
71+
path: wrappers-sample/build/outputs/apk/release
72+
73+
- name: Upload ocr-sample APK
74+
uses: actions/upload-artifact@v4
75+
with:
76+
name: ocr-sample-release
77+
path: ocr-sample/build/outputs/apk/release
78+
79+
- name: Upload autosuggest-sample APK
80+
uses: actions/upload-artifact@v4
81+
with:
82+
name: autosuggest-sample-release
83+
path: autosuggest-sample/build/outputs/apk/release
84+
85+
- name: Upload autosuggest-sample-voice APK
86+
uses: actions/upload-artifact@v4
87+
with:
88+
name: autosuggest-sample-voice-release
89+
path: autosuggest-sample-voice/build/outputs/apk/release
90+
91+
- name: Upload multi-component-sample APK
92+
uses: actions/upload-artifact@v4
93+
with:
94+
name: multi-component-sample-release
95+
path: multi-component-sample/build/outputs/apk/release
96+
97+
- name: Upload maps-googlemaps-sample APK
98+
uses: actions/upload-artifact@v4
99+
with:
100+
name: maps-googlemaps-sample-release
101+
path: maps-googlemaps-sample/build/outputs/apk/release
102+
103+
- name: Upload mapbox-sample APK
104+
uses: actions/upload-artifact@v4
105+
with:
106+
name: mapbox-sample-release
107+
path: mapbox-sample/build/outputs/apk/release
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package com.what3words.ocr.components.extensions
2+
3+
import android.content.ContentResolver
4+
import android.graphics.Bitmap
5+
import android.graphics.BitmapFactory
6+
import android.graphics.ImageDecoder
7+
import android.net.Uri
8+
import android.os.Build
9+
import java.io.InputStream
10+
11+
class BitmapUtils {
12+
companion object {
13+
/**
14+
* Efficiently loads a bitmap from a Uri with automatic downsampling
15+
* to prevent OOM errors with large images
16+
*
17+
* @param contentResolver ContentResolver to access the Uri
18+
* @param uri Uri pointing to the image
19+
* @return Downsampled bitmap or null if loading failed
20+
*/
21+
fun loadDownsampledBitmapFromUri(contentResolver: ContentResolver, uri: Uri): Bitmap? {
22+
return try {
23+
// Get image dimensions first before loading full bitmap
24+
val options = BitmapFactory.Options().apply {
25+
inJustDecodeBounds = true
26+
}
27+
28+
// Read original dimensions
29+
val (originalWidth, originalHeight) = getImageDimensions(contentResolver, uri)
30+
31+
// Calculate scale based on original dimensions
32+
// Target maximum dimension - based on how large the original is
33+
val maxDimension = when {
34+
originalWidth > 3000 || originalHeight > 3000 -> 1280 // ~25% for very large images
35+
originalWidth > 1500 || originalHeight > 1500 -> 800 // ~50% for medium images
36+
else -> 600 // Keep smaller images at reasonable size
37+
}
38+
39+
// Calculate target dimensions maintaining aspect ratio
40+
val (targetWidth, targetHeight) = if (originalWidth > originalHeight) {
41+
// Landscape
42+
Pair(maxDimension, (originalHeight * maxDimension / originalWidth.toFloat()).toInt())
43+
} else {
44+
// Portrait
45+
Pair((originalWidth * maxDimension / originalHeight.toFloat()).toInt(), maxDimension)
46+
}
47+
48+
when {
49+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> {
50+
val source = ImageDecoder.createSource(contentResolver, uri)
51+
ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
52+
decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
53+
54+
// Set target size while maintaining aspect ratio
55+
decoder.setTargetSize(targetWidth, targetHeight)
56+
}
57+
}
58+
else -> {
59+
// For older Android versions
60+
@Suppress("DEPRECATION")
61+
val sampleSize = calculateSampleSize(originalWidth, originalHeight, targetWidth, targetHeight)
62+
63+
val loadOptions = BitmapFactory.Options().apply {
64+
inSampleSize = sampleSize
65+
inPreferredConfig = Bitmap.Config.RGB_565 // Use less memory
66+
}
67+
68+
@Suppress("DEPRECATION")
69+
val inputStream = contentResolver.openInputStream(uri)
70+
val result = BitmapFactory.decodeStream(inputStream, null, loadOptions)
71+
inputStream?.close()
72+
result
73+
}
74+
}
75+
} catch (e: Exception) {
76+
null
77+
}
78+
}
79+
80+
/**
81+
* Gets the dimensions of an image without loading the full bitmap into memory
82+
*
83+
* @param contentResolver ContentResolver to access the Uri
84+
* @param uri Uri pointing to the image
85+
* @return Pair of width and height
86+
*/
87+
private fun getImageDimensions(contentResolver: ContentResolver, uri: Uri): Pair<Int, Int> {
88+
val options = BitmapFactory.Options().apply {
89+
inJustDecodeBounds = true
90+
}
91+
92+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
93+
var inputStream: InputStream? = null
94+
try {
95+
inputStream = contentResolver.openInputStream(uri)
96+
BitmapFactory.decodeStream(inputStream, null, options)
97+
} finally {
98+
inputStream?.close()
99+
}
100+
} else {
101+
val source = ImageDecoder.createSource(contentResolver, uri)
102+
// For API 28+, we need to use ImageDecoder to get dimensions
103+
try {
104+
ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
105+
options.outWidth = info.size.width
106+
options.outHeight = info.size.height
107+
// Cancel the decode after getting dimensions
108+
decoder.setOnPartialImageListener { _: ImageDecoder.DecodeException -> true }
109+
}
110+
} catch (_: Exception) {
111+
// Expected, we canceled the decode
112+
}
113+
}
114+
115+
return Pair(options.outWidth, options.outHeight)
116+
}
117+
118+
/**
119+
* Calculates the appropriate sample size for downsampling based on target dimensions
120+
*
121+
* @param width Original width
122+
* @param height Original height
123+
* @param targetWidth Target width
124+
* @param targetHeight Target height
125+
* @return Sample size (power of 2)
126+
*/
127+
private fun calculateSampleSize(width: Int, height: Int, targetWidth: Int, targetHeight: Int): Int {
128+
var sampleSize = 1
129+
130+
if (width > targetWidth || height > targetHeight) {
131+
val halfWidth = width / 2
132+
val halfHeight = height / 2
133+
134+
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
135+
// height and width larger than the requested height and width
136+
while ((halfWidth / sampleSize) >= targetWidth && (halfHeight / sampleSize) >= targetHeight) {
137+
sampleSize *= 2
138+
}
139+
}
140+
141+
return sampleSize
142+
}
143+
}
144+
}

autosuggest-sample-voice/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
22
apply plugin: 'kotlin-android'
33

44
android {
5-
compileSdk 34
5+
compileSdk 35
66

77
buildFeatures {
88
viewBinding true

0 commit comments

Comments
 (0)