From 4ee31eec5f3f5f0655378547bd8cfc3a8b83e12b Mon Sep 17 00:00:00 2001 From: gzu-liyujiang Date: Fri, 20 Dec 2019 21:33:26 +0800 Subject: [PATCH 1/3] fix gradle sync ERROR: Manifest merger failed : Attribute data@scheme at ... requires a placeholder substitution but no value for is provided. --- android/build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index db22888..8ac7b96 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -28,7 +28,9 @@ android { resourcePrefix 'tencent_kit' defaultConfig { - minSdkVersion 16 + minSdkVersion 16 + + manifestPlaceholders = [TENCENT_APP_ID: "222222"] // library 混淆 -> 随 library 引用,自动添加到 apk 打包混淆 consumerProguardFiles 'consumer-proguard-rules.pro' From 1cb7cafc6f2b10a9ce49be20f47e7dc9553fc26a Mon Sep 17 00:00:00 2001 From: gzu-liyujiang Date: Sat, 21 Dec 2019 00:38:33 +0800 Subject: [PATCH 2/3] =?UTF-8?q?add:=20=E6=94=AF=E6=8C=81=E5=88=A4=E6=96=AD?= =?UTF-8?q?QQ=E6=98=AF=E5=90=A6=E5=B7=B2=E7=99=BB=E5=BD=95=E8=BF=87?= =?UTF-8?q?=E4=BA=86=EF=BC=8C=E7=BB=A7=E8=80=8C=E5=8F=AF=E4=BB=A5=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E8=8E=B7=E5=8F=96=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=88=96=E8=8E=B7=E5=8F=96UnionID=EF=BC=88iOS=20=E5=BE=85?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=EF=BC=89=EF=BC=9B=20add:=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=8B=89=E8=B5=B7=E6=89=8B=E6=9C=BAQQ=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=EF=BC=88=E8=85=BE=E8=AE=AFQQ=E4=BA=92=E8=81=94SDK?= =?UTF-8?q?=E5=8F=AA=E6=94=AF=E6=8C=81Android=EF=BC=89=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v7lin/tencent_kit/TencentKitPlugin.java | 47 ++++++++++++++++++- example/lib/main.dart | 24 +++++++++- ios/Classes/TencentKitPlugin.m | 3 ++ lib/src/tencent.dart | 24 ++++++++++ 4 files changed, 96 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/io/github/v7lin/tencent_kit/TencentKitPlugin.java b/android/src/main/java/io/github/v7lin/tencent_kit/TencentKitPlugin.java index 847f378..f40f7a3 100644 --- a/android/src/main/java/io/github/v7lin/tencent_kit/TencentKitPlugin.java +++ b/android/src/main/java/io/github/v7lin/tencent_kit/TencentKitPlugin.java @@ -13,6 +13,7 @@ import com.tencent.connect.share.QQShare; import com.tencent.connect.share.QzonePublish; import com.tencent.connect.share.QzoneShare; +import com.tencent.open.im.IM; import com.tencent.tauth.IUiListener; import com.tencent.tauth.Tencent; import com.tencent.tauth.UiError; @@ -63,12 +64,14 @@ private static class TencentRetCode { private static final String METHOD_REGISTERAPP = "registerApp"; private static final String METHOD_ISINSTALLED = "isInstalled"; + private static final String METHOD_ISREADY = "isReady"; private static final String METHOD_LOGIN = "login"; private static final String METHOD_LOGOUT = "logout"; private static final String METHOD_SHAREMOOD = "shareMood"; private static final String METHOD_SHAREIMAGE = "shareImage"; private static final String METHOD_SHAREMUSIC = "shareMusic"; private static final String METHOD_SHAREWEBPAGE = "shareWebpage"; + private static final String START_CONVERSATION = "startConversation"; private static final String METHOD_ONLOGINRESP = "onLoginResp"; private static final String METHOD_ONSHARERESP = "onShareResp"; @@ -86,6 +89,7 @@ private static class TencentRetCode { private static final String ARGUMENT_KEY_TARGETURL = "targetUrl"; private static final String ARGUMENT_KEY_APPNAME = "appName"; private static final String ARGUMENT_KEY_EXTINT = "extInt"; + private static final String ARGUMENT_KEY_QQ = "qq"; private static final String ARGUMENT_KEY_RESULT_RET = "ret"; private static final String ARGUMENT_KEY_RESULT_MSG = "msg"; @@ -107,7 +111,7 @@ private TencentKitPlugin(Registrar registrar, MethodChannel channel) { } @Override - public void onMethodCall(MethodCall call, @NonNull Result result) { + public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { if (METHOD_REGISTERAPP.equals(call.method)) { final String appId = call.argument(ARGUMENT_KEY_APPID); // final String universalLink = call.argument(ARGUMENT_KEY_UNIVERSALLINK); @@ -129,6 +133,8 @@ public void onMethodCall(MethodCall call, @NonNull Result result) { } } result.success(isInstalled); + } else if (METHOD_ISREADY.equals(call.method)) { + result.success(tencent != null && tencent.isReady()); } else if (METHOD_LOGIN.equals(call.method)) { login(call, result); } else if (METHOD_LOGOUT.equals(call.method)) { @@ -141,6 +147,8 @@ public void onMethodCall(MethodCall call, @NonNull Result result) { shareMusic(call, result); } else if (METHOD_SHAREWEBPAGE.equals(call.method)) { shareWebpage(call, result); + } else if (START_CONVERSATION.equals(call.method)) { + startConversation(call, result); } else { result.notImplemented(); } @@ -169,6 +177,8 @@ public void onComplete(Object o) { int expiresIn = !object.isNull(ARGUMENT_KEY_RESULT_EXPIRES_IN) ? object.getInt(ARGUMENT_KEY_RESULT_EXPIRES_IN) : 0; long createAt = System.currentTimeMillis(); if (!TextUtils.isEmpty(openId) && !TextUtils.isEmpty(accessToken)) { + tencent.setOpenId(openId); + tencent.setAccessToken(accessToken, String.valueOf(expiresIn)); map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_SUCCESS); map.put(ARGUMENT_KEY_RESULT_OPENID, openId); map.put(ARGUMENT_KEY_RESULT_ACCESS_TOKEN, accessToken); @@ -365,6 +375,41 @@ private void shareWebpage(MethodCall call, Result result) { result.success(null); } + private void startConversation(MethodCall call, Result result) { + if (tencent == null) { + result.error(String.valueOf(IM.IM_UNKNOWN_TYPE), "Should register app at first", null); + return; + } + if (!tencent.isReady()) { + result.error(String.valueOf(IM.IM_UNKNOWN_TYPE), "Should login at first", null); + return; + } + String qq = call.argument(ARGUMENT_KEY_QQ); + android.app.Activity activity = registrar.activity(); + String packageName = activity.getApplicationContext().getPackageName(); + int ret = tencent.startIMAio(activity, qq, packageName); + if (ret == IM.IM_SUCCESS) { + result.success("0"); + return; + } + String errorMsg; + switch (ret) { + case IM.IM_SHOULD_DOWNLOAD: + errorMsg = "Should download latest version of MobileQQ"; + break; + case IM.IM_UIN_EMPTY: + case IM.IM_LENGTH_SHORT: + case IM.IM_UIN_NOT_DIGIT: + errorMsg = "QQ number is invalid"; + break; + case IM.IM_UNKNOWN_TYPE: + default: + errorMsg = "Unknown type"; + break; + } + result.error(String.valueOf(ret), errorMsg, null); + } + private IUiListener shareListener = new IUiListener() { @Override public void onComplete(Object o) { diff --git a/example/lib/main.dart b/example/lib/main.dart index 2222edf..28d0020 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show PlatformException; import 'package:okhttp_kit/okhttp_kit.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart' as path_provider; @@ -75,7 +76,9 @@ class _HomeState extends State { ListTile( title: const Text('环境检查'), onTap: () async { - String content = 'tencent: ${await _tencent.isInstalled()}'; + bool isInstalled = await _tencent.isInstalled(); + bool isReady = await _tencent.isReady(); + String content = 'isInstalled=$isInstalled, isReady=$isReady'; _showTips('环境检查', content); }, ), @@ -90,6 +93,10 @@ class _HomeState extends State { ListTile( title: const Text('获取用户信息'), onTap: () async { + if (!await _tencent.isReady()) { + _showTips('Error', '请先登录'); + return; + } if (_loginResp != null && _loginResp.isSuccessful() && !_loginResp.isExpired()) { @@ -110,6 +117,10 @@ class _HomeState extends State { ListTile( title: const Text('获取UnionID'), onTap: () async { + if (!await _tencent.isReady()) { + _showTips('Error', '请先登录'); + return; + } if (_loginResp != null && _loginResp.isSuccessful() && !_loginResp.isExpired()) { @@ -175,6 +186,17 @@ class _HomeState extends State { ); }, ), + ListTile( + title: const Text('拉起手Q会话窗口'), + onTap: () async { + try { + await _tencent.startConversation('1032694760'); + } on PlatformException catch (e) { + //错误码参见QQ互联文档:https://wiki.connect.qq.com/%E8%81%8A%E5%A4%A9 + _showTips('Error', '${e.code} - ${e.message}'); + } + }, + ), ], ), ); diff --git a/ios/Classes/TencentKitPlugin.m b/ios/Classes/TencentKitPlugin.m index 8052d64..1acb815 100644 --- a/ios/Classes/TencentKitPlugin.m +++ b/ios/Classes/TencentKitPlugin.m @@ -38,6 +38,7 @@ + (void)registerWithRegistrar:(NSObject *)registrar { static NSString *const METHOD_REGISTERAPP = @"registerApp"; static NSString *const METHOD_ISINSTALLED = @"isInstalled"; +static NSString *const METHOD_ISREADY = @"isReady"; static NSString *const METHOD_LOGIN = @"login"; static NSString *const METHOD_LOGOUT = @"logout"; static NSString *const METHOD_SHAREMOOD = @"shareMood"; @@ -96,6 +97,8 @@ - (void)handleMethodCall:(FlutterMethodCall *)call // 普通大众版 > 办公简洁版 BOOL isInstalled = [TencentOAuth iphoneQQInstalled] || [TencentOAuth iphoneTIMInstalled]; result([NSNumber numberWithBool:isInstalled]); + } else if ([METHOD_ISREADY isEqualToString:call.method]) { + result(FlutterMethodNotImplemented); } else if ([METHOD_LOGIN isEqualToString:call.method]) { [self login:call result:result]; } else if ([METHOD_LOGOUT isEqualToString:call.method]) { diff --git a/lib/src/tencent.dart b/lib/src/tencent.dart index ac637b6..a233b63 100644 --- a/lib/src/tencent.dart +++ b/lib/src/tencent.dart @@ -19,12 +19,14 @@ class Tencent { static const String _METHOD_REGISTERAPP = 'registerApp'; static const String _METHOD_ISINSTALLED = 'isInstalled'; + static const String _METHOD_ISREADY = 'isReady'; static const String _METHOD_LOGIN = 'login'; static const String _METHOD_LOGOUT = 'logout'; static const String _METHOD_SHAREMOOD = 'shareMood'; static const String _METHOD_SHAREIMAGE = 'shareImage'; static const String _METHOD_SHAREMUSIC = 'shareMusic'; static const String _METHOD_SHAREWEBPAGE = 'shareWebpage'; + static const String _METHOD_STARTCONVERSATION = 'startConversation'; static const String _METHOD_ONLOGINRESP = 'onLoginResp'; static const String _METHOD_ONSHARERESP = "onShareResp"; @@ -42,6 +44,7 @@ class Tencent { static const String _ARGUMENT_KEY_TARGETURL = 'targetUrl'; static const String _ARGUMENT_KEY_APPNAME = 'appName'; static const String _ARGUMENT_KEY_EXTINT = 'extInt'; + static const String _ARGUMENT_KEY_QQ = 'qq'; static const String _SCHEME_FILE = 'file'; @@ -101,6 +104,11 @@ class Tencent { return _channel.invokeMethod(_METHOD_ISINSTALLED); } + /// 检查Session及OpenId是否有效 + Future isReady() async { + return _channel.invokeMethod(_METHOD_ISREADY); + } + /// 登录 Future login({ @required List scope, @@ -313,4 +321,20 @@ class Tencent { } return _channel.invokeMethod(_METHOD_SHAREWEBPAGE, arguments); } + + /// 拉起手机QQ加好友聊天(仅支持Android) + Future startConversation(String qq) { + assert(qq != null && qq.isNotEmpty); + if (!Platform.isAndroid) { + // iOS的支持需要自行封装这个协议: + // mqqapi://im/chat?chat_type=thirdparty2c&uin={QQ号}&version=1&src_type=app&open_id={OpenId的BASE64编码}&app_id={AppId的BASE64编码}&app_pkg_name={BundleId的BASE64编码} + throw UnsupportedError('Only supported on Android'); + } + return _channel.invokeMethod( + _METHOD_STARTCONVERSATION, + { + _ARGUMENT_KEY_QQ: qq, + }, + ); + } } From 498c54eb4b6ec2649e0f0f3325d2709b1d05043f Mon Sep 17 00:00:00 2001 From: gzu-liyujiang Date: Sat, 21 Dec 2019 15:56:32 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E5=BA=93=E9=A1=B9=E7=9B=AEmanifest?= =?UTF-8?q?=E5=8D=A0=E4=BD=8D=E4=B8=8D=E4=BC=9A=E8=A2=AB=E8=A6=86=E7=9B=96?= =?UTF-8?q?=EF=BC=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 8ac7b96..c39d756 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -29,8 +29,6 @@ android { defaultConfig { minSdkVersion 16 - - manifestPlaceholders = [TENCENT_APP_ID: "222222"] // library 混淆 -> 随 library 引用,自动添加到 apk 打包混淆 consumerProguardFiles 'consumer-proguard-rules.pro'