Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,15 @@ const config: ExpoConfig = {
},
},
],
[
"expo-audio",
{
microphonePermission: "Allow T3 Code to use your microphone for voice input.",
recordAudioAndroid: false,
enableBackgroundPlayback: false,
enableBackgroundRecording: false,
},
],
[
"expo-camera",
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public class T3ComposerEditorModule: Module {
Prop("editable") { (view: T3ComposerEditorView, editable: Bool) in
view.setEditable(editable)
}
Prop("readOnly") { (view: T3ComposerEditorView, readOnly: Bool) in
view.setReadOnly(readOnly)
}
Prop("scrollEnabled") { (view: T3ComposerEditorView, scrollEnabled: Bool) in
view.setScrollEnabled(scrollEnabled)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,21 @@ private final class ComposerTextAttachment: NSTextAttachment {
private final class ComposerTextView: UITextView {
private static let pastedImageDirectoryName = "t3-composer-paste"
private static let stalePastedImageAge: TimeInterval = 60 * 60
private static let readOnlyActions = Set([
"cut:",
"delete:",
"paste:",
"redo:",
"toggleBoldface:",
"toggleItalics:",
"toggleUnderline:",
"undo:",
])

var onPasteImages: (([String]) -> Void)?
var onAttributedMutation: (() -> Void)?
var onSubmit: (() -> Void)?
var isReadOnly = false

override var keyCommands: [UIKeyCommand]? {
var commands = super.keyCommands ?? []
Expand All @@ -83,6 +94,9 @@ private final class ComposerTextView: UITextView {
}

override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if isReadOnly && Self.readOnlyActions.contains(NSStringFromSelector(action)) {
return false
}
if action == #selector(paste(_:)) {
let pasteboard = UIPasteboard.general
if pasteboard.hasImages ||
Expand All @@ -96,6 +110,9 @@ private final class ComposerTextView: UITextView {
}

override func paste(_ sender: Any?) {
guard !isReadOnly else {
return
}
let pasteboard = UIPasteboard.general
let imageProviders = pasteboard.itemProviders.filter {
$0.canLoadObject(ofClass: UIImage.self)
Expand All @@ -117,6 +134,9 @@ private final class ComposerTextView: UITextView {
}

override func deleteBackward() {
guard !isReadOnly else {
return
}
guard selectedRange.length == 0, selectedRange.location > 0 else {
super.deleteBackward()
return
Expand Down Expand Up @@ -160,9 +180,12 @@ private final class ComposerTextView: UITextView {
}

group.notify(queue: .main) { [weak self] in
guard let self, !self.isReadOnly else {
return
}
let urls = images.compactMap { $0 }.compactMap(Self.writeTemporaryImage)
if !urls.isEmpty {
self?.onPasteImages?(urls)
self.onPasteImages?(urls)
}
}
}
Expand All @@ -175,6 +198,9 @@ private final class ComposerTextView: UITextView {
}

override func cut(_ sender: Any?) {
guard !isReadOnly else {
return
}
guard isEditable, selectedRange.length > 0 else {
return super.cut(sender)
}
Expand Down Expand Up @@ -306,6 +332,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro
private var contentInsetVertical: CGFloat = 0
private var shouldAutoFocus = false
private var didAutoFocus = false
private var isReadOnly = false
private var isApplyingControlledValue = false
private var nativeEventCount = 0
private var lastContentSize = CGSize.zero
Expand Down Expand Up @@ -451,6 +478,11 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro
textView.isEditable = editable
}

func setReadOnly(_ readOnly: Bool) {
isReadOnly = readOnly
textView.isReadOnly = readOnly
}

func setScrollEnabled(_ scrollEnabled: Bool) {
textView.isScrollEnabled = scrollEnabled
}
Expand Down Expand Up @@ -504,13 +536,16 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro
replacementText text: String
) -> Bool {
restoreBaseTypingAttributes()
return true
return !isReadOnly
}

public func textDroppableView(
_ textDroppableView: UIView & UITextDroppable,
proposalForDrop drop: UITextDropRequest
) -> UITextDropProposal {
guard !isReadOnly else {
return UITextDropProposal(operation: .cancel)
}
guard droppedImageProviders(in: drop) != nil else {
return drop.suggestedProposal
}
Expand All @@ -527,6 +562,9 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate, UITextDro
_ textDroppableView: UIView & UITextDroppable,
willPerformDrop drop: UITextDropRequest
) {
guard !isReadOnly else {
return
}
guard let imageProviders = droppedImageProviders(in: drop) else {
return
}
Expand Down
5 changes: 4 additions & 1 deletion apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@noble/curves": "catalog:",
"@noble/hashes": "catalog:",
"@pierre/diffs": "catalog:",
"@react-native-ai/apple": "0.12.0",
"@react-native-menu/menu": "^2.0.0",
"@react-navigation/elements": "2.9.26",
"@react-navigation/native": "7.3.4",
Expand All @@ -75,6 +76,7 @@
"effect": "catalog:",
"expo": "~57.0.18",
"expo-asset": "~57.0.15",
"expo-audio": "~57.0.4",
"expo-auth-session": "~57.0.10",
"expo-blur": "~57.0.2",
"expo-build-properties": "~57.0.15",
Expand Down Expand Up @@ -143,7 +145,8 @@
"autolinking": {
"buildFromSource": [
"react-native-screens",
"@react-native-menu/menu"
"@react-native-menu/menu",
"expo-audio"
]
}
},
Expand Down
6 changes: 5 additions & 1 deletion apps/mobile/src/components/GlassSurface.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GlassView, isGlassEffectAPIAvailable } from "expo-glass-effect";
import type { ReactNode } from "react";
import type { ReactNode, Ref } from "react";
import {
Platform,
useColorScheme,
Expand All @@ -20,6 +20,7 @@ const ThemedGlassView = withUniwind(GlassView, {
});

interface GlassSurfaceProps extends ViewProps {
readonly ref?: Ref<View>;
readonly children: ReactNode;
readonly glassEffectStyle?: "clear" | "regular" | "none";
readonly tintColor?: ColorValue;
Expand All @@ -32,6 +33,7 @@ interface GlassSurfaceProps extends ViewProps {
}

export function GlassSurface({
ref,
children,
glassEffectStyle = "regular",
chrome = "default",
Expand Down Expand Up @@ -68,6 +70,7 @@ export function GlassSurface({
return (
<ThemedGlassView
{...props}
ref={ref}
className={cn(
chrome === "none"
? "border-0 border-transparent bg-transparent"
Expand All @@ -90,6 +93,7 @@ export function GlassSurface({
return (
<View
{...props}
ref={ref}
className={cn(
chrome === "none"
? "border-0 border-transparent bg-transparent"
Expand Down
Loading
Loading