Skip to content

Commit 0ef352b

Browse files
committed
sharing: Add functionality to share multiple files to zulip android.
This commit changes the param structure that we pass to share screen from the native code, in order to be more compatible with multiple file sharing. It also makes user interface changes to display list of multiple content.
1 parent 9c1524a commit 0ef352b

File tree

6 files changed

+96
-57
lines changed

6 files changed

+96
-57
lines changed

android/app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@
5656
<category android:name="android.intent.category.DEFAULT" />
5757
<data android:mimeType="*/*" />
5858
</intent-filter>
59+
<intent-filter>
60+
<action android:name="android.intent.action.SEND_MULTIPLE" />
61+
<category android:name="android.intent.category.DEFAULT" />
62+
<data android:mimeType="*/*" />
63+
</intent-filter>
5964
</activity>
6065

6166
<service android:name=".notifications.NotificationIntentService" />

android/app/src/main/java/com/zulipmobile/MainActivity.kt

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,12 @@ open class MainActivity : ReactActivity() {
5858
/* Returns true just if we did handle the intent. */
5959
private fun maybeHandleIntent(intent: Intent?): Boolean {
6060
// We handle intents from "sharing" something to Zulip.
61-
if (intent?.action == Intent.ACTION_SEND) {
62-
handleSend(intent)
63-
return true
61+
if (intent != null) {
62+
if ((intent.action == Intent.ACTION_SEND) or (intent.action == Intent.ACTION_SEND_MULTIPLE)) {
63+
handleSend(intent)
64+
return true
65+
}
6466
}
65-
// TODO also handle ACTION_SEND_MULTIPLE?
66-
// See: https://developer.android.com/training/sharing/receive#receiving-data-activity
6767

6868
// For other intents, let RN handle it. In particular this is
6969
// important for VIEW intents with zulip: URLs.
@@ -121,26 +121,35 @@ open class MainActivity : ReactActivity() {
121121
// For documentation of what fields to expect here, see:
122122
// https://developer.android.com/reference/android/content/Intent#ACTION_SEND
123123
val params = Arguments.createMap()
124-
when {
125-
"text/plain" == intent.type -> {
126-
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
127-
params.putString("type", "text")
128-
params.putString("sharedText", sharedText)
129-
}
130-
intent.type?.startsWith("image/") == true -> {
131-
val url = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
132-
?: throw ShareParamsParseException("Could not extract URL from Image Intent")
133-
params.putString("type", "image")
134-
params.putString("sharedImageUrl", url.toString())
135-
params.putString("fileName", getFileName(url, this.contentResolver))
136-
}
137-
else -> {
138-
val url = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
139-
?: throw ShareParamsParseException("Could not extract URL from File Intent")
140-
params.putString("type", "file")
141-
params.putString("sharedFileUrl", url.toString())
142-
params.putString("fileName", getFileName(url, this.contentResolver))
124+
125+
if ("text/plain" == intent.type) {
126+
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
127+
params.putBoolean("isText", true)
128+
params.putString("sharedText", sharedText)
129+
} else {
130+
val content = Arguments.createArray();
131+
val url = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
132+
val urls = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
133+
val cr = this.contentResolver
134+
135+
if (url != null) {
136+
val singleContent = Arguments.createMap()
137+
singleContent.putString("url", url.toString())
138+
singleContent.putString("type", cr.getType(url))
139+
singleContent.putString("fileName", getFileName(url, cr))
140+
content.pushMap(singleContent)
141+
} else if (urls != null) {
142+
for (u in urls) {
143+
val singleContent = Arguments.createMap()
144+
singleContent.putString("url", u.toString())
145+
singleContent.putString("type", cr.getType(u))
146+
singleContent.putString("fileName", getFileName(u, cr))
147+
content.pushMap(singleContent)
148+
}
143149
}
150+
151+
params.putBoolean("isText", false)
152+
params.putArray("content", content)
144153
}
145154
return params
146155
}

src/sharing/ShareToPm.js

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* @flow strict-local */
22
import React from 'react';
3-
import { View, Image, ScrollView, Modal } from 'react-native';
3+
import { View, Image, ScrollView, Modal, FlatList } from 'react-native';
44
import type { RouteProp } from '../react-navigation';
55
import type { SharingNavigationProp } from './SharingScreen';
66
import * as NavigationService from '../nav/NavigationService';
@@ -80,7 +80,7 @@ class ShareToPm extends React.Component<Props, State> {
8080
const { sharedData } = this.props.route.params;
8181
return {
8282
selectedRecipients: [],
83-
message: sharedData.type === 'text' ? sharedData.sharedText : '',
83+
message: sharedData.isText ? sharedData.sharedText : '',
8484
choosingRecipients: false,
8585
sending: false,
8686
};
@@ -135,7 +135,7 @@ class ShareToPm extends React.Component<Props, State> {
135135
const { message, selectedRecipients } = this.state;
136136
const { sharedData } = this.props.route.params;
137137

138-
if (sharedData.type === 'text') {
138+
if (sharedData.isText) {
139139
return message !== '' && selectedRecipients.length > 0;
140140
}
141141

@@ -155,6 +155,13 @@ class ShareToPm extends React.Component<Props, State> {
155155
return preview;
156156
};
157157

158+
renderItem = ({ item, index, separators }) =>
159+
item.type.startsWith('image') ? (
160+
<Image source={{ uri: item.url }} style={styles.imagePreview} />
161+
) : (
162+
<></>
163+
);
164+
158165
render() {
159166
const { message, choosingRecipients, sending } = this.state;
160167

@@ -167,17 +174,22 @@ class ShareToPm extends React.Component<Props, State> {
167174
}
168175

169176
const { sharedData } = this.props.route.params;
170-
let sharePreview = null;
171-
if (sharedData.type === 'image') {
172-
sharePreview = (
173-
<Image source={{ uri: sharedData.sharedImageUrl }} style={styles.imagePreview} />
174-
);
175-
}
176177

177178
return (
178179
<>
179180
<ScrollView style={styles.wrapper} keyboardShouldPersistTaps="always">
180-
<View style={styles.container}>{sharePreview}</View>
181+
<View style={styles.container}>
182+
{sharedData.isText ? (
183+
<></>
184+
) : (
185+
<FlatList
186+
data={sharedData.content}
187+
renderItem={this.renderItem}
188+
keyExtractor={i => i.url}
189+
horizontal
190+
/>
191+
)}
192+
</View>
181193
<View style={styles.usersPreview}>{this.renderUsersPreview()}</View>
182194
<ZulipButton
183195
onPress={() => this.setState({ choosingRecipients: true })}

src/sharing/ShareToStream.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* @flow strict-local */
22
import React from 'react';
3-
import { View, Image, ScrollView } from 'react-native';
3+
import { View, Image, ScrollView, FlatList } from 'react-native';
44
import type { SharingNavigationProp } from './SharingScreen';
55
import type { RouteProp } from '../react-navigation';
66
import * as NavigationService from '../nav/NavigationService';
@@ -149,13 +149,20 @@ class ShareToStream extends React.Component<Props, State> {
149149
const { stream, topic, message } = this.state;
150150
const { sharedData } = this.props.route.params;
151151

152-
if (sharedData.type !== 'text') {
152+
if (!sharedData.isText) {
153153
return stream !== '' && topic !== '';
154154
}
155155

156156
return stream !== '' && topic !== '' && message !== '';
157157
};
158158

159+
renderItem = ({ item, index, separators }) =>
160+
item.type.startsWith('image') ? (
161+
<Image source={{ uri: item.url }} style={styles.imagePreview} />
162+
) : (
163+
<></>
164+
);
165+
159166
render() {
160167
const { sharedData } = this.props.route.params;
161168
const { stream, topic, message, isStreamFocused, isTopicFocused, sending } = this.state;
@@ -165,8 +172,15 @@ class ShareToStream extends React.Component<Props, State> {
165172
<>
166173
<ScrollView style={styles.wrapper} keyboardShouldPersistTaps="always" nestedScrollEnabled>
167174
<View style={styles.container}>
168-
{sharedData.type === 'image' && (
169-
<Image source={{ uri: sharedData.sharedImageUrl }} style={styles.imagePreview} />
175+
{sharedData.isText ? (
176+
<></>
177+
) : (
178+
<FlatList
179+
data={sharedData.content}
180+
renderItem={this.renderItem}
181+
keyExtractor={i => i.url}
182+
horizontal
183+
/>
170184
)}
171185
<AnimatedScaleComponent visible={isStreamFocused}>
172186
<StreamAutocomplete filter={stream} onAutocomplete={this.handleStreamAutoComplete} />

src/sharing/send.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,16 @@ type SendPm = {|
2929
export const handleSend = async (data: SendStream | SendPm, auth: Auth, _: GetText) => {
3030
const sharedData = data.sharedData;
3131
let messageToSend = data.message;
32+
if (!sharedData.isText) {
33+
const { content } = sharedData;
34+
for (let i = 0; i < content.length; i++) {
35+
const response = await uploadFile(auth, content[i].url, content[i].fileName);
36+
messageToSend += `\n[${content[i].fileName}](${response.uri})\n`;
37+
}
38+
}
3239

3340
showToast(_('Sending Message...'));
3441

35-
if (sharedData.type === 'image' || sharedData.type === 'file') {
36-
const url = sharedData.type === 'image' ? sharedData.sharedImageUrl : sharedData.sharedFileUrl;
37-
const fileName = sharedData.fileName;
38-
const response = await uploadFile(auth, url, fileName);
39-
messageToSend += `\n[${fileName}](${response.uri})`;
40-
}
41-
4242
const messageData =
4343
data.type === 'pm'
4444
? {

src/types.js

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -338,24 +338,23 @@ export type PmConversationData = {|
338338
unread: number,
339339
|};
340340

341-
export type SharedText = {|
342-
type: 'text',
343-
sharedText: string,
341+
export type Content = {|
342+
url: string,
343+
type: string,
344+
fileName: string,
344345
|};
345346

346-
export type SharedImage = {|
347-
type: 'image',
348-
sharedImageUrl: string,
349-
fileName: string,
347+
export type SharedText = {|
348+
isText: true,
349+
sharedText: string,
350350
|};
351351

352-
export type SharedFile = {|
353-
type: 'file',
354-
sharedFileUrl: string,
355-
fileName: string,
352+
export type SharedContent = {|
353+
isText: false,
354+
content: Array<Content>,
356355
|};
357356

358357
/**
359358
* The data we get when the user "shares" to Zulip from another app.
360359
*/
361-
export type SharedData = SharedText | SharedImage | SharedFile;
360+
export type SharedData = SharedText | SharedContent;

0 commit comments

Comments
 (0)