Skip to content

Commit 547c085

Browse files
committed
Added URL share
1 parent 8a90df2 commit 547c085

7 files changed

Lines changed: 184 additions & 18 deletions

File tree

README.md

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ This module solves a critical limitation in Titanium SDK when sharing images on
1212

1313
## Features
1414

15-
- Share images from Resources, Blobs, or file paths
15+
- Share images from URLs, Resources, Blobs, or file paths
16+
- Automatic image download from remote URLs
1617
- Automatic FileProvider URI generation
1718
- Support for text and image combination sharing
1819
- Optional callback to handle share results (success, cancelled, or error)
@@ -90,6 +91,18 @@ imageView.addEventListener('load', function() {
9091
});
9192
```
9293

94+
### Share image from URL
95+
96+
The module will automatically download the image in the background before sharing. If the download fails, it will share text only (or notify via callback if provided).
97+
98+
```javascript
99+
ShareModule.share({
100+
message: "Check out this amazing photo!",
101+
subject: "Photo from the web",
102+
image: "https://www.example.com/photos/sunset.jpg"
103+
});
104+
```
105+
93106
### Share text only
94107

95108
```javascript
@@ -132,6 +145,7 @@ Opens the Android share dialog with the specified content.
132145
- `message` (String, optional): Text content to share
133146
- `subject` (String, optional): Subject line for sharing (used by email apps)
134147
- `image` (String|TiBlob, optional): Image to share. Can be:
148+
- **URL** (e.g., `"https://example.com/photo.jpg"`) - Will be downloaded automatically
135149
- Path to resource file (e.g., `/images/photo.jpg`)
136150
- Path from applicationDataDirectory
137151
- TiBlob object (from `imageView.toBlob()`, camera, etc.)
@@ -165,26 +179,41 @@ ShareModule.share({
165179

166180
The module handles the following automatically:
167181

168-
1. **Resource location**: Attempts to find images in multiple locations:
182+
1. **URL Detection**: Automatically detects if the image parameter is a URL (starts with `http://` or `https://`)
183+
184+
2. **Image Download**: For URL images:
185+
- Downloads the image asynchronously in the background
186+
- Uses a 15-second timeout for connection and read operations
187+
- Saves to the cache directory
188+
- If download fails, shares text only or notifies via callback
189+
190+
3. **Resource location**: For local files, attempts to find images in multiple locations:
169191
- Application resources (`/app/_app_/Resources/`)
170192
- Android assets
171193
- Application data directory
172194
- Cache directory
173195

174-
2. **File preparation**: For resource files and Blobs:
196+
4. **File preparation**: For resource files and Blobs:
175197
- Copies the file to the cache directory
176198
- Ensures the file is accessible by the FileProvider
177199

178-
3. **URI generation**: Creates a secure `content://` URI using Android's FileProvider
200+
5. **URI generation**: Creates a secure `content://` URI using Android's FileProvider
179201

180-
4. **Permission granting**: Adds `FLAG_GRANT_READ_URI_PERMISSION` so receiving apps can access the file
202+
6. **Permission granting**: Adds `FLAG_GRANT_READ_URI_PERMISSION` so receiving apps can access the file
181203

182-
5. **Intent creation**: Builds a proper `ACTION_SEND` intent with all necessary flags
204+
7. **Intent creation**: Builds a proper `ACTION_SEND` intent with all necessary flags
183205

184-
6. **Result handling**: When a callback is provided, uses `startActivityForResult` to capture the share outcome and notify your application
206+
8. **Result handling**: When a callback is provided, uses `startActivityForResult` to capture the share outcome and notify your application
185207

186208
## Troubleshooting
187209

210+
### URL image download issues
211+
212+
If images from URLs fail to download:
213+
214+
- Ensure the URL is publicly accessible (not behind authentication)
215+
- Check that the URL points directly to an image file (ends in .jpg, .png, etc.)
216+
188217
### Callback not firing
189218

190219
The callback relies on Android's activity result system. Note that:
@@ -211,8 +240,15 @@ Ensure:
211240
- Your app has necessary permissions in `tiapp.xml`:
212241

213242
```xml
214-
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
215-
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
243+
<!-- Necessária APENAS se você ler imagens de locais externos ao cache -->
244+
<!-- Only needed if you read images from external sources or cache -->
245+
<!-- Android 6 to 12 -->
246+
<uses-permission
247+
android:name="android.permission.READ_EXTERNAL_STORAGE"
248+
android:maxSdkVersion="32"/>
249+
250+
<!-- Android 13+ (replaced READ_EXTERNAL_STORAGE) -->
251+
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
216252
```
217253

218254
## Contributing
-103 KB
Binary file not shown.
106 KB
Binary file not shown.

android/manifest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# this is your module manifest and used by Titanium
33
# during compilation, packaging, distribution, etc.
44
#
5-
version: 1.0.0
5+
version: 1.0.1
66
apiversion: 4
77
architectures: arm64-v8a armeabi-v7a x86 x86_64
88
description: ti.android.share

android/src/ti/android/share/ShareProxy.java

Lines changed: 137 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import android.app.Activity;
44
import android.content.Intent;
55
import android.net.Uri;
6+
import android.os.AsyncTask;
67
import androidx.core.content.FileProvider;
78
import org.appcelerator.kroll.KrollDict;
89
import org.appcelerator.kroll.KrollFunction;
@@ -16,6 +17,9 @@
1617
import org.appcelerator.titanium.util.TiActivitySupport;
1718
import java.io.File;
1819
import java.io.FileOutputStream;
20+
import java.io.InputStream;
21+
import java.net.HttpURLConnection;
22+
import java.net.URL;
1923
import java.util.HashMap;
2024

2125
public class ShareProxy implements TiActivityResultHandler {
@@ -35,7 +39,7 @@ public static void share(HashMap params, KrollModule module) {
3539
private void executeShare(HashMap params) {
3640
try {
3741
String message = TiConvert.toString(params.get("message"), "");
38-
String subject = TiConvert.toString(params.get("subject"), "Compartilhar");
42+
String subject = TiConvert.toString(params.get("subject"), "Share");
3943
Object imageObj = params.get("image");
4044
Object callbackObj = params.get("callback");
4145

@@ -49,6 +53,35 @@ private void executeShare(HashMap params) {
4953
Log.d(TAG, "Image object type: " + (imageObj != null ? imageObj.getClass().getName() : "null"));
5054
Log.d(TAG, "Callback provided: " + (this.callback != null));
5155

56+
// Check if image is a URL
57+
if (imageObj instanceof String && isUrl((String) imageObj)) {
58+
String imageUrl = (String) imageObj;
59+
Log.d(TAG, "Detected URL image: " + imageUrl);
60+
61+
// Download image asynchronously
62+
new ImageDownloadTask(message, subject).execute(imageUrl);
63+
64+
} else {
65+
// Process normally (local file or blob)
66+
processShare(message, subject, imageObj);
67+
}
68+
69+
} catch (Exception e) {
70+
Log.e(TAG, "FATAL ERROR while sharing: " + e.getMessage(), e);
71+
72+
// Call callback with error if available
73+
if (this.callback != null) {
74+
fireCallback(false, "Error: " + e.getMessage());
75+
}
76+
}
77+
}
78+
79+
private boolean isUrl(String str) {
80+
return str != null && (str.startsWith("http://") || str.startsWith("https://"));
81+
}
82+
83+
private void processShare(String message, String subject, Object imageObj) {
84+
try {
5285
Intent shareIntent = new Intent(Intent.ACTION_SEND);
5386

5487
if (imageObj != null) {
@@ -97,9 +130,8 @@ private void executeShare(HashMap params) {
97130
Log.d(TAG, "=== SHARE INITIATED ===");
98131

99132
} catch (Exception e) {
100-
Log.e(TAG, "FATAL ERROR while sharing: " + e.getMessage(), e);
133+
Log.e(TAG, "ERROR in processShare: " + e.getMessage(), e);
101134

102-
// Call callback with error if available
103135
if (this.callback != null) {
104136
fireCallback(false, "Error: " + e.getMessage());
105137
}
@@ -173,6 +205,12 @@ private static Uri getImageUri(Object imageObj) {
173205
fos.close();
174206

175207
Log.d(TAG, "File created successfully. Size: " + imageFile.length() + " bytes");
208+
209+
}
210+
// If it's a File object (already downloaded from URL)
211+
else if (imageObj instanceof File) {
212+
imageFile = (File) imageObj;
213+
Log.d(TAG, "Using existing File object: " + imageFile.getAbsolutePath());
176214
}
177215
// If it's a file path (String)
178216
else if (imageObj instanceof String) {
@@ -281,4 +319,100 @@ else if (imageObj instanceof String) {
281319

282320
return null;
283321
}
322+
323+
/**
324+
* AsyncTask to download image from URL in background
325+
*/
326+
private class ImageDownloadTask extends AsyncTask<String, Void, File> {
327+
328+
private String message;
329+
private String subject;
330+
private String errorMessage;
331+
332+
public ImageDownloadTask(String message, String subject) {
333+
this.message = message;
334+
this.subject = subject;
335+
}
336+
337+
@Override
338+
protected File doInBackground(String... urls) {
339+
String imageUrl = urls[0];
340+
HttpURLConnection connection = null;
341+
342+
try {
343+
Log.d(TAG, "Starting image download from: " + imageUrl);
344+
345+
URL url = new URL(imageUrl);
346+
connection = (HttpURLConnection) url.openConnection();
347+
connection.setConnectTimeout(15000);
348+
connection.setReadTimeout(15000);
349+
connection.setRequestMethod("GET");
350+
connection.setDoInput(true);
351+
connection.connect();
352+
353+
int responseCode = connection.getResponseCode();
354+
Log.d(TAG, "Response code: " + responseCode);
355+
356+
if (responseCode == HttpURLConnection.HTTP_OK) {
357+
InputStream input = connection.getInputStream();
358+
359+
// Create temporary file
360+
File cacheDir = TiApplication.getInstance().getCacheDir();
361+
File imageFile = new File(cacheDir, "share_url_" + System.currentTimeMillis() + ".jpg");
362+
363+
FileOutputStream output = new FileOutputStream(imageFile);
364+
365+
byte[] buffer = new byte[4096];
366+
int bytesRead;
367+
long totalBytes = 0;
368+
369+
while ((bytesRead = input.read(buffer)) != -1) {
370+
output.write(buffer, 0, bytesRead);
371+
totalBytes += bytesRead;
372+
}
373+
374+
output.close();
375+
input.close();
376+
377+
Log.d(TAG, "✓ Image downloaded successfully. Size: " + totalBytes + " bytes");
378+
Log.d(TAG, "Saved to: " + imageFile.getAbsolutePath());
379+
380+
return imageFile;
381+
382+
} else {
383+
errorMessage = "HTTP error code: " + responseCode;
384+
Log.e(TAG, errorMessage);
385+
return null;
386+
}
387+
388+
} catch (Exception e) {
389+
errorMessage = "Download failed: " + e.getMessage();
390+
Log.e(TAG, errorMessage, e);
391+
return null;
392+
393+
} finally {
394+
if (connection != null) {
395+
connection.disconnect();
396+
}
397+
}
398+
}
399+
400+
@Override
401+
protected void onPostExecute(File imageFile) {
402+
if (imageFile != null && imageFile.exists()) {
403+
Log.d(TAG, "Download completed, proceeding with share");
404+
processShare(message, subject, imageFile);
405+
} else {
406+
Log.e(TAG, "Download failed, sharing text only");
407+
408+
// If callback exists, notify about download failure
409+
if (callback != null) {
410+
fireCallback(false, errorMessage != null ? errorMessage : "Failed to download image");
411+
} else {
412+
// Share text only if no callback
413+
processShare(message, subject, null);
414+
}
415+
}
416+
}
417+
}
284418
}

android/timodule.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<ti:module xmlns:ti="http://ti.appcelerator.org" xmlns:android="http://schemas.android.com/apk/res/android">
33
<android>
44
<manifest>
5+
<uses-permission android:name="android.permission.INTERNET"/>
56
<application>
67
<provider
78
android:name="androidx.core.content.FileProvider"

android/untitled.xml

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)