ReviewOS

also looking at this

craft-native/craft

fix(native): interaction, audio, document and checkpoint gaps stranded after #401

#415
Merged glennmichael123 wants to merge codex/issues-388-400-344 into main
19 files +987 -50
packages/android/src/index.test.tsmodified+50-0
Changes to packages/android/src/index.test.ts
@@ -201,6 +201,10 @@ describe('Craft Android builder', () => {
201201 expect(refreshableFlatList).toContain('isRefreshing = false')
202202 expect(flatList).toContain('fun setKeyboardDismissMode(mode: String, dismissKeyboard: () -> Unit)')
203203 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')
204208 expect(flatList).toContain('renderedLayout?.height ?: LayoutParams.WRAP_CONTENT')
205209 expect(nativeActivity).toContain('val nestedLists = refreshableLists.map { it.list }.toSet()')
206210 expect(nativeActivity).toContain('released.addAll(nestedLists)')
@@ -716,6 +720,9 @@ describe('Craft Android builder', () => {
716720 expect(native).toContain('showError("Missing native-screen.js. Build with --native-bundle after compiling STX.")')
717721 expect(native).toContain('screen.capabilityTimeouts.values.forEach(capabilityHandler::removeCallbacks)')
718722 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)')
719726 expect(native).toContain('screen.close {')
720727 expect(native).toContain('imageJobs.values.forEach { it.cancel(true) }')
721728 expect(native).toContain('imageExecutor.shutdownNow()')
@@ -785,6 +792,49 @@ describe('Craft Android builder', () => {
785792 expect(native).toContain('control.transitionName = craftNativeOptionalString(props.opt("transitionID"))?.takeIf { it.isNotBlank() }')
786793 })
787794
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
788838 it('hands native wallet passes to Google Wallet through an opt-in save URL', async () => {
789839 const output = createTempDir('craft-android-native-wallet-')
790840 await init({ name: 'Wallet', packageName: 'org.example.wallet', output, runtimeDir: null, config: { renderer: 'native', enableWallet: true } })