@multiplayer-app/session-recorder-react-native 0.0.1-beta.7 → 0.0.1-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/docs/NATIVE_MODULE_SETUP.md +175 -0
  2. package/ios/SessionRecorderNative.podspec +5 -0
  3. package/package.json +11 -1
  4. package/plugin/package.json +20 -0
  5. package/plugin/src/index.js +42 -0
  6. package/android/src/main/AndroidManifest.xml +0 -2
  7. package/android/src/main/java/com/multiplayer/sessionrecorder/ScreenMaskingModule.kt +0 -202
  8. package/android/src/main/java/com/multiplayer/sessionrecorder/ScreenMaskingPackage.kt +0 -16
  9. package/android/src/main/java/com/multiplayer/sessionrecorder/SessionRecorderModule.kt +0 -202
  10. package/android/src/main/java/com/multiplayer/sessionrecorder/SessionRecorderPackage.kt +0 -16
  11. package/babel.config.js +0 -13
  12. package/docs/AUTO_METADATA_DETECTION.md +0 -108
  13. package/docs/TROUBLESHOOTING.md +0 -168
  14. package/ios/ScreenMasking.m +0 -12
  15. package/ios/ScreenMasking.podspec +0 -21
  16. package/ios/ScreenMasking.swift +0 -205
  17. package/ios/SessionRecorder.podspec +0 -21
  18. package/scripts/generate-app-metadata.js +0 -173
  19. package/src/components/GestureCaptureWrapper/GestureCaptureWrapper.tsx +0 -86
  20. package/src/components/GestureCaptureWrapper/index.ts +0 -1
  21. package/src/components/ScreenRecorderView/ScreenRecorderView.tsx +0 -72
  22. package/src/components/ScreenRecorderView/index.ts +0 -1
  23. package/src/components/SessionRecorderWidget/FinalPopover.tsx +0 -62
  24. package/src/components/SessionRecorderWidget/FloatingButton.tsx +0 -136
  25. package/src/components/SessionRecorderWidget/InitialPopover.tsx +0 -89
  26. package/src/components/SessionRecorderWidget/ModalContainer.tsx +0 -128
  27. package/src/components/SessionRecorderWidget/ModalHeader.tsx +0 -24
  28. package/src/components/SessionRecorderWidget/SessionRecorderWidget.tsx +0 -109
  29. package/src/components/SessionRecorderWidget/icons.tsx +0 -52
  30. package/src/components/SessionRecorderWidget/index.ts +0 -3
  31. package/src/components/SessionRecorderWidget/styles.ts +0 -150
  32. package/src/components/index.ts +0 -3
  33. package/src/config/constants.ts +0 -60
  34. package/src/config/defaults.ts +0 -83
  35. package/src/config/index.ts +0 -6
  36. package/src/config/masking.ts +0 -28
  37. package/src/config/session-recorder.ts +0 -55
  38. package/src/config/validators.ts +0 -31
  39. package/src/context/SessionRecorderContext.tsx +0 -53
  40. package/src/index.ts +0 -9
  41. package/src/native/ScreenMasking.ts +0 -34
  42. package/src/native/SessionRecorderNative.ts +0 -34
  43. package/src/otel/helpers.ts +0 -275
  44. package/src/otel/index.ts +0 -138
  45. package/src/otel/instrumentations/index.ts +0 -115
  46. package/src/patch/index.ts +0 -1
  47. package/src/patch/xhr.ts +0 -141
  48. package/src/recorder/eventExporter.ts +0 -141
  49. package/src/recorder/gestureRecorder.ts +0 -498
  50. package/src/recorder/index.ts +0 -179
  51. package/src/recorder/navigationTracker.ts +0 -449
  52. package/src/recorder/screenRecorder.ts +0 -527
  53. package/src/services/api.service.ts +0 -203
  54. package/src/services/screenMaskingService.ts +0 -118
  55. package/src/services/storage.service.ts +0 -199
  56. package/src/session-recorder.ts +0 -606
  57. package/src/types/expo.d.ts +0 -23
  58. package/src/types/index.ts +0 -28
  59. package/src/types/session-recorder.ts +0 -429
  60. package/src/types/session.ts +0 -65
  61. package/src/utils/app-metadata.ts +0 -31
  62. package/src/utils/index.ts +0 -8
  63. package/src/utils/logger.ts +0 -225
  64. package/src/utils/nativeModuleTest.ts +0 -60
  65. package/src/utils/platform.ts +0 -384
  66. package/src/utils/request-utils.ts +0 -61
  67. package/src/utils/rrweb-events.ts +0 -309
  68. package/src/utils/session.ts +0 -18
  69. package/src/utils/time.ts +0 -17
  70. package/src/utils/type-utils.ts +0 -75
  71. package/src/version.ts +0 -1
  72. package/tsconfig.json +0 -24
  73. /package/ios/{SessionRecorder.m → SessionRecorderNative.m} +0 -0
  74. /package/ios/{SessionRecorder.swift → SessionRecorderNative.swift} +0 -0
@@ -1,173 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Build script to automatically extract app metadata from configuration files
5
- * This runs without developer intervention and generates app-metadata.ts
6
- */
7
-
8
- const fs = require('fs')
9
- const path = require('path')
10
-
11
- function findProjectRoot() {
12
- let currentDir = process.cwd()
13
-
14
- // Look for package.json going up the directory tree
15
- while (currentDir !== path.dirname(currentDir)) {
16
- if (fs.existsSync(path.join(currentDir, 'package.json'))) {
17
- return currentDir
18
- }
19
- currentDir = path.dirname(currentDir)
20
- }
21
-
22
- return process.cwd()
23
- }
24
-
25
- function extractAppMetadata(projectRoot) {
26
- const metadata = {
27
- name: undefined,
28
- version: undefined,
29
- bundleId: undefined,
30
- buildNumber: undefined,
31
- displayName: undefined,
32
- }
33
-
34
- try {
35
- // Method 1: Try app.json
36
- const appJsonPath = path.join(projectRoot, 'app.json')
37
- if (fs.existsSync(appJsonPath)) {
38
- const appConfig = JSON.parse(fs.readFileSync(appJsonPath, 'utf8'))
39
-
40
- metadata.name = appConfig.name || appConfig.displayName
41
- metadata.version = appConfig.version
42
- metadata.displayName = appConfig.displayName
43
-
44
- // Extract bundle ID from platform-specific configs
45
- if (appConfig.ios?.bundleIdentifier) {
46
- metadata.bundleId = appConfig.ios.bundleIdentifier
47
- } else if (appConfig.android?.package) {
48
- metadata.bundleId = appConfig.android.package
49
- }
50
-
51
- if (appConfig.ios?.buildNumber) {
52
- metadata.buildNumber = appConfig.ios.buildNumber.toString()
53
- } else if (appConfig.android?.versionCode) {
54
- metadata.buildNumber = appConfig.android.versionCode.toString()
55
- }
56
-
57
- console.log('✅ Extracted metadata from app.json')
58
- return metadata
59
- }
60
-
61
- // Method 2: Try app.config.js
62
- const appConfigJsPath = path.join(projectRoot, 'app.config.js')
63
- if (fs.existsSync(appConfigJsPath)) {
64
- try {
65
- // Clear require cache to get fresh config
66
- delete require.cache[require.resolve(appConfigJsPath)]
67
- const appConfig = require(appConfigJsPath)
68
-
69
- metadata.name = appConfig.name || appConfig.displayName
70
- metadata.version = appConfig.version
71
- metadata.displayName = appConfig.displayName
72
-
73
- // Extract bundle ID from platform-specific configs
74
- if (appConfig.ios?.bundleIdentifier) {
75
- metadata.bundleId = appConfig.ios.bundleIdentifier
76
- } else if (appConfig.android?.package) {
77
- metadata.bundleId = appConfig.android.package
78
- }
79
-
80
- if (appConfig.ios?.buildNumber) {
81
- metadata.buildNumber = appConfig.ios.buildNumber.toString()
82
- } else if (appConfig.android?.versionCode) {
83
- metadata.buildNumber = appConfig.android.versionCode.toString()
84
- }
85
-
86
- console.log('✅ Extracted metadata from app.config.js')
87
- return metadata
88
- } catch (error) {
89
- console.warn('⚠️ Could not parse app.config.js:', error.message)
90
- }
91
- }
92
-
93
- // Method 3: Fallback to package.json
94
- const packageJsonPath = path.join(projectRoot, 'package.json')
95
- if (fs.existsSync(packageJsonPath)) {
96
- const packageConfig = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
97
-
98
- metadata.name = packageConfig.name
99
- metadata.version = packageConfig.version
100
-
101
- console.log('✅ Extracted metadata from package.json')
102
- return metadata
103
- }
104
-
105
- } catch (error) {
106
- console.warn('⚠️ Error extracting app metadata:', error.message)
107
- }
108
-
109
- return metadata
110
- }
111
-
112
- function generateAppMetadataFile(metadata, outputPath) {
113
- const content = `/**
114
- * Auto-generated app metadata
115
- * This file is generated at build time to provide app metadata without developer intervention
116
- */
117
-
118
- // This file is automatically generated by the build process
119
- // It extracts metadata from app.json, app.config.js, or package.json
120
-
121
- export interface AppMetadata {
122
- name?: string
123
- version?: string
124
- bundleId?: string
125
- buildNumber?: string
126
- displayName?: string
127
- }
128
-
129
- // Auto-detected values from project configuration files
130
- export const APP_METADATA: AppMetadata = {
131
- name: ${metadata.name ? `'${metadata.name}'` : 'undefined'},
132
- version: ${metadata.version ? `'${metadata.version}'` : 'undefined'},
133
- bundleId: ${metadata.bundleId ? `'${metadata.bundleId}'` : 'undefined'},
134
- buildNumber: ${metadata.buildNumber ? `'${metadata.buildNumber}'` : 'undefined'},
135
- displayName: ${metadata.displayName ? `'${metadata.displayName}'` : 'undefined'},
136
- }
137
-
138
- /**
139
- * Get auto-detected app metadata
140
- */
141
- export function getAutoDetectedAppMetadata(): AppMetadata {
142
- return { ...APP_METADATA }
143
- }
144
- `
145
-
146
- fs.writeFileSync(outputPath, content, 'utf8')
147
- console.log(`✅ Generated app-metadata.ts`)
148
- }
149
-
150
- function main() {
151
- const projectRoot = findProjectRoot()
152
- console.log(`🔍 Looking for app metadata in: ${projectRoot}`)
153
-
154
- const metadata = extractAppMetadata(projectRoot)
155
-
156
- // Show what was detected
157
- console.log('📋 Detected metadata:')
158
- Object.entries(metadata).forEach(([key, value]) => {
159
- if (value) {
160
- console.log(` ${key}: ${value}`)
161
- }
162
- })
163
-
164
- // Generate the TypeScript file
165
- const outputPath = path.join(__dirname, '../src/utils/app-metadata.ts')
166
- generateAppMetadataFile(metadata, outputPath)
167
- }
168
-
169
- if (require.main === module) {
170
- main()
171
- }
172
-
173
- module.exports = { extractAppMetadata, generateAppMetadataFile }
@@ -1,86 +0,0 @@
1
- import React, { ReactNode, useCallback, useMemo } from 'react'
2
- import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'
3
-
4
- export interface GestureCaptureWrapperProps {
5
- children: ReactNode
6
- onGestureRecord: (gestureType: string, data: any) => void
7
- }
8
-
9
- export const GestureCaptureWrapper: React.FC<GestureCaptureWrapperProps> = ({ children, onGestureRecord }) => {
10
- const recordGesture = useCallback(
11
- (gestureType: string, data: any) => {
12
- // Record with session recorder
13
- onGestureRecord(gestureType, data)
14
- },
15
- [onGestureRecord]
16
- )
17
-
18
- // Create tap gesture
19
- const tapGesture = useMemo(() => {
20
- return Gesture.Tap()
21
- .runOnJS(true)
22
- .onStart((event) => {
23
- recordGesture('tap', {
24
- x: event.x,
25
- y: event.y,
26
- timestamp: Date.now()
27
- })
28
- })
29
- }, [recordGesture])
30
-
31
- // Create pan gesture (for swipes and drags)
32
- const panGesture = useMemo(() => {
33
- return Gesture.Pan()
34
- .runOnJS(true)
35
- .onStart((event) => {
36
- recordGesture('pan_start', {
37
- x: event.x,
38
- y: event.y,
39
- timestamp: Date.now()
40
- })
41
- })
42
- .onUpdate((event) => {
43
- recordGesture('pan_update', {
44
- x: event.x,
45
- y: event.y,
46
- translationX: event.translationX,
47
- translationY: event.translationY,
48
- velocityX: event.velocityX,
49
- velocityY: event.velocityY,
50
- timestamp: Date.now()
51
- })
52
- })
53
- .onEnd((event) => {
54
- recordGesture('pan_end', {
55
- x: event.x,
56
- y: event.y,
57
- translationX: event.translationX,
58
- translationY: event.translationY,
59
- velocityX: event.velocityX,
60
- velocityY: event.velocityY,
61
- timestamp: Date.now()
62
- })
63
- })
64
- }, [recordGesture])
65
-
66
- // Create long press gesture
67
- const longPressGesture = useMemo(() => {
68
- return Gesture.LongPress()
69
- .runOnJS(true)
70
- .minDuration(500)
71
- .onStart((event) => {
72
- recordGesture('long_press', {
73
- x: event.x,
74
- y: event.y,
75
- duration: 500,
76
- timestamp: Date.now()
77
- })
78
- })
79
- }, [recordGesture])
80
-
81
- return (
82
- <GestureHandlerRootView style={{ flex: 1 }}>
83
- <GestureDetector gesture={Gesture.Simultaneous(tapGesture, panGesture, longPressGesture)}>{children}</GestureDetector>
84
- </GestureHandlerRootView>
85
- )
86
- }
@@ -1 +0,0 @@
1
- export * from "./GestureCaptureWrapper";
@@ -1,72 +0,0 @@
1
- import SessionRecorder from '@multiplayer-app/session-recorder-react-native'
2
- import React, { PropsWithChildren, useCallback } from 'react'
3
- import { View } from 'react-native'
4
- import { SessionState } from '../../types'
5
- import { logger } from '../../utils'
6
- import { GestureCaptureWrapper } from '../GestureCaptureWrapper'
7
-
8
- interface ScreenRecorderViewProps extends PropsWithChildren {}
9
-
10
- export const ScreenRecorderView = ({ children }: ScreenRecorderViewProps) => {
11
- // Set up gesture recording callback
12
- const handleGestureRecord = useCallback((gestureType: string, data: any) => {
13
- if (SessionRecorder.sessionState !== SessionState.started) {
14
- logger.debug('SessionRecorderContext', 'Gesture recording skipped', {
15
- client: !!SessionRecorder.sessionState,
16
- sessionState: SessionRecorder.sessionState
17
- })
18
- return
19
- }
20
- logger.debug('SessionRecorderContext', 'Gesture recorded', { gestureType, data })
21
- try {
22
- // Record gesture as appropriate touch events
23
- switch (gestureType) {
24
- case 'tap':
25
- // For tap, record both touch start and end
26
- logger.debug('SessionRecorderContext', 'Recording tap as touch start + end')
27
- SessionRecorder.recordTouchStart?.(data.x, data.y, undefined, 1.0)
28
- SessionRecorder.recordTouchEnd?.(data.x, data.y, undefined, 1.0)
29
- break
30
-
31
- case 'pan_start':
32
- logger.debug('SessionRecorderContext', 'Recording pan_start as touch start')
33
- SessionRecorder.recordTouchStart?.(data.x, data.y, undefined, 1.0)
34
- break
35
-
36
- case 'pan_update':
37
- logger.debug('SessionRecorderContext', 'Recording pan_update as touch move')
38
- SessionRecorder.recordTouchMove?.(data.x, data.y, undefined, 1.0)
39
- break
40
-
41
- case 'pan_end':
42
- logger.debug('SessionRecorderContext', 'Recording pan_end as touch end')
43
- SessionRecorder.recordTouchEnd?.(data.x, data.y, undefined, 1.0)
44
- break
45
-
46
- case 'long_press':
47
- logger.debug('SessionRecorderContext', 'Recording long_press as touch start + end')
48
- SessionRecorder.recordTouchStart?.(data.x, data.y, undefined, 1.0)
49
- SessionRecorder.recordTouchEnd?.(data.x, data.y, undefined, 1.0)
50
- break
51
- default:
52
- }
53
- } catch (error) {
54
- logger.error('SessionRecorderContext', 'Failed to record gesture event', error)
55
- }
56
- }, [])
57
-
58
- // Callback ref to set the viewshot ref immediately when available
59
- const setViewShotRef = (ref: View | null) => {
60
- if (ref) {
61
- SessionRecorder.setViewShotRef?.(ref)
62
- }
63
- }
64
-
65
- return (
66
- <GestureCaptureWrapper onGestureRecord={handleGestureRecord}>
67
- <View ref={setViewShotRef} style={{ flex: 1 }}>
68
- {children}
69
- </View>
70
- </GestureCaptureWrapper>
71
- )
72
- }
@@ -1 +0,0 @@
1
- export * from "./ScreenRecorderView";
@@ -1,62 +0,0 @@
1
- import React, { useState } from 'react'
2
- import { View, Text, Pressable, TextInput, Alert } from 'react-native'
3
- import { WidgetTextOverridesConfig } from '../../types'
4
- import { sharedStyles } from './styles'
5
- import ModalHeader from './ModalHeader'
6
-
7
- interface FinalPopoverProps {
8
- textOverrides: WidgetTextOverridesConfig
9
- onStopRecording: (comment: string) => void
10
- onCancelSession: () => void
11
- onClose: () => void
12
- isSubmitting: boolean
13
- }
14
-
15
- const FinalPopover: React.FC<FinalPopoverProps> = ({ textOverrides, onStopRecording, onCancelSession, isSubmitting }) => {
16
- const [comment, setComment] = useState('')
17
-
18
- const handleStopRecording = async () => {
19
- try {
20
- await onStopRecording(comment)
21
- } catch (error) {
22
- Alert.alert('Error', 'Failed to save session')
23
- }
24
- }
25
-
26
- return (
27
- <View style={sharedStyles.popoverContent}>
28
- <ModalHeader>
29
- <Pressable onPress={onCancelSession} style={sharedStyles.cancelButton}>
30
- <Text style={sharedStyles.cancelButtonText}>{textOverrides.cancelButtonText}</Text>
31
- </Pressable>
32
- </ModalHeader>
33
-
34
- <View style={sharedStyles.popoverBody}>
35
- <Text style={sharedStyles.title}>{textOverrides.finalTitle}</Text>
36
- <Text style={sharedStyles.description}>{textOverrides.finalDescription}</Text>
37
-
38
- <TextInput
39
- style={sharedStyles.commentInput}
40
- placeholder={textOverrides.commentPlaceholder}
41
- value={comment}
42
- onChangeText={setComment}
43
- multiline
44
- numberOfLines={3}
45
- textAlignVertical='top'
46
- />
47
-
48
- <View style={sharedStyles.popoverFooter}>
49
- <Pressable
50
- style={[sharedStyles.actionButton, sharedStyles.stopButton]}
51
- onPress={handleStopRecording}
52
- disabled={isSubmitting}
53
- >
54
- <Text style={sharedStyles.actionButtonText}>{isSubmitting ? 'Saving...' : textOverrides.saveButtonText}</Text>
55
- </Pressable>
56
- </View>
57
- </View>
58
- </View>
59
- )
60
- }
61
-
62
- export default FinalPopover
@@ -1,136 +0,0 @@
1
- import React, { useRef, useEffect, useMemo } from 'react'
2
- import { StyleSheet, Platform, Animated, PanResponder, View, Dimensions } from 'react-native'
3
- import { SessionState } from '../../types'
4
- import { StorageService } from '../../services/storage.service'
5
- import { RecordIcon, CapturingIcon, PausedIcon } from './icons'
6
-
7
- interface FloatingButtonProps {
8
- sessionState: SessionState | null
9
- onPress: () => void
10
- }
11
-
12
- const buttonSize = 52
13
- const rightOffset = 20
14
- const topOffset = Platform.OS === 'ios' ? 60 : 40
15
-
16
- const FloatingButton: React.FC<FloatingButtonProps> = ({ sessionState, onPress }) => {
17
- const position = useRef(new Animated.ValueXY({ x: 0, y: 0 })).current
18
- const lastPosition = useRef({ top: topOffset, right: rightOffset })
19
- const storageService = useRef(StorageService.getInstance()).current
20
-
21
- const screenBounds = useMemo(() => {
22
- const { width, height } = Dimensions.get('window')
23
-
24
- return {
25
- minTop: topOffset,
26
- maxTop: height - buttonSize,
27
- minRight: 0,
28
- maxRight: width - buttonSize
29
- }
30
- }, [])
31
-
32
- // Load saved position on component mount
33
- useEffect(() => {
34
- const savedPosition = storageService.getFloatingButtonPosition()
35
- if (savedPosition) {
36
- const { width } = Dimensions.get('window')
37
- const top = savedPosition.y
38
- const right = width - savedPosition.x - buttonSize
39
- lastPosition.current = { top, right }
40
- position.setValue({ x: right, y: top })
41
- } else {
42
- position.setValue({ x: lastPosition.current.right, y: lastPosition.current.top })
43
- }
44
- }, [])
45
-
46
- const panResponder = useRef(
47
- PanResponder.create({
48
- onStartShouldSetPanResponder: () => true,
49
- onMoveShouldSetPanResponder: (evt, gestureState) => {
50
- const distance = Math.sqrt(gestureState.dx * gestureState.dx + gestureState.dy * gestureState.dy)
51
- return distance > 5
52
- },
53
- onPanResponderGrant: () => {
54
- // Set the initial position for this gesture
55
- position.setValue({ x: lastPosition.current.right, y: lastPosition.current.top })
56
- },
57
- onPanResponderMove: (evt, gestureState) => {
58
- // Calculate new position based on gesture movement
59
- const newTop = lastPosition.current.top + gestureState.dy
60
- const newRight = lastPosition.current.right - gestureState.dx
61
-
62
- // Update position during drag
63
- position.setValue({ x: newRight, y: newTop })
64
- },
65
- onPanResponderRelease: (e, gestureState) => {
66
- // Check if this was actually a drag (significant movement)
67
- const distance = Math.sqrt(gestureState.dx * gestureState.dx + gestureState.dy * gestureState.dy)
68
-
69
- // If it was a tap (no significant movement), trigger onPress
70
- if (distance <= 5) {
71
- onPress()
72
- } else {
73
- // Calculate new position after dragging
74
- const newTop = lastPosition.current.top + gestureState.dy
75
- const newRight = lastPosition.current.right - gestureState.dx
76
-
77
- // Clamp to screen bounds
78
- const clampedTop = Math.max(screenBounds.minTop, Math.min(screenBounds.maxTop, newTop))
79
- const clampedRight = Math.max(screenBounds.minRight, Math.min(screenBounds.maxRight, newRight))
80
-
81
- // Update position
82
- lastPosition.current = { top: clampedTop, right: clampedRight }
83
- position.setValue({ x: clampedRight, y: clampedTop })
84
-
85
- // Convert back to x,y coordinates for storage
86
- const { width } = Dimensions.get('window')
87
- const storagePosition = {
88
- x: width - clampedRight - buttonSize,
89
- y: clampedTop
90
- }
91
-
92
- // Persist position to AsyncStorage (debounced)
93
- storageService.saveFloatingButtonPosition(storagePosition)
94
- }
95
- }
96
- })
97
- ).current
98
-
99
- // Memoized button icon and color for performance
100
- const content = useMemo(() => {
101
- switch (sessionState) {
102
- case SessionState.started:
103
- return { icon: <CapturingIcon size={28} color='white' />, color: '#FF4444' }
104
- case SessionState.paused:
105
- return { icon: <PausedIcon size={28} color='white' />, color: '#FFA500' }
106
- default:
107
- return { icon: <RecordIcon size={28} color='#718096' />, color: '#ffffff' }
108
- }
109
- }, [sessionState])
110
-
111
- return (
112
- <Animated.View style={[styles.draggableButton, { top: position.y, right: position.x }]} {...panResponder.panHandlers}>
113
- <View style={[styles.floatingButton, { backgroundColor: content.color }]}>{content.icon}</View>
114
- </Animated.View>
115
- )
116
- }
117
-
118
- const styles = StyleSheet.create({
119
- draggableButton: {
120
- position: 'absolute'
121
- },
122
- floatingButton: {
123
- elevation: 8,
124
- shadowRadius: 4,
125
- width: buttonSize,
126
- shadowColor: '#000',
127
- height: buttonSize,
128
- shadowOpacity: 0.25,
129
- alignItems: 'center',
130
- justifyContent: 'center',
131
- borderRadius: buttonSize / 2,
132
- shadowOffset: { width: 0, height: 2 }
133
- }
134
- })
135
-
136
- export default FloatingButton
@@ -1,89 +0,0 @@
1
- import React, { useState } from 'react'
2
- import { View, Text, Pressable, Alert, Switch } from 'react-native'
3
- import { SessionType } from '@multiplayer-app/session-recorder-common'
4
- import { WidgetTextOverridesConfig } from '../../types'
5
- import { sharedStyles } from './styles'
6
- import ModalHeader from './ModalHeader'
7
-
8
- interface InitialPopoverProps {
9
- textOverrides: WidgetTextOverridesConfig
10
- showContinuousRecording: boolean
11
- onStartRecording: (sessionType: SessionType) => void
12
- onSaveContinuousSession: () => void
13
- onClose: () => void
14
- isSubmitting: boolean
15
- }
16
-
17
- const InitialPopover: React.FC<InitialPopoverProps> = ({
18
- textOverrides,
19
- showContinuousRecording,
20
- onStartRecording,
21
- onSaveContinuousSession,
22
- onClose,
23
- isSubmitting
24
- }) => {
25
- const [continuousRecording, setContinuousRecording] = useState(false)
26
-
27
- const handleStartRecording = async () => {
28
- try {
29
- const sessionType = continuousRecording ? SessionType.CONTINUOUS : SessionType.PLAIN
30
- onStartRecording(sessionType)
31
- } catch (error) {
32
- Alert.alert('Error', 'Failed to start recording')
33
- }
34
- }
35
-
36
- return (
37
- <View style={sharedStyles.popoverContent}>
38
- <ModalHeader />
39
-
40
- <View style={sharedStyles.popoverBody}>
41
- {showContinuousRecording && (
42
- <View style={sharedStyles.continuousRecordingSection}>
43
- <Text style={sharedStyles.continuousRecordingLabel}>{textOverrides.continuousRecordingLabel}</Text>
44
- <Switch
45
- value={continuousRecording}
46
- onValueChange={setContinuousRecording}
47
- trackColor={{ false: '#767577', true: '#81b0ff' }}
48
- thumbColor={continuousRecording ? '#007AFF' : '#f4f3f4'}
49
- />
50
- </View>
51
- )}
52
-
53
- <Text style={sharedStyles.title}>
54
- {showContinuousRecording ? textOverrides.initialTitleWithContinuous : textOverrides.initialTitleWithoutContinuous}
55
- </Text>
56
-
57
- <Text style={sharedStyles.description}>
58
- {showContinuousRecording
59
- ? textOverrides.initialDescriptionWithContinuous
60
- : textOverrides.initialDescriptionWithoutContinuous}
61
- </Text>
62
-
63
- <View style={sharedStyles.popoverFooter}>
64
- <Pressable style={[sharedStyles.actionButton, sharedStyles.startButton]} onPress={handleStartRecording}>
65
- <Text style={sharedStyles.actionButtonText}>{textOverrides.startRecordingButtonText}</Text>
66
- </Pressable>
67
- </View>
68
-
69
- {showContinuousRecording && continuousRecording && (
70
- <View style={sharedStyles.continuousOverlay}>
71
- <View style={sharedStyles.continuousOverlayContent}>
72
- <Text style={sharedStyles.continuousOverlayTitle}>🔴 {textOverrides.continuousOverlayTitle}</Text>
73
- <Text style={sharedStyles.continuousOverlayDescription}>{textOverrides.continuousOverlayDescription}</Text>
74
- </View>
75
- <Pressable
76
- style={[sharedStyles.actionButton, sharedStyles.saveButton]}
77
- onPress={onSaveContinuousSession}
78
- disabled={isSubmitting}
79
- >
80
- <Text style={sharedStyles.actionButtonText}>{textOverrides.saveLastSnapshotButtonText}</Text>
81
- </Pressable>
82
- </View>
83
- )}
84
- </View>
85
- </View>
86
- )
87
- }
88
-
89
- export default InitialPopover