also looking at this
fix(native): interaction, audio, document and checkpoint gaps stranded after #401
#415
19 files
+987
-50
| @@ -201,6 +201,10 @@ describe('Craft Android builder', () => { | ||
| 201 | 201 | expect(refreshableFlatList).toContain('isRefreshing = false') |
| 202 | 202 | expect(flatList).toContain('fun setKeyboardDismissMode(mode: String, dismissKeyboard: () -> Unit)') |
| 203 | 203 | expect(nativeActivity).toContain('setKeyboardDismissMode(craftNativeOptionalString(props.opt("keyboardDismissMode")).orEmpty())') |
| 204 | expect(nativeActivity).toContain('private val frameCallback = android.view.Choreographer.FrameCallback') | |
| 205 | expect(nativeActivity).toContain('android.view.Choreographer.getInstance().postFrameCallback(frameCallback)') | |
| 206 | expect(nativeActivity).toContain('android.view.Choreographer.getInstance().removeFrameCallback(frameCallback)') | |
| 207 | expect(nativeActivity).toContain('requestAnimationFrame') | |
| 204 | 208 | expect(flatList).toContain('renderedLayout?.height ?: LayoutParams.WRAP_CONTENT') |
| 205 | 209 | expect(nativeActivity).toContain('val nestedLists = refreshableLists.map { it.list }.toSet()') |
| 206 | 210 | expect(nativeActivity).toContain('released.addAll(nestedLists)') |
| @@ -716,6 +720,9 @@ describe('Craft Android builder', () => { | ||
| 716 | 720 | expect(native).toContain('showError("Missing native-screen.js. Build with --native-bundle after compiling STX.")') |
| 717 | 721 | expect(native).toContain('screen.capabilityTimeouts.values.forEach(capabilityHandler::removeCallbacks)') |
| 718 | 722 | expect(native).toContain('screen.pendingCapabilities.forEach(capabilities::cancel)') |
| 723 | expect(native).toContain('screen.pendingSyncResults.toList().forEach') | |
| 724 | expect(native).toContain('screen.pendingSyncResults.clear()') | |
| 725 | expect(native).toContain('screen.pendingSyncResults.remove(id)') | |
| 719 | 726 | expect(native).toContain('screen.close {') |
| 720 | 727 | expect(native).toContain('imageJobs.values.forEach { it.cancel(true) }') |
| 721 | 728 | expect(native).toContain('imageExecutor.shutdownNow()') |
| @@ -785,6 +792,49 @@ describe('Craft Android builder', () => { | ||
| 785 | 792 | expect(native).toContain('control.transitionName = craftNativeOptionalString(props.opt("transitionID"))?.takeIf { it.isNotBlank() }') |
| 786 | 793 | }) |
| 787 | 794 | |
| 795 | it('gives native screens screen-owned audio playback with cancellation and teardown', async () => { | |
| 796 | const output = createTempDir('craft-android-native-audio-') | |
| 797 | await init({ name: 'NativeAudio', packageName: 'org.example.nativeaudio', output, runtimeDir: null, config: { renderer: 'native' } }) | |
| 798 | const root = join(output, 'app/src/main/java/org/example/nativeaudio') | |
| 799 | const sounds = readFileSync(join(root, 'CraftNativeSounds.kt'), 'utf8') | |
| 800 | const native = readFileSync(join(root, 'MainActivity.kt'), 'utf8') | |
| 801 | expect(sounds).toContain('setOnPreparedListener') | |
| 802 | expect(sounds).toContain('setOnCompletionListener') | |
| 803 | expect(sounds).toContain('setOnErrorListener') | |
| 804 | expect(sounds).toContain('sound.player.release()') | |
| 805 | expect(sounds).toContain('CraftNativeMedia.local(context, text)') | |
| 806 | expect(sounds).toContain('AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)') | |
| 807 | expect(sounds).toContain('AUDIO_FOCUS_DENIED') | |
| 808 | expect(sounds).toContain('AUDIOFOCUS_LOSS_TRANSIENT') | |
| 809 | expect(sounds).toContain('AudioManager.AUDIOFOCUS_GAIN') | |
| 810 | expect(sounds).toContain('abandonAudioFocusRequest') | |
| 811 | expect(native).toContain('put("audio")') | |
| 812 | expect(native).toContain('screen.soundRequestCancellers[token]') | |
| 813 | expect(native).toContain('sounds.close()') | |
| 814 | expect(native).toContain('"SOUND_EVENT"') | |
| 815 | }) | |
| 816 | ||
| 817 | it('gives native screens an app-owned general document picker', async () => { | |
| 818 | const output = createTempDir('craft-android-native-files-') | |
| 819 | await init({ name: 'NativeFiles', packageName: 'org.example.nativefiles', output, runtimeDir: null, config: { renderer: 'native' } }) | |
| 820 | const root = join(output, 'app/src/main/java/org/example/nativefiles') | |
| 821 | const capabilities = readFileSync(join(root, 'CraftNativeCapabilities.kt'), 'utf8') | |
| 822 | expect(capabilities).toContain('"pickFile" -> pickFile(requestToken, args, completion)') | |
| 823 | expect(capabilities).toContain('Intent.ACTION_OPEN_DOCUMENT') | |
| 824 | expect(capabilities).toContain('Intent.EXTRA_ALLOW_MULTIPLE') | |
| 825 | expect(capabilities).toContain('Intent.EXTRA_MIME_TYPES') | |
| 826 | expect(capabilities).toContain('copyDocuments(pending.requestToken, uris, pending.fileLimit)') | |
| 827 | expect(capabilities).toContain('FILE_TOO_LARGE') | |
| 828 | expect(capabilities).toContain('CANCELLED: File selection was cancelled') | |
| 829 | expect(capabilities).toContain('ownedFile(it)?.parentFile?.deleteRecursively()') | |
| 830 | expect(capabilities).toContain('application/vnd.garmin.tcx+xml') | |
| 831 | expect(capabilities).toContain('copyVideo(pending.requestToken, it, pending.videoLimit)') | |
| 832 | expect(capabilities).toContain('CANCELLED: Video selection was cancelled') | |
| 833 | expect(capabilities).toContain('copyUri(pending.requestToken, it, pending.maxPixels, pending.quality)') | |
| 834 | expect(capabilities).toContain('networkExecutor.execute {') | |
| 835 | expect(capabilities).toContain('file.parentFile?.deleteRecursively()') | |
| 836 | }) | |
| 837 | ||
| 788 | 838 | it('hands native wallet passes to Google Wallet through an opt-in save URL', async () => { |
| 789 | 839 | const output = createTempDir('craft-android-native-wallet-') |
| 790 | 840 | await init({ name: 'Wallet', packageName: 'org.example.wallet', output, runtimeDir: null, config: { renderer: 'native', enableWallet: true } }) |