Merge pull request #26 from v7lin/v2

v2
This commit is contained in:
沉默的湮灰
2020-06-13 17:47:25 +08:00
committed by GitHub
27 changed files with 697 additions and 607 deletions

View File

@ -1,3 +1,8 @@
## 1.1.0 - 2020.06.13
* V2
* Android/iOS SDK
## 1.0.4 - 2019.12.16
* Android Q

View File

@ -48,7 +48,9 @@ android {
```
```
<!-- SDK里居然写死 android:authorities com.tencent.tauth.fileprovider android:authorities -->
<!-- -->
<!-- -->
<!-- 使 FileProvider -->
<manifest>
<!-- targetSdkVersion >= 29, Android Q -->
<application android:requestLegacyExternalStorage="true">

View File

@ -8,7 +8,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
classpath 'com.android.tools.build:gradle:3.5.0'
}
}
@ -31,7 +31,7 @@ android {
minSdkVersion 16
// library 混淆 -> 随 library 引用,自动添加到 apk 打包混淆
consumerProguardFiles 'consumer-proguard-rules.pro'
consumerProguardFiles 'consumer-rules.pro'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@ -1,5 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip

Binary file not shown.

View File

@ -4,10 +4,9 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!--<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />-->
<!--<uses-permission android:name="android.permission.WRITE_APN_SETTINGS"/>-->
<!-- 使 android:requestLegacyExternalStorage="true" Android Q -->
<application
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute">
@ -36,16 +35,15 @@
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<!-- Android Q -->
<!-- SDK里居然写死 android:authorities com.tencent.tauth.fileprovider android:authorities -->
<!-- <provider
<provider
android:name=".content.TencentKitFileProvider"
android:authorities="com.tencent.tauth.fileprovider"
android:authorities="${applicationId}.content.TencentKitFileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/tencent_kit_file_paths" />
</provider> -->
</provider>
</application>
</manifest>

View File

@ -0,0 +1,488 @@
package io.github.v7lin.tencent_kit;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.tencent.connect.common.Constants;
import com.tencent.connect.share.QQShare;
import com.tencent.connect.share.QzonePublish;
import com.tencent.connect.share.QzoneShare;
import com.tencent.tauth.IUiListener;
import com.tencent.tauth.Tencent;
import com.tencent.tauth.UiError;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
import io.github.v7lin.tencent_kit.content.TencentKitFileProvider;
public class TencentKit implements MethodChannel.MethodCallHandler, PluginRegistry.ActivityResultListener {
private static class TencentScene {
static final int SCENE_QQ = 0;
static final int SCENE_QZONE = 1;
}
private static class TencentRetCode {
// 网络请求成功发送至服务器,并且服务器返回数据格式正确
// 这里包括所请求业务操作失败的情况,例如没有授权等原因导致
static final int RET_SUCCESS = 0;
// 网络异常,或服务器返回的数据格式不正确导致无法解析
static final int RET_FAILED = 1;
static final int RET_COMMON = -1;
static final int RET_USERCANCEL = -2;
}
//
private static final String METHOD_REGISTERAPP = "registerApp";
private static final String METHOD_ISQQINSTALLED = "isQQInstalled";
private static final String METHOD_ISTIMINSTALLED = "isTIMInstalled";
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_SHARETEXT = "shareText";
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 METHOD_ONLOGINRESP = "onLoginResp";
private static final String METHOD_ONSHARERESP = "onShareResp";
private static final String ARGUMENT_KEY_APPID = "appId";
// private static final String ARGUMENT_KEY_UNIVERSALLINK = "universalLink";
private static final String ARGUMENT_KEY_SCOPE = "scope";
private static final String ARGUMENT_KEY_SCENE = "scene";
private static final String ARGUMENT_KEY_TITLE = "title";
private static final String ARGUMENT_KEY_SUMMARY = "summary";
private static final String ARGUMENT_KEY_IMAGEURI = "imageUri";
private static final String ARGUMENT_KEY_IMAGEURIS = "imageUris";
private static final String ARGUMENT_KEY_VIDEOURI = "videoUri";
private static final String ARGUMENT_KEY_MUSICURL = "musicUrl";
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_RESULT_RET = "ret";
private static final String ARGUMENT_KEY_RESULT_MSG = "msg";
private static final String ARGUMENT_KEY_RESULT_OPENID = "openid";
private static final String ARGUMENT_KEY_RESULT_ACCESS_TOKEN = "access_token";
private static final String ARGUMENT_KEY_RESULT_EXPIRES_IN = "expires_in";
private static final String ARGUMENT_KEY_RESULT_CREATE_AT = "create_at";
private static final String SCHEME_FILE = "file";
//
private Context applicationContext;
private Activity activity;
private MethodChannel channel;
private Tencent tencent;
public TencentKit() {
super();
}
public TencentKit(Context applicationContext, Activity activity) {
this.applicationContext = applicationContext;
this.activity = activity;
}
//
public void setApplicationContext(@Nullable Context applicationContext) {
this.applicationContext = applicationContext;
}
public void setActivity(@Nullable Activity activity) {
this.activity = activity;
}
public void startListening(@NonNull BinaryMessenger messenger) {
channel = new MethodChannel(messenger, "v7lin.github.io/tencent_kit");
channel.setMethodCallHandler(this);
}
public void stopListening() {
channel.setMethodCallHandler(null);
channel = null;
}
// --- MethodCallHandler
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
if (METHOD_REGISTERAPP.equals(call.method)) {
final String appId = call.argument(ARGUMENT_KEY_APPID);
// final String universalLink = call.argument(ARGUMENT_KEY_UNIVERSALLINK);
String authority = null;
try {
ProviderInfo providerInfo = applicationContext.getPackageManager().getProviderInfo(new ComponentName(applicationContext, TencentKitFileProvider.class), PackageManager.MATCH_DEFAULT_ONLY);
authority = providerInfo.authority;
} catch (PackageManager.NameNotFoundException e) {
}
if (!TextUtils.isEmpty(authority)) {
tencent = Tencent.createInstance(appId, applicationContext, authority);
} else {
tencent = Tencent.createInstance(appId, applicationContext);
}
result.success(null);
} else if (METHOD_ISQQINSTALLED.equals(call.method)) {
result.success(isAppInstalled(applicationContext, "com.tencent.mobileqq"));
} else if (METHOD_ISTIMINSTALLED.equals(call.method)) {
result.success(isAppInstalled(applicationContext, "com.tencent.tim"));
} else if (METHOD_LOGIN.equals(call.method)) {
login(call, result);
} else if (METHOD_LOGOUT.equals(call.method)) {
logout(call, result);
} else if (METHOD_SHAREMOOD.equals(call.method)) {
shareMood(call, result);
} else if (METHOD_SHARETEXT.equals(call.method)) {
shareText(call, result);
} else if (METHOD_SHAREIMAGE.equals(call.method)) {
shareImage(call, result);
} else if (METHOD_SHAREMUSIC.equals(call.method)) {
shareMusic(call, result);
} else if (METHOD_SHAREWEBPAGE.equals(call.method)) {
shareWebpage(call, result);
} else {
result.notImplemented();
}
}
private void login(MethodCall call, MethodChannel.Result result) {
if (tencent != null) {
String scope = call.argument(ARGUMENT_KEY_SCOPE);
tencent.login(activity, scope, loginListener);
}
result.success(null);
}
private IUiListener loginListener = new IUiListener() {
@Override
public void onComplete(Object o) {
Map<String, Object> map = new HashMap<>();
try {
if (o != null && o instanceof JSONObject) {
JSONObject object = (JSONObject) o;
int ret = !object.isNull(ARGUMENT_KEY_RESULT_RET) ? object.getInt(ARGUMENT_KEY_RESULT_RET) : TencentRetCode.RET_FAILED;
String msg = !object.isNull(ARGUMENT_KEY_RESULT_MSG) ? object.getString(ARGUMENT_KEY_RESULT_MSG) : null;
if (ret == TencentRetCode.RET_SUCCESS) {
String openId = !object.isNull(ARGUMENT_KEY_RESULT_OPENID) ? object.getString(ARGUMENT_KEY_RESULT_OPENID) : null;
String accessToken = !object.isNull(ARGUMENT_KEY_RESULT_ACCESS_TOKEN) ? object.getString(ARGUMENT_KEY_RESULT_ACCESS_TOKEN) : null;
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)) {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_SUCCESS);
map.put(ARGUMENT_KEY_RESULT_OPENID, openId);
map.put(ARGUMENT_KEY_RESULT_ACCESS_TOKEN, accessToken);
map.put(ARGUMENT_KEY_RESULT_EXPIRES_IN, expiresIn);
map.put(ARGUMENT_KEY_RESULT_CREATE_AT, createAt);
} else {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, "openId or accessToken is null.");
}
} else {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, msg);
}
}
} catch (JSONException e) {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, e.getMessage());
}
channel.invokeMethod(METHOD_ONLOGINRESP, map);
}
@Override
public void onError(UiError uiError) {
// 登录失败
Map<String, Object> map = new HashMap<>();
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, uiError.errorMessage);
channel.invokeMethod(METHOD_ONLOGINRESP, map);
}
@Override
public void onCancel() {
// 取消登录
Map<String, Object> map = new HashMap<>();
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_USERCANCEL);
channel.invokeMethod(METHOD_ONLOGINRESP, map);
}
};
private void logout(MethodCall call, MethodChannel.Result result) {
if (tencent != null) {
tencent.logout(applicationContext);
result.success(null);
}
}
private void shareMood(MethodCall call, MethodChannel.Result result) {
if (tencent != null) {
int scene = call.argument(ARGUMENT_KEY_SCENE);
if (scene == TencentScene.SCENE_QZONE) {
String summary = call.argument(ARGUMENT_KEY_SUMMARY);
List<String> imageUris = call.argument(ARGUMENT_KEY_IMAGEURIS);
String videoUri = call.argument(ARGUMENT_KEY_VIDEOURI);
Bundle params = new Bundle();
if (!TextUtils.isEmpty(summary)) {
params.putString(QzonePublish.PUBLISH_TO_QZONE_SUMMARY, summary);
}
if (imageUris != null && !imageUris.isEmpty()) {
ArrayList<String> uris = new ArrayList<>();
for (String imageUri : imageUris) {
uris.add(Uri.parse(imageUri).getPath());
}
params.putStringArrayList(QzonePublish.PUBLISH_TO_QZONE_IMAGE_URL, uris);
}
if (!TextUtils.isEmpty(videoUri)) {
String videoPath = Uri.parse(videoUri).getPath();
params.putString(QzonePublish.PUBLISH_TO_QZONE_VIDEO_PATH, videoPath);
params.putInt(QzonePublish.PUBLISH_TO_QZONE_KEY_TYPE, QzonePublish.PUBLISH_TO_QZONE_TYPE_PUBLISHVIDEO);
} else {
params.putInt(QzonePublish.PUBLISH_TO_QZONE_KEY_TYPE, QzonePublish.PUBLISH_TO_QZONE_TYPE_PUBLISHMOOD);
}
tencent.publishToQzone(activity, params, shareListener);
}
}
result.success(null);
}
private void shareText(MethodCall call, MethodChannel.Result result) {
int scene = call.argument(ARGUMENT_KEY_SCENE);
if (scene == TencentScene.SCENE_QQ) {
String summary = call.argument(ARGUMENT_KEY_SUMMARY);
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, summary);
sendIntent.setType("text/*");
// 普通大众版 > 办公简洁版 > 急速轻聊版
PackageManager packageManager = applicationContext.getPackageManager();
List<PackageInfo> infos = packageManager.getInstalledPackages(0);
if (infos != null && !infos.isEmpty()) {
for (String packageName : Arrays.asList("com.tencent.mobileqq", "com.tencent.tim", "com.tencent.qqlite")) {
for (PackageInfo info : infos) {
if (packageName.equals(info.packageName)) {
sendIntent.setPackage(packageName);
if (sendIntent.resolveActivity(applicationContext.getPackageManager()) != null) {
sendIntent.setComponent(new ComponentName(packageName, "com.tencent.mobileqq.activity.JumpActivity"));
activity.startActivity(sendIntent);
break;
}
}
}
}
}
}
result.success(null);
}
private void shareImage(MethodCall call, MethodChannel.Result result) {
if (tencent != null) {
int scene = call.argument(ARGUMENT_KEY_SCENE);
if (scene == TencentScene.SCENE_QQ) {
String imageUri = call.argument(ARGUMENT_KEY_IMAGEURI);
String appName = call.argument(ARGUMENT_KEY_APPNAME);
int extInt = call.argument(ARGUMENT_KEY_EXTINT);
Bundle params = new Bundle();
params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_IMAGE);
params.putString(QQShare.SHARE_TO_QQ_IMAGE_LOCAL_URL, Uri.parse(imageUri).getPath());
if (!TextUtils.isEmpty(appName)) {
params.putString(QQShare.SHARE_TO_QQ_APP_NAME, appName);
}
params.putInt(QQShare.SHARE_TO_QQ_EXT_INT, extInt);
tencent.shareToQQ(activity, params, shareListener);
}
}
result.success(null);
}
private void shareMusic(MethodCall call, MethodChannel.Result result) {
if (tencent != null) {
int scene = call.argument(ARGUMENT_KEY_SCENE);
if (scene == TencentScene.SCENE_QQ) {
String title = call.argument(ARGUMENT_KEY_TITLE);
String summary = call.argument(ARGUMENT_KEY_SUMMARY);
String imageUri = call.argument(ARGUMENT_KEY_IMAGEURI);
String musicUrl = call.argument(ARGUMENT_KEY_MUSICURL);
String targetUrl = call.argument(ARGUMENT_KEY_TARGETURL);
String appName = call.argument(ARGUMENT_KEY_APPNAME);
int extInt = call.argument(ARGUMENT_KEY_EXTINT);
Bundle params = new Bundle();
params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_AUDIO);
params.putString(QQShare.SHARE_TO_QQ_TITLE, title);
if (!TextUtils.isEmpty(summary)) {
params.putString(QQShare.SHARE_TO_QQ_SUMMARY, summary);
}
if (!TextUtils.isEmpty(imageUri)) {
Uri uri = Uri.parse(imageUri);
if (TextUtils.equals(SCHEME_FILE, uri.getScheme())) {
params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL, uri.getPath());
} else {
params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL, imageUri);
}
}
params.putString(QQShare.SHARE_TO_QQ_AUDIO_URL, musicUrl);
params.putString(QQShare.SHARE_TO_QQ_TARGET_URL, targetUrl);
if (!TextUtils.isEmpty(appName)) {
params.putString(QQShare.SHARE_TO_QQ_APP_NAME, appName);
}
params.putInt(QQShare.SHARE_TO_QQ_EXT_INT, extInt);
tencent.shareToQQ(activity, params, shareListener);
}
}
result.success(null);
}
private void shareWebpage(MethodCall call, MethodChannel.Result result) {
if (tencent != null) {
int scene = call.argument(ARGUMENT_KEY_SCENE);
String title = call.argument(ARGUMENT_KEY_TITLE);
String summary = call.argument(ARGUMENT_KEY_SUMMARY);
String imageUri = call.argument(ARGUMENT_KEY_IMAGEURI);
String targetUrl = call.argument(ARGUMENT_KEY_TARGETURL);
String appName = call.argument(ARGUMENT_KEY_APPNAME);
int extInt = call.argument(ARGUMENT_KEY_EXTINT);
Bundle params = new Bundle();
switch (scene) {
case TencentScene.SCENE_QQ:
params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_DEFAULT);
params.putString(QQShare.SHARE_TO_QQ_TITLE, title);
if (!TextUtils.isEmpty(summary)) {
params.putString(QQShare.SHARE_TO_QQ_SUMMARY, summary);
}
if (!TextUtils.isEmpty(imageUri)) {
Uri uri = Uri.parse(imageUri);
if (TextUtils.equals(SCHEME_FILE, uri.getScheme())) {
params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL, uri.getPath());
} else {
params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL, imageUri);
}
}
params.putString(QQShare.SHARE_TO_QQ_TARGET_URL, targetUrl);
if (!TextUtils.isEmpty(appName)) {
params.putString(QQShare.SHARE_TO_QQ_APP_NAME, appName);
}
params.putInt(QQShare.SHARE_TO_QQ_EXT_INT, extInt);
tencent.shareToQQ(activity, params, shareListener);
break;
case TencentScene.SCENE_QZONE:
params.putInt(QzoneShare.SHARE_TO_QZONE_KEY_TYPE, QzoneShare.SHARE_TO_QZONE_TYPE_IMAGE_TEXT);
params.putString(QzoneShare.SHARE_TO_QQ_TITLE, title);
if (!TextUtils.isEmpty(summary)) {
params.putString(QzoneShare.SHARE_TO_QQ_SUMMARY, summary);
}
if (!TextUtils.isEmpty(imageUri)) {
ArrayList<String> uris = new ArrayList<>();
Uri uri = Uri.parse(imageUri);
if (TextUtils.equals(SCHEME_FILE, uri.getScheme())) {
uris.add(uri.getPath());
} else {
uris.add(imageUri);
}
params.putStringArrayList(QzoneShare.SHARE_TO_QQ_IMAGE_URL, uris);
}
params.putString(QzoneShare.SHARE_TO_QQ_TARGET_URL, targetUrl);
tencent.shareToQzone(activity, params, shareListener);
break;
default:
break;
}
}
result.success(null);
}
private IUiListener shareListener = new IUiListener() {
@Override
public void onComplete(Object o) {
Map<String, Object> map = new HashMap<>();
try {
if (o != null && o instanceof JSONObject) {
JSONObject object = (JSONObject) o;
int ret = !object.isNull(ARGUMENT_KEY_RESULT_RET) ? object.getInt(ARGUMENT_KEY_RESULT_RET) : TencentRetCode.RET_FAILED;
String msg = !object.isNull(ARGUMENT_KEY_RESULT_MSG) ? object.getString(ARGUMENT_KEY_RESULT_MSG) : null;
if (ret == TencentRetCode.RET_SUCCESS) {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_SUCCESS);
} else {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, msg);
}
}
} catch (JSONException e) {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, e.getMessage());
}
channel.invokeMethod(METHOD_ONSHARERESP, map);
}
@Override
public void onError(UiError error) {
Map<String, Object> map = new HashMap<>();
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, error.errorMessage);
channel.invokeMethod(METHOD_ONSHARERESP, map);
}
@Override
public void onCancel() {
Map<String, Object> map = new HashMap<>();
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_USERCANCEL);
channel.invokeMethod(METHOD_ONSHARERESP, map);
}
};
// --- ActivityResultListener
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case Constants.REQUEST_LOGIN:
return Tencent.onActivityResultData(requestCode, resultCode, data, loginListener);
case Constants.REQUEST_QQ_SHARE:
case Constants.REQUEST_QZONE_SHARE:
return Tencent.onActivityResultData(requestCode, resultCode, data, shareListener);
default:
break;
}
return false;
}
// ---
private static boolean isAppInstalled(Context context, String packageName) {
PackageInfo packageInfo = null;
try {
packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
}
return packageInfo != null;
}
}

View File

@ -1,460 +1,78 @@
package io.github.v7lin.tencent_kit;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.tencent.connect.common.Constants;
import com.tencent.connect.share.QQShare;
import com.tencent.connect.share.QzonePublish;
import com.tencent.connect.share.QzoneShare;
import com.tencent.tauth.IUiListener;
import com.tencent.tauth.Tencent;
import com.tencent.tauth.UiError;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.Registrar;
/**
* TencentKitPlugin
*/
public class TencentKitPlugin implements MethodCallHandler, PluginRegistry.ActivityResultListener {
/**
* Plugin registration.
*/
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "v7lin.github.io/tencent_kit");
TencentKitPlugin plugin = new TencentKitPlugin(registrar, channel);
registrar.addActivityResultListener(plugin);
channel.setMethodCallHandler(plugin);
public class TencentKitPlugin implements FlutterPlugin, ActivityAware {
// This static function is optional and equivalent to onAttachedToEngine. It supports the old
// pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
// plugin registration via this function while apps migrate to use the new Android APIs
// post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
//
// It is encouraged to share logic between onAttachedToEngine and registerWith to keep
// them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called
// depending on the user's project. onAttachedToEngine or registerWith must both be defined
// in the same class.
public static void registerWith(PluginRegistry.Registrar registrar) {
TencentKit tencentKit = new TencentKit(registrar.context(), registrar.activity());
registrar.addActivityResultListener(tencentKit);
tencentKit.startListening(registrar.messenger());
}
private static class TencentScene {
static final int SCENE_QQ = 0;
static final int SCENE_QZONE = 1;
}
// --- FlutterPlugin
private static class TencentRetCode {
// 网络请求成功发送至服务器,并且服务器返回数据格式正确
// 这里包括所请求业务操作失败的情况,例如没有授权等原因导致
static final int RET_SUCCESS = 0;
// 网络异常,或服务器返回的数据格式不正确导致无法解析
static final int RET_FAILED = 1;
static final int RET_COMMON = -1;
static final int RET_USERCANCEL = -2;
}
private final TencentKit tencentKit;
private static final String METHOD_REGISTERAPP = "registerApp";
private static final String METHOD_ISQQINSTALLED = "isQQInstalled";
private static final String METHOD_ISTIMINSTALLED = "isTIMInstalled";
private static final String METHOD_ISQQLITEINSTALLED = "isQQLiteInstalled";
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_SHARETEXT = "shareText";
private static final String METHOD_SHAREIMAGE = "shareImage";
private static final String METHOD_SHAREMUSIC = "shareMusic";
private static final String METHOD_SHAREWEBPAGE = "shareWebpage";
private ActivityPluginBinding pluginBinding;
private static final String METHOD_ONLOGINRESP = "onLoginResp";
private static final String METHOD_ONSHARERESP = "onShareResp";
private static final String ARGUMENT_KEY_APPID = "appId";
// private static final String ARGUMENT_KEY_UNIVERSALLINK = "universalLink";
private static final String ARGUMENT_KEY_SCOPE = "scope";
private static final String ARGUMENT_KEY_SCENE = "scene";
private static final String ARGUMENT_KEY_TITLE = "title";
private static final String ARGUMENT_KEY_SUMMARY = "summary";
private static final String ARGUMENT_KEY_IMAGEURI = "imageUri";
private static final String ARGUMENT_KEY_IMAGEURIS = "imageUris";
private static final String ARGUMENT_KEY_VIDEOURI = "videoUri";
private static final String ARGUMENT_KEY_MUSICURL = "musicUrl";
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_RESULT_RET = "ret";
private static final String ARGUMENT_KEY_RESULT_MSG = "msg";
private static final String ARGUMENT_KEY_RESULT_OPENID = "openid";
private static final String ARGUMENT_KEY_RESULT_ACCESS_TOKEN = "access_token";
private static final String ARGUMENT_KEY_RESULT_EXPIRES_IN = "expires_in";
private static final String ARGUMENT_KEY_RESULT_CREATE_AT = "create_at";
private static final String SCHEME_FILE = "file";
private final Registrar registrar;
private final MethodChannel channel;
private Tencent tencent;
private TencentKitPlugin(Registrar registrar, MethodChannel channel) {
this.registrar = registrar;
this.channel = channel;
public TencentKitPlugin() {
tencentKit = new TencentKit();
}
@Override
public void onMethodCall(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);
tencent = Tencent.createInstance(appId, registrar.context().getApplicationContext());
result.success(null);
} else if (METHOD_ISQQINSTALLED.equals(call.method)) {
result.success(isAppInstalled(registrar.context(), "com.tencent.mobileqq"));
} else if (METHOD_ISTIMINSTALLED.equals(call.method)) {
result.success(isAppInstalled(registrar.context(), "com.tencent.tim"));
} else if (METHOD_ISQQLITEINSTALLED.equals(call.method)) {
result.success(isAppInstalled(registrar.context(), "com.tencent.qqlite"));
} else if (METHOD_LOGIN.equals(call.method)) {
login(call, result);
} else if (METHOD_LOGOUT.equals(call.method)) {
logout(call, result);
} else if (METHOD_SHAREMOOD.equals(call.method)) {
shareMood(call, result);
} else if (METHOD_SHARETEXT.equals(call.method)) {
shareText(call, result);
} else if (METHOD_SHAREIMAGE.equals(call.method)) {
shareImage(call, result);
} else if (METHOD_SHAREMUSIC.equals(call.method)) {
shareMusic(call, result);
} else if (METHOD_SHAREWEBPAGE.equals(call.method)) {
shareWebpage(call, result);
} else {
result.notImplemented();
}
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
tencentKit.setApplicationContext(flutterPluginBinding.getApplicationContext());
tencentKit.setActivity(null);
tencentKit.startListening(flutterPluginBinding.getBinaryMessenger());
}
private void login(MethodCall call, Result result) {
if (tencent != null) {
String scope = call.argument(ARGUMENT_KEY_SCOPE);
tencent.login(registrar.activity(), scope, loginListener);
}
result.success(null);
}
private IUiListener loginListener = new IUiListener() {
@Override
public void onComplete(Object o) {
Map<String, Object> map = new HashMap<>();
try {
if (o != null && o instanceof JSONObject) {
JSONObject object = (JSONObject) o;
int ret = !object.isNull(ARGUMENT_KEY_RESULT_RET) ? object.getInt(ARGUMENT_KEY_RESULT_RET) : TencentRetCode.RET_FAILED;
String msg = !object.isNull(ARGUMENT_KEY_RESULT_MSG) ? object.getString(ARGUMENT_KEY_RESULT_MSG) : null;
if (ret == TencentRetCode.RET_SUCCESS) {
String openId = !object.isNull(ARGUMENT_KEY_RESULT_OPENID) ? object.getString(ARGUMENT_KEY_RESULT_OPENID) : null;
String accessToken = !object.isNull(ARGUMENT_KEY_RESULT_ACCESS_TOKEN) ? object.getString(ARGUMENT_KEY_RESULT_ACCESS_TOKEN) : null;
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)) {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_SUCCESS);
map.put(ARGUMENT_KEY_RESULT_OPENID, openId);
map.put(ARGUMENT_KEY_RESULT_ACCESS_TOKEN, accessToken);
map.put(ARGUMENT_KEY_RESULT_EXPIRES_IN, expiresIn);
map.put(ARGUMENT_KEY_RESULT_CREATE_AT, createAt);
} else {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, "openId or accessToken is null.");
}
} else {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, msg);
}
}
} catch (JSONException e) {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, e.getMessage());
}
channel.invokeMethod(METHOD_ONLOGINRESP, map);
}
@Override
public void onError(UiError uiError) {
// 登录失败
Map<String, Object> map = new HashMap<>();
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, uiError.errorMessage);
channel.invokeMethod(METHOD_ONLOGINRESP, map);
}
@Override
public void onCancel() {
// 取消登录
Map<String, Object> map = new HashMap<>();
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_USERCANCEL);
channel.invokeMethod(METHOD_ONLOGINRESP, map);
}
};
private void logout(MethodCall call, Result result) {
if (tencent != null) {
tencent.logout(registrar.context());
result.success(null);
}
}
private void shareMood(MethodCall call, Result result) {
if (tencent != null) {
int scene = call.argument(ARGUMENT_KEY_SCENE);
if (scene == TencentScene.SCENE_QZONE) {
String summary = call.argument(ARGUMENT_KEY_SUMMARY);
List<String> imageUris = call.argument(ARGUMENT_KEY_IMAGEURIS);
String videoUri = call.argument(ARGUMENT_KEY_VIDEOURI);
Bundle params = new Bundle();
if (!TextUtils.isEmpty(summary)) {
params.putString(QzonePublish.PUBLISH_TO_QZONE_SUMMARY, summary);
}
if (imageUris != null && !imageUris.isEmpty()) {
ArrayList<String> uris = new ArrayList<>();
for (String imageUri : imageUris) {
uris.add(Uri.parse(imageUri).getPath());
}
params.putStringArrayList(QzonePublish.PUBLISH_TO_QZONE_IMAGE_URL, uris);
}
if (!TextUtils.isEmpty(videoUri)) {
String videoPath = Uri.parse(videoUri).getPath();
params.putString(QzonePublish.PUBLISH_TO_QZONE_VIDEO_PATH, videoPath);
params.putInt(QzonePublish.PUBLISH_TO_QZONE_KEY_TYPE, QzonePublish.PUBLISH_TO_QZONE_TYPE_PUBLISHVIDEO);
} else {
params.putInt(QzonePublish.PUBLISH_TO_QZONE_KEY_TYPE, QzonePublish.PUBLISH_TO_QZONE_TYPE_PUBLISHMOOD);
}
tencent.publishToQzone(registrar.activity(), params, shareListener);
}
}
result.success(null);
}
private void shareText(MethodCall call, Result result) {
int scene = call.argument(ARGUMENT_KEY_SCENE);
if (scene == TencentScene.SCENE_QQ) {
String summary = call.argument(ARGUMENT_KEY_SUMMARY);
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, summary);
sendIntent.setType("text/*");
// 普通大众版 > 办公简洁版 > 急速轻聊版
PackageManager packageManager = registrar.context().getPackageManager();
List<PackageInfo> infos = packageManager.getInstalledPackages(0);
if (infos != null && !infos.isEmpty()) {
for (String packageName : Arrays.asList("com.tencent.mobileqq", "com.tencent.tim", "com.tencent.qqlite")) {
for (PackageInfo info : infos) {
if (packageName.equals(info.packageName)) {
sendIntent.setPackage(packageName);
if (sendIntent.resolveActivity(registrar.context().getPackageManager()) != null) {
sendIntent.setComponent(new ComponentName(packageName, "com.tencent.mobileqq.activity.JumpActivity"));
registrar.activity().startActivity(sendIntent);
break;
}
}
}
}
}
}
result.success(null);
}
private void shareImage(MethodCall call, Result result) {
if (tencent != null) {
int scene = call.argument(ARGUMENT_KEY_SCENE);
if (scene == TencentScene.SCENE_QQ) {
String imageUri = call.argument(ARGUMENT_KEY_IMAGEURI);
String appName = call.argument(ARGUMENT_KEY_APPNAME);
int extInt = call.argument(ARGUMENT_KEY_EXTINT);
Bundle params = new Bundle();
params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_IMAGE);
params.putString(QQShare.SHARE_TO_QQ_IMAGE_LOCAL_URL, Uri.parse(imageUri).getPath());
if (!TextUtils.isEmpty(appName)) {
params.putString(QQShare.SHARE_TO_QQ_APP_NAME, appName);
}
params.putInt(QQShare.SHARE_TO_QQ_EXT_INT, extInt);
tencent.shareToQQ(registrar.activity(), params, shareListener);
}
}
result.success(null);
}
private void shareMusic(MethodCall call, Result result) {
if (tencent != null) {
int scene = call.argument(ARGUMENT_KEY_SCENE);
if (scene == TencentScene.SCENE_QQ) {
String title = call.argument(ARGUMENT_KEY_TITLE);
String summary = call.argument(ARGUMENT_KEY_SUMMARY);
String imageUri = call.argument(ARGUMENT_KEY_IMAGEURI);
String musicUrl = call.argument(ARGUMENT_KEY_MUSICURL);
String targetUrl = call.argument(ARGUMENT_KEY_TARGETURL);
String appName = call.argument(ARGUMENT_KEY_APPNAME);
int extInt = call.argument(ARGUMENT_KEY_EXTINT);
Bundle params = new Bundle();
params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_AUDIO);
params.putString(QQShare.SHARE_TO_QQ_TITLE, title);
if (!TextUtils.isEmpty(summary)) {
params.putString(QQShare.SHARE_TO_QQ_SUMMARY, summary);
}
if (!TextUtils.isEmpty(imageUri)) {
Uri uri = Uri.parse(imageUri);
if (TextUtils.equals(SCHEME_FILE, uri.getScheme())) {
params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL, uri.getPath());
} else {
params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL, imageUri);
}
}
params.putString(QQShare.SHARE_TO_QQ_AUDIO_URL, musicUrl);
params.putString(QQShare.SHARE_TO_QQ_TARGET_URL, targetUrl);
if (!TextUtils.isEmpty(appName)) {
params.putString(QQShare.SHARE_TO_QQ_APP_NAME, appName);
}
params.putInt(QQShare.SHARE_TO_QQ_EXT_INT, extInt);
tencent.shareToQQ(registrar.activity(), params, shareListener);
}
}
result.success(null);
}
private void shareWebpage(MethodCall call, Result result) {
if (tencent != null) {
int scene = call.argument(ARGUMENT_KEY_SCENE);
String title = call.argument(ARGUMENT_KEY_TITLE);
String summary = call.argument(ARGUMENT_KEY_SUMMARY);
String imageUri = call.argument(ARGUMENT_KEY_IMAGEURI);
String targetUrl = call.argument(ARGUMENT_KEY_TARGETURL);
String appName = call.argument(ARGUMENT_KEY_APPNAME);
int extInt = call.argument(ARGUMENT_KEY_EXTINT);
Bundle params = new Bundle();
switch (scene) {
case TencentScene.SCENE_QQ:
params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_DEFAULT);
params.putString(QQShare.SHARE_TO_QQ_TITLE, title);
if (!TextUtils.isEmpty(summary)) {
params.putString(QQShare.SHARE_TO_QQ_SUMMARY, summary);
}
if (!TextUtils.isEmpty(imageUri)) {
Uri uri = Uri.parse(imageUri);
if (TextUtils.equals(SCHEME_FILE, uri.getScheme())) {
params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL, uri.getPath());
} else {
params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL, imageUri);
}
}
params.putString(QQShare.SHARE_TO_QQ_TARGET_URL, targetUrl);
if (!TextUtils.isEmpty(appName)) {
params.putString(QQShare.SHARE_TO_QQ_APP_NAME, appName);
}
params.putInt(QQShare.SHARE_TO_QQ_EXT_INT, extInt);
tencent.shareToQQ(registrar.activity(), params, shareListener);
break;
case TencentScene.SCENE_QZONE:
params.putInt(QzoneShare.SHARE_TO_QZONE_KEY_TYPE, QzoneShare.SHARE_TO_QZONE_TYPE_IMAGE_TEXT);
params.putString(QzoneShare.SHARE_TO_QQ_TITLE, title);
if (!TextUtils.isEmpty(summary)) {
params.putString(QzoneShare.SHARE_TO_QQ_SUMMARY, summary);
}
if (!TextUtils.isEmpty(imageUri)) {
ArrayList<String> uris = new ArrayList<>();
Uri uri = Uri.parse(imageUri);
if (TextUtils.equals(SCHEME_FILE, uri.getScheme())) {
uris.add(uri.getPath());
} else {
uris.add(imageUri);
}
params.putStringArrayList(QzoneShare.SHARE_TO_QQ_IMAGE_URL, uris);
}
params.putString(QzoneShare.SHARE_TO_QQ_TARGET_URL, targetUrl);
tencent.shareToQzone(registrar.activity(), params, shareListener);
break;
default:
break;
}
}
result.success(null);
}
private IUiListener shareListener = new IUiListener() {
@Override
public void onComplete(Object o) {
Map<String, Object> map = new HashMap<>();
try {
if (o != null && o instanceof JSONObject) {
JSONObject object = (JSONObject) o;
int ret = !object.isNull(ARGUMENT_KEY_RESULT_RET) ? object.getInt(ARGUMENT_KEY_RESULT_RET) : TencentRetCode.RET_FAILED;
String msg = !object.isNull(ARGUMENT_KEY_RESULT_MSG) ? object.getString(ARGUMENT_KEY_RESULT_MSG) : null;
if (ret == TencentRetCode.RET_SUCCESS) {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_SUCCESS);
} else {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, msg);
}
}
} catch (JSONException e) {
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, e.getMessage());
}
channel.invokeMethod(METHOD_ONSHARERESP, map);
}
@Override
public void onError(UiError error) {
Map<String, Object> map = new HashMap<>();
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_COMMON);
map.put(ARGUMENT_KEY_RESULT_MSG, error.errorMessage);
channel.invokeMethod(METHOD_ONSHARERESP, map);
}
@Override
public void onCancel() {
Map<String, Object> map = new HashMap<>();
map.put(ARGUMENT_KEY_RESULT_RET, TencentRetCode.RET_USERCANCEL);
channel.invokeMethod(METHOD_ONSHARERESP, map);
}
};
// --- ActivityResultListener
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case Constants.REQUEST_LOGIN:
return Tencent.onActivityResultData(requestCode, resultCode, data, loginListener);
case Constants.REQUEST_QQ_SHARE:
case Constants.REQUEST_QZONE_SHARE:
return Tencent.onActivityResultData(requestCode, resultCode, data, shareListener);
default:
break;
}
return false;
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
tencentKit.setApplicationContext(null);
tencentKit.setActivity(null);
tencentKit.stopListening();
}
// ---
// --- ActivityAware
private static boolean isAppInstalled(Context context, String packageName) {
PackageInfo packageInfo = null;
try {
packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
}
return packageInfo != null;
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
tencentKit.setActivity(binding.getActivity());
pluginBinding = binding;
pluginBinding.addActivityResultListener(tencentKit);
}
@Override
public void onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity();
}
@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
onAttachedToActivity(binding);
}
@Override
public void onDetachedFromActivity() {
tencentKit.setActivity(null);
pluginBinding.removeActivityResultListener(tencentKit);
pluginBinding = null;
}
}

View File

@ -1,11 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!--suppress AndroidDomInspection -->
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path name="root" path="" />
<files-path name="files" path="" />
<cache-path name="cache" path="" />
<external-path name="external" path="" />
<external-files-path name="external_files" path="" />
<external-cache-path name="external_cache" path="" />
<external-media-path name="external_media" path="" />
<paths>
<external-files-path name="opensdk_external" path="Images/tmp"/>
<root-path name="opensdk_root" path=""/>
</paths>

View File

@ -55,10 +55,6 @@ android {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

View File

@ -1,9 +0,0 @@
# Flutter Wrapper
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
-dontwarn io.flutter.**

View File

@ -1,6 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.github.v7lin.tencent_kit_example">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
@ -14,20 +13,18 @@
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@ -1,13 +1,7 @@
package io.github.v7lin.tencent_kit_example;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}

View File

@ -5,7 +5,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
classpath 'com.android.tools.build:gradle:3.5.0'
}
}

View File

@ -1,5 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true

View File

@ -1,6 +1,6 @@
#Wed Nov 06 18:22:26 CST 2019
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip

View File

@ -75,7 +75,8 @@ class _HomeState extends State<Home> {
ListTile(
title: const Text('环境检查'),
onTap: () async {
String content = 'tencent: ${await _tencent.isInstalled()}';
String content =
'QQ install: ${await _tencent.isQQInstalled()}\nTIM install: ${await _tencent.isTIMInstalled()}';
_showTips('环境检查', content);
},
),
@ -139,7 +140,7 @@ class _HomeState extends State<Home> {
title: const Text('文本分享'),
onTap: () {
_tencent.shareText(
scene: TencentScene.SCENE_QZONE,
scene: TencentScene.SCENE_QQ,
summary: '分享测试',
);
},

View File

@ -39,7 +39,6 @@ enum TencentRetCode {
static NSString *const METHOD_REGISTERAPP = @"registerApp";
static NSString *const METHOD_ISQQINSTALLED = @"isQQInstalled";
static NSString *const METHOD_ISTIMINSTALLED = @"isTIMInstalled";
static NSString *const METHOD_ISQQLITEINSTALLED = @"isQQLiteInstalled";
static NSString *const METHOD_LOGIN = @"login";
static NSString *const METHOD_LOGOUT = @"logout";
static NSString *const METHOD_SHAREMOOD = @"shareMood";
@ -99,8 +98,6 @@ static NSString *const SCHEME_FILE = @"file";
result([NSNumber numberWithBool:[TencentOAuth iphoneQQInstalled]]);
} else if ([METHOD_ISTIMINSTALLED isEqualToString:call.method]) {
result([NSNumber numberWithBool:[TencentOAuth iphoneTIMInstalled]]);
} else if ([METHOD_ISQQLITEINSTALLED isEqualToString:call.method]) {
result([NSNumber numberWithBool:NO]);
} else if ([METHOD_LOGIN isEqualToString:call.method]) {
[self login:call result:result];
} else if ([METHOD_LOGOUT isEqualToString:call.method]) {

View File

@ -155,4 +155,20 @@ typedef void (^sendResultBlock)(NSDictionary *result);
\return iPhoneTIM下载地址
*/
+ (NSString *)getTIMInstallUrl;
#pragma mark - Log
/*! @brief 调用此函数可以导出QQSDK的Log到第三方中用于定位问题
注意1:SDK会强引用这个block,注意不要导致内存泄漏,注意不要导致内存泄漏
注意2:调用过一次startLog by block之后如果再调用一次任意方式的startLoad,会释放上一次logBlock不再回调上一个logBlock
*
* @param logBlock 打印log的回调block
*/
+ (void)startLogWithBlock:(QQApiLogBolock)logBlock;
///停止回调打印
+ (void)stopLog;
///设置打印日志到文件开关on/off如果不设置默认不打印到文件
+ (void)setSwitchPrintLogToFile:(BOOL)on;
///日志文件目录
+ (NSString*)getLogFilePath;
@end

View File

@ -37,6 +37,7 @@ typedef NS_ENUM(NSInteger,QQApiSendResultCode) {
EQQAPIQZONENOTSUPPORTIMAGE = 10001, //qzone分享不支持image类型分享
EQQAPIVERSIONNEEDUPDATE = 10002, //QQ版本太低
ETIMAPIVERSIONNEEDUPDATE = 10004, //TIM版本太低
EAPPURLTYPESILLEGALITY = 20000, //(>=3.3.8)APP的info.plist中UrlTypes字段存在QQ的UrlScheme
};
#pragma mark - QQApiObject()
@ -66,6 +67,9 @@ typedef NS_ENUM(NSUInteger, MiniProgramType) {
MiniProgramType_Preview=4, // 预览版
};
/// 打印回调的block
typedef void(^QQApiLogBolock)(NSString *logStr);
// QQApiObject
/** \brief 所有在QQ及插件间发送的数据对象的根类。
*/
@ -106,9 +110,16 @@ __attribute__((visibility("default"))) @interface QQApiMiniProgramObject : NSObj
//唤起小程序 - QQ 8.1.8
__attribute__((visibility("default"))) @interface QQApiLaunchMiniProgramObject : QQApiObject
@property(nonatomic,retain) NSString* miniAppID; //必填小程序的AppId必须在QQ互联平台中将该小程序与分享的App绑定
@property(nonatomic,retain) NSString* miniPath; //必填,小程序的展示路径
@property(nonatomic,retain) NSString* miniPath; //小程序的展示路径,不填展示默认小程序首页
@property(nonatomic,assign) MiniProgramType miniprogramType; //非必填,小程序的类型,默认正式版(3),可选测试版(1)、开发版(0)
@end
//小程序唤起第三方 - SDK 3.3.9
__attribute__((visibility("default"))) @interface QQApiMiniProgramLaunchObject : QQApiObject
@property(nonatomic,copy) NSString* appParameter; //小程序带来的数据,透传
+ (instancetype)newWithAppParameter:(NSString*)parameter;
@end
// QQApiResultObject
/** \brief 用于请求回应的数据类型。
<h3>可能错误码及描述如下:</h3>

View File

@ -164,8 +164,8 @@ typedef NS_ENUM(NSUInteger, TencentAuthMode) {
/**
* 初始化TencentOAuth对象
* \param appId 第三方应用在互联开放平台申请的唯一标识
* \param delegate 第三方应用用于接收请求返回结果的委托对象
* \param appId 不可为nil第三方应用在互联开放平台申请的唯一标识
* \param delegate 不可为nil第三方应用用于接收请求返回结果的委托对象
* \return 初始化后的授权登录对象
*/
- (id)initWithAppId:(NSString *)appId
@ -173,15 +173,38 @@ typedef NS_ENUM(NSUInteger, TencentAuthMode) {
/**
* 初始化TencentOAuth对象>=3.3.7
* \param appId 第三方应用在互联开放平台申请的唯一标识
* \param universalLink 第三方应用在互联开放平台注册的universallink和bundleID一一对应
* \param delegate 第三方应用用于接收请求返回结果的委托对象
* \param appId 不可为nil第三方应用在互联开放平台申请的唯一标识
* \param universalLink 可以为nil第三方应用在互联开放平台注册的UniversalLink和bundleID一一对应当为nil时互联平台会按规则生成universallink详见官网说明
* \param delegate 不可为nil第三方应用用于接收请求返回结果的委托对象
* \return 初始化后的授权登录对象
*
****【使用说明】*****
* 1、支持BundleId与UniversalLink的一一对应主要目的“是为了解决应用的iPhone版本和iPad HD版本共用同一个AppId导致同时安装情况下的跳转问题"。
* 2 、由于手Q版本在 >=8.1.8 后才支持了这种对应方式所以一旦使用“务必做到”及时知会用户升级手Q版本。
****
*/
- (id)initWithAppId:(NSString *)appId
andUniversalLink:(NSString *)universalLink
andDelegate:(id<TencentSessionDelegate>)delegate;
/**
* 初始化TencentOAuth对象>=3.3.8
* \param appId 不可为nil第三方应用在互联开放平台申请的唯一标识
* \param enabled 默认为NO第三方应用是否将sdk和手机QQ的交互方式切换为UniversalLink方式启用后则在iOS9及以上的系统都会生效UniversalLink方式否则默认仅在iOS13及以上的系统生效UniversalLink方式。
* \param universalLink 可以为nil第三方应用在互联开放平台注册的UniversalLink和bundleID一一对应当为nil时互联平台会按规则生成UniversalLink详见官网说明
* \param delegate 不可为nil第三方应用用于接收请求返回结果的委托对象
* \return 初始化后的授权登录对象
*
*****【使用说明】*****
* 1、支持sdk与手Q的交互切换为UniversalLink模式主要目的"是为了避免手Q的UrlScheme被其他应用抢注后导致sdk接口功能受到影响"。
* 2 、由于手Q版本在 >=8.1.3 后才适配了UniversalLink所以一旦开启了enabled开关“务必做到”及时知会用户升级手Q版本。
*****
*/
- (id)initWithAppId:(NSString *)appId
enableUniveralLink:(BOOL)enabled
universalLink:(NSString *)universalLink
delegate:(id<TencentSessionDelegate>)delegate;
/**
* 判断用户手机上是否安装手机QQ
* \return YES:安装 NO:没安装

View File

@ -20,7 +20,6 @@ class Tencent {
static const String _METHOD_REGISTERAPP = 'registerApp';
static const String _METHOD_ISQQINSTALLED = 'isQQInstalled';
static const String _METHOD_ISTIMINSTALLED = 'isTIMInstalled';
static const String _METHOD_ISQQLITEINSTALLED = 'isQQLiteInstalled';
static const String _METHOD_LOGIN = 'login';
static const String _METHOD_LOGOUT = 'logout';
static const String _METHOD_SHAREMOOD = 'shareMood';
@ -77,16 +76,14 @@ class Tencent {
}) {
assert(appId != null && appId.isNotEmpty);
assert(!Platform.isIOS || universalLink == null);
final Map<String, dynamic> arguments = <String, dynamic>{
_ARGUMENT_KEY_APPID: appId,
// _ARGUMENT_KEY_UNIVERSALLINK: universalLink,
};
/// 兼容 iOS 空安全 -> NSNull
if (universalLink != null) {
arguments[_ARGUMENT_KEY_UNIVERSALLINK] = universalLink;
}
return _channel.invokeMethod(_METHOD_REGISTERAPP, arguments);
return _channel.invokeMethod(
_METHOD_REGISTERAPP,
<String, dynamic>{
_ARGUMENT_KEY_APPID: appId,
if (universalLink?.isNotEmpty ?? false)
_ARGUMENT_KEY_UNIVERSALLINK: universalLink,
},
);
}
/// 登录
@ -99,13 +96,6 @@ class Tencent {
return _shareRespStreamController.stream;
}
/// 检查QQ是否已安装
Future<bool> isInstalled() async {
return (await isQQInstalled()) ||
(await isTIMInstalled()) ||
(await isQQLiteInstalled());
}
/// 检查QQ是否已安装
Future<bool> isQQInstalled() {
return _channel.invokeMethod(_METHOD_ISQQINSTALLED);
@ -116,11 +106,6 @@ class Tencent {
return _channel.invokeMethod(_METHOD_ISTIMINSTALLED);
}
/// 检查QQ是否已安装
Future<bool> isQQLiteInstalled() {
return _channel.invokeMethod(_METHOD_ISQQLITEINSTALLED);
}
/// 登录
Future<void> login({
@required List<String> scope,
@ -214,27 +199,17 @@ class Tencent {
assert(imageUri != null && imageUri.isScheme(_SCHEME_FILE));
});
}
final Map<String, dynamic> arguments = <String, dynamic>{
_ARGUMENT_KEY_SCENE: scene,
// _ARGUMENT_KEY_SUMMARY: summary,
// _ARGUMENT_KEY_IMAGEURIS: imageUris != null ? new List.generate(imageUris.length, (int index) {
// return imageUris[index].toString();
// }) : null,
// _ARGUMENT_KEY_VIDEOURI: videoUri != null ? videoUri.toString() : null,
};
/// 兼容 iOS 空安全 -> NSNull
if (summary != null && summary.isNotEmpty) {
arguments[_ARGUMENT_KEY_SUMMARY] = summary;
}
if (imageUris != null && imageUris.isNotEmpty) {
arguments[_ARGUMENT_KEY_IMAGEURIS] =
imageUris.map((Uri imageUri) => imageUri.toString()).toList();
}
if (videoUri != null) {
arguments[_ARGUMENT_KEY_VIDEOURI] = videoUri.toString();
}
return _channel.invokeMethod(_METHOD_SHAREMOOD, arguments);
return _channel.invokeMethod(
_METHOD_SHAREMOOD,
<String, dynamic>{
_ARGUMENT_KEY_SCENE: scene,
if (summary?.isNotEmpty ?? false) _ARGUMENT_KEY_SUMMARY: summary,
if (imageUris?.isNotEmpty ?? false)
_ARGUMENT_KEY_IMAGEURIS:
imageUris.map((Uri imageUri) => imageUri.toString()).toList(),
if (videoUri != null) _ARGUMENT_KEY_VIDEOURI: videoUri.toString(),
},
);
}
/// 分享 - 文本Android调用的是系统API故而不会有回调
@ -244,11 +219,13 @@ class Tencent {
}) {
assert(scene == TencentScene.SCENE_QQ);
assert(summary != null && summary.isNotEmpty);
final Map<String, dynamic> arguments = <String, dynamic>{
_ARGUMENT_KEY_SCENE: scene,
_ARGUMENT_KEY_SUMMARY: summary,
};
return _channel.invokeMethod(_METHOD_SHARETEXT, arguments);
return _channel.invokeMethod(
_METHOD_SHARETEXT,
<String, dynamic>{
_ARGUMENT_KEY_SCENE: scene,
_ARGUMENT_KEY_SUMMARY: summary,
},
);
}
/// 分享 - 图片
@ -260,18 +237,15 @@ class Tencent {
}) {
assert(scene == TencentScene.SCENE_QQ);
assert(imageUri != null && imageUri.isScheme(_SCHEME_FILE));
final Map<String, dynamic> arguments = <String, dynamic>{
_ARGUMENT_KEY_SCENE: scene,
_ARGUMENT_KEY_IMAGEURI: imageUri.toString(),
// _ARGUMENT_KEY_APPNAME: appName,
_ARGUMENT_KEY_EXTINT: extInt,
};
/// 兼容 iOS 空安全 -> NSNull
if (appName != null && appName.isNotEmpty) {
arguments[_ARGUMENT_KEY_APPNAME] = appName;
}
return _channel.invokeMethod(_METHOD_SHAREIMAGE, arguments);
return _channel.invokeMethod(
_METHOD_SHAREIMAGE,
<String, dynamic>{
_ARGUMENT_KEY_SCENE: scene,
_ARGUMENT_KEY_IMAGEURI: imageUri.toString(),
if (appName?.isNotEmpty ?? false) _ARGUMENT_KEY_APPNAME: appName,
_ARGUMENT_KEY_EXTINT: extInt,
},
);
}
/// 分享 - 音乐
@ -289,28 +263,19 @@ class Tencent {
assert(title != null && title.isNotEmpty);
assert(musicUrl != null && musicUrl.isNotEmpty);
assert(targetUrl != null && targetUrl.isNotEmpty);
final Map<String, dynamic> arguments = <String, dynamic>{
_ARGUMENT_KEY_SCENE: scene,
_ARGUMENT_KEY_TITLE: title,
// _ARGUMENT_KEY_SUMMARY: summary,
// _ARGUMENT_KEY_IMAGEURI: imageUri != null ? imageUri.toString() : null,
_ARGUMENT_KEY_MUSICURL: musicUrl,
_ARGUMENT_KEY_TARGETURL: targetUrl,
// _ARGUMENT_KEY_APPNAME: appName,
_ARGUMENT_KEY_EXTINT: extInt,
};
/// 兼容 iOS 空安全 -> NSNull
if (summary != null && summary.isNotEmpty) {
arguments[_ARGUMENT_KEY_SUMMARY] = summary;
}
if (imageUri != null) {
arguments[_ARGUMENT_KEY_IMAGEURI] = imageUri.toString();
}
if (appName != null && appName.isNotEmpty) {
arguments[_ARGUMENT_KEY_APPNAME] = appName;
}
return _channel.invokeMethod(_METHOD_SHAREMUSIC, arguments);
return _channel.invokeMethod(
_METHOD_SHAREMUSIC,
<String, dynamic>{
_ARGUMENT_KEY_SCENE: scene,
_ARGUMENT_KEY_TITLE: title,
if (summary?.isNotEmpty ?? false) _ARGUMENT_KEY_SUMMARY: summary,
if (imageUri != null) _ARGUMENT_KEY_IMAGEURI: imageUri.toString(),
_ARGUMENT_KEY_MUSICURL: musicUrl,
_ARGUMENT_KEY_TARGETURL: targetUrl,
if (appName?.isNotEmpty ?? false) _ARGUMENT_KEY_APPNAME: appName,
_ARGUMENT_KEY_EXTINT: extInt,
},
);
}
/// 分享 - 网页
@ -325,26 +290,17 @@ class Tencent {
}) {
assert(title != null && title.isNotEmpty);
assert(targetUrl != null && targetUrl.isNotEmpty);
final Map<String, dynamic> arguments = <String, dynamic>{
_ARGUMENT_KEY_SCENE: scene,
_ARGUMENT_KEY_TITLE: title,
// _ARGUMENT_KEY_SUMMARY: summary,
// _ARGUMENT_KEY_IMAGEURI: imageUri != null ? imageUri.toString() : null,
_ARGUMENT_KEY_TARGETURL: targetUrl,
// _ARGUMENT_KEY_APPNAME: appName,
_ARGUMENT_KEY_EXTINT: extInt,
};
/// 兼容 iOS 空安全 -> NSNull
if (summary != null && summary.isNotEmpty) {
arguments[_ARGUMENT_KEY_SUMMARY] = summary;
}
if (imageUri != null) {
arguments[_ARGUMENT_KEY_IMAGEURI] = imageUri.toString();
}
if (appName != null && appName.isNotEmpty) {
arguments[_ARGUMENT_KEY_APPNAME] = appName;
}
return _channel.invokeMethod(_METHOD_SHAREWEBPAGE, arguments);
return _channel.invokeMethod(
_METHOD_SHAREWEBPAGE,
<String, dynamic>{
_ARGUMENT_KEY_SCENE: scene,
_ARGUMENT_KEY_TITLE: title,
if (summary?.isNotEmpty ?? false) _ARGUMENT_KEY_SUMMARY: summary,
if (imageUri != null) _ARGUMENT_KEY_IMAGEURI: imageUri.toString(),
_ARGUMENT_KEY_TARGETURL: targetUrl,
if (appName?.isNotEmpty ?? false) _ARGUMENT_KEY_APPNAME: appName,
_ARGUMENT_KEY_EXTINT: extInt,
},
);
}
}

View File

@ -1,11 +1,11 @@
name: tencent_kit
description: A powerful Flutter plugin allowing developers to share or authorize with natvie android & iOS Tencent SDKs.
version: 1.0.4
version: 1.1.0
# author: v7lin <v7lin@qq.com>
homepage: https://github.com/v7lin/fake_tencent
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.3.0 <3.0.0"
dependencies:
flutter: