feat: Enhance editor functionality with autocorrect handling and text replacement
feat: Implement confirmation dialog for room sharing in ShareRoomPicker fix: Improve server input synchronization and blur handling in ServerPicker feat: Integrate UnifiedPush for background sync in Android, including pusher management refactor: Streamline Android share handling and file upload processes fix: Update notification handling to use Capacitor's local notifications API
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, {
|
||||
ChangeEventHandler,
|
||||
FocusEventHandler,
|
||||
KeyboardEventHandler,
|
||||
MouseEventHandler,
|
||||
useEffect,
|
||||
@@ -39,8 +40,13 @@ export function ServerPicker({
|
||||
const serverInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// sync input with it outside server changes
|
||||
if (serverInputRef.current && serverInputRef.current.value !== server) {
|
||||
// Only sync input when server changes externally (e.g., from menu selection)
|
||||
// and input is not focused to avoid cursor jumping during typing
|
||||
if (
|
||||
serverInputRef.current &&
|
||||
serverInputRef.current.value !== server &&
|
||||
document.activeElement !== serverInputRef.current
|
||||
) {
|
||||
serverInputRef.current.value = server;
|
||||
}
|
||||
}, [server]);
|
||||
@@ -52,6 +58,13 @@ export function ServerPicker({
|
||||
if (inputServer) debounceServerSelect(inputServer);
|
||||
};
|
||||
|
||||
const handleBlur: FocusEventHandler<HTMLInputElement> = (evt) => {
|
||||
const inputServer = evt.target.value.trim();
|
||||
if (inputServer && inputServer !== server) {
|
||||
onServerChange(inputServer);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (evt) => {
|
||||
if (evt.key === 'ArrowDown') {
|
||||
evt.preventDefault();
|
||||
@@ -85,6 +98,7 @@ export function ServerPicker({
|
||||
outlined
|
||||
defaultValue={server}
|
||||
onChange={handleServerChange}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
size="500"
|
||||
readOnly={!allowCustomServer}
|
||||
|
||||
@@ -517,86 +517,118 @@ function TaskbarFlashStopper() {
|
||||
|
||||
function AndroidShareIntentHandler() {
|
||||
const mx = useMatrixClient();
|
||||
const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown');
|
||||
const navigate = useNavigate();
|
||||
const [pendingShare, setPendingShare] = useState<AndroidSharePayload | null>(null);
|
||||
const [pickedRoomId, setPickedRoomId] = useState<string | null>(null);
|
||||
const [msgDraft, setMsgDraft] = useAtom(roomIdToMsgDraftAtomFamily(pickedRoomId ?? '__android_share__'));
|
||||
const setUploadItems = useSetAtom(roomIdToUploadItemsAtomFamily(pickedRoomId ?? '__android_share__'));
|
||||
const mDirects = useAtomValue(mDirectAtom);
|
||||
const roomToParents = useAtomValue(roomToParentsAtom);
|
||||
|
||||
const applyPendingShare = useCallback(
|
||||
async (share: AndroidSharePayload, roomId: string) => {
|
||||
const room = mx.getRoom(roomId);
|
||||
if (!room) return false;
|
||||
|
||||
// Send text message if present
|
||||
const nextParts = [share.subject?.trim(), share.text?.trim()].filter(
|
||||
(value): value is string => !!value
|
||||
);
|
||||
if (nextParts.length > 0) {
|
||||
const existingText = toPlainText(msgDraft, isMarkdown).trim();
|
||||
const nextText = existingText ? `${existingText}\n${nextParts.join('\n')}` : nextParts.join('\n');
|
||||
setMsgDraft(plainToEditorInput(nextText, isMarkdown));
|
||||
const textContent = {
|
||||
msgtype: 'm.text' as const,
|
||||
body: nextParts.join('\n'),
|
||||
};
|
||||
await mx.sendMessage(roomId, textContent);
|
||||
}
|
||||
|
||||
// Upload and send files if present
|
||||
if (share.files.length > 0) {
|
||||
const uploadItems: TUploadItem[] = [];
|
||||
for (const sharedFile of share.files) {
|
||||
const originalFile = await materializeSharedFile(sharedFile, share.receivedAt);
|
||||
|
||||
let fileToUpload = originalFile;
|
||||
let encInfo: any = undefined;
|
||||
|
||||
if (room.hasEncryptionStateEvent()) {
|
||||
const encrypted = await encryptFile(originalFile);
|
||||
uploadItems.push({
|
||||
file: encrypted.file,
|
||||
originalFile,
|
||||
metadata: { markedAsSpoiler: false },
|
||||
encInfo: encrypted.encInfo,
|
||||
});
|
||||
continue;
|
||||
fileToUpload = encrypted.file;
|
||||
encInfo = encrypted.encInfo;
|
||||
}
|
||||
|
||||
uploadItems.push({
|
||||
file: originalFile,
|
||||
originalFile,
|
||||
metadata: { markedAsSpoiler: false },
|
||||
encInfo: undefined,
|
||||
});
|
||||
}
|
||||
// Upload file
|
||||
const uploadResult = await mx.uploadContent(fileToUpload);
|
||||
const mxc = uploadResult?.content_uri;
|
||||
if (!mxc) continue;
|
||||
|
||||
setUploadItems({
|
||||
type: 'PUT',
|
||||
item: uploadItems,
|
||||
});
|
||||
// Determine message type and send
|
||||
const fileType = originalFile.type;
|
||||
let msgtype = 'm.file' as const;
|
||||
if (fileType.startsWith('image/')) msgtype = 'm.image' as const;
|
||||
else if (fileType.startsWith('video/')) msgtype = 'm.video' as const;
|
||||
else if (fileType.startsWith('audio/')) msgtype = 'm.audio' as const;
|
||||
|
||||
const fileContent: any = {
|
||||
msgtype,
|
||||
body: originalFile.name,
|
||||
filename: originalFile.name,
|
||||
info: {
|
||||
mimetype: originalFile.type,
|
||||
size: originalFile.size,
|
||||
},
|
||||
};
|
||||
|
||||
if (encInfo) {
|
||||
fileContent.file = {
|
||||
...encInfo,
|
||||
url: mxc,
|
||||
};
|
||||
} else {
|
||||
fileContent.url = mxc;
|
||||
}
|
||||
|
||||
await mx.sendMessage(roomId, fileContent);
|
||||
}
|
||||
}
|
||||
|
||||
await clearPendingAndroidShare();
|
||||
return true;
|
||||
},
|
||||
[isMarkdown, msgDraft, mx, setMsgDraft, setUploadItems]
|
||||
[mx]
|
||||
);
|
||||
|
||||
const handlePickRoom = useCallback(
|
||||
(roomId: string) => {
|
||||
setPickedRoomId(roomId);
|
||||
if (!pendingShare) return;
|
||||
|
||||
applyPendingShare(pendingShare, roomId)
|
||||
.then((applied) => {
|
||||
if (applied) {
|
||||
setPendingShare(null);
|
||||
setPickedRoomId(null);
|
||||
|
||||
// Navigate to the selected room
|
||||
const isDirect = mDirects.has(roomId);
|
||||
if (isDirect) {
|
||||
navigate(getDirectRoomPath(roomId));
|
||||
} else {
|
||||
const parents = roomToParents.get(roomId);
|
||||
const parent = parents && parents.length > 0 ? parents[0] : undefined;
|
||||
if (parent) {
|
||||
navigate(getSpaceRoomPath(parent, roomId));
|
||||
} else {
|
||||
navigate(getHomeRoomPath(roomId));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('[AndroidShare] Failed to apply share after pick:', err);
|
||||
setPendingShare(null);
|
||||
setPickedRoomId(null);
|
||||
});
|
||||
},
|
||||
[applyPendingShare, pendingShare]
|
||||
[applyPendingShare, pendingShare, navigate, mDirects, roomToParents]
|
||||
);
|
||||
|
||||
const handleDismiss = useCallback(() => {
|
||||
clearPendingAndroidShare().catch(() => {});
|
||||
setPendingShare(null);
|
||||
setPickedRoomId(null);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user