diff --git a/android/build.gradle b/android/build.gradle index db22888..c39d756 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -28,7 +28,7 @@ android { resourcePrefix 'tencent_kit' defaultConfig { - minSdkVersion 16 + minSdkVersion 16 // library 混淆 -> 随 library 引用,自动添加到 apk 打包混淆 consumerProguardFiles 'consumer-proguard-rules.pro' 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, + }, + ); + } }