@crumbsdk/react-native 0.0.1-rc.3 → 0.0.1-rc.4

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 (356) hide show
  1. package/README.md +270 -8
  2. package/android/build.gradle +0 -1
  3. package/android/src/main/java/com/margelo/nitro/crumbsdk/reactnative/CrumbReactNative.kt +121 -3
  4. package/app.plugin.js +22 -0
  5. package/ios/CrumbReactNative.swift +135 -3
  6. package/lib/module/NativeCrumbReactNative.web.js +5 -1
  7. package/lib/module/NativeCrumbReactNative.web.js.map +1 -1
  8. package/lib/module/index.js +91 -1
  9. package/lib/module/index.js.map +1 -1
  10. package/lib/module/javascript-crash-capture.js +497 -0
  11. package/lib/module/javascript-crash-capture.js.map +1 -0
  12. package/lib/module/log-buffer.js +22 -2
  13. package/lib/module/log-buffer.js.map +1 -1
  14. package/lib/module/screen-context.js +127 -0
  15. package/lib/module/screen-context.js.map +1 -0
  16. package/lib/typescript/src/CrumbReactNative.nitro.d.ts +5 -1
  17. package/lib/typescript/src/CrumbReactNative.nitro.d.ts.map +1 -1
  18. package/lib/typescript/src/NativeCrumbReactNative.web.d.ts.map +1 -1
  19. package/lib/typescript/src/index.d.ts +7 -1
  20. package/lib/typescript/src/index.d.ts.map +1 -1
  21. package/lib/typescript/src/javascript-crash-capture.d.ts +74 -0
  22. package/lib/typescript/src/javascript-crash-capture.d.ts.map +1 -0
  23. package/lib/typescript/src/log-buffer.d.ts +1 -0
  24. package/lib/typescript/src/log-buffer.d.ts.map +1 -1
  25. package/lib/typescript/src/screen-context.d.ts +19 -0
  26. package/lib/typescript/src/screen-context.d.ts.map +1 -0
  27. package/lib/typescript/src/types.d.ts +38 -0
  28. package/lib/typescript/src/types.d.ts.map +1 -1
  29. package/native/ios/CrumbSDKCore.podspec +28 -0
  30. package/native/ios/CrumbSDKUI.podspec +38 -0
  31. package/native/ios/LICENSE +201 -0
  32. package/native/ios/PLCrashReporter/CrashReporter.xcframework/Info.plist +96 -0
  33. package/native/ios/PLCrashReporter/CrashReporter.xcframework/PrivacyInfo.xcprivacy +39 -0
  34. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/CrashReporter +0 -0
  35. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/CrashReporter.h +366 -0
  36. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashFeatureConfig.h +108 -0
  37. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashMacros.h +143 -0
  38. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashNamespace.h +320 -0
  39. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReport.h +193 -0
  40. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportApplicationInfo.h +52 -0
  41. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportBinaryImageInfo.h +76 -0
  42. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportExceptionInfo.h +59 -0
  43. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportFormatter.h +55 -0
  44. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportMachExceptionInfo.h +41 -0
  45. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportMachineInfo.h +64 -0
  46. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportProcessInfo.h +80 -0
  47. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportProcessorInfo.h +64 -0
  48. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportRegisterInfo.h +45 -0
  49. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportSignalInfo.h +50 -0
  50. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportStackFrameInfo.h +50 -0
  51. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportSymbolInfo.h +51 -0
  52. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportSystemInfo.h +158 -0
  53. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportTextFormatter.h +59 -0
  54. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReportThreadInfo.h +75 -0
  55. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReporter.h +131 -0
  56. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Headers/PLCrashReporterConfig.h +193 -0
  57. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Info.plist +0 -0
  58. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/Modules/module.modulemap +8 -0
  59. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_arm64e/CrashReporter.framework/PrivacyInfo.xcprivacy +39 -0
  60. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/CrashReporter +0 -0
  61. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/CrashReporter.h +366 -0
  62. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashFeatureConfig.h +108 -0
  63. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashMacros.h +143 -0
  64. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashNamespace.h +320 -0
  65. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReport.h +193 -0
  66. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportApplicationInfo.h +52 -0
  67. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportBinaryImageInfo.h +76 -0
  68. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportExceptionInfo.h +59 -0
  69. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportFormatter.h +55 -0
  70. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportMachExceptionInfo.h +41 -0
  71. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportMachineInfo.h +64 -0
  72. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportProcessInfo.h +80 -0
  73. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportProcessorInfo.h +64 -0
  74. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportRegisterInfo.h +45 -0
  75. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportSignalInfo.h +50 -0
  76. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportStackFrameInfo.h +50 -0
  77. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportSymbolInfo.h +51 -0
  78. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportSystemInfo.h +158 -0
  79. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportTextFormatter.h +59 -0
  80. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReportThreadInfo.h +75 -0
  81. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReporter.h +131 -0
  82. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Headers/PLCrashReporterConfig.h +193 -0
  83. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Modules/module.modulemap +8 -0
  84. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Resources/Info.plist +52 -0
  85. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Resources/PrivacyInfo.xcprivacy +39 -0
  86. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/CrashReporter +0 -0
  87. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/CrashReporter.h +366 -0
  88. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashFeatureConfig.h +108 -0
  89. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashMacros.h +143 -0
  90. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashNamespace.h +320 -0
  91. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReport.h +193 -0
  92. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportApplicationInfo.h +52 -0
  93. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportBinaryImageInfo.h +76 -0
  94. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportExceptionInfo.h +59 -0
  95. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportFormatter.h +55 -0
  96. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportMachExceptionInfo.h +41 -0
  97. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportMachineInfo.h +64 -0
  98. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportProcessInfo.h +80 -0
  99. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportProcessorInfo.h +64 -0
  100. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportRegisterInfo.h +45 -0
  101. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportSignalInfo.h +50 -0
  102. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportStackFrameInfo.h +50 -0
  103. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportSymbolInfo.h +51 -0
  104. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportSystemInfo.h +158 -0
  105. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportTextFormatter.h +59 -0
  106. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReportThreadInfo.h +75 -0
  107. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReporter.h +131 -0
  108. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Headers/PLCrashReporterConfig.h +193 -0
  109. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Modules/module.modulemap +8 -0
  110. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Resources/Info.plist +52 -0
  111. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/A/Resources/PrivacyInfo.xcprivacy +39 -0
  112. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/CrashReporter +0 -0
  113. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/CrashReporter.h +366 -0
  114. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashFeatureConfig.h +108 -0
  115. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashMacros.h +143 -0
  116. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashNamespace.h +320 -0
  117. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReport.h +193 -0
  118. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportApplicationInfo.h +52 -0
  119. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportBinaryImageInfo.h +76 -0
  120. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportExceptionInfo.h +59 -0
  121. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportFormatter.h +55 -0
  122. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportMachExceptionInfo.h +41 -0
  123. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportMachineInfo.h +64 -0
  124. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportProcessInfo.h +80 -0
  125. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportProcessorInfo.h +64 -0
  126. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportRegisterInfo.h +45 -0
  127. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportSignalInfo.h +50 -0
  128. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportStackFrameInfo.h +50 -0
  129. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportSymbolInfo.h +51 -0
  130. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportSystemInfo.h +158 -0
  131. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportTextFormatter.h +59 -0
  132. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReportThreadInfo.h +75 -0
  133. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReporter.h +131 -0
  134. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Headers/PLCrashReporterConfig.h +193 -0
  135. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Modules/module.modulemap +8 -0
  136. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Resources/Info.plist +52 -0
  137. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-maccatalyst/CrashReporter.framework/Versions/Current/Resources/PrivacyInfo.xcprivacy +39 -0
  138. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/CrashReporter +0 -0
  139. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/CrashReporter.h +366 -0
  140. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashFeatureConfig.h +108 -0
  141. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashMacros.h +143 -0
  142. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashNamespace.h +320 -0
  143. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReport.h +193 -0
  144. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportApplicationInfo.h +52 -0
  145. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportBinaryImageInfo.h +76 -0
  146. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportExceptionInfo.h +59 -0
  147. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportFormatter.h +55 -0
  148. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportMachExceptionInfo.h +41 -0
  149. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportMachineInfo.h +64 -0
  150. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportProcessInfo.h +80 -0
  151. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportProcessorInfo.h +64 -0
  152. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportRegisterInfo.h +45 -0
  153. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportSignalInfo.h +50 -0
  154. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportStackFrameInfo.h +50 -0
  155. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportSymbolInfo.h +51 -0
  156. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportSystemInfo.h +158 -0
  157. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportTextFormatter.h +59 -0
  158. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportThreadInfo.h +75 -0
  159. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReporter.h +131 -0
  160. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReporterConfig.h +193 -0
  161. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Info.plist +0 -0
  162. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/Modules/module.modulemap +8 -0
  163. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/PrivacyInfo.xcprivacy +39 -0
  164. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/_CodeSignature/CodeDirectory +0 -0
  165. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/_CodeSignature/CodeRequirements +0 -0
  166. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/_CodeSignature/CodeRequirements-1 +0 -0
  167. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/_CodeSignature/CodeResources +462 -0
  168. package/native/ios/PLCrashReporter/CrashReporter.xcframework/ios-arm64_x86_64-simulator/CrashReporter.framework/_CodeSignature/CodeSignature +0 -0
  169. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/CrashReporter +0 -0
  170. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/CrashReporter.h +366 -0
  171. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashFeatureConfig.h +108 -0
  172. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashMacros.h +143 -0
  173. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashNamespace.h +320 -0
  174. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReport.h +193 -0
  175. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportApplicationInfo.h +52 -0
  176. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportBinaryImageInfo.h +76 -0
  177. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportExceptionInfo.h +59 -0
  178. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportFormatter.h +55 -0
  179. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportMachExceptionInfo.h +41 -0
  180. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportMachineInfo.h +64 -0
  181. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportProcessInfo.h +80 -0
  182. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportProcessorInfo.h +64 -0
  183. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportRegisterInfo.h +45 -0
  184. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportSignalInfo.h +50 -0
  185. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportStackFrameInfo.h +50 -0
  186. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportSymbolInfo.h +51 -0
  187. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportSystemInfo.h +158 -0
  188. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportTextFormatter.h +59 -0
  189. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReportThreadInfo.h +75 -0
  190. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReporter.h +131 -0
  191. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Headers/PLCrashReporterConfig.h +193 -0
  192. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Modules/module.modulemap +8 -0
  193. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Resources/Info.plist +48 -0
  194. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Resources/PrivacyInfo.xcprivacy +39 -0
  195. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/CrashReporter +0 -0
  196. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/CrashReporter.h +366 -0
  197. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashFeatureConfig.h +108 -0
  198. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashMacros.h +143 -0
  199. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashNamespace.h +320 -0
  200. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReport.h +193 -0
  201. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportApplicationInfo.h +52 -0
  202. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportBinaryImageInfo.h +76 -0
  203. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportExceptionInfo.h +59 -0
  204. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportFormatter.h +55 -0
  205. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportMachExceptionInfo.h +41 -0
  206. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportMachineInfo.h +64 -0
  207. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportProcessInfo.h +80 -0
  208. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportProcessorInfo.h +64 -0
  209. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportRegisterInfo.h +45 -0
  210. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportSignalInfo.h +50 -0
  211. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportStackFrameInfo.h +50 -0
  212. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportSymbolInfo.h +51 -0
  213. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportSystemInfo.h +158 -0
  214. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportTextFormatter.h +59 -0
  215. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReportThreadInfo.h +75 -0
  216. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReporter.h +131 -0
  217. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Headers/PLCrashReporterConfig.h +193 -0
  218. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Modules/module.modulemap +8 -0
  219. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Resources/Info.plist +48 -0
  220. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/A/Resources/PrivacyInfo.xcprivacy +39 -0
  221. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/CrashReporter +0 -0
  222. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/CrashReporter.h +366 -0
  223. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashFeatureConfig.h +108 -0
  224. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashMacros.h +143 -0
  225. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashNamespace.h +320 -0
  226. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReport.h +193 -0
  227. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportApplicationInfo.h +52 -0
  228. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportBinaryImageInfo.h +76 -0
  229. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportExceptionInfo.h +59 -0
  230. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportFormatter.h +55 -0
  231. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportMachExceptionInfo.h +41 -0
  232. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportMachineInfo.h +64 -0
  233. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportProcessInfo.h +80 -0
  234. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportProcessorInfo.h +64 -0
  235. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportRegisterInfo.h +45 -0
  236. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportSignalInfo.h +50 -0
  237. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportStackFrameInfo.h +50 -0
  238. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportSymbolInfo.h +51 -0
  239. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportSystemInfo.h +158 -0
  240. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportTextFormatter.h +59 -0
  241. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReportThreadInfo.h +75 -0
  242. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReporter.h +131 -0
  243. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Headers/PLCrashReporterConfig.h +193 -0
  244. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Modules/module.modulemap +8 -0
  245. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Resources/Info.plist +48 -0
  246. package/native/ios/PLCrashReporter/CrashReporter.xcframework/macos-arm64_x86_64/CrashReporter.framework/Versions/Current/Resources/PrivacyInfo.xcprivacy +39 -0
  247. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/CrashReporter +0 -0
  248. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/CrashReporter.h +366 -0
  249. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashFeatureConfig.h +108 -0
  250. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashMacros.h +143 -0
  251. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashNamespace.h +320 -0
  252. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReport.h +193 -0
  253. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportApplicationInfo.h +52 -0
  254. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportBinaryImageInfo.h +76 -0
  255. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportExceptionInfo.h +59 -0
  256. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportFormatter.h +55 -0
  257. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportMachExceptionInfo.h +41 -0
  258. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportMachineInfo.h +64 -0
  259. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportProcessInfo.h +80 -0
  260. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportProcessorInfo.h +64 -0
  261. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportRegisterInfo.h +45 -0
  262. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportSignalInfo.h +50 -0
  263. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportStackFrameInfo.h +50 -0
  264. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportSymbolInfo.h +51 -0
  265. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportSystemInfo.h +158 -0
  266. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportTextFormatter.h +59 -0
  267. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReportThreadInfo.h +75 -0
  268. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReporter.h +131 -0
  269. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Headers/PLCrashReporterConfig.h +193 -0
  270. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Info.plist +0 -0
  271. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/Modules/module.modulemap +8 -0
  272. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64/CrashReporter.framework/PrivacyInfo.xcprivacy +39 -0
  273. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/CrashReporter +0 -0
  274. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/CrashReporter.h +366 -0
  275. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashFeatureConfig.h +108 -0
  276. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashMacros.h +143 -0
  277. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashNamespace.h +320 -0
  278. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReport.h +193 -0
  279. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportApplicationInfo.h +52 -0
  280. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportBinaryImageInfo.h +76 -0
  281. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportExceptionInfo.h +59 -0
  282. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportFormatter.h +55 -0
  283. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportMachExceptionInfo.h +41 -0
  284. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportMachineInfo.h +64 -0
  285. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportProcessInfo.h +80 -0
  286. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportProcessorInfo.h +64 -0
  287. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportRegisterInfo.h +45 -0
  288. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportSignalInfo.h +50 -0
  289. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportStackFrameInfo.h +50 -0
  290. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportSymbolInfo.h +51 -0
  291. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportSystemInfo.h +158 -0
  292. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportTextFormatter.h +59 -0
  293. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReportThreadInfo.h +75 -0
  294. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReporter.h +131 -0
  295. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Headers/PLCrashReporterConfig.h +193 -0
  296. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Info.plist +0 -0
  297. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/Modules/module.modulemap +8 -0
  298. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/PrivacyInfo.xcprivacy +39 -0
  299. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/_CodeSignature/CodeDirectory +0 -0
  300. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/_CodeSignature/CodeRequirements +0 -0
  301. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/_CodeSignature/CodeRequirements-1 +0 -0
  302. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/_CodeSignature/CodeResources +462 -0
  303. package/native/ios/PLCrashReporter/CrashReporter.xcframework/tvos-arm64_x86_64-simulator/CrashReporter.framework/_CodeSignature/CodeSignature +0 -0
  304. package/native/ios/PLCrashReporter/LICENSE.txt +54 -0
  305. package/native/ios/PLCrashReporter/PLCrashReporter.podspec +19 -0
  306. package/native/ios/VERSION +1 -0
  307. package/native/ios/packages/ios/Sources/CrumbCore/Crumb.swift +108 -0
  308. package/native/ios/packages/ios/Sources/CrumbCore/CrumbConfiguration.swift +212 -0
  309. package/native/ios/packages/ios/Sources/CrumbCore/CrumbDiagnostics.swift +158 -0
  310. package/native/ios/packages/ios/Sources/CrumbCore/CrumbFailureStacks.swift +94 -0
  311. package/native/ios/packages/ios/Sources/CrumbCore/CrumbInfrastructureHealthProbe.swift +110 -0
  312. package/native/ios/packages/ios/Sources/CrumbCore/CrumbJavaScriptCrashRecovery.swift +136 -0
  313. package/native/ios/packages/ios/Sources/CrumbCore/CrumbJavaScriptCrashStore.swift +502 -0
  314. package/native/ios/packages/ios/Sources/CrumbCore/CrumbJavaScriptFailureContext.swift +166 -0
  315. package/native/ios/packages/ios/Sources/CrumbCore/CrumbLogs.swift +138 -0
  316. package/native/ios/packages/ios/Sources/CrumbCore/CrumbPolicy.swift +336 -0
  317. package/native/ios/packages/ios/Sources/CrumbCore/CrumbRendering.swift +68 -0
  318. package/native/ios/packages/ios/Sources/CrumbCore/CrumbReportEnvelope.swift +666 -0
  319. package/native/ios/packages/ios/Sources/CrumbCore/CrumbReportQueue.swift +543 -0
  320. package/native/ios/packages/ios/Sources/CrumbCore/CrumbReportUploader.swift +304 -0
  321. package/native/ios/packages/ios/Sources/CrumbCore/CrumbRuntime.swift +306 -0
  322. package/native/ios/packages/ios/Sources/CrumbCore/CrumbSDKVersion.swift +5 -0
  323. package/native/ios/packages/ios/Sources/CrumbCore/CrumbScreenContext.swift +29 -0
  324. package/native/ios/packages/ios/Sources/CrumbUI/CrumbDesign.swift +151 -0
  325. package/native/ios/packages/ios/Sources/CrumbUI/CrumbLocalization.swift +23 -0
  326. package/native/ios/packages/ios/Sources/CrumbUI/CrumbQualityInstrumentation.swift +30 -0
  327. package/native/ios/packages/ios/Sources/CrumbUI/CrumbRenderingMonitor.swift +40 -0
  328. package/native/ios/packages/ios/Sources/CrumbUI/CrumbReporter.swift +1705 -0
  329. package/native/ios/packages/ios/Sources/CrumbUI/CrumbReporterLifecycle.swift +119 -0
  330. package/native/ios/packages/ios/Sources/CrumbUI/CrumbScreenshotArtifact.swift +186 -0
  331. package/native/ios/packages/ios/Sources/CrumbUI/CrumbShakeGestureRecognizer.swift +62 -0
  332. package/native/ios/packages/ios/Sources/CrumbUI/CrumbUploadCoordinator.swift +113 -0
  333. package/native/ios/packages/ios/Sources/CrumbUI/CrumbWorkspacePolicyCoordinator.swift +86 -0
  334. package/native/ios/packages/ios/Sources/CrumbUI/OnDemandDiagnosticsCollector.swift +269 -0
  335. package/native/ios/packages/ios/Sources/CrumbUI/OnDemandLogCollector.swift +213 -0
  336. package/native/ios/packages/ios/Sources/CrumbUI/Resources/en.lproj/Localizable.strings +75 -0
  337. package/native/ios/provenance.json +8 -0
  338. package/nitrogen/generated/android/c++/JHybridCrumbReactNativeSpec.cpp +32 -3
  339. package/nitrogen/generated/android/c++/JHybridCrumbReactNativeSpec.hpp +5 -1
  340. package/nitrogen/generated/android/kotlin/com/margelo/nitro/crumbsdk/reactnative/HybridCrumbReactNativeSpec.kt +17 -1
  341. package/nitrogen/generated/ios/CrumbReactNative-Swift-Cxx-Bridge.hpp +9 -0
  342. package/nitrogen/generated/ios/c++/HybridCrumbReactNativeSpecSwift.hpp +30 -2
  343. package/nitrogen/generated/ios/swift/HybridCrumbReactNativeSpec.swift +5 -1
  344. package/nitrogen/generated/ios/swift/HybridCrumbReactNativeSpec_cxx.swift +55 -2
  345. package/nitrogen/generated/shared/c++/HybridCrumbReactNativeSpec.cpp +4 -0
  346. package/nitrogen/generated/shared/c++/HybridCrumbReactNativeSpec.hpp +5 -1
  347. package/package.json +15 -5
  348. package/plugin/podfile.cjs +21 -0
  349. package/scripts/ios.rb +23 -0
  350. package/src/CrumbReactNative.nitro.ts +5 -1
  351. package/src/NativeCrumbReactNative.web.ts +4 -0
  352. package/src/index.ts +218 -1
  353. package/src/javascript-crash-capture.ts +774 -0
  354. package/src/log-buffer.ts +27 -2
  355. package/src/screen-context.ts +154 -0
  356. package/src/types.ts +53 -0
@@ -0,0 +1,1705 @@
1
+ import CrumbCore
2
+ #if canImport(UIKit)
3
+ import UIKit
4
+
5
+ private extension CrumbTheme {
6
+ var uiStyle: UIUserInterfaceStyle {
7
+ switch self {
8
+ case .system: .unspecified
9
+ case .light: .light
10
+ case .dark: .dark
11
+ }
12
+ }
13
+ }
14
+
15
+ public extension Crumb {
16
+ @MainActor
17
+ @discardableResult
18
+ static func show(trigger: CrumbInvocation = .programmatic, screenContextJSON: String? = nil) -> Bool {
19
+ CrumbWorkspacePolicyCoordinator.shared.install()
20
+ return CrumbReporterPresenter.shared.show(trigger: trigger, screenContextJSON: screenContextJSON)
21
+ }
22
+ }
23
+
24
+ @MainActor
25
+ private final class CrumbReporterPresenter: NSObject, UIAdaptivePresentationControllerDelegate {
26
+ static let shared = CrumbReporterPresenter()
27
+
28
+ private weak var presentedController: UIViewController?
29
+ private var accessibilityHiddenHostViews: [(view: UIView, wasHidden: Bool)] = []
30
+ private var activeSessionID: UUID?
31
+ private var isTransitioningFromShakePrompt = false
32
+
33
+ private struct PresentationContext {
34
+ let screenshotArtifact: CrumbScreenshotArtifact?
35
+ let screenshotCapture: CrumbScreenshotCaptureState
36
+ let screenshotMasking: CrumbScreenshotMaskingState
37
+ let settings: CrumbReportSettings
38
+ let location: String
39
+ let trigger: CrumbInvocation
40
+ let triggeredAt: Date
41
+ let invocationStartedAtNanoseconds: UInt64
42
+ }
43
+
44
+ func show(trigger: CrumbInvocation, screenContextJSON: String? = nil) -> Bool {
45
+ let invocationStartedAtNanoseconds = DispatchTime.now().uptimeNanoseconds
46
+ reconcilePresentationState()
47
+ guard activeSessionID == nil else { return false }
48
+ guard let presenter = Self.topViewController() else { return false }
49
+ guard var settings = try? Crumb.reportSettings() else { return false }
50
+ if let screenContextJSON {
51
+ settings.screenContext = settings.evidence.contains(.customContext) ? CrumbScreenContext.decode(screenContextJSON) : nil
52
+ }
53
+ guard settings.invocation.contains(trigger) else { return false }
54
+ let triggeredAt = Date()
55
+ let sessionID = UUID()
56
+
57
+ let screenshotArtifact: CrumbScreenshotArtifact?
58
+ if settings.evidence.contains(.screenshot),
59
+ settings.capture.screenshot,
60
+ let window = presenter.view.window {
61
+ screenshotArtifact = CrumbScreenshotArtifactPipeline.capture(
62
+ window: window,
63
+ capture: settings.capture,
64
+ privacy: settings.privacy
65
+ )
66
+ } else {
67
+ screenshotArtifact = nil
68
+ }
69
+ CrumbQualityInstrumentation.record(
70
+ kind: .screenshotReady,
71
+ startedAtNanoseconds: invocationStartedAtNanoseconds
72
+ )
73
+ let screenshotCapture: CrumbScreenshotCaptureState = if !settings.capture.screenshot {
74
+ .disabledByConfiguration
75
+ } else if !settings.evidence.contains(.screenshot) {
76
+ .disabledByPolicy
77
+ } else if screenshotArtifact != nil {
78
+ .enabled
79
+ } else {
80
+ .unavailable
81
+ }
82
+ let screenshotMasking: CrumbScreenshotMaskingState = if let screenshotArtifact {
83
+ screenshotArtifact.maskingState
84
+ } else if settings.evidence.contains(.screenshot)
85
+ && settings.capture.screenshot
86
+ && (settings.privacy.maskAllTextInputs
87
+ || settings.privacy.maskScreenshotsBeforeUpload) {
88
+ .failed
89
+ } else {
90
+ .notApplicable
91
+ }
92
+
93
+ let context = PresentationContext(
94
+ screenshotArtifact: screenshotArtifact,
95
+ screenshotCapture: screenshotCapture,
96
+ screenshotMasking: screenshotMasking,
97
+ settings: settings,
98
+ location: String(reflecting: type(of: presenter)),
99
+ trigger: trigger,
100
+ triggeredAt: triggeredAt,
101
+ invocationStartedAtNanoseconds: invocationStartedAtNanoseconds
102
+ )
103
+
104
+ activeSessionID = sessionID
105
+ accessibilityHiddenHostViews = [
106
+ presenter.view,
107
+ presenter.navigationController?.navigationBar,
108
+ presenter.tabBarController?.tabBar
109
+ ].compactMap { $0 }.map { ($0, $0.accessibilityElementsHidden) }
110
+ accessibilityHiddenHostViews.forEach { $0.view.accessibilityElementsHidden = true }
111
+ CrumbReporterLifecycle.shared.reporterPresentationDidBegin()
112
+
113
+ if trigger == .shake {
114
+ presentShakePrompt(context: context, from: presenter, sessionID: sessionID)
115
+ } else {
116
+ presentReporter(context: context, from: presenter, sessionID: sessionID)
117
+ }
118
+ return true
119
+ }
120
+
121
+ private func presentReporter(
122
+ context: PresentationContext,
123
+ from presenter: UIViewController,
124
+ sessionID: UUID
125
+ ) {
126
+ guard activeSessionID == sessionID else { return }
127
+ let reporter = ReporterViewController(
128
+ screenshotArtifact: context.screenshotArtifact,
129
+ screenshotCapture: context.screenshotCapture,
130
+ screenshotMasking: context.screenshotMasking,
131
+ settings: context.settings,
132
+ location: context.location,
133
+ trigger: context.trigger,
134
+ triggeredAt: context.triggeredAt,
135
+ invocationStartedAtNanoseconds: context.invocationStartedAtNanoseconds,
136
+ onFinish: { [weak self] in self?.finish(sessionID: sessionID) }
137
+ )
138
+ let navigationController = UINavigationController(rootViewController: reporter)
139
+ // A system page sheet promotes itself for the keyboard. The reporter owns
140
+ // its content-sized panel instead, coordinating one keyboard transition.
141
+ navigationController.modalPresentationStyle = .overFullScreen
142
+ navigationController.modalTransitionStyle = .crossDissolve
143
+ navigationController.view.backgroundColor = .clear
144
+ navigationController.overrideUserInterfaceStyle = context.settings.reporter.theme.uiStyle
145
+ navigationController.setNavigationBarHidden(true, animated: false)
146
+ let appearance = CrumbDesign.navigationAppearance()
147
+ navigationController.navigationBar.standardAppearance = appearance
148
+ navigationController.navigationBar.scrollEdgeAppearance = appearance
149
+ navigationController.navigationBar.compactAppearance = appearance
150
+ navigationController.navigationBar.tintColor = CrumbDesign.Color.accentDark
151
+ navigationController.view.accessibilityViewIsModal = true
152
+ presentedController = navigationController
153
+ navigationController.presentationController?.delegate = self
154
+ presenter.present(navigationController, animated: true)
155
+ CrumbQualityInstrumentation.record(
156
+ kind: .formReady,
157
+ startedAtNanoseconds: context.invocationStartedAtNanoseconds
158
+ )
159
+ navigationController.presentationController?.delegate = self
160
+ isTransitioningFromShakePrompt = false
161
+ }
162
+
163
+ private func presentShakePrompt(
164
+ context: PresentationContext,
165
+ from presenter: UIViewController,
166
+ sessionID: UUID
167
+ ) {
168
+ let prompt = ShakePromptViewController()
169
+ prompt.overrideUserInterfaceStyle = context.settings.reporter.theme.uiStyle
170
+ prompt.modalPresentationStyle = .overFullScreen
171
+ prompt.modalTransitionStyle = .crossDissolve
172
+ prompt.onReport = { [weak self, weak prompt, weak presenter] in
173
+ guard let self, let prompt, let presenter,
174
+ self.activeSessionID == sessionID else { return }
175
+ self.isTransitioningFromShakePrompt = true
176
+ self.presentedController = nil
177
+ prompt.dismiss(animated: true) { [weak self, weak presenter] in
178
+ guard let self, let presenter,
179
+ self.activeSessionID == sessionID else { return }
180
+ self.presentReporter(context: context, from: presenter, sessionID: sessionID)
181
+ }
182
+ }
183
+ prompt.onDismiss = { [weak self, weak prompt] in
184
+ guard let self, let prompt,
185
+ self.activeSessionID == sessionID else { return }
186
+ self.presentedController = nil
187
+ prompt.dismiss(animated: true) { [weak self] in
188
+ self?.finish(sessionID: sessionID)
189
+ }
190
+ }
191
+ presentedController = prompt
192
+ presenter.present(prompt, animated: true)
193
+ }
194
+
195
+ func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
196
+ guard presentationController.presentedViewController === presentedController else { return }
197
+ if let activeSessionID {
198
+ finish(sessionID: activeSessionID)
199
+ }
200
+ }
201
+
202
+ func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
203
+ guard let navigation = presentationController.presentedViewController as? UINavigationController,
204
+ let reporter = navigation.viewControllers.first as? ReporterViewController else {
205
+ return true
206
+ }
207
+ return !reporter.hasMeaningfulInput
208
+ }
209
+
210
+ func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
211
+ guard let navigation = presentationController.presentedViewController as? UINavigationController,
212
+ let reporter = navigation.viewControllers.first as? ReporterViewController else {
213
+ return
214
+ }
215
+ reporter.requestCancel()
216
+ }
217
+
218
+ private func finish(sessionID: UUID) {
219
+ guard activeSessionID == sessionID else { return }
220
+ activeSessionID = nil
221
+ presentedController = nil
222
+ isTransitioningFromShakePrompt = false
223
+ accessibilityHiddenHostViews.forEach {
224
+ $0.view.accessibilityElementsHidden = $0.wasHidden
225
+ }
226
+ accessibilityHiddenHostViews = []
227
+ CrumbReporterLifecycle.shared.reporterPresentationDidEnd()
228
+ CrumbQualityInstrumentation.record(
229
+ kind: .reporterClosed,
230
+ startedAtNanoseconds: DispatchTime.now().uptimeNanoseconds
231
+ )
232
+ }
233
+
234
+ private func reconcilePresentationState() {
235
+ guard activeSessionID != nil,
236
+ presentedController == nil,
237
+ !isTransitioningFromShakePrompt else { return }
238
+ activeSessionID = nil
239
+ }
240
+
241
+ private static func topViewController() -> UIViewController? {
242
+ let window = UIApplication.shared.connectedScenes
243
+ .compactMap { $0 as? UIWindowScene }
244
+ .flatMap(\.windows)
245
+ .first { $0.isKeyWindow }
246
+
247
+ var current = window?.rootViewController
248
+ while true {
249
+ if let presented = current?.presentedViewController {
250
+ current = presented
251
+ } else if let navigation = current as? UINavigationController {
252
+ current = navigation.visibleViewController
253
+ } else if let tabs = current as? UITabBarController {
254
+ current = tabs.selectedViewController
255
+ } else {
256
+ return current
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ @MainActor
263
+ private final class ShakePromptViewController: UIViewController {
264
+ var onReport: (() -> Void)?
265
+ var onDismiss: (() -> Void)?
266
+
267
+ private let titleLabel = UILabel()
268
+ private var didMoveInitialAccessibilityFocus = false
269
+
270
+ override func viewDidLoad() {
271
+ super.viewDidLoad()
272
+ view.backgroundColor = .clear
273
+ view.accessibilityViewIsModal = true
274
+
275
+ let dismissSurface = UIButton(type: .custom)
276
+ dismissSurface.backgroundColor = .clear
277
+ dismissSurface.accessibilityLabel = crumbLocalized("Cancel")
278
+ dismissSurface.accessibilityIdentifier = "crumb.shake-dismiss"
279
+ dismissSurface.addAction(UIAction { [weak self] _ in self?.onDismiss?() }, for: .touchUpInside)
280
+ dismissSurface.translatesAutoresizingMaskIntoConstraints = false
281
+ view.addSubview(dismissSurface)
282
+
283
+ let card = UIStackView()
284
+ card.axis = .horizontal
285
+ card.alignment = .center
286
+ card.spacing = 13
287
+ card.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
288
+ card.isLayoutMarginsRelativeArrangement = true
289
+ card.backgroundColor = CrumbDesign.Color.canvas.withAlphaComponent(0.94)
290
+ card.layer.cornerRadius = 20
291
+ card.layer.cornerCurve = .continuous
292
+ card.layer.shadowColor = CrumbDesign.Color.ink.cgColor
293
+ card.layer.shadowOpacity = 0.16
294
+ card.layer.shadowRadius = 17
295
+ card.layer.shadowOffset = CGSize(width: 0, height: 12)
296
+ card.translatesAutoresizingMaskIntoConstraints = false
297
+ view.addSubview(card)
298
+
299
+ let mark = CrumbMarkView(showsTile: true)
300
+ mark.translatesAutoresizingMaskIntoConstraints = false
301
+ NSLayoutConstraint.activate([
302
+ mark.widthAnchor.constraint(equalToConstant: 42),
303
+ mark.heightAnchor.constraint(equalToConstant: 42)
304
+ ])
305
+ card.addArrangedSubview(mark)
306
+
307
+ let copy = UIStackView()
308
+ copy.axis = .vertical
309
+ copy.spacing = 2
310
+ copy.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
311
+
312
+ titleLabel.text = crumbLocalized("Report a problem?")
313
+ titleLabel.font = .preferredFont(forTextStyle: .headline).withWeight(.semibold)
314
+ titleLabel.adjustsFontForContentSizeCategory = true
315
+ titleLabel.textColor = CrumbDesign.Color.ink
316
+ titleLabel.accessibilityIdentifier = "crumb.shake-prompt-title"
317
+ copy.addArrangedSubview(titleLabel)
318
+
319
+ let message = UILabel()
320
+ message.text = crumbLocalized("We noticed a shake on this screen.")
321
+ message.font = .preferredFont(forTextStyle: .subheadline)
322
+ message.adjustsFontForContentSizeCategory = true
323
+ message.textColor = CrumbDesign.Color.secondaryText
324
+ message.numberOfLines = 0
325
+ copy.addArrangedSubview(message)
326
+ card.addArrangedSubview(copy)
327
+
328
+ let report = UIButton(type: .system)
329
+ var reportConfiguration = UIButton.Configuration.plain()
330
+ reportConfiguration.title = crumbLocalized("Report")
331
+ reportConfiguration.baseForegroundColor = CrumbDesign.Color.accentDark
332
+ reportConfiguration.contentInsets = NSDirectionalEdgeInsets(
333
+ top: 10,
334
+ leading: 12,
335
+ bottom: 10,
336
+ trailing: 12
337
+ )
338
+ reportConfiguration.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer {
339
+ attributes in
340
+ var updated = attributes
341
+ updated.font = .preferredFont(forTextStyle: .headline).withWeight(.semibold)
342
+ return updated
343
+ }
344
+ report.configuration = reportConfiguration
345
+ report.accessibilityIdentifier = "crumb.shake-report"
346
+ report.setContentHuggingPriority(.required, for: .horizontal)
347
+ report.setContentCompressionResistancePriority(.required, for: .horizontal)
348
+ report.addAction(UIAction { [weak self] _ in self?.onReport?() }, for: .touchUpInside)
349
+ card.addArrangedSubview(report)
350
+
351
+ NSLayoutConstraint.activate([
352
+ dismissSurface.leadingAnchor.constraint(equalTo: view.leadingAnchor),
353
+ dismissSurface.trailingAnchor.constraint(equalTo: view.trailingAnchor),
354
+ dismissSurface.topAnchor.constraint(equalTo: view.topAnchor),
355
+ dismissSurface.bottomAnchor.constraint(equalTo: view.bottomAnchor),
356
+ card.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 12),
357
+ card.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -12),
358
+ card.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
359
+ card.heightAnchor.constraint(greaterThanOrEqualToConstant: 74)
360
+ ])
361
+ }
362
+
363
+ override func viewDidAppear(_ animated: Bool) {
364
+ super.viewDidAppear(animated)
365
+ guard !didMoveInitialAccessibilityFocus else { return }
366
+ didMoveInitialAccessibilityFocus = true
367
+ UIAccessibility.post(notification: .screenChanged, argument: titleLabel)
368
+ }
369
+ }
370
+
371
+ private final class CrumbMarkView: UIView {
372
+ private let showsTile: Bool
373
+
374
+ init(showsTile: Bool = false) {
375
+ self.showsTile = showsTile
376
+ super.init(frame: .zero)
377
+ backgroundColor = showsTile ? CrumbDesign.Color.markTile : .clear
378
+ if showsTile {
379
+ layer.cornerRadius = 11
380
+ layer.cornerCurve = .continuous
381
+ }
382
+ isAccessibilityElement = false
383
+ }
384
+
385
+ @available(*, unavailable)
386
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
387
+
388
+ override func draw(_ rect: CGRect) {
389
+ super.draw(rect)
390
+ guard let context = UIGraphicsGetCurrentContext() else { return }
391
+ let glyphSize = min(24, min(bounds.width, bounds.height))
392
+ let scale = glyphSize / 64
393
+ let offset = CGPoint(
394
+ x: (bounds.width - glyphSize) / 2,
395
+ y: (bounds.height - glyphSize) / 2
396
+ )
397
+ let dotColor = showsTile ? UIColor.white : CrumbDesign.Color.ink
398
+ context.setFillColor(dotColor.cgColor)
399
+ let points: [CGPoint] = [
400
+ CGPoint(x: 12, y: 12), CGPoint(x: 32, y: 12), CGPoint(x: 52, y: 12),
401
+ CGPoint(x: 12, y: 32), CGPoint(x: 52, y: 32),
402
+ CGPoint(x: 12, y: 52), CGPoint(x: 32, y: 52), CGPoint(x: 52, y: 52)
403
+ ]
404
+ for point in points {
405
+ let center = CGPoint(x: offset.x + point.x * scale, y: offset.y + point.y * scale)
406
+ let radius = 5.5 * scale
407
+ context.fillEllipse(in: CGRect(
408
+ x: center.x - radius,
409
+ y: center.y - radius,
410
+ width: radius * 2,
411
+ height: radius * 2
412
+ ))
413
+ }
414
+ context.setStrokeColor(CrumbDesign.Color.accent.cgColor)
415
+ context.setLineWidth(6 * scale)
416
+ let center = CGPoint(x: offset.x + 32 * scale, y: offset.y + 32 * scale)
417
+ let radius = 8.5 * scale
418
+ context.strokeEllipse(in: CGRect(
419
+ x: center.x - radius,
420
+ y: center.y - radius,
421
+ width: radius * 2,
422
+ height: radius * 2
423
+ ))
424
+ }
425
+ }
426
+
427
+ private final class CrumbCategoryControl: UIControl {
428
+ private let buttons: [UIButton]
429
+ private(set) var selectedSegmentIndex = 0
430
+
431
+ init(items: [String]) {
432
+ buttons = items.enumerated().map { index, title in
433
+ let button = UIButton(type: .custom)
434
+ button.tag = index
435
+ button.setTitle(title, for: .normal)
436
+ button.titleLabel?.font = UIFont.systemFont(ofSize: 15)
437
+ button.setTitleColor(CrumbDesign.Color.secondaryText, for: .normal)
438
+ button.accessibilityLabel = title
439
+ return button
440
+ }
441
+ super.init(frame: .zero)
442
+
443
+ backgroundColor = CrumbDesign.Color.mutedSurface
444
+ layer.cornerRadius = 11
445
+ layer.cornerCurve = .continuous
446
+ accessibilityIdentifier = "crumb.category"
447
+
448
+ let stack = UIStackView(arrangedSubviews: buttons)
449
+ stack.axis = .horizontal
450
+ stack.distribution = .fillEqually
451
+ stack.spacing = 0
452
+ stack.translatesAutoresizingMaskIntoConstraints = false
453
+ addSubview(stack)
454
+ NSLayoutConstraint.activate([
455
+ stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 3),
456
+ stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -3),
457
+ stack.topAnchor.constraint(equalTo: topAnchor, constant: 3),
458
+ stack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -3),
459
+ heightAnchor.constraint(equalToConstant: 42)
460
+ ])
461
+ buttons.forEach { button in
462
+ button.addAction(UIAction { [weak self, weak button] _ in
463
+ guard let self, let button else { return }
464
+ self.setSelectedSegmentIndex(button.tag, sendsEvent: true)
465
+ }, for: .touchUpInside)
466
+ }
467
+ updateAppearance()
468
+ }
469
+
470
+ @available(*, unavailable)
471
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
472
+
473
+ func setSelectedSegmentIndex(_ index: Int, sendsEvent: Bool = false) {
474
+ guard buttons.indices.contains(index) else { return }
475
+ selectedSegmentIndex = index
476
+ updateAppearance()
477
+ if sendsEvent { sendActions(for: .valueChanged) }
478
+ }
479
+
480
+ private func updateAppearance() {
481
+ for (index, button) in buttons.enumerated() {
482
+ let selected = index == selectedSegmentIndex
483
+ button.backgroundColor = selected ? CrumbDesign.Color.elevatedSurface : .clear
484
+ button.layer.cornerRadius = selected ? 9 : 0
485
+ button.layer.cornerCurve = .continuous
486
+ button.layer.shadowColor = selected ? CrumbDesign.Color.ink.cgColor : nil
487
+ button.layer.shadowOpacity = selected ? 0.10 : 0
488
+ button.layer.shadowRadius = selected ? 3 : 0
489
+ button.layer.shadowOffset = selected ? CGSize(width: 0, height: 1) : .zero
490
+ button.titleLabel?.font = UIFont.systemFont(
491
+ ofSize: 15,
492
+ weight: selected ? .semibold : .regular
493
+ )
494
+ button.setTitleColor(
495
+ selected ? CrumbDesign.Color.ink : CrumbDesign.Color.secondaryText,
496
+ for: .normal
497
+ )
498
+ button.accessibilityTraits = selected ? [.button, .selected] : .button
499
+ }
500
+ }
501
+ }
502
+
503
+ @MainActor
504
+ private final class ReporterViewController: UIViewController, UITextViewDelegate {
505
+ private var screenshotArtifact: CrumbScreenshotArtifact?
506
+ private let screenshotCapture: CrumbScreenshotCaptureState
507
+ private let screenshotMasking: CrumbScreenshotMaskingState
508
+ private let settings: CrumbReportSettings
509
+ private let location: String
510
+ private let trigger: CrumbInvocation
511
+ private let triggeredAt: Date
512
+ private let invocationStartedAtNanoseconds: UInt64
513
+ private let onFinish: () -> Void
514
+
515
+ private var diagnostics: CrumbDiagnosticsSnapshot?
516
+ private let categoryControl = CrumbCategoryControl(
517
+ items: ["Bug", "Feedback", "Other"].map(crumbLocalized)
518
+ )
519
+ private let descriptionView = UITextView()
520
+ private let descriptionPlaceholder = UILabel()
521
+ private let submitButton = UIButton(type: .system)
522
+ private let reviewHeaderButton = UIButton(type: .system)
523
+ private let keyboardStatusRow = UIStackView()
524
+ private let screenshotStatusLabel = UILabel()
525
+ private let formSurfaceView = UIView()
526
+ private var composerHeightConstraint: NSLayoutConstraint?
527
+ private var composerBottomConstraint: NSLayoutConstraint?
528
+ private var keyboardFrameInScreen: CGRect?
529
+ private var isEditingDescription = false
530
+ private weak var formScrollView: UIScrollView?
531
+ private weak var contentStack: UIStackView?
532
+ private weak var descriptionCard: UIView?
533
+ private weak var screenshotCard: UIView?
534
+ private var diagnosticsTask: Task<Void, Never>?
535
+ private var didNotifyFinish = false
536
+ private var didMoveInitialAccessibilityFocus = false
537
+
538
+ var hasMeaningfulInput: Bool {
539
+ (settings.reporter.visibleFields.contains(.category) && categoryControl.selectedSegmentIndex != 0)
540
+ || !descriptionView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
541
+ }
542
+
543
+ init(
544
+ screenshotArtifact: CrumbScreenshotArtifact?,
545
+ screenshotCapture: CrumbScreenshotCaptureState,
546
+ screenshotMasking: CrumbScreenshotMaskingState,
547
+ settings: CrumbReportSettings,
548
+ location: String,
549
+ trigger: CrumbInvocation,
550
+ triggeredAt: Date,
551
+ invocationStartedAtNanoseconds: UInt64,
552
+ onFinish: @escaping () -> Void
553
+ ) {
554
+ self.screenshotArtifact = screenshotArtifact
555
+ self.screenshotCapture = screenshotCapture
556
+ self.screenshotMasking = screenshotMasking
557
+ self.settings = settings
558
+ self.location = location
559
+ self.trigger = trigger
560
+ self.triggeredAt = triggeredAt
561
+ self.invocationStartedAtNanoseconds = invocationStartedAtNanoseconds
562
+ self.onFinish = onFinish
563
+ super.init(nibName: nil, bundle: nil)
564
+ }
565
+
566
+ @available(*, unavailable)
567
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
568
+
569
+ deinit {
570
+ diagnosticsTask?.cancel()
571
+ }
572
+
573
+ override func viewDidLoad() {
574
+ super.viewDidLoad()
575
+ title = crumbLocalized("Report a problem")
576
+ view.backgroundColor = UIColor.black.withAlphaComponent(0.2)
577
+
578
+ formSurfaceView.backgroundColor = CrumbDesign.Color.canvas
579
+ formSurfaceView.layer.cornerRadius = 26
580
+ formSurfaceView.layer.cornerCurve = .continuous
581
+ formSurfaceView.translatesAutoresizingMaskIntoConstraints = false
582
+ view.addSubview(formSurfaceView)
583
+
584
+ NSLayoutConstraint.activate([
585
+ formSurfaceView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
586
+ formSurfaceView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
587
+ formSurfaceView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
588
+ ])
589
+
590
+ let grabber = UIButton(type: .system)
591
+ grabber.accessibilityLabel = crumbLocalized("Cancel")
592
+ grabber.accessibilityIdentifier = "crumb.reporter-grabber"
593
+ grabber.translatesAutoresizingMaskIntoConstraints = false
594
+ grabber.addAction(UIAction { [weak self] _ in self?.requestCancel() }, for: .touchUpInside)
595
+ grabber.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(dragGrabber(_:))))
596
+ formSurfaceView.addSubview(grabber)
597
+ let grabberLine = UIView()
598
+ grabberLine.isUserInteractionEnabled = false
599
+ grabberLine.backgroundColor = CrumbDesign.Color.disabled
600
+ grabberLine.layer.cornerRadius = 2.5
601
+ grabberLine.translatesAutoresizingMaskIntoConstraints = false
602
+ grabber.addSubview(grabberLine)
603
+ NSLayoutConstraint.activate([
604
+ grabber.topAnchor.constraint(equalTo: formSurfaceView.topAnchor),
605
+ grabber.centerXAnchor.constraint(equalTo: formSurfaceView.centerXAnchor),
606
+ grabber.widthAnchor.constraint(equalToConstant: 76),
607
+ grabber.heightAnchor.constraint(equalToConstant: 27),
608
+ grabberLine.centerXAnchor.constraint(equalTo: grabber.centerXAnchor),
609
+ grabberLine.centerYAnchor.constraint(equalTo: grabber.centerYAnchor),
610
+ grabberLine.widthAnchor.constraint(equalToConstant: 38),
611
+ grabberLine.heightAnchor.constraint(equalToConstant: 5)
612
+ ])
613
+
614
+ let content = UIStackView()
615
+ content.axis = .vertical
616
+ content.spacing = 16
617
+ content.translatesAutoresizingMaskIntoConstraints = false
618
+ contentStack = content
619
+
620
+ let scrollView = UIScrollView()
621
+ scrollView.translatesAutoresizingMaskIntoConstraints = false
622
+ view.addSubview(scrollView)
623
+ scrollView.addSubview(content)
624
+
625
+ formScrollView = scrollView
626
+ let composerHeight = scrollView.heightAnchor.constraint(equalToConstant: 580)
627
+ composerHeight.priority = UILayoutPriority(999)
628
+ composerHeightConstraint = composerHeight
629
+
630
+ let composerBottom = scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
631
+ composerBottomConstraint = composerBottom
632
+ NSLayoutConstraint.activate([
633
+ scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
634
+ scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
635
+ composerHeight,
636
+ scrollView.topAnchor.constraint(equalTo: formSurfaceView.topAnchor, constant: 27),
637
+ formSurfaceView.topAnchor.constraint(greaterThanOrEqualTo: view.safeAreaLayoutGuide.topAnchor, constant: 8),
638
+ composerBottom,
639
+ content.leadingAnchor.constraint(
640
+ equalTo: scrollView.contentLayoutGuide.leadingAnchor,
641
+ constant: 18
642
+ ),
643
+ content.trailingAnchor.constraint(
644
+ equalTo: scrollView.contentLayoutGuide.trailingAnchor,
645
+ constant: -18
646
+ ),
647
+ content.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 16),
648
+ content.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -16),
649
+ content.widthAnchor.constraint(
650
+ equalTo: scrollView.frameLayoutGuide.widthAnchor,
651
+ constant: -36
652
+ )
653
+ ])
654
+
655
+ let header = UIStackView()
656
+ header.axis = .horizontal
657
+ header.alignment = .center
658
+
659
+ let cancel = UIButton(type: .system)
660
+ cancel.setTitle(crumbLocalized("Cancel"), for: .normal)
661
+ cancel.setTitleColor(CrumbDesign.Color.secondaryText, for: .normal)
662
+ cancel.titleLabel?.font = .preferredFont(forTextStyle: .body)
663
+ cancel.titleLabel?.adjustsFontForContentSizeCategory = true
664
+ cancel.contentHorizontalAlignment = .leading
665
+ cancel.addAction(UIAction { [weak self] _ in self?.requestCancel() }, for: .touchUpInside)
666
+ cancel.widthAnchor.constraint(equalToConstant: 72).isActive = true
667
+ header.addArrangedSubview(cancel)
668
+
669
+ let headerTitle = UILabel()
670
+ headerTitle.text = crumbLocalized("Report a problem")
671
+ headerTitle.font = .preferredFont(forTextStyle: .headline).withWeight(.semibold)
672
+ headerTitle.adjustsFontForContentSizeCategory = true
673
+ headerTitle.textColor = CrumbDesign.Color.ink
674
+ headerTitle.textAlignment = .center
675
+ headerTitle.accessibilityIdentifier = "crumb.reporter-title"
676
+ header.addArrangedSubview(headerTitle)
677
+
678
+ reviewHeaderButton.setTitle(crumbLocalized("Review"), for: .normal)
679
+ reviewHeaderButton.setTitleColor(CrumbDesign.Color.accentDark, for: .normal)
680
+ reviewHeaderButton.setTitleColor(CrumbDesign.Color.disabled, for: .disabled)
681
+ reviewHeaderButton.titleLabel?.font = .preferredFont(forTextStyle: .headline).withWeight(.semibold)
682
+ reviewHeaderButton.titleLabel?.adjustsFontForContentSizeCategory = true
683
+ reviewHeaderButton.contentHorizontalAlignment = .trailing
684
+ reviewHeaderButton.isEnabled = false
685
+ reviewHeaderButton.addAction(UIAction { [weak self] _ in self?.reviewDraft() }, for: .touchUpInside)
686
+ reviewHeaderButton.widthAnchor.constraint(equalToConstant: 72).isActive = true
687
+ header.addArrangedSubview(reviewHeaderButton)
688
+ content.addArrangedSubview(header)
689
+
690
+ categoryControl.addAction(
691
+ UIAction { [weak self] _ in self?.updateSubmitButton() },
692
+ for: .valueChanged
693
+ )
694
+ categoryControl.isHidden = !settings.reporter.visibleFields.contains(.category)
695
+ content.addArrangedSubview(categoryControl)
696
+
697
+ let descriptionCard = UIStackView()
698
+ descriptionCard.axis = .vertical
699
+ descriptionCard.spacing = 4
700
+ descriptionCard.layoutMargins = .zero
701
+ descriptionCard.isLayoutMarginsRelativeArrangement = true
702
+ CrumbDesign.styleCard(descriptionCard, fill: CrumbDesign.Color.elevatedSurface)
703
+ self.descriptionCard = descriptionCard
704
+
705
+ descriptionView.font = .preferredFont(forTextStyle: .body)
706
+ descriptionView.adjustsFontForContentSizeCategory = true
707
+ descriptionView.backgroundColor = .clear
708
+ descriptionView.textColor = CrumbDesign.Color.ink
709
+ descriptionView.isScrollEnabled = false
710
+ descriptionView.textContainerInset = UIEdgeInsets(top: 16, left: 14, bottom: 16, right: 14)
711
+ descriptionView.textContainer.lineFragmentPadding = 0
712
+ descriptionView.accessibilityLabel = crumbLocalized("Problem description")
713
+ descriptionView.accessibilityIdentifier = "crumb.description"
714
+ descriptionView.delegate = self
715
+ descriptionPlaceholder.text = crumbLocalized("What happened?")
716
+ descriptionPlaceholder.font = .preferredFont(forTextStyle: .body)
717
+ descriptionPlaceholder.textColor = CrumbDesign.Color.tertiaryText
718
+ descriptionPlaceholder.isAccessibilityElement = false
719
+ descriptionPlaceholder.translatesAutoresizingMaskIntoConstraints = false
720
+ descriptionView.addSubview(descriptionPlaceholder)
721
+ NSLayoutConstraint.activate([
722
+ descriptionPlaceholder.leadingAnchor.constraint(equalTo: descriptionView.leadingAnchor, constant: 14),
723
+ descriptionPlaceholder.topAnchor.constraint(equalTo: descriptionView.topAnchor, constant: 16)
724
+ ])
725
+ let descriptionHeightConstraint = descriptionView.heightAnchor.constraint(
726
+ greaterThanOrEqualToConstant: 118
727
+ )
728
+ descriptionHeightConstraint.isActive = true
729
+ descriptionCard.addArrangedSubview(descriptionView)
730
+
731
+ content.addArrangedSubview(descriptionCard)
732
+
733
+ keyboardStatusRow.axis = .horizontal
734
+ keyboardStatusRow.alignment = .center
735
+ keyboardStatusRow.spacing = 8
736
+ keyboardStatusRow.isHidden = true
737
+
738
+ let screenshotChip = UIStackView()
739
+ screenshotChip.axis = .horizontal
740
+ screenshotChip.layoutMargins = UIEdgeInsets(top: 7, left: 14, bottom: 7, right: 14)
741
+ screenshotChip.isLayoutMarginsRelativeArrangement = true
742
+ screenshotChip.backgroundColor = CrumbDesign.Color.mutedSurface
743
+ screenshotChip.layer.cornerRadius = 16
744
+ screenshotChip.layer.cornerCurve = .continuous
745
+ screenshotStatusLabel.text = crumbLocalized(screenshotArtifact == nil ? "Screenshot off" : "Screenshot on")
746
+ screenshotStatusLabel.font = .preferredFont(forTextStyle: .caption1)
747
+ screenshotStatusLabel.adjustsFontForContentSizeCategory = true
748
+ screenshotStatusLabel.textColor = CrumbDesign.Color.secondaryText
749
+ screenshotStatusLabel.accessibilityIdentifier = "crumb.keyboard-screenshot-status"
750
+ screenshotChip.addArrangedSubview(screenshotStatusLabel)
751
+ screenshotChip.setContentHuggingPriority(.required, for: .horizontal)
752
+ keyboardStatusRow.addArrangedSubview(screenshotChip)
753
+ keyboardStatusRow.addArrangedSubview(UIView())
754
+ content.addArrangedSubview(keyboardStatusRow)
755
+
756
+ if let screenshot = screenshotArtifact?.preview {
757
+ let screenshotCard = UIStackView()
758
+ screenshotCard.axis = .horizontal
759
+ screenshotCard.alignment = .center
760
+ screenshotCard.spacing = 12
761
+ screenshotCard.layoutMargins = UIEdgeInsets(top: 16, left: 14, bottom: 16, right: 14)
762
+ screenshotCard.isLayoutMarginsRelativeArrangement = true
763
+ CrumbDesign.styleCard(screenshotCard, fill: CrumbDesign.Color.surface)
764
+ self.screenshotCard = screenshotCard
765
+
766
+ let imageView = UIImageView(image: screenshot)
767
+ imageView.contentMode = .scaleAspectFill
768
+ imageView.backgroundColor = CrumbDesign.Color.darkSurface
769
+ imageView.layer.cornerRadius = 7
770
+ imageView.layer.cornerCurve = .continuous
771
+ imageView.clipsToBounds = true
772
+ imageView.accessibilityLabel = crumbLocalized("Masked screenshot preview")
773
+ imageView.accessibilityTraits = [.image, .button]
774
+ imageView.isAccessibilityElement = true
775
+ imageView.widthAnchor.constraint(equalToConstant: 44).isActive = true
776
+ imageView.heightAnchor.constraint(equalToConstant: 58).isActive = true
777
+ screenshotCard.addArrangedSubview(imageView)
778
+
779
+ let screenshotCopy = UIStackView()
780
+ screenshotCopy.axis = .vertical
781
+ screenshotCopy.spacing = 2
782
+ screenshotCopy.addArrangedSubview(CrumbDesign.label(
783
+ crumbLocalized("Screenshot attached"),
784
+ style: .subheadline,
785
+ weight: .medium
786
+ ))
787
+ screenshotCopy.addArrangedSubview(CrumbDesign.label(
788
+ screenshotMasking == .applied
789
+ ? crumbLocalized("Sensitive fields masked · tap to preview")
790
+ : crumbLocalized("Tap to preview"),
791
+ style: .caption1,
792
+ color: CrumbDesign.Color.mutedText
793
+ ))
794
+ screenshotCard.addArrangedSubview(screenshotCopy)
795
+
796
+ imageView.isUserInteractionEnabled = true
797
+ imageView.addGestureRecognizer(
798
+ UITapGestureRecognizer(target: self, action: #selector(previewScreenshot))
799
+ )
800
+ screenshotCopy.isUserInteractionEnabled = true
801
+ screenshotCopy.addGestureRecognizer(
802
+ UITapGestureRecognizer(target: self, action: #selector(previewScreenshot))
803
+ )
804
+ imageView.accessibilityHint = crumbLocalized("Opens the screenshot preview")
805
+
806
+ let remove = UIButton(type: .system)
807
+ remove.setTitle(crumbLocalized("Remove"), for: .normal)
808
+ remove.setTitleColor(CrumbDesign.Color.accentDark, for: .normal)
809
+ remove.titleLabel?.font = .preferredFont(forTextStyle: .subheadline)
810
+ remove.addAction(UIAction { [weak self, weak screenshotCard, weak content] _ in
811
+ self?.screenshotArtifact = nil
812
+ self?.screenshotStatusLabel.text = crumbLocalized("Screenshot off")
813
+ if let screenshotCard {
814
+ content?.removeArrangedSubview(screenshotCard)
815
+ screenshotCard.removeFromSuperview()
816
+ }
817
+ }, for: .touchUpInside)
818
+ screenshotCard.addArrangedSubview(remove)
819
+ content.addArrangedSubview(screenshotCard)
820
+ }
821
+
822
+ submitButton.configuration = CrumbDesign.primaryButton(title: crumbLocalized("Review report"))
823
+ submitButton.isEnabled = false
824
+ submitButton.accessibilityIdentifier = "crumb.review-draft"
825
+ submitButton.addAction(UIAction { [weak self] _ in self?.reviewDraft() }, for: .touchUpInside)
826
+ submitButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 52).isActive = true
827
+ content.addArrangedSubview(submitButton)
828
+
829
+ NotificationCenter.default.addObserver(
830
+ self, selector: #selector(keyboardWillChangeFrame(_:)),
831
+ name: UIResponder.keyboardWillChangeFrameNotification, object: nil
832
+ )
833
+ gatherDiagnostics()
834
+ }
835
+
836
+ override func viewWillAppear(_ animated: Bool) {
837
+ super.viewWillAppear(animated)
838
+ if !descriptionView.isFirstResponder {
839
+ isEditingDescription = false
840
+ keyboardFrameInScreen = nil
841
+ composerBottomConstraint?.constant = 0
842
+ setKeyboardLayout(false)
843
+ setExpandedControlsVisible(true)
844
+ }
845
+ }
846
+
847
+ override func viewDidAppear(_ animated: Bool) {
848
+ super.viewDidAppear(animated)
849
+ updateComposerHeight()
850
+ guard !didMoveInitialAccessibilityFocus else { return }
851
+ didMoveInitialAccessibilityFocus = true
852
+ UIAccessibility.post(notification: .screenChanged, argument: navigationController?.navigationBar)
853
+ }
854
+
855
+ override func viewDidDisappear(_ animated: Bool) {
856
+ super.viewDidDisappear(animated)
857
+ if isBeingDismissed || navigationController?.isBeingDismissed == true {
858
+ notifyFinish()
859
+ }
860
+ }
861
+
862
+ func textViewDidChange(_ textView: UITextView) {
863
+ let isEmpty = textView.text.isEmpty
864
+ descriptionPlaceholder.isHidden = !isEmpty
865
+ updateSubmitButton()
866
+ updateComposerHeight()
867
+ view.layoutIfNeeded()
868
+ if let caret = descriptionView.selectedTextRange.map({ descriptionView.caretRect(for: $0.end) }),
869
+ let formScrollView {
870
+ let visibleCaret = descriptionView.convert(caret, to: formScrollView).insetBy(dx: 0, dy: -12)
871
+ formScrollView.scrollRectToVisible(visibleCaret, animated: false)
872
+ }
873
+ }
874
+
875
+ func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
876
+ isEditingDescription = true
877
+ setExpandedControlsVisible(false)
878
+ UIView.performWithoutAnimation { self.keyboardStatusRow.alpha = 1 }
879
+ return true
880
+ }
881
+
882
+ func textViewDidBeginEditing(_ textView: UITextView) {
883
+ descriptionCard?.layer.borderWidth = 1.5
884
+ descriptionCard?.layer.borderColor = CrumbDesign.Color.accent.cgColor
885
+ }
886
+
887
+ func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
888
+ // Capture intent before keyboard notifications: isFirstResponder can still
889
+ // be true during dismissal, especially after rotation.
890
+ isEditingDescription = false
891
+ UIView.performWithoutAnimation { self.keyboardStatusRow.alpha = 0 }
892
+ return true
893
+ }
894
+
895
+ func textViewDidEndEditing(_ textView: UITextView) {
896
+ setExpandedControlsVisible(true)
897
+ descriptionCard?.layer.borderWidth = 1
898
+ descriptionCard?.layer.borderColor = CrumbDesign.Color.divider.cgColor
899
+ }
900
+
901
+ private func setExpandedControlsVisible(_ visible: Bool) {
902
+ // Remove these pixels as soon as focus changes, but retain their layout
903
+ // until keyboardWillChangeFrame coordinates the panel's size and position.
904
+ let controls: [UIView?] = [categoryControl, screenshotCard, submitButton]
905
+ UIView.performWithoutAnimation {
906
+ for control in controls.compactMap({ $0 }) {
907
+ control.alpha = visible ? 1 : 0
908
+ control.isUserInteractionEnabled = visible
909
+ control.accessibilityElementsHidden = !visible
910
+ }
911
+ }
912
+ }
913
+
914
+ private func setKeyboardLayout(_ editing: Bool) {
915
+ categoryControl.isHidden = editing || !settings.reporter.visibleFields.contains(.category)
916
+ screenshotCard?.isHidden = editing || screenshotArtifact == nil
917
+ submitButton.isHidden = editing
918
+ keyboardStatusRow.isHidden = !editing
919
+ updateSubmitButton()
920
+ updateComposerHeight()
921
+ }
922
+
923
+ @objc private func keyboardWillChangeFrame(_ notification: Notification) {
924
+ guard navigationController?.topViewController === self,
925
+ view.window != nil,
926
+ let frame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
927
+ view.layoutIfNeeded()
928
+ keyboardFrameInScreen = frame
929
+ let overlap = keyboardOverlap()
930
+ // Collapse the form and move it above the keyboard in the SAME transaction.
931
+ // Updating the form in didBeginEditing first would move it down, then up.
932
+ setKeyboardLayout(isEditingDescription && overlap > 0)
933
+ composerBottomConstraint?.constant = -overlap
934
+ let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double ?? 0.25
935
+ let curve = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt ?? 0
936
+ UIView.animate(withDuration: duration, delay: 0,
937
+ options: [UIView.AnimationOptions(rawValue: curve << 16), .beginFromCurrentState, .allowUserInteraction]) {
938
+ self.view.layoutIfNeeded()
939
+ }
940
+ }
941
+
942
+ private func keyboardOverlap() -> CGFloat {
943
+ guard let keyboardFrameInScreen, let window = view.window else { return 0 }
944
+ let windowFrame = window.convert(keyboardFrameInScreen, from: window.screen.coordinateSpace)
945
+ let frame = view.convert(windowFrame, from: window)
946
+ let intersection = view.bounds.intersection(frame)
947
+ // Undocked keyboards do not determine the panel's bottom edge.
948
+ guard !intersection.isNull, intersection.maxY >= view.bounds.maxY - 1,
949
+ intersection.width >= view.bounds.width * 0.8 else { return 0 }
950
+ return max(0, view.bounds.maxY - view.safeAreaInsets.bottom - intersection.minY)
951
+ }
952
+
953
+ override func viewDidLayoutSubviews() {
954
+ super.viewDidLayoutSubviews()
955
+ composerBottomConstraint?.constant = -keyboardOverlap()
956
+ updateComposerHeight()
957
+ }
958
+
959
+ @objc private func dragGrabber(_ gesture: UIPanGestureRecognizer) {
960
+ guard gesture.state == .ended else { return }
961
+ if gesture.translation(in: view).y > 20 || gesture.velocity(in: view).y > 200 {
962
+ requestCancel()
963
+ }
964
+ }
965
+
966
+ private func updateComposerHeight() {
967
+ guard let contentStack, view.bounds.width > 36 else { return }
968
+ let fittingSize = contentStack.systemLayoutSizeFitting(
969
+ CGSize(width: view.bounds.width - 36, height: UIView.layoutFittingCompressedSize.height),
970
+ withHorizontalFittingPriority: .required,
971
+ verticalFittingPriority: .fittingSizeLevel
972
+ )
973
+ // Top/bottom scroll-content padding. The required safe-area constraint
974
+ // caps this preferred height; longer forms scroll rather than overflowing.
975
+ let requestedHeight = ceil(fittingSize.height) + 32
976
+ guard let composerHeightConstraint,
977
+ abs(composerHeightConstraint.constant - requestedHeight) > 0.5 else { return }
978
+ composerHeightConstraint.constant = requestedHeight
979
+ }
980
+
981
+ func textView(
982
+ _ textView: UITextView,
983
+ shouldChangeTextIn range: NSRange,
984
+ replacementText text: String
985
+ ) -> Bool {
986
+ if text == "\n" {
987
+ textView.resignFirstResponder()
988
+ return false
989
+ }
990
+ guard let current = textView.text,
991
+ let swiftRange = Range(range, in: current) else { return false }
992
+ return current.replacingCharacters(in: swiftRange, with: text).unicodeScalars.count <= 4_000
993
+ }
994
+
995
+ @objc private func previewScreenshot() {
996
+ guard let screenshot = screenshotArtifact?.preview else { return }
997
+ present(ScreenshotPreviewViewController(image: screenshot), animated: true)
998
+ }
999
+
1000
+ private func gatherDiagnostics() {
1001
+ let options = settings.diagnostics
1002
+ let evidence = settings.evidence
1003
+ let location = location
1004
+ let screenContext = settings.screenContext
1005
+ diagnosticsTask = Task { [weak self] in
1006
+ let diagnostics = await Task.detached(priority: .userInitiated) {
1007
+ OnDemandDiagnosticsCollector.capture(
1008
+ location: location,
1009
+ options: options,
1010
+ evidence: evidence,
1011
+ screenContext: screenContext
1012
+ )
1013
+ }.value
1014
+ guard !Task.isCancelled else { return }
1015
+ guard let self else { return }
1016
+ self.diagnostics = diagnostics
1017
+ CrumbQualityInstrumentation.record(
1018
+ kind: .diagnosticsReady,
1019
+ startedAtNanoseconds: self.invocationStartedAtNanoseconds
1020
+ )
1021
+ self.updateSubmitButton()
1022
+ }
1023
+ }
1024
+
1025
+ private func updateSubmitButton() {
1026
+ let hasDescription = !descriptionView.text
1027
+ .trimmingCharacters(in: .whitespacesAndNewlines)
1028
+ .isEmpty
1029
+ let isReady = hasDescription && diagnostics != nil
1030
+ submitButton.isEnabled = isReady
1031
+ reviewHeaderButton.isEnabled = isReady
1032
+ }
1033
+
1034
+ private func reviewDraft() {
1035
+ guard let diagnostics else { return }
1036
+ let categoryIndex = max(0, min(categoryControl.selectedSegmentIndex, 2))
1037
+ let category = ["Bug", "Feedback", "Other"][categoryIndex]
1038
+ let description = descriptionView.text.trimmingCharacters(in: .whitespacesAndNewlines)
1039
+ let input = CrumbReportBuildInput(
1040
+ reportID: Crumb.newReportID(),
1041
+ trigger: trigger,
1042
+ triggeredAt: triggeredAt,
1043
+ submittedAt: Date(),
1044
+ runtime: CrumbReportRuntime(
1045
+ osVersion: UIDevice.current.systemVersion,
1046
+ deviceFamily: UIDevice.current.model,
1047
+ locale: Locale.current.identifier,
1048
+ timezone: TimeZone.current.identifier
1049
+ ),
1050
+ category: category,
1051
+ description: description,
1052
+ diagnostics: diagnostics,
1053
+ screenshotCapture: screenshotCapture,
1054
+ screenshotMasking: screenshotMasking,
1055
+ artifacts: screenshotArtifact.map { [$0.manifest] } ?? [],
1056
+ customContext: settings.customContext,
1057
+ policyStatus: settings.policyStatus,
1058
+ workspacePolicyVersion: settings.workspacePolicyVersion
1059
+ )
1060
+
1061
+ let envelope: CrumbSerializedReportEnvelope
1062
+ do {
1063
+ envelope = try Crumb.buildReport(input)
1064
+ } catch {
1065
+ let alert = UIAlertController(
1066
+ title: crumbLocalized("Couldn’t create draft"),
1067
+ message: crumbLocalized("The local report envelope could not be serialized."),
1068
+ preferredStyle: .alert
1069
+ )
1070
+ alert.addAction(UIAlertAction(title: crumbLocalized("OK"), style: .default))
1071
+ present(alert, animated: true)
1072
+ return
1073
+ }
1074
+
1075
+ let summary = DraftSummaryViewController(
1076
+ category: category,
1077
+ description: description,
1078
+ screenshotArtifact: screenshotArtifact,
1079
+ screenshotWasMasked: screenshotArtifact != nil && screenshotMasking == .applied,
1080
+ diagnostics: diagnostics,
1081
+ envelope: envelope,
1082
+ onDone: { [weak self] in self?.finish() }
1083
+ )
1084
+ // End editing explicitly so navigation does not restore keyboard focus
1085
+ // before the expanded form is restored when returning from review.
1086
+ descriptionView.resignFirstResponder()
1087
+ navigationController?.pushViewController(summary, animated: true)
1088
+ }
1089
+
1090
+ func requestCancel() {
1091
+ guard hasMeaningfulInput else {
1092
+ cancelImmediately()
1093
+ return
1094
+ }
1095
+
1096
+ let alert = UIAlertController(
1097
+ title: crumbLocalized("Discard this report?"),
1098
+ message: crumbLocalized("Your description and category changes will be lost."),
1099
+ preferredStyle: .alert
1100
+ )
1101
+ alert.addAction(UIAlertAction(title: crumbLocalized("Keep editing"), style: .cancel))
1102
+ alert.addAction(UIAlertAction(title: crumbLocalized("Discard report"), style: .destructive) { [weak self] _ in
1103
+ self?.cancelImmediately()
1104
+ })
1105
+ descriptionView.resignFirstResponder()
1106
+ present(alert, animated: true)
1107
+ }
1108
+
1109
+ private func cancelImmediately() {
1110
+ dismiss(animated: true) { [weak self] in self?.notifyFinish() }
1111
+ }
1112
+
1113
+ private func finish() {
1114
+ dismiss(animated: true) { [weak self] in self?.notifyFinish() }
1115
+ }
1116
+
1117
+ private func notifyFinish() {
1118
+ guard !didNotifyFinish else { return }
1119
+ didNotifyFinish = true
1120
+ diagnosticsTask?.cancel()
1121
+ onFinish()
1122
+ }
1123
+ }
1124
+
1125
+ @MainActor
1126
+ private final class ScreenshotPreviewViewController: UIViewController {
1127
+ private let image: UIImage
1128
+
1129
+ init(image: UIImage) {
1130
+ self.image = image
1131
+ super.init(nibName: nil, bundle: nil)
1132
+ modalPresentationStyle = .overFullScreen
1133
+ modalTransitionStyle = .crossDissolve
1134
+ }
1135
+
1136
+ @available(*, unavailable)
1137
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
1138
+
1139
+ override func viewDidLoad() {
1140
+ super.viewDidLoad()
1141
+ view.backgroundColor = UIColor.black.withAlphaComponent(0.96)
1142
+
1143
+ let imageView = UIImageView(image: image)
1144
+ imageView.contentMode = .scaleAspectFit
1145
+ imageView.translatesAutoresizingMaskIntoConstraints = false
1146
+ imageView.isAccessibilityElement = true
1147
+ imageView.accessibilityLabel = crumbLocalized("Masked screenshot preview")
1148
+ imageView.accessibilityTraits = .image
1149
+ view.addSubview(imageView)
1150
+
1151
+ let close = UIButton(type: .system)
1152
+ close.setTitle(crumbLocalized("Close"), for: .normal)
1153
+ close.setTitleColor(.white, for: .normal)
1154
+ close.titleLabel?.font = .preferredFont(forTextStyle: .headline)
1155
+ close.addAction(UIAction { [weak self] _ in self?.dismiss(animated: true) }, for: .touchUpInside)
1156
+ close.translatesAutoresizingMaskIntoConstraints = false
1157
+ view.addSubview(close)
1158
+
1159
+ NSLayoutConstraint.activate([
1160
+ close.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 12),
1161
+ close.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -18),
1162
+ close.heightAnchor.constraint(greaterThanOrEqualToConstant: 44),
1163
+ imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 12),
1164
+ imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -12),
1165
+ imageView.topAnchor.constraint(equalTo: close.bottomAnchor, constant: 8),
1166
+ imageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12)
1167
+ ])
1168
+ }
1169
+ }
1170
+
1171
+ private enum DiagnosticsFormatting {
1172
+ static func byteCount(_ count: UInt64) -> String {
1173
+ ByteCountFormatter.string(fromByteCount: Int64(clamping: count), countStyle: .memory)
1174
+ }
1175
+ }
1176
+
1177
+ @MainActor
1178
+ private final class DraftSummaryViewController: UIViewController {
1179
+ private let onDone: () -> Void
1180
+ private let envelope: CrumbSerializedReportEnvelope
1181
+ private let screenshotArtifact: CrumbScreenshotArtifact?
1182
+ private var submissionControls: [UIControl] = []
1183
+
1184
+ init(
1185
+ category: String,
1186
+ description: String,
1187
+ screenshotArtifact: CrumbScreenshotArtifact?,
1188
+ screenshotWasMasked: Bool,
1189
+ diagnostics: CrumbDiagnosticsSnapshot,
1190
+ envelope: CrumbSerializedReportEnvelope,
1191
+ onDone: @escaping () -> Void
1192
+ ) {
1193
+ self.onDone = onDone
1194
+ self.envelope = envelope
1195
+ self.screenshotArtifact = screenshotArtifact
1196
+ super.init(nibName: nil, bundle: nil)
1197
+
1198
+ let screenshot = screenshotArtifact?.preview
1199
+
1200
+ view.backgroundColor = CrumbDesign.Color.canvas
1201
+
1202
+ let scrollView = UIScrollView()
1203
+ scrollView.translatesAutoresizingMaskIntoConstraints = false
1204
+ view.addSubview(scrollView)
1205
+
1206
+ let content = UIStackView()
1207
+ content.axis = .vertical
1208
+ content.spacing = 12
1209
+ content.translatesAutoresizingMaskIntoConstraints = false
1210
+ scrollView.addSubview(content)
1211
+
1212
+ NSLayoutConstraint.activate([
1213
+ scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
1214
+ scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
1215
+ scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
1216
+ scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
1217
+ content.leadingAnchor.constraint(
1218
+ equalTo: scrollView.contentLayoutGuide.leadingAnchor,
1219
+ constant: 18
1220
+ ),
1221
+ content.trailingAnchor.constraint(
1222
+ equalTo: scrollView.contentLayoutGuide.trailingAnchor,
1223
+ constant: -18
1224
+ ),
1225
+ content.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 14),
1226
+ content.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -28),
1227
+ content.widthAnchor.constraint(
1228
+ equalTo: scrollView.frameLayoutGuide.widthAnchor,
1229
+ constant: -36
1230
+ )
1231
+ ])
1232
+
1233
+ let header = UIStackView()
1234
+ header.axis = .horizontal
1235
+ header.alignment = .center
1236
+
1237
+ let back = UIButton(type: .system)
1238
+ back.setTitle(crumbLocalized("Back"), for: .normal)
1239
+ back.setTitleColor(CrumbDesign.Color.accentDark, for: .normal)
1240
+ back.titleLabel?.font = .preferredFont(forTextStyle: .body)
1241
+ back.titleLabel?.adjustsFontForContentSizeCategory = true
1242
+ back.contentHorizontalAlignment = .leading
1243
+ back.addAction(UIAction { [weak self] _ in
1244
+ self?.navigationController?.popViewController(animated: true)
1245
+ }, for: .touchUpInside)
1246
+ submissionControls.append(back)
1247
+ back.widthAnchor.constraint(equalToConstant: 72).isActive = true
1248
+ header.addArrangedSubview(back)
1249
+
1250
+ let headerTitle = UILabel()
1251
+ headerTitle.text = crumbLocalized("Review")
1252
+ headerTitle.font = .preferredFont(forTextStyle: .headline).withWeight(.semibold)
1253
+ headerTitle.adjustsFontForContentSizeCategory = true
1254
+ headerTitle.textColor = CrumbDesign.Color.ink
1255
+ headerTitle.textAlignment = .center
1256
+ headerTitle.accessibilityIdentifier = "crumb.review-title"
1257
+ header.addArrangedSubview(headerTitle)
1258
+
1259
+ let cancel = UIButton(type: .system)
1260
+ cancel.setTitle(crumbLocalized("Cancel"), for: .normal)
1261
+ cancel.setTitleColor(CrumbDesign.Color.secondaryText, for: .normal)
1262
+ cancel.titleLabel?.font = .preferredFont(forTextStyle: .body)
1263
+ cancel.titleLabel?.adjustsFontForContentSizeCategory = true
1264
+ cancel.contentHorizontalAlignment = .trailing
1265
+ cancel.addAction(UIAction { [weak self] _ in self?.onDone() }, for: .touchUpInside)
1266
+ submissionControls.append(cancel)
1267
+ cancel.widthAnchor.constraint(equalToConstant: 72).isActive = true
1268
+ header.addArrangedSubview(cancel)
1269
+ content.addArrangedSubview(header)
1270
+
1271
+ let localBanner = UIStackView()
1272
+ localBanner.axis = .horizontal
1273
+ localBanner.alignment = .top
1274
+ localBanner.spacing = 10
1275
+ localBanner.layoutMargins = UIEdgeInsets(top: 12, left: 14, bottom: 12, right: 14)
1276
+ localBanner.isLayoutMarginsRelativeArrangement = true
1277
+ CrumbDesign.styleCard(localBanner, fill: CrumbDesign.Color.mutedSurface, border: .clear)
1278
+ localBanner.addArrangedSubview(CrumbDesign.statusDot(color: CrumbDesign.Color.ink))
1279
+ let localOnlyLabel = CrumbDesign.label(
1280
+ crumbLocalized("Nothing has been sent yet. This is saved only on your phone."),
1281
+ style: .caption1,
1282
+ color: CrumbDesign.Color.secondaryText
1283
+ )
1284
+ localOnlyLabel.font = .systemFont(ofSize: 13.5)
1285
+ localBanner.addArrangedSubview(localOnlyLabel)
1286
+ localBanner.accessibilityLabel = crumbLocalized(
1287
+ "Nothing has been sent yet. This is saved only on your phone."
1288
+ )
1289
+ content.addArrangedSubview(localBanner)
1290
+
1291
+ let reportCard = UIStackView()
1292
+ reportCard.axis = .vertical
1293
+ reportCard.spacing = 8
1294
+ reportCard.layoutMargins = UIEdgeInsets(top: 14, left: 14, bottom: 14, right: 14)
1295
+ reportCard.isLayoutMarginsRelativeArrangement = true
1296
+ CrumbDesign.styleCard(reportCard, fill: CrumbDesign.Color.elevatedSurface)
1297
+ let reportHeader = UIStackView()
1298
+ reportHeader.axis = .horizontal
1299
+ reportHeader.alignment = .center
1300
+ reportHeader.addArrangedSubview(CrumbDesign.metadataLabel("Your report · \(category)"))
1301
+ let editButton = UIButton(type: .system)
1302
+ editButton.setTitle(crumbLocalized("Edit"), for: .normal)
1303
+ editButton.tintColor = CrumbDesign.Color.accentDark
1304
+ editButton.titleLabel?.font = .preferredFont(forTextStyle: .subheadline)
1305
+ editButton.addAction(UIAction { [weak self] _ in
1306
+ self?.navigationController?.popViewController(animated: true)
1307
+ }, for: .touchUpInside)
1308
+ submissionControls.append(editButton)
1309
+ reportHeader.addArrangedSubview(editButton)
1310
+ reportCard.addArrangedSubview(reportHeader)
1311
+ let reportDescription = CrumbDesign.label(description, style: .body)
1312
+ reportDescription.font = .systemFont(ofSize: 16)
1313
+ reportCard.addArrangedSubview(reportDescription)
1314
+ content.addArrangedSubview(reportCard)
1315
+
1316
+ let evidenceCard = UIStackView()
1317
+ evidenceCard.axis = .vertical
1318
+ evidenceCard.spacing = 0
1319
+ CrumbDesign.styleCard(evidenceCard, fill: CrumbDesign.Color.elevatedSurface)
1320
+ evidenceCard.clipsToBounds = true
1321
+
1322
+ if let screenshot {
1323
+ let screenshotCard = UIStackView()
1324
+ screenshotCard.axis = .horizontal
1325
+ screenshotCard.alignment = .center
1326
+ screenshotCard.spacing = 12
1327
+ screenshotCard.layoutMargins = UIEdgeInsets(top: 12, left: 15, bottom: 12, right: 15)
1328
+ screenshotCard.isLayoutMarginsRelativeArrangement = true
1329
+
1330
+ let imageView = UIImageView(image: screenshot)
1331
+ imageView.contentMode = .scaleAspectFill
1332
+ imageView.backgroundColor = CrumbDesign.Color.darkSurface
1333
+ imageView.layer.cornerRadius = 9
1334
+ imageView.layer.cornerCurve = .continuous
1335
+ imageView.clipsToBounds = true
1336
+ imageView.widthAnchor.constraint(equalToConstant: 34).isActive = true
1337
+ imageView.heightAnchor.constraint(equalToConstant: 46).isActive = true
1338
+ imageView.isAccessibilityElement = true
1339
+ imageView.accessibilityLabel = screenshotWasMasked
1340
+ ? crumbLocalized("Screenshot attached. Text fields masked.")
1341
+ : crumbLocalized("Screenshot attached. Text-field masking is off.")
1342
+ screenshotCard.addArrangedSubview(imageView)
1343
+
1344
+ let screenshotCopy = UIStackView()
1345
+ screenshotCopy.axis = .vertical
1346
+ screenshotCopy.spacing = 3
1347
+ screenshotCopy.addArrangedSubview(CrumbDesign.label(
1348
+ screenshotWasMasked ? crumbLocalized("Screenshot · masked") : crumbLocalized("Screenshot attached"),
1349
+ style: .subheadline,
1350
+ weight: .semibold
1351
+ ))
1352
+ screenshotCopy.addArrangedSubview(CrumbDesign.label(
1353
+ screenshotWasMasked ? crumbLocalized("4 text fields hidden") : crumbLocalized("Text-field masking is off"),
1354
+ style: .caption1,
1355
+ color: CrumbDesign.Color.mutedText
1356
+ ))
1357
+ screenshotCard.addArrangedSubview(screenshotCopy)
1358
+
1359
+ let removeButton = UIButton(type: .system)
1360
+ removeButton.setTitle(crumbLocalized("Remove"), for: .normal)
1361
+ removeButton.setTitleColor(CrumbDesign.Color.accentDark, for: .normal)
1362
+ removeButton.titleLabel?.font = .preferredFont(forTextStyle: .subheadline)
1363
+ removeButton.addAction(UIAction { [weak self] _ in
1364
+ self?.navigationController?.popViewController(animated: true)
1365
+ }, for: .touchUpInside)
1366
+ screenshotCard.addArrangedSubview(removeButton)
1367
+ evidenceCard.addArrangedSubview(screenshotCard)
1368
+
1369
+ let screenshotDivider = UIView()
1370
+ screenshotDivider.backgroundColor = CrumbDesign.Color.divider
1371
+ screenshotDivider.heightAnchor.constraint(equalToConstant: 1).isActive = true
1372
+ evidenceCard.addArrangedSubview(screenshotDivider)
1373
+ }
1374
+
1375
+ let attachmentCard = UIStackView()
1376
+ attachmentCard.axis = .vertical
1377
+ attachmentCard.spacing = 0
1378
+ attachmentCard.layoutMargins = UIEdgeInsets(top: 12, left: 15, bottom: 12, right: 15)
1379
+ attachmentCard.isLayoutMarginsRelativeArrangement = true
1380
+ attachmentCard.addArrangedSubview(CrumbDesign.metadataLabel(
1381
+ crumbLocalized("What’s attached · 21 items")
1382
+ ))
1383
+
1384
+ let appVersion = [
1385
+ Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String,
1386
+ Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String
1387
+ ].compactMap { $0 }.joined(separator: " (")
1388
+ let version = appVersion.isEmpty ? crumbLocalized("Unavailable") : appVersion + (appVersion.contains("(") ? ")" : "")
1389
+ Self.addAttachmentRow(
1390
+ to: attachmentCard,
1391
+ title: crumbLocalized("App release"),
1392
+ value: version
1393
+ )
1394
+ Self.addAttachmentRow(
1395
+ to: attachmentCard,
1396
+ title: crumbLocalized("Screen at the time"),
1397
+ value: diagnostics.screenContext?.name ?? diagnostics.location
1398
+ )
1399
+ Self.addAttachmentRow(
1400
+ to: attachmentCard,
1401
+ title: crumbLocalized("Device & OS"),
1402
+ value: "\(UIDevice.current.model) · iOS \(UIDevice.current.systemVersion)"
1403
+ )
1404
+ Self.addAttachmentRow(
1405
+ to: attachmentCard,
1406
+ title: crumbLocalized("Network"),
1407
+ value: Self.compactNetworkSummary(diagnostics.network)
1408
+ )
1409
+ Self.addAttachmentRow(
1410
+ to: attachmentCard,
1411
+ title: crumbLocalized("Memory & CPU"),
1412
+ value: Self.compactPerformanceSummary(diagnostics)
1413
+ )
1414
+ Self.addAttachmentRow(
1415
+ to: attachmentCard,
1416
+ title: crumbLocalized("Recent app logs"),
1417
+ value: "\(diagnostics.logs.entries.count) lines · sanitized"
1418
+ )
1419
+ Self.addAttachmentRow(
1420
+ to: attachmentCard,
1421
+ title: crumbLocalized("Not available"),
1422
+ value: crumbLocalized("3 items"),
1423
+ isLast: true
1424
+ )
1425
+ evidenceCard.addArrangedSubview(attachmentCard)
1426
+ content.addArrangedSubview(evidenceCard)
1427
+
1428
+ let text = UITextView()
1429
+ text.isEditable = false
1430
+ text.isScrollEnabled = false
1431
+ text.backgroundColor = CrumbDesign.Color.darkSurface
1432
+ text.textColor = CrumbDesign.Color.textOnDark
1433
+ text.layer.cornerRadius = CrumbDesign.Radius.card
1434
+ text.layer.cornerCurve = .continuous
1435
+ text.textContainerInset = UIEdgeInsets(top: 14, left: 12, bottom: 14, right: 12)
1436
+ text.font = .monospacedSystemFont(ofSize: 12, weight: .regular)
1437
+ text.adjustsFontForContentSizeCategory = true
1438
+ text.accessibilityIdentifier = "crumb.draft-summary"
1439
+ text.text = Self.summary(
1440
+ category: category,
1441
+ description: description,
1442
+ hasScreenshot: screenshot != nil,
1443
+ diagnostics: diagnostics,
1444
+ envelope: envelope
1445
+ )
1446
+ text.isHidden = true
1447
+ content.addArrangedSubview(text)
1448
+
1449
+ var disclosureConfiguration = UIButton.Configuration.plain()
1450
+ disclosureConfiguration.title = crumbLocalized("See everything that’s attached ›")
1451
+ disclosureConfiguration.baseForegroundColor = CrumbDesign.Color.accentDark
1452
+ disclosureConfiguration.contentInsets = .zero
1453
+ disclosureConfiguration.titleAlignment = .leading
1454
+ let disclosureButton = UIButton(configuration: disclosureConfiguration)
1455
+ disclosureButton.contentHorizontalAlignment = .leading
1456
+ disclosureButton.accessibilityIdentifier = "crumb.show-technical-detail"
1457
+ disclosureButton.addAction(UIAction { [weak text, weak disclosureButton] _ in
1458
+ guard let text, let disclosureButton else { return }
1459
+ text.isHidden.toggle()
1460
+ disclosureButton.configuration?.title = text.isHidden
1461
+ ? crumbLocalized("See everything that’s attached ›")
1462
+ : crumbLocalized("Hide technical detail")
1463
+ }, for: .touchUpInside)
1464
+ attachmentCard.addArrangedSubview(disclosureButton)
1465
+
1466
+ let submitButton = UIButton(type: .system)
1467
+ submitButton.configuration = CrumbDesign.primaryButton(title: crumbLocalized("Send report"))
1468
+ submitButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 52).isActive = true
1469
+ submitButton.accessibilityIdentifier = "crumb.submit-report"
1470
+ submitButton.addAction(UIAction { [weak self, weak submitButton] _ in
1471
+ guard let self, let submitButton else { return }
1472
+ self.submitReport(using: submitButton)
1473
+ }, for: .touchUpInside)
1474
+ content.addArrangedSubview(submitButton)
1475
+ submissionControls.append(submitButton)
1476
+
1477
+ let privacyNote = CrumbDesign.label(
1478
+ crumbLocalized("No passwords, card numbers, keystrokes or location are ever included."),
1479
+ style: .caption2,
1480
+ color: CrumbDesign.Color.mutedText
1481
+ )
1482
+ privacyNote.textAlignment = .center
1483
+ content.addArrangedSubview(privacyNote)
1484
+
1485
+ navigationItem.rightBarButtonItem = UIBarButtonItem(
1486
+ title: crumbLocalized("Cancel"),
1487
+ primaryAction: UIAction { [weak self] _ in self?.onDone() }
1488
+ )
1489
+ }
1490
+
1491
+ override func viewDidAppear(_ animated: Bool) {
1492
+ super.viewDidAppear(animated)
1493
+ guard let sheet = navigationController?.sheetPresentationController else { return }
1494
+ sheet.detents = [.large()]
1495
+ sheet.selectedDetentIdentifier = .large
1496
+ }
1497
+
1498
+ @available(*, unavailable)
1499
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
1500
+
1501
+ private func submitReport(using button: UIButton) {
1502
+ submissionControls.forEach { $0.isEnabled = false }
1503
+ button.configuration = CrumbDesign.primaryButton(title: crumbLocalized("Saving locally…"))
1504
+ isModalInPresentation = true
1505
+ navigationController?.isModalInPresentation = true
1506
+ UIAccessibility.post(
1507
+ notification: .announcement,
1508
+ argument: crumbLocalized("Saving report locally")
1509
+ )
1510
+
1511
+ let envelope = envelope
1512
+ let artifacts = screenshotArtifact.map {
1513
+ [CrumbQueueArtifact(manifest: $0.manifest, data: $0.encodedData)]
1514
+ } ?? []
1515
+ Task { [weak self, weak button] in
1516
+ do {
1517
+ _ = try await CrumbReportQueue.shared.enqueue(
1518
+ envelope: envelope,
1519
+ artifacts: artifacts
1520
+ )
1521
+ guard let self else { return }
1522
+ CrumbReporterLifecycle.shared.reportDidQueue()
1523
+ self.isModalInPresentation = false
1524
+ self.navigationController?.isModalInPresentation = false
1525
+ UIAccessibility.post(
1526
+ notification: .announcement,
1527
+ argument: crumbLocalized("Report saved")
1528
+ )
1529
+ let alert = UIAlertController(
1530
+ title: crumbLocalized("Report saved"),
1531
+ message: crumbLocalized(
1532
+ "Safely queued on this device. You can close Crumb without losing it."
1533
+ ),
1534
+ preferredStyle: .alert
1535
+ )
1536
+ alert.addAction(UIAlertAction(title: crumbLocalized("Done"), style: .default) { [weak self] _ in
1537
+ self?.onDone()
1538
+ })
1539
+ self.present(alert, animated: true)
1540
+ } catch {
1541
+ guard let self, let button else { return }
1542
+ self.isModalInPresentation = false
1543
+ self.navigationController?.isModalInPresentation = false
1544
+ self.submissionControls.forEach { $0.isEnabled = true }
1545
+ button.configuration = CrumbDesign.primaryButton(title: crumbLocalized("Try again"))
1546
+ let message = error as? CrumbReportQueueError == .queueFull
1547
+ ? crumbLocalized("The on-device queue is full. No existing report was deleted.")
1548
+ : crumbLocalized("The report was not saved. Nothing was sent; please try again.")
1549
+ let alert = UIAlertController(
1550
+ title: crumbLocalized("Couldn’t save report"),
1551
+ message: message,
1552
+ preferredStyle: .alert
1553
+ )
1554
+ alert.addAction(UIAlertAction(title: crumbLocalized("OK"), style: .default))
1555
+ self.present(alert, animated: true)
1556
+ }
1557
+ }
1558
+ }
1559
+
1560
+ private static func addAttachmentRow(
1561
+ to stack: UIStackView,
1562
+ title: String,
1563
+ value: String,
1564
+ isLast: Bool = false
1565
+ ) {
1566
+ if isLast {
1567
+ let divider = UIView()
1568
+ divider.backgroundColor = CrumbDesign.Color.divider
1569
+ divider.heightAnchor.constraint(equalToConstant: 1).isActive = true
1570
+ stack.addArrangedSubview(divider)
1571
+ }
1572
+ let row = UIStackView()
1573
+ row.axis = .horizontal
1574
+ row.alignment = .firstBaseline
1575
+ row.spacing = 10
1576
+ row.layoutMargins = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0)
1577
+ row.isLayoutMarginsRelativeArrangement = true
1578
+ row.addArrangedSubview(CrumbDesign.label(
1579
+ title,
1580
+ style: .subheadline,
1581
+ color: CrumbDesign.Color.secondaryText
1582
+ ))
1583
+ let valueLabel = CrumbDesign.label(value, style: .caption1)
1584
+ valueLabel.font = .monospacedSystemFont(ofSize: 12, weight: .regular)
1585
+ valueLabel.textAlignment = .right
1586
+ valueLabel.numberOfLines = 1
1587
+ valueLabel.adjustsFontSizeToFitWidth = true
1588
+ valueLabel.minimumScaleFactor = 0.78
1589
+ valueLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
1590
+ row.addArrangedSubview(valueLabel)
1591
+ stack.addArrangedSubview(row)
1592
+ }
1593
+
1594
+ private static func summary(
1595
+ category: String,
1596
+ description: String,
1597
+ hasScreenshot: Bool,
1598
+ diagnostics: CrumbDiagnosticsSnapshot,
1599
+ envelope: CrumbSerializedReportEnvelope
1600
+ ) -> String {
1601
+ var lines = [
1602
+ "LOCAL ONLY — NOT UPLOADED",
1603
+ "",
1604
+ "Envelope: \(envelope.reportID)",
1605
+ "Serialized size: \(envelope.data.count) bytes",
1606
+ "Category: \(category)",
1607
+ "Description: \(description)",
1608
+ "Screenshot: \(hasScreenshot ? "captured for this report" : "not captured")",
1609
+ "",
1610
+ "ON-DEMAND DIAGNOSTICS",
1611
+ "Captured: \(ISO8601DateFormatter().string(from: diagnostics.capturedAt))",
1612
+ "Location: \(diagnostics.location)",
1613
+ "Process: \(diagnostics.processName) (\(diagnostics.processID))",
1614
+ "CPU: \(diagnostics.cpuUsagePercent.map { String(format: "%.1f%%", $0) } ?? "unavailable")",
1615
+ "Resident memory: \(diagnostics.residentMemoryBytes.map(DiagnosticsFormatting.byteCount) ?? "unavailable")",
1616
+ "Physical footprint: \(diagnostics.physicalFootprintBytes.map(DiagnosticsFormatting.byteCount) ?? "unavailable")",
1617
+ "Thermal state: \(diagnostics.thermalState)",
1618
+ "Threads: \(diagnostics.threadCount)",
1619
+ "GPU: \(diagnostics.gpuStatus)",
1620
+ "Network: \(networkSummary(diagnostics.network))"
1621
+ ]
1622
+
1623
+ if let health = diagnostics.network.healthCheck {
1624
+ let outcome = health.succeeded ? "available" : "unavailable"
1625
+ let status = health.statusCode.map(String.init) ?? "no status"
1626
+ lines.append("Crumb API: \(health.host) · \(outcome) · \(status) · \(health.latencyMilliseconds) ms\(health.failure.map { " · \($0)" } ?? "")")
1627
+ } else {
1628
+ lines.append("Crumb API: not configured")
1629
+ }
1630
+
1631
+ let logSources = diagnostics.logs.sources.isEmpty
1632
+ ? "none"
1633
+ : diagnostics.logs.sources.joined(separator: ", ")
1634
+ lines.append(
1635
+ "Logs: \(diagnostics.logs.status.rawValue) · \(diagnostics.logs.entries.count) entries · \(logSources)"
1636
+ )
1637
+ if diagnostics.logs.truncated {
1638
+ lines.append("Logs truncated: \(diagnostics.logs.droppedEntryCount) entries omitted")
1639
+ }
1640
+ diagnostics.logs.failures.forEach { lines.append("Log source warning: \($0)") }
1641
+ lines.append(
1642
+ "Live stacks: \(diagnostics.stackTraces.status.rawValue) · "
1643
+ + "\(diagnostics.stackTraces.scope) · \(diagnostics.stackTraces.threads.count) threads"
1644
+ )
1645
+ if let reason = diagnostics.stackTraces.unavailableReason {
1646
+ lines.append("Live stack reason: \(reason)")
1647
+ }
1648
+
1649
+ if !diagnostics.busiestThreads.isEmpty {
1650
+ lines.append("")
1651
+ lines.append("BUSIEST APP THREADS")
1652
+ for thread in diagnostics.busiestThreads {
1653
+ let cpu = thread.cpuUsagePercent.map { String(format: "%.1f%%", $0) } ?? "unavailable"
1654
+ lines.append("#\(thread.id) \(thread.name) · \(thread.state) · \(cpu)")
1655
+ }
1656
+ }
1657
+
1658
+ if !diagnostics.logs.entries.isEmpty {
1659
+ lines.append("")
1660
+ lines.append("RECENT APP LOGS")
1661
+ for entry in diagnostics.logs.entries {
1662
+ let timestamp = ISO8601DateFormatter().string(from: entry.timestamp)
1663
+ lines.append(
1664
+ "\(timestamp) [\(entry.level.rawValue.uppercased())] "
1665
+ + "\(entry.source)/\(entry.category) · \(entry.message)"
1666
+ )
1667
+ }
1668
+ }
1669
+
1670
+ if !diagnostics.stackTraces.threads.isEmpty {
1671
+ lines.append("")
1672
+ lines.append("LIVE THREAD STACKS")
1673
+ for thread in diagnostics.stackTraces.threads {
1674
+ lines.append("#\(thread.id) \(thread.name) · \(thread.state)")
1675
+ lines.append(contentsOf: thread.frames.map { " \($0)" })
1676
+ }
1677
+ }
1678
+ lines.append("")
1679
+ lines.append("SERIALIZED REPORT ENVELOPE")
1680
+ lines.append(envelope.json)
1681
+ return lines.joined(separator: "\n")
1682
+ }
1683
+
1684
+ private static func networkSummary(_ network: CrumbNetworkDiagnostic) -> String {
1685
+ let generation = network.cellularGeneration.map { " · \($0)" } ?? ""
1686
+ let flags = [network.isExpensive ? "expensive" : nil, network.isConstrained ? "constrained" : nil]
1687
+ .compactMap { $0 }
1688
+ let suffix = flags.isEmpty ? "" : " · \(flags.joined(separator: ", "))"
1689
+ return "\(network.status) · \(network.transport)\(generation)\(suffix)"
1690
+ }
1691
+
1692
+ private static func compactNetworkSummary(_ network: CrumbNetworkDiagnostic) -> String {
1693
+ let transport = network.transport.lowercased() == "wifi" ? "Wi-Fi" : network.transport
1694
+ return "\(transport) · \(network.status)"
1695
+ }
1696
+
1697
+ private static func compactPerformanceSummary(_ diagnostics: CrumbDiagnosticsSnapshot) -> String {
1698
+ let memory = diagnostics.residentMemoryBytes.map(DiagnosticsFormatting.byteCount)
1699
+ ?? crumbLocalized("Unavailable")
1700
+ let cpu = diagnostics.cpuUsagePercent.map { String(format: "%.0f%%", $0) }
1701
+ ?? crumbLocalized("Unavailable")
1702
+ return "\(memory) · \(cpu)"
1703
+ }
1704
+ }
1705
+ #endif