diff --git a/README.md b/README.md index 1a4219f..95069be 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,38 @@ DeviceEventEmitter.addListener('finishedShare',function(event){ }); ``` +#### WechatAPI.shareToTimeline(data) +分享到朋友圈 + +```javascript +// 分享文字 +{ + type: 'text', + text: 文字内容, +} +``` + +```javascript +// 分享图片 +{ + type: 'image', + imageUrl: 图片地址, + title : 标题, + description : 描述, +} +``` + +```javascript +// 分享网页 +{ + type: 'news', + title : 标题, + description : 描述, + webpageUrl : 链接地址, + imageUrl: 缩略图地址, +} +``` + ### weChatPay(options,callback) options : [微信支付需要的参数](https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12&index=2) diff --git a/android/src/main/java/com/heng/wechat/WeChatModule.java b/android/src/main/java/com/heng/wechat/WeChatModule.java index bd18d61..0c99a3d 100644 --- a/android/src/main/java/com/heng/wechat/WeChatModule.java +++ b/android/src/main/java/com/heng/wechat/WeChatModule.java @@ -1,14 +1,45 @@ package com.heng.wechat; +import android.content.Context; +import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.net.Uri; +import android.support.annotation.Nullable; import android.text.TextUtils; - +import android.util.Log; + +import com.facebook.common.executors.UiThreadImmediateExecutorService; +import com.facebook.common.internal.Preconditions; +import com.facebook.common.references.CloseableReference; +import com.facebook.common.util.UriUtil; +import com.facebook.datasource.BaseDataSubscriber; +import com.facebook.datasource.DataSource; +import com.facebook.datasource.DataSubscriber; +import com.facebook.drawee.backends.pipeline.Fresco; +import com.facebook.drawee.drawable.OrientedDrawable; +import com.facebook.imagepipeline.common.ResizeOptions; +import com.facebook.imagepipeline.core.ImagePipeline; +import com.facebook.imagepipeline.image.CloseableImage; +import com.facebook.imagepipeline.image.CloseableStaticBitmap; +import com.facebook.imagepipeline.image.EncodedImage; +import com.facebook.imagepipeline.request.ImageRequest; +import com.facebook.imagepipeline.request.ImageRequestBuilder; +import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.RCTNativeAppEventEmitter; +import com.tencent.mm.sdk.modelbase.BaseReq; +import com.tencent.mm.sdk.modelbase.BaseResp; import com.tencent.mm.sdk.modelmsg.SendAuth; import com.tencent.mm.sdk.modelmsg.SendMessageToWX; import com.tencent.mm.sdk.modelmsg.WXImageObject; @@ -18,6 +49,7 @@ import com.tencent.mm.sdk.modelmsg.WXVideoObject; import com.tencent.mm.sdk.modelmsg.WXWebpageObject; import com.tencent.mm.sdk.modelpay.PayReq; +import com.tencent.mm.sdk.modelpay.PayResp; import com.tencent.mm.sdk.openapi.IWXAPI; import com.tencent.mm.sdk.openapi.WXAPIFactory; @@ -90,6 +122,23 @@ public class WeChatModule extends ReactContextBaseJavaModule { public static final String OPTIONS_SIGN = "sign"; /*============ WeChat pay options key ==============*/ + private static final String RCTWXShareTypeNews = "news"; + private static final String RCTWXShareTypeImage = "image"; + private static final String RCTWXShareTypeText = "text"; + private static final String RCTWXShareTypeVideo = "video"; + private static final String RCTWXShareTypeAudio = "audio"; + + private static final String RCTWXShareType = "type"; + private static final String RCTWXShareText = "text"; + private static final String RCTWXShareTitle = "title"; + private static final String RCTWXShareDescription = "description"; + private static final String RCTWXShareWebpageUrl = "webpageUrl"; + private static final String RCTWXShareImageUrl = "imageUrl"; + private static final String RCTWXShareThumbImageSize = "thumbImageSize"; + + private final static String NOT_REGISTERED = "registerApp required."; + private final static String INVOKE_FAILED = "WeChat API invoke returns false."; + public static final int TYPE_TEXT = 1; //文字 public static final int TYPE_IMAGE = 2; //图片 @@ -311,6 +360,14 @@ public void sendReq(ReadableMap options, Callback callback) { } } + /** + * 分享到朋友圈 + */ + @ReactMethod + public void shareToTimeline(ReadableMap data, Callback callback) { + _share(SendMessageToWX.Req.WXSceneTimeline, data, callback); + } + /** * 微信支付 @@ -495,4 +552,261 @@ private WXVideoObject getVideoObj(ReadableMap options){ return videoObject; } + + /** + * 分享到微信 + * */ + @ReactMethod + public void shareToSession(ReadableMap data, Callback callback){ + _share(SendMessageToWX.Req.WXSceneSession, data, callback); + } + + + private String _getErrorMsg(int errCode) { + switch (errCode) { + case BaseResp.ErrCode.ERR_OK: + return "成功"; + case BaseResp.ErrCode.ERR_COMM: + return "普通错误类型"; + case BaseResp.ErrCode.ERR_USER_CANCEL: + return "用户点击取消并返回"; + case BaseResp.ErrCode.ERR_SENT_FAILED: + return "发送失败"; + case BaseResp.ErrCode.ERR_AUTH_DENIED: + return "授权失败"; + case BaseResp.ErrCode.ERR_UNSUPPORT: + return "微信不支持"; + default: + return "失败"; + } + } + + + private void _share(final int scene, final ReadableMap data, final Callback callBack){ + + if (data.hasKey(RCTWXShareImageUrl)) { + String imageUrl = data.getString(RCTWXShareImageUrl); + DataSubscriber> dataSubscriber = + new BaseDataSubscriber>() { + @Override + public void onNewResultImpl(DataSource> dataSource) { + // isFinished must be obtained before image, otherwise we might set intermediate result + // as final image. + boolean isFinished = dataSource.isFinished(); +// float progress = dataSource.getProgress(); + CloseableReference image = dataSource.getResult(); + if (image != null) { + Drawable drawable = _createDrawable(image); + Bitmap bitmap = _drawable2Bitmap(drawable); + _share(scene, data, bitmap, callBack); + } else if (isFinished) { + _share(scene, data, null, callBack); + } + dataSource.close(); + } + @Override + public void onFailureImpl(DataSource> dataSource) { + dataSource.close(); + _share(scene, data, null, callBack); + } + + @Override + public void onProgressUpdate(DataSource> dataSource) { + } + }; + ResizeOptions resizeOptions = null; + if (!(data.hasKey(RCTWXShareType) && data.getString(RCTWXShareType).equals(RCTWXShareTypeImage))) { + int size = 80; + if (data.hasKey(RCTWXShareThumbImageSize)) { + size = data.getInt(RCTWXShareThumbImageSize); + } + resizeOptions = new ResizeOptions(size, size); + } + _downloadImage(imageUrl, resizeOptions, dataSubscriber); + } + else { + _share(scene, data, null, callBack); + } + } + + private void _share(int scene, ReadableMap data, Bitmap image, Callback callback) { + WXMediaMessage message = new WXMediaMessage(); + if (data.hasKey(RCTWXShareTitle)){ + message.title = data.getString(RCTWXShareTitle); + } + if (data.hasKey(RCTWXShareDescription)) { + message.description = data.getString(RCTWXShareDescription); + } + + String type = RCTWXShareTypeNews; + if (data.hasKey(RCTWXShareType)) { + type = data.getString(RCTWXShareType); + } + + if (type.equals(RCTWXShareTypeText)) { + WXTextObject object = new WXTextObject(); + if (data.hasKey(RCTWXShareText)) { + object.text = data.getString(RCTWXShareText); + } + message.mediaObject = object; + } + else if (type.equals(RCTWXShareTypeImage)) { + WXImageObject object = new WXImageObject(); + if (data.hasKey(RCTWXShareImageUrl)) { + if (image != null) { + object.imageData = _Bitmap2Bytes(image); + Bitmap thumb = Bitmap.createScaledBitmap(image, 80, 80, true); + message.thumbData = _Bitmap2Bytes(thumb); + } + } + message.mediaObject = object; + } + else { + if (type.equals(RCTWXShareTypeNews)) { + WXWebpageObject object = new WXWebpageObject(); + if (data.hasKey(RCTWXShareWebpageUrl)){ + object.webpageUrl = data.getString(RCTWXShareWebpageUrl); + } + if (data.hasKey("extInfo")){ + object.extInfo = data.getString("extInfo"); + } + message.mediaObject = object; + } + else if (type.equals(RCTWXShareTypeVideo)) { + WXMusicObject object = new WXMusicObject(); + if (data.hasKey(RCTWXShareWebpageUrl)) { + object.musicUrl = data.getString(RCTWXShareWebpageUrl); + } + message.mediaObject = object; + } + else if (type.equals(RCTWXShareTypeAudio)) { + WXVideoObject object = new WXVideoObject(); + if (data.hasKey(RCTWXShareWebpageUrl)) { + object.videoUrl = data.getString(RCTWXShareWebpageUrl); + } + message.mediaObject = object; + } + + if (image != null) { + Log.e("share", "image no null"); + message.setThumbImage(image); + }else { + Log.e("share", "image null"); + } + } + + //TODO: create Thumb Data. + if (data.hasKey("mediaTagName")) { + message.mediaTagName = data.getString("mediaTagName"); + } + if (data.hasKey("messageAction")) { + message.mediaTagName = data.getString("messageAction"); + } + if (data.hasKey("messageExt")) { + message.mediaTagName = data.getString("messageExt"); + } + + SendMessageToWX.Req req = new SendMessageToWX.Req(); + req.message = message; + req.scene = scene; + + boolean success = WeChatModule.wxApi.sendReq(req); + + if (success == false) { + callback.invoke("INVOKE_FAILED"); + }else { + callback.invoke(); + } + + } + + private static @Nullable + Uri getResourceDrawableUri(Context context, @Nullable String name) { + if (name == null || name.isEmpty()) { + return null; + } + name = name.toLowerCase().replace("-", "_"); + int resId = context.getResources().getIdentifier( + name, + "drawable", + context.getPackageName()); + return new Uri.Builder() + .scheme(UriUtil.LOCAL_RESOURCE_SCHEME) + .path(String.valueOf(resId)) + .build(); + } + + private void _downloadImage(String imageUrl, ResizeOptions resizeOptions, DataSubscriber> dataSubscriber) { + + Uri uri = null; + try { + uri = Uri.parse(imageUrl); + // Verify scheme is set, so that relative uri (used by static resources) are not handled. + if (uri.getScheme() == null) { + uri = null; + } + } catch (Exception e) { + // ignore malformed uri, then attempt to extract resource ID. + } + if (uri == null) { + uri = getResourceDrawableUri(getReactApplicationContext(), imageUrl); + } else { + } + + ImageRequestBuilder builder = ImageRequestBuilder.newBuilderWithSource(uri); + if (resizeOptions != null) { + builder = builder.setResizeOptions(resizeOptions); + } + ImageRequest imageRequest = builder.build(); + + ImagePipeline imagePipeline = Fresco.getImagePipeline(); + DataSource> dataSource = imagePipeline.fetchDecodedImage(imageRequest, null); + dataSource.subscribe(dataSubscriber, UiThreadImmediateExecutorService.getInstance()); + } + + private Drawable _createDrawable(CloseableReference image) { + Preconditions.checkState(CloseableReference.isValid(image)); + CloseableImage closeableImage = image.get(); + if (closeableImage instanceof CloseableStaticBitmap) { + CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) closeableImage; + BitmapDrawable bitmapDrawable = new BitmapDrawable( + getReactApplicationContext().getResources(), + closeableStaticBitmap.getUnderlyingBitmap()); + if (closeableStaticBitmap.getRotationAngle() == 0 || + closeableStaticBitmap.getRotationAngle() == EncodedImage.UNKNOWN_ROTATION_ANGLE) { + return bitmapDrawable; + } else { + return new OrientedDrawable(bitmapDrawable, closeableStaticBitmap.getRotationAngle()); + } + } else { + throw new UnsupportedOperationException("Unrecognized image class: " + closeableImage); + } + } + + private Bitmap _drawable2Bitmap(Drawable drawable) { + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + } else if (drawable instanceof NinePatchDrawable) { + Bitmap bitmap = Bitmap + .createBitmap( + drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 + : Bitmap.Config.RGB_565); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight()); + drawable.draw(canvas); + return bitmap; + } else { + return null; + } + } + + private byte[] _Bitmap2Bytes(Bitmap bm){ + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bm.compress(Bitmap.CompressFormat.JPEG, 70, baos); + return baos.toByteArray(); + } + }