提交 eccedfef authored 作者: 史晓晨's avatar 史晓晨

init

上级
# Default ignored files
/shelf/
/workspace.xml
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxBlameSettings">
<option name="version" value="2" />
</component>
</project>
\ No newline at end of file
<component name="libraryTable">
<library name="Dart SDK">
<CLASSES>
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/async" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/cli" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/collection" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/convert" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/core" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/developer" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/ffi" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/html" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/indexed_db" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/io" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/isolate" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/js" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/js_interop" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/js_interop_unsafe" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/js_util" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/math" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/mirrors" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/svg" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/typed_data" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/web_audio" />
<root url="file://$PROJECT_DIR$/../../flutter/bin/cache/dart-sdk/lib/web_gl" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/fluwx-4.4.0.iml" filepath="$PROJECT_DIR$/.idea/fluwx-4.4.0.iml" />
</modules>
</component>
</project>
\ No newline at end of file
# 4.4.0
* universal_link 不再是必选项
# 4.3.2
* iOS新增ignore_security选项,详见#576
# 4.3.1
* Merge #574
# 4.3.0
* Android minSdkVersion升级至19
* 主要解决了 iOS 端的冷启动参数传递问题
# 4.2.7
* 修复在Flutter module中编译不过的问题
# 4.2.6
* Fix #549
# 4.2.5
* iOS支持解析包含Anchors & Alias的Yaml
# 4.2.4+1
* 优化Android代码生成逻辑
# 4.2.4
* 修复iOS extMsg的问题
# 4.2.3
* 修复iOS冷启动extMsg的问题
# 4.2.2+1
* 更新iOS冷启动处理
# 4.2.2
* 删除iOS在registerApi并且开启debug_logging时自动自检
* 为iOS增加自检方法selfCheck
* subscribeResponse, unsubscribeResponse已废弃。
* 新增addSubscriber, removeSubscriber
* addSubscriber会返回Cancelable对象,可以直接调用cancel()函数。
# 4.2.1
* 支持选用scene_delegate.
# 4.2.0
* 修复ios上微信唤醒app崩溃的问题
* Android端重构
* app_id不再必填
# 4.1.1+1
* Fix Android compile issue
# 4.1.1
* 重构Android app_id处理
# 4.1.0
* 修复冷启动问题
* iOS端代码清理
# 4.0.1+1
* 更新iOS引用方式
# 4.0.1
* Fix #531
# 4.0.0+2
* flutter: ">=3.3.0"
# 4.0.0+1
* Fix iOS compile issue
# 4.0.0
* 重构Flutter端,现在需要`Fluwx fluwx = Fluwx();`调用fluwx实例
* 支持取消回传值的监听
* 枚举例按照dart语言规范进行了重命名
* 一些包含`WeChat`的方法删除了`WeChat`
* 部分类改为sealed class
* No_pay现已合并入Fluwx
* 将一些设置移到pubspec.yaml,具体可以参看`example/pubspec.yaml`
* 删除了log相关操作,因为现在可以通过yaml配置
* 新增一些open功能
# 4.0.0-pre.3
* `Fluwx`接口优化。合并了一些函数以优化使用体验。
* 修复Logging在iOS端不好的问题。
# 4.0.0-pre.2
* No_pay现已合并入Fluwx
* 将一些设置移到pubspec.yaml,具体可以参看`example/pubspec.yaml`
* 删除了log相关操作,因为现在可以通过yaml配置
* 增加了open()方法并删除了openWeChatApp
# 4.0.0-pre.1
* 重构Flutter端,现在需要`Fluwx fluwx = Fluwx();`调用fluwx实例
* 支持取消回传值的监听
* 枚举例按照dart语言规范进行了重命名
* 一些包含`WeChat`的方法删除了`WeChat`
* 部分类改为sealed class
* 最低dart版本>=3.1.0-26.0.dev
# 3.13.1
* 分享到小程序的thumbnail为必填
# 3.13.0
* Android SDK升级到6.8.24
* Kotlin升级到1.7.10
* iOS切换到WechatOpenSDK-XCFramework
# 3.12.2
* Fix #509
# 3.12.1
* 升级AGP
* Fix #512
# 3.12.0
* 授权登录支持关闭自动授权
* 分享支持添加签名,防止篡改
# 3.11.0+1
* Fix #506
# 3.11.0
* Fix #504
# 3.10.0
* 更新微信SDK
# 3.9.2
* 修复分享图片会导致Android无反应问题
# 3.9.1
* Fix issue getting extData on iOS
# 3.9.0+2
* Merge #485
# 3.9.0+1
* Merge #482
# 3.9.0
* 支持微信卡包
# 3.8.5
* Fix #471
# 3.8.4+3
* Fix #478 #466 #470 #472
# 3.8.4+2
* Fix #471
* 更换pod源
# 3.8.4+1
* Fix #471
# 3.8.4
* 增加微信的日志开关
# 3.8.2+1
* 升级kotlin-coroutine
# 3.8.2
* 新加自动订阅续费功能
# 3.8.1+1
* Just update docs
# 3.8.1
* 在iOS中增加FluwxDelegate
* 尝试修复iOS冷启动获取extMsg问题
# 3.8.0+2
* Fix #461
# 3.8.0
* APP调起支付分-订单详情
# 3.7.0
* Fix #453
# 3.6.1+4
* Android P support
# 3.6.1+3
* Fix #431
# 3.6.1+2
* Fix #422
# 3.6.1+1
* Fix #414
# 3.6.1
* Fix #415
# 3.6.0
* APP拉起微信客服功能
## 3.5.1
* 自动释放extMsg
## 3.5.0
* update compileSdkVersion
## 3.4.3
* update Android SDK version
## 3.4.2
* Merge #370
## 3.4.1
* 修复热启动传值问题
## 3.4.0
* 修复从外部拉起App白屏问题
* 修复从外部拉起App无法传值问题
## 3.3.2
* Fix #357
## 3.3.1
* Fix #354
## 3.3.0
* Null-safety support
* Fix #350
## 2.6.2
* Fix #338 on Android
## 2.6.1
* Fix #338
## 2.6.0+2
* Remove trailing
## 2.6.0+1
* Nothing
## 2.6.0
* Android支持通过H5冷启动app传递<wx-open-launch-app>中的extinfo数据
* Android新加<meta-data>handleWeChatRequestByFluwx</meta-data>
## 2.5.0+1
* Fix trailing , issue.
## 2.5.0
* App获取开放标签<wx-open-launch-app>中的extinfo数据
## 2.4.2
* Fix #317
## 2.4.1
* 修复Android 11无法分享图片的问题
## 2.4.0
* 支持compressThumbnail
* 升级OkHttp
## 2.3.0
* 适配Flutter 1.20
* 升级Android的Gradle以及更库的版本
## 2.2.0
* Merged #249
## 2.1.0
* Specifying supported platforms
* Fix: Android分享小程序时,缩略图压缩过重的问题
* 更改分享文件的实现形式
## 2.0.9
* Android SDK 升级到6.6.4
* iOS SDK升级到1.8.7.1
* Kotlin->1.3.72
## 2.0.8+3
* Merge #218
## 2.0.8+2
* Merge #218
## 2.0.8+1
* 修复ios编译错误
## 2.0.8
* Fix #212
## 2.0.7
* Fix #207
## 2.0.6+2
* Fix: Android分享大图时存储权限问题
## 2.0.6
* Fix: Android请求权限崩溃的问题
## 2.0.5+1
* 升级
## 2.0.5
* Fix:Android分享file文件时,会crash
## 2.0.4
* Fix:hdImage为空时,ios会crash
## 2.0.3
* 添加混淆文件
## 2.0.2
* Fix #199
## 2.0.1
* 修复Android没有回调的问题
## 2.0.0+1
* 按照pub建议改进
## 2.0.0
* 代码重构,现在代码结构更清晰
* 所有图片由WeChatImage构建
* 现在iOS对分享微信小程序的高清图也会压缩
* 微信回调监听形式变更
* Android增加新的Action以防微信打开小程序出错不会返回原app的问题
* iOS改用Pod引用微信SDK
* iOS隐藏一些header
* kotlin 1.3.70
## 1.2.1+2
* iOS的StringUtil重命名了
## 1.2.1+1
* Fix #178
## 1.2.1
* Fix #175
## 1.2.0
* 分享文件
* compileSdkVersion 29
## 1.1.4
* 注册微信时会对universal link进行简单校验
## 1.1.3
* Fix #146
## 1.1.2
* Fix #122
## 1.1.1+1
* Android CompileSDKVersion 提升到28
### 1.1.1
* registerWxApi
## 1.1.0
* iOS SDK升级至1.8.6.1,本版本开始支持universal link。
* Android SDK更换至without-mat:5.4.3
* Android配置升级
* 移除MTA选项
## 1.0.6
* Fix #110
## 1.0.5
* 增加分享内存图片
## 1.0.4
* 解决Android上打开小程序返回白屏问题(非官方解决方案)
## 1.0.3
* 修复一些小问题
## 1.0.2
* 修复无法Android上分享大图的问题
## 1.0.1
* 修复一些小问题
## 1.0.0
* ios不必再重写AppDelegate
## 0.6.3
* 免密支付
* 支持打开微信App了
* 升级了Android
## 0.6.2
* 对android进行了升级
## 0.6.1
* 支持二维码登录
## 0.6.0
* kotlin升级至1.3.21。
* ios SDK升级至1.8.4。
* android SDK升级至5.3.6。
## 0.5.7
* 修复问题43。
## 0.5.6
## 0.5.5
* 修复ios分享小程序标题不正确的问题。
## 0.5.4
* 增加一次性订阅消息功能。
## 0.5.3
* 修复唤起小程序返回值类型不一致的问题。
## 0.5.2
* 修复ios上sendAuth无返回的问题。
* kotlin升级至1.3.10
* android WeChatSDK升级到5.1.6
## 0.5.1
* Kotlin升级到了1.3.0
* 代码格化
## 0.5.0
* 增加了对拉起小程序的支持
* 删除了一些不必要的类
* 发送Auth验证Api调整
## 0.4.1
* 修复iOS与其他库共存时,会有重复的错误
## 0.4.0
* 移除WeChatPayModel
* 移除ios最小支持。
* 优化*Android*微信回调。
* *build.gradle*升级到了*3.2.1*
## 0.3.2
* *build.gradle*升级到了*3.2.0*
* *kotlin*升级到了*1.2.71*
## 0.3.1
* 修复了由于Flutter-dev-0.9.7-pre在android中添加了*@Nullable*注解而引起的编译问题
## 0.3.0
* 回调方式发生变化,由Map变更为实体类。
* iOS的WeChatSDK更换为内部依赖,并升级到了1.8.3。
* 修复iOS支付返回结果缺少*returnKey*的问题。
* API现在更加友善了。
* 对swift支持更友好了。
## 0.2.1
* 修复在Android处理网络图片后缀不对的问题。
## 0.2.0
* iOS支持Swift了。
## 0.1.9
* 修复了不传*thumbnail*在Android上会崩溃的bug。
## 0.1.8
* `WeChatPayModel`里的字段不再是`dynamic`
* 修复了iOS对支付功能中timestamp处理不正确的问题。
## 0.1.7
* 删除`Fluwx.registerApp(RegisterModel)`,现在使用`Fluwx.register()`
## 0.1.6
* 修复transitive dependencies。
## 0.1.5
* 增加了本地图片的支持
## 0.1.4
* 修复了iOS分享去处错误的问题
## 0.1.3
* `ResponseType` 更名为`WeChatResponseType`
## 0.1.2
* 修复iOS中FluwxShareHandler.h的导入问题
## 0.1.1
* 修复iOS分享去处错误的bug
## 0.1.0
* 增加了MTA选项
* Android部分的微信SDK提供方式由implementation更换为api
## 0.0.8
* 修复了iOS无法分享小程序的bug
* 修复了iOS分享音乐崩溃的问题
* 修复了iOS发送Auth偶尔会崩溃的问题
## 0.0.7
* 修复了iOS回调崩溃的bug
## 0.0.6
* 修复iOS拉起支付崩溃的问题
## 0.0.5
* 格式化代码
## 0.0.4
* 支付
* demo
## 0.0.3
* 发送Auth认证。
## 0.0.2
* 文本分享。
* 网站分享。
* 图片分享。
* 音乐分享。
* 视频分享。
* 小程序分享。
## 0.0.1
* Android部分的分享已完成.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2020] [OpenFlutter]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
# Fluwx
![pub package](https://img.shields.io/pub/v/fluwx.svg)
![Build status](https://github.com/OpenFlutter/fluwx/actions/workflows/build_test.yml/badge.svg)
======
![logo](https://gitee.com/OpenFlutter/resoures-repository/raw/master/fluwx/fluwx_logo.png)
[中文请移步此处](./README_CN.md)
## What's Fluwx
`Fluwx` is flutter plugin for [WeChatSDK](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html) which allows developers to call
[WeChatSDK](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html) native APIs.
> Join QQ Group now: 1003811176
![QQGroup](https://gitee.com/OpenFlutter/resoures-repository/raw/master/common/flutter.png)
## Capability
- Share images, texts, music and so on to WeChat, including session, favorite and timeline.
- Payment with WeChat.
- Get auth code before you login in with WeChat.
- Launch mini program in WeChat.
- Subscribe Message.
- Just open WeChat app.
- Launch app From wechat link.
## Preparation
[Migrate to V4 now](./doc/MIGRATE_TO_V4_CN.md)
`Fluwx` is good but not God. You'd better read [official documents](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html) before
integrating `Fluwx`. Then you'll understand how to generate Android signature, what's universal link for iOS, how to add URL schema for iOS and so on.
## Install
Add the following dependencies in your `pubspec.yaml` file:
`Fluwx` with pay:
```yaml
dependencies:
fluwx: ^${latestVersion}
```
![pub package](https://img.shields.io/pub/v/fluwx.svg)
`Fluwx` without pay:
> Developers who need to exclude payment for iOS can enable `no_pay` in [pubspec.yaml](./example/pubspec.yaml#L86).
> NOTE: Never forget to replace ^${latestVersion} with actual version.
## Configurations
`Fluwx` enables multiple configurations in the section `fluwx` of `pubspec.yaml` from v4, you can reference [pubspec.yaml](./example/pubspec.yaml#L10)
for more details.
> For iOS, some configurations, such as url_scheme,universal_link, LSApplicationQueriesSchemes, can be configured by `fluwx`,
> what you need to do is to fill configurations in `pubspec.yaml`
- app_id. Recommend. It'll be used to generate scheme on iOS。This is not used to init WeChat SDK so you still need to call `fluwx.registerApi` manually.
- debug_logging. Optional. Enable logs by setting it `true`.
- flutter_activity. Optional. This is usually used by cold boot from WeChat on Android. `Fluwx` will try to launch launcher activity if not set.
- universal_link. Recommend for iOS. It'll be used to generate universal link on your projects.
- scene_delegate. Optional. Use `AppDelegate` or `SceneDelegate`. See [official documents](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html) for more details.
* For iOS
If you are failing `cannot load such file -- plist` on iOS, please do the following steps:
```shell
# step.1 install missing dependencies
sudo gem install plist
# step.2 enter iOS folder(example/ios/,ios/)
cd example/ios/
# step.3 execute
pod install
```
## Register WxAPI
Register your app via `fluwx` if necessary.
```dart
Fluwx fluwx = Fluwx();
fluwx.registerApi(appId: "wxd930ea5d5a228f5f",universalLink: "https://your.univerallink.com/link/");
```
The param `universalLink` only works with iOS. You can read [this document](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html) to learn
how to create universalLink. You can also learn how to add URL schema, how to add `LSApplicationQueriesSchemes` in your iOS project. This is essential.
For Android, you shall know to how generate signature for your app in [this page](https://developers.weixin.qq.com/doc/oplatform/Downloads/Android_Resource.html).
And you have to understand the difference between debug signature and release signature. Once the signature is incorrect, then you'll get `errCode = -1`.
It' better to register your API as early as possible.
## Capability Document
- [Basic knowledge](./doc/BASIC_KNOWLEDGE.md)
- [Share](./doc/SHARE.md)
- [Payment](./doc/PAYMENT.md)
- [Auth](./doc/AUTH.md)
- [Launch app from h5](./doc/LAUNCH_APP_FROM_H5.md)
For more capabilities, you can read the public functions of `fluwx`.
## QA
[These questions maybe help](./doc/QA_CN.md)
## Donate
Buy the writer a cup of coffee。
<img src="https://gitee.com/OpenFlutter/resoures-repository/raw/master/common/wx.jpeg" height="300"> <img src="https://gitee.com/OpenFlutter/resoures-repository/raw/master/common/ali.jpeg" height="300">
## Subscribe Us On WeChat
![subscribe](https://gitee.com/OpenFlutter/resoures-repository/raw/master/fluwx/wx_subscription.png)
## Star history
![stars](https://starchart.cc/OpenFlutter/fluwx.svg)
## LICENSE
Copyright 2023 OpenFlutter Project
Licensed to the Apache Software Foundation (ASF) under one or more contributor
license agreements. See the NOTICE file distributed with this work for
additional information regarding copyright ownership. The ASF licenses this
file to you under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
# Fluwx
![pub package](https://img.shields.io/pub/v/fluwx.svg)
![Build status](https://github.com/OpenFlutter/fluwx/actions/workflows/build_test.yml/badge.svg)
======
![logo](https://gitee.com/OpenFlutter/resoures-repository/raw/master/fluwx/fluwx_logo.png)
## 什么是Fluwx
`Fluwx` 是一个[微信SDK](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html)插件,它允许开发者调用
[微信原生SDK ](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html).
> Fluwx 4.0 强势开发中!
> 加入我们的QQ群: 1003811176
![QQGroup](https://gitee.com/OpenFlutter/resoures-repository/raw/master/common/flutter.png)
## 能力
- 分享图片,文本,音乐,视频等。支持分享到会话,朋友圈以及收藏.
- 微信支付.
- 在微信登录时,获取Auth Code.
- 拉起小程序.
- 订阅消息.
- 打开微信.
- 从微信标签打开应用
## 准备
[迁移到V4指南](./doc/MIGRATE_TO_V4_CN.md)
`Fluwx` 可以做很多工作但不是所有. 在集成之前,最好读一下[官方文档](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html).
然后你才知道怎么生成签名,怎么使用universal link以及怎么添加URL schema等.
## 安装
`pubspec.yaml` 文件中添加`fluwx`依赖:
`Fluwx`,带支付:
```yaml
dependencies:
fluwx: ^${latestVersion}
```
![pub package](https://img.shields.io/pub/v/fluwx.svg)
不带支付的`Fluwx`:
> 一些开发者并不需要在iOS端使用支付能力,此时您可以通过在[pubspec.yaml](./example/pubspec.yaml).
![pub package](https://img.shields.io/pub/v/fluwx_no_pay.svg)中开启`no_pay`
> NOTE: 别忘记替换 ^${latestVersion} !!!!
## 配置
`Fluwx` 从v4开始可以在`pubspec.yaml``fluwx`进行一些配置。具体可以参考[pubspec.yaml](./example/pubspec.yaml#L10)
> V4开始,iOS中的url_scheme,universal_link, LSApplicationQueriesSchemes可以不必开发者手动配动。只需在`pubspec.yaml`
> 中填写即可。
- app_id. 推荐. 它将用于生成iOS的url_scheme。这并不会替你初始化微信SDK,所以你还是自己调用`fluwx.registerApi`
- debug_logging. 可选. 把它设置成`true`可以开启日志。
- flutter_activity. 可选. 这个通常是用于Android的冷启动。如果不设置任何值,`Fluwx`将尝试启动launcher activity.
- universal_link. iOS 推荐. 它将用自动配置universal_link。
- scene_delegate. iOS 可选. 使用 `AppDelegate` 还是使用 `SceneDelegate`. 查阅[官方文档](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html)了解更多.
* For iOS
如果你在iOS上遇到了 `cannot load such file -- plist`, 请按照以下步骤进行操作:
```shell
# step.1 安装必要依赖
sudo gem install plist
# step.2 进行iOS文件夹(example/ios/,ios/)
cd example/ios/
# step.3 执行脚本
pod install
```
## 注册 WxAPI
通过 `fluwx` 注册WxApi.
```dart
Fluwx fluwx = Fluwx();
fluwx.registerApi(appId: "wxd930ea5d5a228f5f",universalLink: "https://your.univerallink.com/link/");
```
参数 `universalLink` 只在iOS上有用. 查看[文档](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html) 以便了解如何生成通用链接.
你也可以学习到怎么在iOS工程中添加URL schema,怎么添加`LSApplicationQueriesSchemes`。这很重要。
对于Android, 可以查看[本文](https://developers.weixin.qq.com/doc/oplatform/Downloads/Android_Resource.html)以便了解怎么获取app签名.
然后你需要知道release和debug时,app签名有什么区别。如果签名不对,你会得一个错误 `errCode = -1`.
建议越早注册越好。
## 能力文档
- [基础知识](./doc/BASIC_KNOWLEDGE_CN.md)
- [分享](./doc/SHARE_CN.md)
- [支付](./doc/PAYMENT_CN.md)
- [登录](./doc/AUTH_CN.md)
- [从微信标签打开应用](./doc/LAUNCH_APP_FROM_H5_CN.md)
对于更多功能,可以查看源码。
## QA
[这些问题可能对你有帮助](./doc/QA_CN.md)
## 捐助
开源不易,请作者喝杯咖啡。
<img src="https://gitee.com/OpenFlutter/resoures-repository/raw/master/common/wx.jpeg" height="300"> <img src="https://gitee.com/OpenFlutter/resoures-repository/raw/master/common/ali.jpeg" height="300">
## 关注公众号
![subscribe](https://gitee.com/OpenFlutter/resoures-repository/raw/master/fluwx/wx_subscription.png)
## 关注趋势
![stars](https://starchart.cc/OpenFlutter/fluwx.svg)
## LICENSE
Copyright 2018 OpenFlutter Project
Licensed to the Apache Software Foundation (ASF) under one or more contributor
license agreements. See the NOTICE file distributed with this work for
additional information regarding copyright ownership. The ASF licenses this
file to you under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
import org.yaml.snakeyaml.Yaml
group 'com.jarvan.fluwx'
version '1.0-SNAPSHOT'
Map projectYaml = loadPubspec()
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
classpath "org.yaml:snakeyaml:2.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
namespace "com.jarvan.fluwx"
compileSdk 31
sourceSets {
main.java.srcDirs += ['src/main/kotlin', "${buildDir}/generated/src/kotlin"]
test.java.srcDirs += 'src/test/kotlin'
}
defaultConfig {
minSdkVersion 19
consumerProguardFiles 'consumer-proguard-rules.txt'
}
dependencies {
api 'com.tencent.mm.opensdk:wechat-sdk-android:6.8.24'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'id.zelory:compressor:3.0.1'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.mockito:mockito-core:5.0.0'
}
testOptions {
unitTests.all {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen { false }
showStandardStreams = true
}
}
}
}
Map loadPubspec() {
def path = rootProject.projectDir.parent + File.separator + "pubspec.yaml"
InputStream input = new FileInputStream(new File(path))
Yaml yaml = new Yaml()
Map projectConfig = yaml.load(input)
return projectConfig
}
tasks.register("generateFluwxHelperFile") {
Map config = loadPubspec()
Map fluwx = (Map) config.get("fluwx")
String enableLogging = "false"
String interruptWeChatRequestByFluwx = "true"
String flutterActivity = ""
if (fluwx) {
Map android = (Map) fluwx.get("android")
if (android) {
def iwr = android.get("interrupt_wx_request")
if (iwr && iwr == "true" || iwr == "false") {
interruptWeChatRequestByFluwx = (String) iwr
}
def activity = android.get("flutter_activity")
if (activity) {
flutterActivity = (String) activity
}
}
def logging = fluwx.get("debug_logging")
if (logging && logging == "true" || logging == "false") {
enableLogging = (String) logging
}
}
generateFluwxConfigurations(interruptWeChatRequestByFluwx, flutterActivity, enableLogging)
}
def generateFluwxConfigurations(String interruptWeChatRequestByFluwx, String flutterActivity, String enableLogging) {
File generateFolder = new File("${buildDir}/generated/src/kotlin/com/jarvan/fluwx")
String template = "package com.jarvan.fluwx\n" +
"\n" +
"// auto generated\n" +
"internal object FluwxConfigurations {\n" +
" val flutterActivity: String = \"&&flutterActivity&&\"\n" +
" val enableLogging: Boolean = &&enableLogging&&\n" +
" val interruptWeChatRequestByFluwx: Boolean = &&interruptWeChatRequestByFluwx&&\n" +
"}"
if (!generateFolder.exists()) {
generateFolder.mkdirs()
}
String source = template.replace("&&interruptWeChatRequestByFluwx&&", interruptWeChatRequestByFluwx)
.replace("&&flutterActivity&&", flutterActivity)
.replace("&&enableLogging&&", enableLogging)
file("${generateFolder.absolutePath}/FluwxConfigurations.kt").text = source
}
tasks.withType(JavaCompile) { javaCompile ->
javaCompile.configure {
dependsOn("generateFluwxHelperFile")
}
}
group = "com.jarvan.fluwx"
version = "1.0-SNAPSHOT"
plugins {
id("com.android.library")
kotlin("android")
}
allprojects {
repositories {
google()
mavenCentral()
}
}
android {
namespace = "com.jarvan.fluwx"
compileSdk = 31
sourceSets {
val main by getting
main.java.srcDirs("src/main/kotlin")
val test by getting
test.java.srcDirs("src/test/kotlin")
}
defaultConfig {
minSdk = 16
consumerProguardFile("consumer-proguard-rules.txt")
}
testOptions {
unitTests.all {
it.useJUnitPlatform()
it.testLogging {
events("passed", "skipped", "failed", "standardOut", "standardError")
showStandardStreams = true
it.outputs.upToDateWhen {
false
}
}
}
}
}
dependencies {
api("com.tencent.mm.opensdk:wechat-sdk-android:6.8.24")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
implementation("id.zelory:compressor:3.0.1")
implementation("com.squareup.okhttp3:okhttp:4.10.0")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0")
}
# 微信
-keep class com.tencent.mm.opensdk.** {*;}
-keep class com.tencent.wxop.** {*;}
-keep class com.tencent.mm.sdk.** {*;}
## Kotlin
# ServiceLoader support
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
-keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {}
-keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {}
# Most of volatile fields are updated with AFU and should not be mangled
-keepclassmembernames class kotlinx.** {
volatile <fields>;
}
abstract class GenFluwxHelperTask : DefaultTask() {
@get:Incremental
@get:PathSensitive(PathSensitivity.NAME_ONLY)
@get:InputDirectory
abstract val inputDir: DirectoryProperty
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:Input
abstract val inputProperty: Property<String>
@TaskAction
fun execute(inputChanges: InputChanges) {
println(
if (inputChanges.isIncremental) "Executing incrementally"
else "Executing non-incrementally"
)
inputChanges.getFileChanges(inputDir).forEach { change ->
if (change.fileType == FileType.DIRECTORY) return@forEach
println("${change.changeType}: ${change.normalizedPath}")
val targetFile = outputDir.file(change.normalizedPath).get().asFile
// if (change.changeType == ChangeType.REMOVED) {
// targetFile.delete()
// } else {
// targetFile.writeText(change.file.readText().reversed())
// }
}
}
}
\ No newline at end of file
rootProject.name = 'fluwx'
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<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.WRITE_EXTERNAL_STORAGE" />
<!-- Support WeChat query on Android P -->
<queries>
<package android:name="com.tencent.mm" />
</queries>
<application>
<activity
android:name=".wxapi.FluwxWXEntryActivity"
android:exported="false"
android:launchMode="singleTask"
android:taskAffinity="${applicationId}"
android:theme="@style/DisablePreviewTheme" />
<activity-alias
android:name="${applicationId}.wxapi.WXEntryActivity"
android:exported="true"
android:launchMode="singleTop"
android:targetActivity="com.jarvan.fluwx.wxapi.FluwxWXEntryActivity"
android:taskAffinity="${applicationId}"
android:theme="@style/DisablePreviewTheme" />
<activity-alias
android:name="${applicationId}.wxapi.WXPayEntryActivity"
android:exported="true"
android:launchMode="singleInstance"
android:targetActivity="com.jarvan.fluwx.wxapi.FluwxWXEntryActivity"
android:theme="@style/DisablePreviewTheme" />
<provider
android:name="com.jarvan.fluwx.FluwxFileProvider"
android:authorities="${applicationId}.fluwxprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/fluwx_file_provider_paths" />
</provider>
</application>
</manifest>
package com.jarvan.fluwx
import androidx.core.content.FileProvider
/***
* Created by mo on 2020/5/13
* 冷风如刀,以大地为砧板,视众生为鱼肉。
* 万里飞雪,将穹苍作烘炉,熔万物为白银。
**/
class FluwxFileProvider: FileProvider()
\ No newline at end of file
package com.jarvan.fluwx
import android.content.Context
import android.content.Intent
import com.jarvan.fluwx.handlers.FluwxAuthHandler
import com.jarvan.fluwx.handlers.FluwxRequestHandler
import com.jarvan.fluwx.handlers.FluwxShareHandler
import com.jarvan.fluwx.handlers.FluwxShareHandlerEmbedding
import com.jarvan.fluwx.handlers.PermissionHandler
import com.jarvan.fluwx.handlers.WXAPiHandler
import com.jarvan.fluwx.utils.WXApiUtils
import com.jarvan.fluwx.utils.readWeChatCallbackIntent
import com.tencent.mm.opensdk.modelbase.BaseReq
import com.tencent.mm.opensdk.modelbase.BaseResp
import com.tencent.mm.opensdk.modelbiz.ChooseCardFromWXCardPackage
import com.tencent.mm.opensdk.modelbiz.OpenRankList
import com.tencent.mm.opensdk.modelbiz.OpenWebview
import com.tencent.mm.opensdk.modelbiz.SubscribeMessage
import com.tencent.mm.opensdk.modelbiz.WXLaunchMiniProgram
import com.tencent.mm.opensdk.modelbiz.WXOpenBusinessView
import com.tencent.mm.opensdk.modelbiz.WXOpenBusinessWebview
import com.tencent.mm.opensdk.modelbiz.WXOpenCustomerServiceChat
import com.tencent.mm.opensdk.modelmsg.LaunchFromWX
import com.tencent.mm.opensdk.modelmsg.SendAuth
import com.tencent.mm.opensdk.modelmsg.SendMessageToWX
import com.tencent.mm.opensdk.modelmsg.ShowMessageFromWX
import com.tencent.mm.opensdk.modelpay.PayReq
import com.tencent.mm.opensdk.modelpay.PayResp
import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler
import com.tencent.mm.opensdk.openapi.SendReqCallback
import com.tencent.mm.opensdk.utils.ILog
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.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry
import java.util.concurrent.atomic.AtomicBoolean
/** FluwxPlugin */
class FluwxPlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
PluginRegistry.NewIntentListener, IWXAPIEventHandler {
companion object {
// 主动获取的启动参数
var extMsg: String? = null
}
private val errStr = "errStr"
private val errCode = "errCode"
private val openId = "openId"
private val type = "type"
private val weChatLogger = object : ILog {
override fun d(p0: String?, p1: String?) {
logToFlutter(p0, p1)
}
override fun i(p0: String?, p1: String?) {
logToFlutter(p0, p1)
}
override fun e(p0: String?, p1: String?) {
logToFlutter(p0, p1)
}
override fun v(p0: String?, p1: String?) {
logToFlutter(p0, p1)
}
override fun w(p0: String?, p1: String?) {
logToFlutter(p0, p1)
}
}
private var shareHandler: FluwxShareHandler? = null
private var authHandler: FluwxAuthHandler? = null
private var fluwxChannel: MethodChannel? = null
private var context: Context? = null
private val attemptToResumeMsgFromWxFlag = AtomicBoolean(false)
private var activityPluginBinding: ActivityPluginBinding? = null
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
val channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.jarvanmo/fluwx")
channel.setMethodCallHandler(this)
fluwxChannel = channel
context = flutterPluginBinding.applicationContext
authHandler = FluwxAuthHandler(channel)
shareHandler = FluwxShareHandlerEmbedding(
flutterPluginBinding.flutterAssets, flutterPluginBinding.applicationContext
)
}
override fun onMethodCall(call: MethodCall, result: Result) {
when {
call.method == "registerApp" -> {
WXAPiHandler.registerApp(call, result, context)
if (FluwxConfigurations.enableLogging) {
WXAPiHandler.wxApi?.setLogImpl(weChatLogger)
}
}
call.method == "sendAuth" -> authHandler?.sendAuth(call, result)
call.method == "authByQRCode" -> authHandler?.authByQRCode(call, result)
call.method == "stopAuthByQRCode" -> authHandler?.stopAuthByQRCode(result)
call.method == "payWithFluwx" -> pay(call, result)
call.method == "payWithHongKongWallet" -> payWithHongKongWallet(call, result)
call.method == "launchMiniProgram" -> launchMiniProgram(call, result)
call.method == "subscribeMsg" -> subScribeMsg(call, result)
call.method == "autoDeduct" -> signAutoDeduct(call, result)
call.method == "autoDeductV2" -> autoDeductV2(call, result)
call.method == "openWXApp" -> openWXApp(result)
call.method.startsWith("share") -> shareHandler?.share(call, result)
call.method == "isWeChatInstalled" -> WXAPiHandler.checkWeChatInstallation(result)
call.method == "getExtMsg" -> getExtMsg(result)
call.method == "openWeChatCustomerServiceChat" -> openWeChatCustomerServiceChat(
call, result
)
call.method == "checkSupportOpenBusinessView" -> WXAPiHandler.checkSupportOpenBusinessView(
result
)
call.method == "openBusinessView" -> openBusinessView(call, result)
call.method == "openWeChatInvoice" -> openWeChatInvoice(call, result)
call.method == "openUrl" -> openUrl(call, result)
call.method == "openRankList" -> openRankList(result)
call.method == "attemptToResumeMsgFromWx" -> attemptToResumeMsgFromWx(result)
call.method == "selfCheck" -> result.success(null)
else -> result.notImplemented()
}
}
private fun attemptToResumeMsgFromWx(result: Result) {
if (attemptToResumeMsgFromWxFlag.compareAndSet(false, true)) {
activityPluginBinding?.activity?.intent?.let {
letWeChatHandleIntent(it)
}
}
result.success(null)
}
private fun openWeChatInvoice(call: MethodCall, result: Result) {
if (WXAPiHandler.wxApi == null) {
result.error("Unassigned WxApi", "please config wxapi first", null)
return
} else {
//android 只有ChooseCard, IOS直接有ChooseInvoice
val request = ChooseCardFromWXCardPackage.Req()
request.cardType = call.argument("cardType")
request.appId = call.argument("appId")
request.locationId = call.argument("locationId")
request.cardId = call.argument("cardId")
request.canMultiSelect = call.argument("canMultiSelect")
request.timeStamp = System.currentTimeMillis().toString()
request.nonceStr = System.currentTimeMillis().toString()
request.signType = "SHA1"
request.cardSign = WXApiUtils.createSign(
request.appId, request.nonceStr, request.timeStamp, request.cardType
)
val done = WXAPiHandler.wxApi?.sendReq(request)
result.success(done)
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
shareHandler?.onDestroy()
authHandler?.removeAllListeners()
activityPluginBinding = null
}
override fun onDetachedFromActivity() {
shareHandler?.permissionHandler = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
shareHandler?.permissionHandler = PermissionHandler(binding.activity)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
// WXAPiHandler.setContext(binding.activity.applicationContext)
activityPluginBinding = binding
binding.addOnNewIntentListener(this)
shareHandler?.permissionHandler = PermissionHandler(binding.activity)
}
override fun onDetachedFromActivityForConfigChanges() {
}
private fun getExtMsg(result: Result) {
result.success(extMsg)
extMsg = null
}
private fun pay(call: MethodCall, result: Result) {
if (WXAPiHandler.wxApi == null) {
result.error("Unassigned WxApi", "please config wxapi first", null)
return
} else {
// 将该app注册到微信
val request = PayReq()
request.appId = call.argument("appId")
request.partnerId = call.argument("partnerId")
request.prepayId = call.argument("prepayId")
request.packageValue = call.argument("packageValue")
request.nonceStr = call.argument("nonceStr")
request.timeStamp = call.argument<Long>("timeStamp").toString()
request.sign = call.argument("sign")
request.signType = call.argument("signType")
request.extData = call.argument("extData")
val done = WXAPiHandler.wxApi?.sendReq(request)
result.success(done)
}
}
private fun payWithHongKongWallet(call: MethodCall, result: Result) {
val prepayId = call.argument<String>("prepayId") ?: ""
val request = WXOpenBusinessWebview.Req()
request.businessType = 1
request.queryInfo = hashMapOf(
"token" to prepayId
)
result.success(WXAPiHandler.wxApi?.sendReq(request))
}
private fun openWeChatCustomerServiceChat(call: MethodCall, result: Result) {
val url = call.argument<String>("url") ?: ""
val corpId = call.argument<String>("corpId") ?: ""
val request = WXOpenCustomerServiceChat.Req()
request.corpId = corpId // 企业ID
request.url = url
result.success(WXAPiHandler.wxApi?.sendReq(request))
}
private fun openBusinessView(call: MethodCall, result: Result) {
val request = WXOpenBusinessView.Req()
request.businessType = call.argument<String>("businessType") ?: ""
request.query = call.argument<String>("query") ?: ""
request.extInfo = "{\"miniProgramType\": 0}"
result.success(WXAPiHandler.wxApi?.sendReq(request))
}
private fun signAutoDeduct(call: MethodCall, result: Result) {
val appId: String = call.argument<String>("appid") ?: ""
val mchId = call.argument<String>("mch_id") ?: ""
val planId = call.argument<String>("plan_id") ?: ""
val contractCode = call.argument<String>("contract_code") ?: ""
val requestSerial = call.argument<String>("request_serial") ?: ""
val contractDisplayAccount = call.argument<String>("contract_display_account") ?: ""
val notifyUrl = call.argument<String>("notify_url") ?: ""
val version = call.argument<String>("version") ?: ""
val sign = call.argument<String>("sign") ?: ""
val timestamp = call.argument<String>("timestamp") ?: ""
val returnApp = call.argument<String>("return_app") ?: ""
val businessType = call.argument<Int>("businessType") ?: 12
val req = WXOpenBusinessWebview.Req()
req.businessType = businessType
req.queryInfo = hashMapOf(
"appid" to appId,
"mch_id" to mchId,
"plan_id" to planId,
"contract_code" to contractCode,
"request_serial" to requestSerial,
"contract_display_account" to contractDisplayAccount,
"notify_url" to notifyUrl,
"version" to version,
"sign" to sign,
"timestamp" to timestamp,
"return_app" to returnApp
)
result.success(WXAPiHandler.wxApi?.sendReq(req))
}
private fun autoDeductV2(call: MethodCall, result: Result) {
val businessType = call.argument<Int>("businessType") ?: 12
val req = WXOpenBusinessWebview.Req()
req.businessType = businessType
req.queryInfo = call.argument<HashMap<String, String>>("queryInfo") ?: hashMapOf()
result.success(WXAPiHandler.wxApi?.sendReq(req))
}
private fun subScribeMsg(call: MethodCall, result: Result) {
val appId = call.argument<String>("appId")
val scene = call.argument<Int>("scene")
val templateId = call.argument<String>("templateId")
val reserved = call.argument<String>("reserved")
val req = SubscribeMessage.Req()
req.openId = appId
req.scene = scene!!
req.reserved = reserved
req.templateID = templateId
val b = WXAPiHandler.wxApi?.sendReq(req)
result.success(b)
}
private fun launchMiniProgram(call: MethodCall, result: Result) {
val req = WXLaunchMiniProgram.Req()
req.userName = call.argument("userName") // 填小程序原始id
req.path = call.argument<String?>("path") ?: "" //拉起小程序页面的可带参路径,不填默认拉起小程序首页
val type = call.argument("miniProgramType") ?: 0
req.miniprogramType = when (type) {
1 -> WXLaunchMiniProgram.Req.MINIPROGRAM_TYPE_TEST
2 -> WXLaunchMiniProgram.Req.MINIPROGRAM_TYPE_PREVIEW
else -> WXLaunchMiniProgram.Req.MINIPTOGRAM_TYPE_RELEASE
}// 可选打开 开发版,体验版和正式版
val done = WXAPiHandler.wxApi?.sendReq(req)
result.success(done)
}
private fun openWXApp(result: Result) = result.success(WXAPiHandler.wxApi?.openWXApp())
private fun openUrl(call: MethodCall, result: Result) {
val req = OpenWebview.Req()
req.url = call.argument("url")
WXAPiHandler.wxApi?.sendReq(req, SendReqCallback {
result.success(it)
}) ?: kotlin.run {
result.success(false)
}
}
private fun openRankList(result: Result) {
val req = OpenRankList.Req()
WXAPiHandler.wxApi?.sendReq(req, SendReqCallback {
result.success(it)
}) ?: kotlin.run {
result.success(false)
}
}
override fun onNewIntent(intent: Intent): Boolean {
return letWeChatHandleIntent(intent)
}
private fun letWeChatHandleIntent(intent: Intent): Boolean =
intent.readWeChatCallbackIntent()?.let {
WXAPiHandler.wxApi?.handleIntent(it, this) ?: false
} ?: run {
false
}
override fun onReq(req: BaseReq?) {
activityPluginBinding?.activity?.let { activity ->
req?.let {
if (FluwxConfigurations.interruptWeChatRequestByFluwx) {
when (req) {
is ShowMessageFromWX.Req -> handleShowMessageFromWX(req)
is LaunchFromWX.Req -> handleLaunchFromWX(req)
else -> {}
}
} else {
FluwxRequestHandler.customOnReqDelegate?.invoke(req, activity)
}
}
}
}
private fun handleShowMessageFromWX(req: ShowMessageFromWX.Req) {
val result = mapOf(
"extMsg" to req.message.messageExt,
"messageAction" to req.message.messageAction,
"description" to req.message.description,
"lang" to req.lang,
"description" to req.country,
)
extMsg = req.message.messageExt
fluwxChannel?.invokeMethod("onWXShowMessageFromWX", result)
}
private fun handleLaunchFromWX(req: LaunchFromWX.Req) {
val result = mapOf(
"extMsg" to req.messageExt,
"messageAction" to req.messageAction,
"lang" to req.lang,
"country" to req.country,
)
extMsg = req.messageExt
fluwxChannel?.invokeMethod("onWXLaunchFromWX", result)
}
override fun onResp(response: BaseResp?) {
when (response) {
is SendAuth.Resp -> handleAuthResponse(response)
is SendMessageToWX.Resp -> handleSendMessageResp(response)
is PayResp -> handlePayResp(response)
is WXLaunchMiniProgram.Resp -> handleLaunchMiniProgramResponse(response)
is SubscribeMessage.Resp -> handleSubscribeMessage(response)
is WXOpenBusinessWebview.Resp -> handlerWXOpenBusinessWebviewResponse(response)
is WXOpenCustomerServiceChat.Resp -> handlerWXOpenCustomerServiceChatResponse(response)
is WXOpenBusinessView.Resp -> handleWXOpenBusinessView(response)
is ChooseCardFromWXCardPackage.Resp -> handleWXOpenInvoiceResponse(response)
else -> {}
}
}
private fun handleWXOpenInvoiceResponse(response: ChooseCardFromWXCardPackage.Resp) {
val result = mapOf(
"cardItemList" to response.cardItemList,
"transaction" to response.transaction,
"openid" to response.openId,
errStr to response.errStr,
type to response.type,
errCode to response.errCode
)
fluwxChannel?.invokeMethod("onOpenWechatInvoiceResponse", result)
}
private fun handleWXOpenBusinessView(response: WXOpenBusinessView.Resp) {
val result = mapOf(
"openid" to response.openId,
"extMsg" to response.extMsg,
"businessType" to response.businessType,
errStr to response.errStr,
type to response.type,
errCode to response.errCode
)
fluwxChannel?.invokeMethod("onOpenBusinessViewResponse", result)
}
private fun handleSubscribeMessage(response: SubscribeMessage.Resp) {
val result = mapOf(
"openid" to response.openId,
"templateId" to response.templateID,
"action" to response.action,
"reserved" to response.reserved,
"scene" to response.scene,
type to response.type
)
fluwxChannel?.invokeMethod("onSubscribeMsgResp", result)
}
private fun handleLaunchMiniProgramResponse(response: WXLaunchMiniProgram.Resp) {
val result = mutableMapOf(
errStr to response.errStr,
type to response.type,
errCode to response.errCode,
openId to response.openId
)
response.extMsg?.let {
result["extMsg"] = response.extMsg
}
fluwxChannel?.invokeMethod("onLaunchMiniProgramResponse", result)
}
private fun handlePayResp(response: PayResp) {
val result = mapOf(
"prepayId" to response.prepayId,
"returnKey" to response.returnKey,
"extData" to response.extData,
errStr to response.errStr,
type to response.type,
errCode to response.errCode
)
fluwxChannel?.invokeMethod("onPayResponse", result)
}
private fun handleSendMessageResp(response: SendMessageToWX.Resp) {
val result = mapOf(
errStr to response.errStr,
type to response.type,
errCode to response.errCode,
openId to response.openId
)
fluwxChannel?.invokeMethod("onShareResponse", result)
}
private fun handleAuthResponse(response: SendAuth.Resp) {
val result = mapOf(
errCode to response.errCode,
"code" to response.code,
"state" to response.state,
"lang" to response.lang,
"country" to response.country,
errStr to response.errStr,
openId to response.openId,
"url" to response.url,
type to response.type
)
fluwxChannel?.invokeMethod("onAuthResponse", result)
}
private fun handlerWXOpenBusinessWebviewResponse(response: WXOpenBusinessWebview.Resp) {
val result = mapOf(
errCode to response.errCode,
"businessType" to response.businessType,
"resultInfo" to response.resultInfo,
errStr to response.errStr,
openId to response.openId,
type to response.type
)
fluwxChannel?.invokeMethod("onWXOpenBusinessWebviewResponse", result)
}
private fun handlerWXOpenCustomerServiceChatResponse(response: WXOpenCustomerServiceChat.Resp) {
val result = mapOf(
errCode to response.errCode,
errStr to response.errStr,
openId to response.openId,
type to response.type
)
fluwxChannel?.invokeMethod("onWXOpenCustomerServiceChatResponse", result)
}
private fun logToFlutter(tag: String?, message: String?) {
fluwxChannel?.invokeMethod(
"wechatLog", mapOf(
"detail" to "$tag : $message"
)
)
}
}
/*
* Copyright (C) 2020 The OpenFlutter Organization
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jarvan.fluwx.handlers
import com.tencent.mm.opensdk.diffdev.DiffDevOAuthFactory
import com.tencent.mm.opensdk.diffdev.OAuthErrCode
import com.tencent.mm.opensdk.diffdev.OAuthListener
import com.tencent.mm.opensdk.modelmsg.SendAuth
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
internal class FluwxAuthHandler(private val methodChannel: MethodChannel) {
// private DiffDevOAuthFactory.getDiffDevOAuth()
private val qrCodeAuth by lazy {
DiffDevOAuthFactory.getDiffDevOAuth()
}
private val qrCodeAuthListener by lazy {
object : OAuthListener {
override fun onAuthFinish(p0: OAuthErrCode, authCode: String?) {
methodChannel.invokeMethod("onAuthByQRCodeFinished", mapOf(
"errCode" to p0.code,
"authCode" to authCode
))
}
override fun onAuthGotQrcode(p0: String?, p1: ByteArray) {
methodChannel.invokeMethod("onAuthGotQRCode", mapOf("errCode" to 0, "qrCode" to p1))
}
override fun onQrcodeScanned() {
methodChannel.invokeMethod("onQRCodeScanned", mapOf("errCode" to 0))
}
}
}
fun sendAuth(call: MethodCall, result: MethodChannel.Result) {
val req = SendAuth.Req()
req.scope = call.argument("scope")
req.state = call.argument("state")
val openId = call.argument<String?>("openId")
if (!openId.isNullOrBlank()) {
req.openId = call.argument("openId")
}
req.nonAutomatic = call.argument<Boolean?>("nonAutomatic") ?: false
result.success(WXAPiHandler.wxApi?.sendReq(req))
}
fun authByQRCode(call: MethodCall, result: MethodChannel.Result) {
val appId = call.argument("appId") ?: ""
val scope = call.argument("scope") ?: ""
val nonceStr = call.argument("nonceStr") ?: ""
val timeStamp = call.argument("timeStamp") ?: ""
val signature = call.argument("signature") ?: ""
// val schemeData = call.argument("schemeData")?:""
result.success(qrCodeAuth.auth(appId, scope, nonceStr, timeStamp, signature, qrCodeAuthListener))
}
fun stopAuthByQRCode(result: MethodChannel.Result) {
result.success(qrCodeAuth.stopAuth())
}
fun removeAllListeners() {
qrCodeAuth.removeAllListeners()
}
}
\ No newline at end of file
/*
* Copyright (C) 2020 The OpenFlutter Organization
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jarvan.fluwx.handlers
import android.app.Activity
import com.tencent.mm.opensdk.modelbase.BaseReq
object FluwxRequestHandler {
var customOnReqDelegate: ((baseReq: BaseReq, activity: Activity) -> Unit)? = null
}
\ No newline at end of file
package com.jarvan.fluwx.handlers
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.AssetFileDescriptor
import android.net.Uri
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import com.jarvan.fluwx.io.*
import com.tencent.mm.opensdk.modelbase.BaseReq
import com.tencent.mm.opensdk.modelmsg.*
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.*
import java.io.File
import java.util.*
import kotlin.coroutines.CoroutineContext
/***
* Created by mo on 2020/3/6
* 冷风如刀,以大地为砧板,视众生为鱼肉。
* 万里飞雪,将穹苍作烘炉,熔万物为白银。
**/
internal class FluwxShareHandlerEmbedding(private val flutterAssets: FlutterPlugin.FlutterAssets, override val context: Context) : FluwxShareHandler {
override val assetFileDescriptor: (String) -> AssetFileDescriptor = {
val uri = Uri.parse(it)
val packageName = uri.getQueryParameter("package")
val subPath = if (packageName.isNullOrBlank()) {
flutterAssets.getAssetFilePathBySubpath(uri.path.orEmpty())
} else {
flutterAssets.getAssetFilePathBySubpath(uri.path.orEmpty(), packageName)
}
context.assets.openFd(subPath)
}
override val job: Job = Job()
override var permissionHandler: PermissionHandler? = null
}
internal interface FluwxShareHandler : CoroutineScope {
companion object {
const val SHARE_IMAGE_THUMB_LENGTH = 32 * 1024
const val SHARE_MINI_PROGRAM_THUMB_LENGTH = 120 * 1024
private const val keyTitle = "title"
private const val keyThumbnail = "thumbnail"
private const val keyDescription = "description"
}
fun share(call: MethodCall, result: MethodChannel.Result) {
if (WXAPiHandler.wxApi == null) {
result.error("Unassigned WxApi", "please config wxapi first", null)
return
}
when (call.method) {
"shareText" -> shareText(call, result)
"shareMiniProgram" -> shareMiniProgram(call, result)
"shareImage" -> shareImage(call, result)
"shareMusic" -> shareMusic(call, result)
"shareVideo" -> shareVideo(call, result)
"shareWebPage" -> shareWebPage(call, result)
"shareFile" -> shareFile(call, result)
else -> {
result.notImplemented()
}
}
}
private fun shareText(call: MethodCall, result: MethodChannel.Result) {
val textObj = WXTextObject(call.argument("source"))
val msg = WXMediaMessage()
msg.mediaObject = textObj
val req = SendMessageToWX.Req()
setCommonArguments(call, req, msg)
req.message = msg
result.success(WXAPiHandler.wxApi?.sendReq(req))
}
private fun shareMiniProgram(call: MethodCall, result: MethodChannel.Result) {
val miniProgramObj = WXMiniProgramObject()
miniProgramObj.webpageUrl = call.argument("webPageUrl") // 兼容低版本的网页链接
miniProgramObj.miniprogramType = call.argument("miniProgramType") ?: 0// 正式版:0,测试版:1,体验版:2
miniProgramObj.userName = call.argument("userName") // 小程序原始id
miniProgramObj.path = call.argument("path") //小程序页面路径
miniProgramObj.withShareTicket = call.argument("withShareTicket") ?: true
val msg = WXMediaMessage(miniProgramObj)
msg.title = call.argument(keyTitle) // 小程序消息title
msg.description = call.argument(keyDescription) // 小程序消息desc
launch {
msg.thumbData = readThumbnailByteArray(call, length = SHARE_MINI_PROGRAM_THUMB_LENGTH)
val req = SendMessageToWX.Req()
setCommonArguments(call, req, msg)
req.message = msg
sendRequestInMain(result, req)
}
}
private fun shareImage(call: MethodCall, result: MethodChannel.Result) {
launch {
val map: Map<String, Any> = call.argument("source") ?: mapOf()
val sourceImage = WeChatFile.createWeChatFile(map, assetFileDescriptor)
val thumbData = readThumbnailByteArray(call)
val sourceByteArray = sourceImage.readByteArray()
val imageObject = when {
sourceByteArray.isEmpty() -> {
WXImageObject()
}
else -> {
WXImageObject().apply {
if (supportFileProvider && targetHigherThanN) {
setImagePath(getFileContentUri(sourceByteArray.toCacheFile(context, sourceImage.suffix)))
} else {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
setImagePath(sourceByteArray.toExternalCacheFile(context, sourceImage.suffix)?.absolutePath)
} else {
permissionHandler?.requestStoragePermission()
}
}
}
}
}
val msg = WXMediaMessage()
msg.mediaObject = imageObject
msg.thumbData = thumbData
msg.description = call.argument(keyDescription)
val req = SendMessageToWX.Req()
setCommonArguments(call, req, msg)
req.message = msg
sendRequestInMain(result, req)
}
}
private fun shareMusic(call: MethodCall, result: MethodChannel.Result) {
val music = WXMusicObject()
val musicUrl: String? = call.argument("musicUrl")
val musicLowBandUrl: String? = call.argument("musicLowBandUrl")
if (musicUrl != null && musicUrl.isNotBlank()) {
music.musicUrl = musicUrl
music.musicDataUrl = call.argument("musicDataUrl")
} else {
music.musicLowBandUrl = musicLowBandUrl
music.musicLowBandDataUrl = call.argument("musicLowBandDataUrl")
}
val msg = WXMediaMessage()
msg.mediaObject = music
msg.description = call.argument(keyDescription)
launch {
msg.thumbData = readThumbnailByteArray(call)
val req = SendMessageToWX.Req()
setCommonArguments(call, req, msg)
req.message = msg
sendRequestInMain(result, req)
}
}
private fun shareVideo(call: MethodCall, result: MethodChannel.Result) {
val video = WXVideoObject()
val videoUrl: String? = call.argument("videoUrl")
val videoLowBandUrl: String? = call.argument("videoLowBandUrl")
if (videoUrl != null && videoUrl.isNotBlank()) {
video.videoUrl = videoUrl
} else {
video.videoLowBandUrl = videoLowBandUrl
}
val msg = WXMediaMessage()
msg.mediaObject = video
msg.description = call.argument(keyDescription)
launch {
msg.thumbData = readThumbnailByteArray(call)
val req = SendMessageToWX.Req()
setCommonArguments(call, req, msg)
req.message = msg
sendRequestInMain(result, req)
}
}
private fun shareWebPage(call: MethodCall, result: MethodChannel.Result) {
val webPage = WXWebpageObject()
webPage.webpageUrl = call.argument("webPage")
val msg = WXMediaMessage()
msg.mediaObject = webPage
msg.description = call.argument(keyDescription)
launch {
msg.thumbData = readThumbnailByteArray(call)
val req = SendMessageToWX.Req()
setCommonArguments(call, req, msg)
req.message = msg
sendRequestInMain(result, req)
}
}
private fun shareFile(call: MethodCall, result: MethodChannel.Result) {
launch {
val wxFileObject = WXFileObject()
// val filePath: String? = call.argument("filePath")
// wxFileObject.filePath = filePath
val msg = WXMediaMessage()
msg.mediaObject = wxFileObject
msg.description = call.argument("description")
val map: Map<String, Any> = call.argument("source") ?: mapOf()
val sourceFile = WeChatFile.createWeChatFile(map, assetFileDescriptor)
val sourceByteArray = sourceFile.readByteArray()
wxFileObject.apply {
if (supportFileProvider && targetHigherThanN) {
setFilePath(getFileContentUri(sourceByteArray.toCacheFile(context, sourceFile.suffix)))
} else {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
filePath = sourceByteArray.toExternalCacheFile(context, sourceFile.suffix)?.absolutePath
} else {
permissionHandler?.requestStoragePermission()
}
}
}
msg.thumbData = readThumbnailByteArray(call)
val req = SendMessageToWX.Req()
setCommonArguments(call, req, msg)
req.message = msg
sendRequestInMain(result, req)
}
}
private suspend fun sendRequestInMain(result: MethodChannel.Result, request: BaseReq) = withContext(Dispatchers.Main) {
result.success(WXAPiHandler.wxApi?.sendReq(request))
}
private suspend fun readThumbnailByteArray(call: MethodCall, length: Int = SHARE_IMAGE_THUMB_LENGTH): ByteArray? {
val thumbnailMap: Map<String, Any>? = call.argument(keyThumbnail)
val compress:Boolean = call.argument("compressThumbnail")?:true
return thumbnailMap?.run {
val thumbnailImage = WeChatFile.createWeChatFile(thumbnailMap, assetFileDescriptor)
val thumbnailImageIO = ImagesIOIml(thumbnailImage)
if(compress){
compressThumbnail(thumbnailImageIO, length)
}else{
thumbnailImageIO.readByteArray()
}
}
}
private suspend fun compressThumbnail(ioIml: ImagesIO, length: Int) = ioIml.compressedByteArray(context, length)
// SESSION, TIMELINE, FAVORITE
private fun setCommonArguments(call: MethodCall, req: SendMessageToWX.Req, msg: WXMediaMessage) {
msg.messageAction = call.argument("messageAction")
call.argument<String?>("msgSignature")?.let {
msg.msgSignature = it
}
msg.messageExt = call.argument("messageExt")
msg.mediaTagName = call.argument("mediaTagName")
msg.title = call.argument(keyTitle)
msg.description = call.argument(keyDescription)
req.transaction = UUID.randomUUID().toString().replace("-", "")
val sceneIndex = call.argument<Int?>("scene")
req.scene = when (sceneIndex) {
0 -> SendMessageToWX.Req.WXSceneSession
1 -> SendMessageToWX.Req.WXSceneTimeline
2 -> SendMessageToWX.Req.WXSceneFavorite
else -> SendMessageToWX.Req.WXSceneSession
}
}
private fun getFileContentUri(file: File?): String? {
if (file == null || !file.exists())
return null
val contentUri = FileProvider.getUriForFile(context,
"${context.packageName}.fluwxprovider", // 要与`AndroidManifest.xml`里配置的`authorities`一致,假设你的应用包名为com.example.app
file)
// 授权给微信访问路径
context.grantUriPermission("com.tencent.mm", // 这里填微信包名
contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
return contentUri.toString() // contentUri.toString() 即是以"content://"开头的用于共享的路径
}
private val supportFileProvider: Boolean get() = (WXAPiHandler.wxApi?.wxAppSupportAPI ?: 0) >= 0x27000D00
private val targetHigherThanN: Boolean get() = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N
val context: Context
val assetFileDescriptor: (String) -> AssetFileDescriptor
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
val job: Job
var permissionHandler: PermissionHandler?
fun onDestroy() = job.cancel()
}
package com.jarvan.fluwx.handlers
import android.Manifest
import android.app.Activity
import android.app.Fragment
import android.os.Build
/***
* Created by mo on 2020/3/27
* 冷风如刀,以大地为砧板,视众生为鱼肉。
* 万里飞雪,将穹苍作烘炉,熔万物为白银。
**/
class PermissionHandler(private val activity: Activity?) {
private val tag = "Fragment_TAG"
private val fragment: Fragment = Fragment()
fun requestStoragePermission() {
if (oldFragment != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
oldFragment?.requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 12121)
}
} else {
activity?.run {
val ft = fragmentManager.beginTransaction()
ft.add(fragment, tag)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
ft.commitNow()
} else {
ft.commit()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fragment.requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 12121)
}
}
}
}
private val oldFragment get() = activity?.fragmentManager?.findFragmentByTag(tag)
}
\ No newline at end of file
/*
* Copyright (c) 2020. OpenFlutter Project
*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. The ASF licenses this
* file to you under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.jarvan.fluwx.handlers
import android.content.Context
import android.content.pm.PackageManager
import android.util.Log
import com.jarvan.fluwx.BuildConfig
import com.jarvan.fluwx.FluwxPlugin
import com.tencent.mm.opensdk.constants.Build
import com.tencent.mm.opensdk.openapi.IWXAPI
import com.tencent.mm.opensdk.openapi.WXAPIFactory
import com.tencent.mm.opensdk.utils.ILog
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
object WXAPiHandler {
var wxApi: IWXAPI? = null
// private var context: Context? = null
private var registered: Boolean = false
val wxApiRegistered get() = registered
//是否为冷启动
var coolBoot: Boolean = false
fun setupWxApi(appId: String, context: Context, force: Boolean = true): Boolean {
if (force || !registered) {
// setContext(context)
registerWxAPIInternal(appId, context)
}
return registered
}
//
// fun setContext(context: Context?) {
// WXAPiHandler.context = context
// }
fun registerApp(call: MethodCall, result: MethodChannel.Result, context: Context?) {
if (call.argument<Boolean?>("android") == false) {
return
}
if (wxApi != null) {
result.success(true)
return
}
val appId: String? = call.argument("appId")
if (appId.isNullOrBlank()) {
result.error("invalid app id", "are you sure your app id is correct ?", appId)
return
}
context?.let {
registerWxAPIInternal(appId, it)
}
result.success(registered)
}
fun checkWeChatInstallation(result: MethodChannel.Result) {
if (wxApi == null) {
result.error("Unassigned WxApi", "please config wxapi first", null)
return
} else {
result.success(wxApi?.isWXAppInstalled)
}
}
fun checkSupportOpenBusinessView(result: MethodChannel.Result) {
when {
wxApi == null -> {
result.error("Unassigned WxApi", "please config wxapi first", null)
}
wxApi?.isWXAppInstalled != true -> {
result.error("WeChat Not Installed", "Please install the WeChat first", null)
}
(wxApi?.wxAppSupportAPI ?: 0) < Build.OPEN_BUSINESS_VIEW_SDK_INT -> {
result.error("WeChat Not Supported", "Please upgrade the WeChat version", null)
}
else -> {
result.success(true)
}
}
}
private fun registerWxAPIInternal(appId: String, context: Context) {
val api = WXAPIFactory.createWXAPI(context.applicationContext, appId)
registered = api.registerApp(appId)
wxApi = api
}
}
package com.jarvan.fluwx.io
import android.content.Context
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okio.*
import java.io.*
import java.io.IOException
import java.util.*
/***
* Created by mo on 2020/5/13
* 冷风如刀,以大地为砧板,视众生为鱼肉。
* 万里飞雪,将穹苍作烘炉,熔万物为白银。
**/
private const val cachePathName = "fluwxSharedData"
internal suspend fun ByteArray.toExternalCacheFile(context: Context, suffix: String): File? {
var file: File? = null
val externalFile = context.externalCacheDir ?: return file
val dir = File(externalFile.absolutePath + File.separator + cachePathName).apply {
if (!exists()) {
mkdirs()
}
}
file = File(dir.absolutePath + File.separator + UUID.randomUUID().toString() + suffix)
return saveToLocal(this, file)
}
internal suspend fun ByteArray.toCacheFile(context: Context, suffix: String): File? {
var file: File? = null
val externalFile = context.cacheDir ?: return file
val dir = File(externalFile.absolutePath + File.separator + cachePathName).apply {
if (!exists()) {
mkdirs()
}
}
file = File(dir.absolutePath + File.separator + UUID.randomUUID().toString() + suffix)
return saveToLocal(this, file)
}
private suspend fun saveToLocal(byteArray: ByteArray, file: File): File? {
return withContext(Dispatchers.IO) {
var sink: BufferedSink? = null
var source: Source? = null
var outputStream: OutputStream? = null
try {
outputStream = FileOutputStream(file)
sink = outputStream.sink().buffer()
source = ByteArrayInputStream(byteArray).source()
sink.writeAll(source)
sink.flush()
} catch (e: IOException) {
Log.w("Fluwx", "failed to create cache files")
} finally {
sink?.close()
source?.close()
outputStream?.close()
}
file
}
}
package com.jarvan.fluwx.io
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Bitmap.CompressFormat
import android.graphics.BitmapFactory
import id.zelory.compressor.Compressor
import id.zelory.compressor.constraint.size
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okio.*
import java.io.*
import java.io.IOException
import java.util.*
import kotlin.math.sqrt
/***
* Created by mo on 2020/3/7
* 冷风如刀,以大地为砧板,视众生为鱼肉。
* 万里飞雪,将穹苍作烘炉,熔万物为白银。
**/
class ImagesIOIml(override val image: WeChatFile) : ImagesIO {
override suspend fun readByteArray(): ByteArray = image.readByteArray()
override suspend fun compressedByteArray(context: Context, maxSize: Int): ByteArray =
withContext(Dispatchers.IO) {
val originalByteArray = readByteArray()
if (originalByteArray.isEmpty())
return@withContext originalByteArray
val originFile = inputStreamToFile(ByteArrayInputStream(originalByteArray))
if (image.suffix.contains("gif")) {
return@withContext originalByteArray
}
val compressedFile = Compressor.compress(context, originFile) {
size(maxFileSize = maxSize * 1024L)
}
if (compressedFile.length() < maxSize) {
val source = compressedFile.source()
val bufferedSource = source.buffer()
val bytes = bufferedSource.readByteArray()
source.close()
bufferedSource.close()
bytes
} else {
createScaledBitmapWithRatio(compressedFile, maxSize)
}
}
private fun inputStreamToFile(inputStream: InputStream): File {
val file = File.createTempFile(UUID.randomUUID().toString(), image.suffix)
val outputStream: OutputStream = FileOutputStream(file)
val sink = outputStream.sink().buffer()
val source = inputStream.source()
sink.writeAll(source)
source.close()
sink.close()
return file
}
private fun createScaledBitmapWithRatio(file: File, maxSize: Int): ByteArray {
val originBitmap = BitmapFactory.decodeFile(file.absolutePath)
val result: Bitmap? = createScaledBitmapWithRatio(originBitmap, maxSize, true)
result ?: return byteArrayOf()
return bmpToByteArray(result, image.suffix) ?: byteArrayOf()
}
private fun createScaledBitmapWithRatio(
bitmap: Bitmap,
maxLength: Int,
recycle: Boolean
): Bitmap? {
var result = bitmap
while (true) {
val ratio = maxLength.toDouble() / result.byteCount
val width = result.width * sqrt(ratio)
val height = result.height * sqrt(ratio)
val tmp = Bitmap.createScaledBitmap(result, width.toInt(), height.toInt(), true)
if (result != bitmap) {
result.recycle()
}
result = tmp
if (result.byteCount <= maxLength) {
break
}
}
if (recycle) {
bitmap.recycle()
}
return result
}
private fun bmpToByteArray(
bitmap: Bitmap,
suffix: String
): ByteArray? { // int bytes = bitmap.getByteCount();
val byteArrayOutputStream = ByteArrayOutputStream()
var format = CompressFormat.PNG
if (suffix.toLowerCase(Locale.US) == ".jpg" || suffix.toLowerCase(Locale.US) == ".jpeg") {
format = CompressFormat.JPEG
}
bitmap.compress(format, 100, byteArrayOutputStream)
val inputStream: InputStream = ByteArrayInputStream(byteArrayOutputStream.toByteArray())
var result: ByteArray? = null
bitmap.recycle()
val source = inputStream.source()
val bufferedSource = source.buffer()
try {
result = bufferedSource.readByteArray()
source.close()
bufferedSource.close()
} catch (e: IOException) {
e.printStackTrace()
}
return result
}
}
interface ImagesIO {
val image: WeChatFile
suspend fun readByteArray(): ByteArray
suspend fun compressedByteArray(context: Context, maxSize: Int): ByteArray
}
package com.jarvan.fluwx.io
import android.content.res.AssetFileDescriptor
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.BufferedSource
import okio.buffer
import okio.source
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
/***
* Created by mo on 2020/5/13
* 冷风如刀,以大地为砧板,视众生为鱼肉。
* 万里飞雪,将穹苍作烘炉,熔万物为白银。
**/
class WeChatFileFile(override val source: Any, override val suffix: String) : WeChatFile {
private var internalSource: File
init {
if (source !is File)
throw IllegalArgumentException("source should be File but it's ${source::class.java.name}")
else
internalSource = source
}
override suspend fun readByteArray(): ByteArray = withContext(Dispatchers.IO) {
var source: BufferedSource? = null
try {
source = internalSource.source().buffer()
val array = source.readByteArray()
array
} catch (e: FileNotFoundException) {
byteArrayOf()
} catch (io: IOException) {
byteArrayOf()
} finally {
source?.close()
}
}
}
private class WeChatAssetFile(override val source: Any, override val suffix: String) : WeChatFile {
private var internalSource: AssetFileDescriptor
init {
if (source !is AssetFileDescriptor)
throw IllegalArgumentException("source should be AssetFileDescriptor but it's ${source::class.java.name}")
else
internalSource = source
}
override suspend fun readByteArray(): ByteArray = withContext(Dispatchers.IO) {
var source: BufferedSource? = null
try {
source = internalSource.createInputStream().source().buffer()
val array = source.readByteArray()
array
} catch (e: FileNotFoundException) {
byteArrayOf()
} catch (io: IOException) {
byteArrayOf()
} finally {
source?.close()
}
}
}
private class WeChatNetworkFile(override val source: Any, override val suffix: String) : WeChatFile {
private var internalSource: String
init {
if (source !is String)
throw IllegalArgumentException("source should be String but it's ${source::class.java.name}")
else
internalSource = source
}
override suspend fun readByteArray(): ByteArray = withContext(Dispatchers.IO) {
val okHttpClient = OkHttpClient.Builder().build()
val request: Request = Request.Builder().url(internalSource).get().build()
try {
val response = okHttpClient.newCall(request).execute()
val responseBody = response.body
if (response.isSuccessful && responseBody != null) {
responseBody.bytes()
} else {
byteArrayOf()
}
} catch (e: IOException) {
Log.w("Fluwx", "reading file from $internalSource failed")
byteArrayOf()
}
}
}
private class WeChatMemoryFile(override val source: Any, override val suffix: String) : WeChatFile {
private var internalSource: ByteArray
init {
if (source !is ByteArray)
throw IllegalArgumentException("source should be String but it's ${source::class.java.name}")
else
internalSource = source
}
override suspend fun readByteArray(): ByteArray = internalSource
}
interface WeChatFile {
val source: Any
val suffix: String
suspend fun readByteArray(): ByteArray
companion object {
// NETWORK,
// ASSET,
// FILE,
// BINARY,
fun createWeChatFile(params: Map<String, Any>, assetFileDescriptor: (String) -> AssetFileDescriptor): WeChatFile {
// Map toMap() => {"source": source, "schema": schema.index, "suffix": suffix};
val suffix = (params["suffix"] as String?) ?: ".jpeg"
return when ((params["schema"] as? Int) ?: 0) {
0 -> WeChatNetworkFile(source = (params["source"] as? String).orEmpty(), suffix = suffix)
1 -> WeChatAssetFile(source = assetFileDescriptor(((params["source"] as? String).orEmpty())), suffix = suffix)
2 -> WeChatFileFile(source = File((params["source"] as? String).orEmpty()), suffix = suffix)
3 -> WeChatMemoryFile(source = (params["source"] as? ByteArray)
?: byteArrayOf(), suffix = suffix)
else -> WeChatNetworkFile(source = (params["source"] as? String).orEmpty(), suffix = suffix)
}
}
}
}
package com.jarvan.fluwx.utils
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import com.jarvan.fluwx.FluwxConfigurations
internal const val KEY_FLUWX_REQUEST_INFO_EXT_MSG = "KEY_FLUWX_REQUEST_INFO_EXT_MSG"
internal const val KEY_FLUWX_REQUEST_INFO_BUNDLE = "KEY_FLUWX_REQUEST_INFO_BUNDLE"
internal const val KEY_FLUWX_EXTRA = "KEY_FLUWX_EXTRA"
internal const val FLAG_PAYLOAD_FROM_WECHAT = "FLAG_PAYLOAD_FROM_WECHAT"
internal fun Activity.startFlutterActivity(
extra: Intent,
) {
flutterActivityIntent()?.also { intent ->
intent.addFluwxExtras()
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra(KEY_FLUWX_EXTRA, extra)
intent.putExtra(FLAG_PAYLOAD_FROM_WECHAT, true)
try {
startActivity(intent)
} catch (e: ActivityNotFoundException) {
Log.w("fluwx", "Can not start activity for Intent: $intent")
}
}
}
internal fun Context.flutterActivityIntent(): Intent? {
return if (FluwxConfigurations.flutterActivity.isBlank()) {
packageManager.getLaunchIntentForPackage(packageName)
} else {
Intent().also {
it.setClassName(this, "${packageName}.${FluwxConfigurations.flutterActivity}")
}
}
}
internal fun Intent.addFluwxExtras() {
putExtra("fluwx_payload_from_fluwx", true)
}
internal fun Intent.readWeChatCallbackIntent(): Intent? {
return if (getBooleanExtra(FLAG_PAYLOAD_FROM_WECHAT, false)) {
getParcelableExtra(KEY_FLUWX_EXTRA)
} else {
null
}
}
package com.jarvan.fluwx.utils;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
public class WXApiUtils {
public static String createSign(String appId, String nonceStr, String timestamp, String card_type) {
SortedMap<Object, Object> parameters = new TreeMap<>();
parameters.put("app_id", appId);
parameters.put("nonce_str", nonceStr);
parameters.put("card_type", timestamp);
parameters.put("time_stamp", card_type);
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
// 所有参与传参的参数按照accsii排序(升序)
Iterator it = es.iterator();
while (it.hasNext()) {
@SuppressWarnings("rawtypes")
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v) && !"sign".equals(k)
&& !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
String sign = shaEncode(sb.toString()).toUpperCase();
return sign;
}
public static String shaEncode(String inStr) {
MessageDigest sha = null;
try {
sha = MessageDigest.getInstance("SHA");
} catch (Exception e) {
System.out.println(e.toString());
e.printStackTrace();
return "";
}
byte[] byteArray = new byte[0];
try {
byteArray = inStr.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
byte[] md5Bytes = sha.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}
}
/*
* Copyright (C) 2020 The OpenFlutter Organization
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jarvan.fluwx.wxapi
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.jarvan.fluwx.utils.startFlutterActivity
open class FluwxWXEntryActivity : Activity() {
// IWXAPI 是第三方app和微信通信的openapi接口
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startFlutterActivity(intent)
finish()
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
startFlutterActivity(intent)
finish()
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="DisablePreviewTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowIsTranslucent">false</item>
<item name="android:windowDisablePreview">true</item>
</style>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="fluwxSharedData" path="fluwxSharedData/"/>
</paths>
\ No newline at end of file
package com.jarvan.fluwx
/*
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/
internal class FluwxPluginTest {
}
## AUTH
The purpose of `sendWeChatAuth` is to get auth code and then get information for WeChat login.
Getting `access_token` is not supported in `fluwx`. For `access_token`, please visit [official documents](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Development_Guide.html).
```dart
Fluwx fluwx = Fluwx();
fluwx.authBy(which: NormalAuth(scope: 'snsapi_userinfo', state: 'wechat_sdk_demo_test'));
```
> WHY? I think we shall fetch access_token or user info at backend.
## 登录
`sendWeChatAuth`的目的是为了获取code,拿到了code才能进行微信登录,可以通过[官方文档](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Development_Guide.html)查看具体流程。
```dart
Fluwx fluwx = Fluwx();
fluwx.authBy(which: NormalAuth(scope: 'snsapi_userinfo', state: 'wechat_sdk_demo_test'));
```
> 为什么不支持获取用户信息? 我认为获取用户信息应该后端来做,即使没有后端,你也可以在dart层自己实现.
## Basic knowledge
### Response from WeChat
Actually, almost every result from functions like `share` or `pay` which call `sendRequest` in native doesn't makes sense. The `bool` value is the result of `sendRequest`.
So if you want get the real result you shall do like this:
```dart
var listener = (response) {
if (response is WeChatAuthResponse) {
}
};
fluwx.addSubscriber(listener); // subscribe response from WeChat
fluwx.removeSubscriber(listener);// unsubscribe response from WeChat
```
Or
```dart
var cancelable = fluwx.addSubscriber(listener);
cancelable.cancel(); // unsubscribe response from WeChat
```
Take a look at subclasses of `WeChatResponse` for help.
> NOTE: If you get `errCode = -1`, please read the WeChatSDK document for help. There are to many cases lead to that.
### Images in WeChat
The are four built-in types of `WeChatImage` in `fluwx`:
```dart
WeChatImage.network(String source, {String suffix});
WeChatImage.file(File source, {String suffix = ".jpeg"});
WeChatImage.asset(String source, {String suffix});
WeChatImage.binary(Uint8List source, {String suffix = ".jpeg"});
```
The priority of `suffix` is highest, `fluwx` will try to read suffix from paths if `suffix` is blank.
The max size of image youcan share to WeChat is `10M`.`Fluwx` wil compress `WeChatImage` itself if it's used as `thumbnail` or `hdImagePath`,
otherwise, it doesn't. However, you'd better compress thumbnail yourself as the result of compression is unpredictable.
## 基础知识
### 微信回调
实际上,像`shareToWeChat` or `payWithWeChat`这种的函数,底层上是调用了原生SDK的`sendRequest`方法,所以他们的返回结果意义不大,他们的返回结果仅仅是`sendRequest`的返回值。
为了获取真实的回调,你应该这样做:
```dart
var listener = (response) {
if (response is WeChatAuthResponse) {
}
};
fluwx.addSubscriber(listener); // 订阅消息
fluwx.removeSubscriber(listener);// 取消订阅消息
```
Or
```dart
var cancelable = fluwx.addSubscriber(listener);
cancelable.cancel(); // 取消订阅消息
```
> 笔记: 如果你的 `errCode = -1`, 那请阅读微信官方文档,因为-1的原因数不胜数.
### 图片
有四种内置 `WeChatImage`:
```dart
WeChatImage.network(String source, {String suffix});
WeChatImage.file(File source, {String suffix = ".jpeg"});
WeChatImage.asset(String source, {String suffix});
WeChatImage.binary(Uint8List source, {String suffix = ".jpeg"});
```
其中, `suffix` 优先级最高, 如果`suffix`是空白的,`fluwx` 将会尝试从文件路径中读取后缀.
在分享图片的功能,图片不能超过`10M`.如果图片被用作`thumbnail``hdImagePath``Fluwx` 会对 `WeChatImage` 进行压缩,
否则不会压缩. 但是,最好还是自己压缩,因为不保证`fluwx`压缩效果。
## WeChat reference document
- [WeChat open label jumps to APP: &lt;wx-open-launch-app&gt;](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html#%E8%B7%B3%E8%BD%ACAPP%EF%BC%9Awx-open-launch-app)
- [App gets the extinfo data in the open tag <wx-open-launch-app>](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/APP_GET_EXTINF.html)
## Platform differences
- The event type of **Launch-App-From-H5** on Android is `WeChatShowMessageFromWXRequest`
- The event type of **Launch-App-From-H5** on IOS is `WeChatLaunchFromWXRequest`
## Example
```dart
void handle_launch_from_h5() {
Fluwx fluwx = Fluwx();
fluwx.addSubscriber((response) {
// 1. Handle responses separately for android and ios
if (response is WeChatShowMessageFromWXRequest) {
debugPrint("launch-app-from-h5 on android");
// do something here only for android after launch from wechat
} else if (response is WeChatLaunchFromWXRequest) {
debugPrint("launch-app-from-h5 on ios");
// do something here only for android after launch from wechat
}
// 2. Or handling responses together for android and ios
if (response is WeChatLaunchFromWXRequest ||
response is WeChatShowMessageFromWXRequest) {
debugPrint("launch-app-from-h5");
// do something here for both android and ios after launch from wechat
}
});
}
```
> If you want to get ext from website, please call `fluwx.getExtMsg()`。For details, please read the example project.
## 微信参考文档
- [微信开放标签 &lt;wx-open-launch-app&gt; 跳转App](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html#%E8%B7%B3%E8%BD%ACAPP%EF%BC%9Awx-open-launch-app)
- [App获取开放标签 &lt;wx-open-launch-app&gt; 中的 extinfo 数据](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/APP_GET_EXTINF.html)
## 平台差异
- 安卓端 **微信唤起App** 的事件类型为 `WeChatShowMessageFromWXRequest`
- IOS端 **微信唤起App** 的事件类型为 `WeChatLaunchFromWXRequest`
## 示例
```dart
void handle_launch_from_h5() {
Fluwx fluwx = Fluwx();
fluwx.addSubscriber((response) {
// 1. 为安卓和ios分开处理响应
if (response is WeChatShowMessageFromWXRequest) {
debugPrint("launch-app-from-h5 on android");
// 从微信启动后,在这里只为 android 做一些事情
} else if (response is WeChatLaunchFromWXRequest) {
debugPrint("launch-app-from-h5 on ios");
// 从微信启动后,在这里只为 ios 做一些事情
}
// 2. 或者为安卓和ios一起处理响应
if (response is WeChatLaunchFromWXRequest ||
response is WeChatShowMessageFromWXRequest) {
debugPrint("launch-app-from-h5");
// 从微信启动后,在这里为 android 和 ios 做一些事情
}
});
}
```
> 如你想主动获取从网页传进来的值 ,请主动调用`fluwx.getExtMsg()`。更多信息请参考example项目.
## Upgrade to V4
`Fluwx v4` not only brings a lot exciting functionalities but also breaking changes。
- Now we need to initialize the instance using `Fluwx fluwx = Fluwx()`
- Listening response from WeChat changed to `subscribeResponse` and also adding `unsubscribeResponse` to support
cancel listening.
- Keyword `wechat` in some functions is removed.
- Some functions are extracted to a single function,and now you can pass different params instead.
- Some configurations are moved to[pubspec.yaml](../example/pubspec.yaml),for example, you can enable/disable log in `pubspec.yaml`.
- `no_pay` can be enabled by [pubspec.yaml](../example/pubspec.yaml), reference example for more details.
\ No newline at end of file
## 升级到V4
`Fluwx v4`带来了很多令人兴奋的功能,但也带来了少破坏性更新。
- 现在我们需要使用`Fluwx fluwx = Fluwx()`初始化实例
- 监听微信回调变成了`subscribeResponse`并且增加了`unsubscribeResponse`以支持取消监听
- 很多带有`wechat`关键字的函数已经把`wechat`关键字删除了
- 很多方法被整到了一个函数中,现在你可以传递不同的对象实现对应的业务
- 一些配置被移动到了[pubspec.yaml](../example/pubspec.yaml),可以通过`pubspec.yaml`配置是否开启日志等等
- `no_pay`现在也通过[pubspec.yaml](../example/pubspec.yaml)配置,具体可以参加example.
\ No newline at end of file
## Payment
Calling payment is easy but to make it work isn't not so easy:
```dart
fluwx.pay(
which: Payment(
appId: result['appid'].toString(),
partnerId: result['partnerid'].toString(),
prepayId: result['prepayid'].toString(),
packageValue: result['package'].toString(),
nonceStr: result['noncestr'].toString(),
timestamp: result['timestamp'],
sign: result['sign'].toString(),
));
```
Take a look at [payment document](https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=11_1#) for help.
## 支付
调用支付方法很简单,但想成功并不简单:
```dart
fluwx.pay(
which: Payment(
appId: result['appid'].toString(),
partnerId: result['partnerid'].toString(),
prepayId: result['prepayid'].toString(),
packageValue: result['package'].toString(),
nonceStr: result['noncestr'].toString(),
timestamp: result['timestamp'],
sign: result['sign'].toString(),
));
```
## 安卓支付
* 登录[微信开放平台](https://open.weixin.qq.com/cgi-bin/index?t=home/index&lang=zh_CN&token=f3443bb5b660c02dbbc86fb324adce3239e5ab22),填写相关信息
![image-20210523132928727](https://gitee.com/inkkk0516/typora/raw/master/image-20210523132928727.png)
* 根据`应用包名`生成`应用签名` [点击这里下载应用签名工具](https://developers.weixin.qq.com/doc/oplatform/Downloads/Android_Resource.html), 安装好签名工具后,输入应用包名就可以生成应用签名了
![image-20210523133551034](https://gitee.com/inkkk0516/typora/raw/master/image-20210523133551034.png)
更多信息还查看[支付文档](https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=11_1#)吧.
## WeChat Not Installed on iOS?
if you have installed WeChat on your iPhone but you still catch an exception called "wechat not installed",just add the following
code to your *info.plist*:
```xml
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
```
## Can't Launch WeChat on Android?
Check your signature please.
## Failed to notify project evalution listener
[Failed to notify project evalution listener](https://www.jianshu.com/p/f74fed94be96)
## Can't receive response after upgrading to 1.0.0 on iOS
There's no need to override `AppDelegate` since `fluwx 1.0.0`. If you have did thad before, please remove
the following code in your `AppDelegate`:
```objective-c
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
return [WXApi handleOpenURL:url delegate:[FluwxResponseHandler defaultManager]];
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options
{
return [WXApi handleOpenURL:url delegate:[FluwxResponseHandler defaultManager]];
}
```
If you have to override these two functions,make sure you have called the `super`:
```objective-c
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
return [super application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options
{
return [super application:application openURL:url options:options];
}
```
**!!!!请先看[文档](https://github.com/OpenFlutter/fluwx/blob/master/README_CN.md),再看常见Q&A,再查看issue,自我排查错误,方便你我他。依然无法解决的问题可以加群提问, QQ Group:892398530。!!!!**
## 常见Q&A
#### Fluwx调起失败?
请检查APPID、包名、以及App签名是否一致。debug 和release的签名默认不一样,请注意。
#### Android Flutter编译失败
1、检查Kotlin版本,打开```build.gradle```文件,查看以下配置
```
buildscript {
······
ext.kotlin_version = '1.3.11'
······
}
```
确保项目中使用的Kotlin版本符合要求;
2、检查Android目录下```build.gradle```文件中gradle插件版本:```classpath 'com.android.tools.build:gradle:3.2.1'``````gradle-wrapper.properties```文件中的gradle版本是否匹配:```distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip```,两者的匹配规则见Android官网:[Update Gradle](https://developer.android.com/studio/releases/gradle-plugin.html#updating-gradle)
#### WeChat Not Installed on iOS?
iOS 9系统策略更新,限制了http协议的访问,此外应用需要在“Info.plist”中将要使用的URL Schemes列为白名单,才可正常检查其他应用是否安装。
受此影响,当你的应用在iOS 9中需要使用微信SDK的相关能力(分享、收藏、支付、登录等)时,需要在“Info.plist”里增加如下代码:
```xml
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
```
#### 如果没有安装微信,微信登录不了,导致iOS审核失败
fluwx提供了检查用户是否安装微信的方法:```isWeChatInstalled()```,iOS使用微信相关功能前,务必先检查微信是否安装。
#### Failed to notify project evalution listener
[Failed to notify project evalution listener](https://www.jianshu.com/p/f74fed94be96)
#### isWeChatInstalled返回false
请查看该 [issue](https://github.com/OpenFlutter/fluwx/issues/34) ,检查```AppDelegate```中配置是否正确。
#### Kotlin报错:XXX is only available since Kotlin 1.3 and cannot be used in Kotlin 1.2
1、请检查IDE安装的Kotlin插件版本是否符合fluwx要求:AS打开设置-->Plugin-->Koltin查看插件版本;
2、请检查项目中使用的Kotlin版本:打开```build.gradle```文件,查看以下配置
```
buildscript {
······
ext.kotlin_version = '1.3.11'
······
}
```
#### listen监听多次调用
请查看该 [issue](https://github.com/OpenFlutter/fluwx/issues/36) 。这个问题是由于listen被多次注册导致的,使用者自己代码的问题,非fluwx导致的,请在合适的时机将listen cancel掉:
```
StreamSubscription<WeChatAuthResponse> _wxlogin;
_wxlogin = fluwx.responseFromAuth.listen((val) {})
@override
void dispose() {
_wxlogin.cancel();
}
```
#### 分享完成或者取消分享后App崩溃
如果你手动注册了```WXEntryActivity```and```WXPayEntryActivity```,请检查```Manifest```中包名是否写对了。
#### IOS编译错误:No such module 'fluwx'
如果项目本身是在Android环境配置的,移到iOS的环境的时候,会出现该问题,请按照正常步骤配置。
#### 支付成功后,按物理按键或手机自动返回商户APP,监听不到返回数据
有人反应会出现```fluwx.responseFromPayment.listen```监听无效,无法获取支付结果,建议可以直接向服务器查询是否成功。
#### iOS报错:Specs satisfying the `fluwx (from `.symlinks/plugins/fluwx/ios`)` dependency were found, but they required a higher minimum deployment target.
请在在pod file里将iOS项目deployment target改到9.0。
#### ResponseType与Dio插件中的命名冲突
使用as的方式导包即可:```import 'package:fluwx/fluwx.dart' as fluwx;```
**!!!!请先看[文档](https://github.com/OpenFlutter/fluwx/blob/master/README_CN.md),再看常见Q&A,再查看issue,自我排查错误,方便你我他。依然无法解决的问题可以加群提问, QQ Group:892398530。!!!!**
## 常见Q&A
[无法拉起微信](#无法拉起微信)
[安卓端编译失败](#安卓端编译失败)
[无法调起支付](#无法调起支付)
[安卓端编译报错AndroidX相关](#安卓端编译报错androidx相关)
[WeChat Not Installed on iOS?](#wechat-not-installed-on-ios)
[没有安装微信,微信登录不了,导致iOS审核失败](#没有安装微信微信登录不了导致ios审核失败)
[Failed to notify project evalution listener](#failed-to-notify-project-evalution-listener)
[isWeChatInstalled返回false](#iswechatinstalled返回false)
[Kotlin报错:XXX is only available since Kotlin x.xx and cannot be used in Kotlin x.xx](#kotlin报错xxx-is-only-available-since-kotlin-xxx-and-cannot-be-used-in-kotlin-xxx)
[listen监听多次调用](#listen监听多次调用)
[分享完成或者取消分享后App崩溃](#分享完成或者取消分享后app崩溃)
[IOS编译错误:No such module 'fluwx'](#ios编译错误no-such-module-fluwx)
[支付成功后,按物理按键或手机自动返回商户APP,监听不到返回数据](#支付成功后按物理按键或手机自动返回商户app监听不到返回数据)
[iOS报错:Specs satisfying the fluwx (from .symlinks/plugins/fluwx/ios) dependency were found, but they required a higher minimum deployment target.](#ios报错specs-satisfying-the-fluwx-from-symlinkspluginsfluwxios-dependency-were-found-but-they-required-a-higher-minimum-deployment-target)
[ResponseType与Dio插件中的命名冲突](#responsetype与dio插件中的命名冲突)
[ShareSDK(分享插件)和Fluwx(微信支付插件)存在冲突](#sharesdk分享插件和fluwx微信支付插件存在冲突)
[图片加载失败?](#图片加载失败)
[iOS registerWxApi 返回 -1](#iosregisterwxapi返回-1)
[分享后,打开微信出现未审核应用](#分享后打开微信出现未审核应用)
[分享怎样知道是成功分享了还是取消了没有分享](#分享怎样知道是成功分享了还是取消了没有分享)
[运行时报错kotlinx相关](#运行时报错kotlinx相关)
[android手机在拉起微信前会弹出选择微信分身页面](#android手机在拉起微信前会弹出选择微信分身页面)
[android代码混淆](#android代码混淆)
### 无法拉起微信?
1. Android 端:
* 请检查 AppId、App签名是否和微信开放平台中填写的一致。
* debug 和 release 的签名默认不一样,请注意。注意签名规则:[issue 89](https://github.com/OpenFlutter/fluwx/issues/89#issuecomment-515948671) ,keystore工具生成的MD5需要去除横杆并且全部转换为小写,然后配置到 ```build.gradle```
* 签名的生成建议使用微信提供的 sign 工具:[签名校验工具](https://open.weixin.qq.com/zh_CN/htmledition/res/dev/download/sdk/Gen_Signature_Android.apk)
* 签名算法请参考微信文档:[签名算法](https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3)
2. iOS 端:
* 检查 UniversalLink 是否配置正确,简单的检验方法为:将链接写入备忘录,点击后是否可以跳转至 Safari,且顶部提示打开 App。
* 检查是否将微信加入了白名单,具体参考 example 中 [Info.plist](https://github.com/OpenFlutter/fluwx/blob/master/example/ios/Runner/Info.plist) 中的配置
### 安卓端编译失败
1. 检查Kotlin版本,打开```build.gradle```文件,查看以下配置
```
buildscript {
······
ext.kotlin_version = '1.4.10'
······
}
```
确保项目中使用的 Kotlin 版本符合要求(具体版本号以 [example](https://github.com/OpenFlutter/fluwx/blob/master/example/android/build.gradle) 中为准);
2. 检查 Android 目录下```build.gradle```文件中 gradle 插件版本:```classpath 'com.android.tools.build:gradle:3.6.3'```和```gradle-wrapper.properties```文件中的 gradle 版本是否匹配:```distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip```,两者的匹配规则见Android官网:[Update Gradle](https://developer.android.com/studio/releases/gradle-plugin.html#updating-gradle)
### 无法调起支付
1. 先认真阅读 [微信支付业务流程](https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_3),确保操作没问题;
2. 打印微信返回的错误码,对照下面的表格排查:
| 名称 | 描述 | 解决方案 |
| ----- | ------- | ------------------- |
| 0 | 成功 | 展示成功页面 |
| -1 | 错误 | 可能的原因:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等。|
| -2 | 用户取消 | 无需处理。发生场景:用户不支付了,点击取消,返回APP。 |
3. 大部分情况下 errorCode 为 -1,请按照以下两部来进一步检查:
* 检查签名、AppId、商户 ID 等配置是否正确(前两项可以通过检查是否可以成功调用分享来决定);
* 如果确保配置正确,那就只可能是后端生成的订单信息有误,请让后端排查错误,大概率是 AppId 写错了。
### 安卓端编译报错AndroidX相关
如果报错含有 “AndroidX”、“support” 等相关字眼,提示包重复,请将自己的项目升级,支持 AndroidX,具体参考:[Migrating to AndroidX](https://www.kikt.top/posts/flutter/migrate-android-x/)
### WeChat Not Installed on iOS?
iOS 9系统策略更新,限制了http协议的访问,此外应用需要在“Info.plist”中将要使用的URL Schemes列为白名单,才可正常检查其他应用是否安装。
受此影响,当你的应用在iOS 9中需要使用微信SDK的相关能力(分享、收藏、支付、登录等)时,需要在“Info.plist”里增加如下代码(不会配请参考[example](https://github.com/OpenFlutter/fluwx/blob/master/example/ios/Runner/Info.plist)):
```
xml
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
<string>weixinULAPI</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
```
### 如果没有安装微信,微信登录不了,导致iOS审核失败
fluwx 提供了检查用户是否安装微信的方法:```isWeChatInstalled()```,iOS 使用微信相关功能前,务必先检查微信是否安装,没有安装微信的话请务必隐藏微信相关功能。
### Failed to notify project evalution listener
[Failed to notify project evalution listener](https://www.jianshu.com/p/f74fed94be96)
### isWeChatInstalled返回false
请查看该 [issue 34](https://github.com/OpenFlutter/fluwx/issues/34) ,检查 ```UniversalLink``` 是否配置正确,是否配置了白名单;如果重写了 ```AppDelegate```,请检查配置是否正确。
### Kotlin报错:XXX is only available since Kotlin x.xx and cannot be used in Kotlin x.xx
1. 请检查 IDE 安装的 Kotlin 插件版本是否符合 fluwx 要求:AS 打开"设置-->Plugin-->Koltin"查看插件版本;
2. 请检查项目中使用的 Kotlin 版本:打开 ```build.gradle``` 文件,查看以下配置(具体版本号以 [example](https://github.com/OpenFlutter/fluwx/blob/master/example/android/build.gradle) 为准)
```
buildscript {
······
ext.kotlin_version = '1.3.11'
······
}
```
### listen监听多次调用
请查看该 [issue 36](https://github.com/OpenFlutter/fluwx/issues/36) 。这个问题是由于 listen 被多次注册导致的,使用者自己代码的问题,非 fluwx 导致的,请在合适的时机将 listen cancel 掉:
```
StreamSubscription<WeChatAuthResponse> _wxlogin;
_wxlogin = fluwx.responseFromAuth.listen((val) {})
@override
void dispose() {
_wxlogin.cancel();
}
```
### 分享完成或者取消分享后App崩溃
如果你手动注册了 ```WXEntryActivity``` 和 ```WXPayEntryActivity```,请检查 ```Manifest``` 中包名是否写对了。
### iOS编译错误:No such module 'fluwx'
如果项目本身是在 Android 环境配置的,移到 iOS 环境的时候,会出现该问题,请按照正常步骤配置。
### 支付成功后,按物理按键或手机自动返回商户APP,监听不到返回数据
有人反应会出现 ```fluwx.responseFromPayment.listen``` 监听无效,无法获取支付结果,建议可以直接向服务器查询是否成功。
### iOS报错:Specs satisfying the `fluwx (from `.symlinks/plugins/fluwx/ios`)` dependency were found, but they required a higher minimum deployment target.
请在在 pod file 里将 iOS 项目 deployment target 改到 9.0。
### ResponseType与Dio插件中的命名冲突
使用as的方式导包即可:```import 'package:fluwx/fluwx.dart' as fluwx;```
### ShareSDK(分享插件)和Fluwx(微信支付插件)存在冲突
1. 将 ShareSDK 的```/ios/sharesdk.podspec```里的 ```s.dependency 'mob_sharesdk/ShareSDKPlatforms/WeChat' ```改为 ``` s.dependency 'mob_sharesdk/ShareSDKPlatforms/WeChatFull'```
2. 删除 fluwx 的```/ios/Lib```里的```libWeChatSDK.a```,在```/ios/fluwx.podspec```里添加```s.dependency 'mob_sharesdk/ShareSDKPlatforms/WeChatFull'```
### 图片加载失败?
1. 检查是否是图片大小过大,过大请压缩;
2. 检查图片路径是否是符合要求的 scheme 形式,具体规则请看:[都支持什么图片](https://github.com/yumi0629/fluwx/blob/master/doc/SHARE_CN.md#%E9%83%BD%E6%94%AF%E6%8C%81%E4%BB%80%E4%B9%88%E5%9B%BE%E7%89%87)
3. 如果是使用的 Asset 图片,请不要将图片放在```assets```文件夹中,可能会读取不到。
### iOS registerWxApi 返回 -1
检查初始化时 APP ID 以及 Universalink 是不是写对了,简单的检验方法为:将链接写入备忘录,点击后是否可以跳转至 Safari,且顶部提示打开 App。
### 分享后,打开微信出现未审核应用
微信自己做的限制,非 fluwx 问题,建议找微信客服
### 分享怎样知道是成功分享了还是取消了没有分享
无法获取。请阅读微信分享文档:[微信App分享功能调整](https://open.weixin.qq.com/cgi-bin/announce?spm=a311a.9588098.0.0&action=getannouncement&key=11534138374cE6li&version=)。现在开始微信SDK不再返回用户是否分享完成事件,取消/成功统一全部返回成功。
### 运行时报错kotlinx相关
检查是否开启混淆;可以参考这个 [issue](https://github.com/OpenFlutter/fluwx/issues/94),请确保宿主 app Kotlin 版本支持 kotlinx。
### android手机在拉起微信前会弹出选择微信分身页面
微信分身是属于系统范畴的问题,与Fluwx无关。
### android代码混淆
请手动给 lib 里的 package 添加混淆规则,具体参考微信文档。
### 参考文章
[iOS-微信SDK更新后需要UniversalLink解决方案](https://juejin.cn/post/6844904051042156551)
[flutter使用fluwx调通微信支付](https://www.jianshu.com/p/caf5b32c4772)
[Flutter ios 使用fluwx配置微信分享](https://www.jianshu.com/p/3d8b2d65e57a)
## Share
Simple and easy:
```dart
fluwx.share(WeChatShareTextModel("source text", scene: WeChatScene.SESSION));
```
The destination of sharing can be SESSION(default),TIMELINE or FAVORITE.However,mini-program only support SESSION.
```dart
///[WeChatScene.session]会话
///[WeChatScene.timeline]朋友圈
///[WeChatScene.favorite]收藏
enum WeChatScene {
session,
timeline,
favorite
}
```
You can share these models:
- WeChatShareTextModel
- WeChatShareMiniProgramModel
- WeChatShareImageModel
- WeChatShareMusicModel
- WeChatShareVideoModel
- WeChatShareWebPageModel
- WeChatShareFileModel
## 分享
简单:
```dart
fluwx.share(WeChatShareTextModel("source text", scene: WeChatScene.SESSION));
```
绝大部分分享可以分享到会话,朋友圈,收藏(小程序目前只能分享到会话)。默认分享到会话。
```dart
///[WeChatScene.session]会话
///[WeChatScene.timeline]朋友圈
///[WeChatScene.favorite]收藏
enum WeChatScene {
session,
timeline,
favorite
}
```
支持的分享各类:
- WeChatShareTextModel
- WeChatShareMiniProgramModel
- WeChatShareImageModel
- WeChatShareMusicModel
- WeChatShareVideoModel
- WeChatShareWebPageModel
- WeChatShareFileModel
//
// FluwxDelegate.m
// fluwx
//
// Created by Mo on 2022/3/6.
//
#import <Foundation/Foundation.h>
#import <fluwx/FluwxDelegate.h>
#import <WXApi.h>
@implementation FluwxDelegate
+ (instancetype)defaultManager {
static dispatch_once_t onceToken;
static FluwxDelegate *instance;
dispatch_once(&onceToken, ^{
instance = [[FluwxDelegate alloc] init];
});
return instance;
}
- (void) registerWxAPI:(NSString *)appId universalLink:(NSString *)universalLink {
[WXApi registerApp:appId universalLink:universalLink];
}
@end
#import <fluwx/FluwxPlugin.h>
#import <fluwx/FluwxStringUtil.h>
#import <fluwx/FluwxDelegate.h>
#import <fluwx/ThumbnailHelper.h>
#import <fluwx/FluwxStringUtil.h>
#import <fluwx/NSStringWrapper.h>
#import <WXApi.h>
#import <WXApiObject.h>
#import <WechatAuthSDK.h>
#import <WXApi.h>
NSString *const fluwxKeyTitle = @"title";
NSString *const fluwxKeyImage = @ "image";
NSString *const fluwxKeyImageData = @ "imageData";
NSString *const fluwxKeyThumbnail = @"thumbnail";
NSString *const fluwxKeyDescription = @"description";
NSString *const fluwxKeyMsgSignature = @"msgSignature";
NSString *const fluwxKeyPackage = @"?package=";
NSString *const fluwxKeyMessageExt = @"messageExt";
NSString *const fluwxKeyMediaTagName = @"mediaTagName";
NSString *const fluwxKeyMessageAction = @"messageAction";
NSString *const fluwxKeyScene = @"scene";
NSString *const fluwxKeyTimeline = @"timeline";
NSString *const fluwxKeySession = @"session";
NSString *const fluwxKeyFavorite = @"favorite";
NSString *const fluwxKeyCompressThumbnail = @"compressThumbnail";
NSString *const keySource = @"source";
NSString *const keySuffix = @"suffix";
CGFloat thumbnailWidth;
NSUInteger defaultThumbnailSize = 32 * 1024;
@interface FluwxPlugin()<WXApiDelegate,WechatAuthAPIDelegate>
@property (strong,nonatomic)NSString *extMsg;
@end
typedef void(^FluwxWXReqRunnable)(void);
@implementation FluwxPlugin {
FlutterMethodChannel *_channel;
WechatAuthSDK *_qrauth;
BOOL _isRunning;
BOOL _attemptToResumeMsgFromWxFlag;
FluwxWXReqRunnable _attemptToResumeMsgFromWxRunnable;
// cache open url request when WXApi is not registered, and handle it once WXApi is registered
FluwxWXReqRunnable _cachedOpenUrlRequest;
}
const NSString *errStr = @"errStr";
const NSString *errCode = @"errCode";
const NSString *openId = @"openId";
const NSString *type = @"type";
const NSString *lang = @"lang";
const NSString *country = @"country";
const NSString *description = @"description";
BOOL handleOpenURLByFluwx = YES;
NSObject <FlutterPluginRegistrar> *_fluwxRegistrar;
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
FlutterMethodChannel *channel =
[FlutterMethodChannel methodChannelWithName:@"com.jarvanmo/fluwx"
binaryMessenger:[registrar messenger]];
FluwxPlugin *instance = [[FluwxPlugin alloc] initWithChannel:channel];
[registrar addApplicationDelegate:instance];
[registrar addMethodCallDelegate:instance channel:channel];
}
- (instancetype)initWithChannel:(FlutterMethodChannel *)channel {
self = [super init];
if (self) {
_channel = channel;
_qrauth = [[WechatAuthSDK alloc] init];
_qrauth.delegate = self;
_isRunning = NO;
thumbnailWidth = 150;
_attemptToResumeMsgFromWxFlag = NO;
#if WECHAT_LOGGING
[WXApi startLogByLevel:WXLogLevelDetail logBlock:^(NSString *log) {
[self logToFlutterWithDetail:log];
}];
#endif
}
return self;
}
- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
if ([@"registerApp" isEqualToString:call.method]) {
[self registerApp:call result:result];
} else if ([@"isWeChatInstalled" isEqualToString:call.method]) {
[self checkWeChatInstallation:call result:result];
} else if ([@"sendAuth" isEqualToString:call.method]) {
[self handleAuth:call result:result];
} else if ([@"authByQRCode" isEqualToString:call.method]) {
[self authByQRCode:call result:result];
} else if ([@"stopAuthByQRCode" isEqualToString:call.method]) {
[self stopAuthByQRCode:call result:result];
} else if ([@"openWXApp" isEqualToString:call.method]) {
result(@([WXApi openWXApp]));
} else if ([@"launchMiniProgram" isEqualToString:call.method]) {
[self handleLaunchMiniProgram:call result:result];
} else if ([@"subscribeMsg" isEqualToString:call.method]) {
[self handleSubscribeWithCall:call result:result];
} else if ([@"autoDeduct" isEqualToString:call.method]) {
[self handleAutoDeductWithCall:call result:result];
} else if ([@"autoDeductV2" isEqualToString:call.method]) {
[self handleautoDeductV2:call result:result];
} else if ([@"openBusinessView" isEqualToString:call.method]) {
[self handleOpenBusinessView:call result:result];
}else if([@"authByPhoneLogin" isEqualToString:call.method]){
[self handleAuthByPhoneLogin:call result:result];
}else if([@"getExtMsg" isEqualToString:call.method]){
[self handelGetExtMsgWithCall:call result:result];
} else if ([call.method hasPrefix:@"share"]) {
[self handleShare:call result:result];
} else if ([@"openWeChatCustomerServiceChat" isEqualToString:call.method]) {
[self openWeChatCustomerServiceChat:call result:result];
} else if ([@"checkSupportOpenBusinessView" isEqualToString:call.method]) {
[self checkSupportOpenBusinessView:call result:result];
} else if ([@"openRankList" isEqualToString:call.method]) {
[self handleOpenRankListCall:call result:result];
} else if ([@"openUrl" isEqualToString:call.method]) {
[self handleOpenUrlCall:call result:result];
} else if([@"openWeChatInvoice" isEqualToString:call.method]) {
[self openWeChatInvoice:call result:result];
} else if([@"selfCheck" isEqualToString:call.method]) {
#ifndef __OPTIMIZE__
[WXApi checkUniversalLinkReady:^(WXULCheckStep step, WXCheckULStepResult* result) {
NSString *log = [NSString stringWithFormat:@"%@, %u, %@, %@", @(step), result.success, result.errorInfo, result.suggestion];
[self logToFlutterWithDetail:log];
}];
#endif
result(nil);
} else if([@"attemptToResumeMsgFromWx" isEqualToString:call.method]){
if (_attemptToResumeMsgFromWxRunnable != nil) {
_attemptToResumeMsgFromWxRunnable();
_attemptToResumeMsgFromWxRunnable = nil;
}
result(nil);
}
else if ([@"payWithFluwx" isEqualToString:call.method]) {
#ifndef NO_PAY
[self handlePayment:call result:result];
#else
result(@NO);
#endif
} else if ([@"payWithHongKongWallet" isEqualToString:call.method]) {
#ifndef NO_PAY
[self handleHongKongWalletPayment:call result:result];
#else
result(@NO);
#endif
}
else {
result(FlutterMethodNotImplemented);
}
}
- (void)openWeChatInvoice:(FlutterMethodCall *)call result:(FlutterResult)result {
NSString *appId = call.arguments[@"appId"];
if ([FluwxStringUtil isBlank:appId]) {
result([FlutterError errorWithCode:@"invalid app id" message:@"are you sure your app id is correct ? " details:appId]);
return;
}
WXChooseInvoiceReq *chooseInvoiceReq = [[WXChooseInvoiceReq alloc] init];
chooseInvoiceReq.appID = appId;
chooseInvoiceReq.timeStamp = [[NSDate date] timeIntervalSince1970];
chooseInvoiceReq.signType = @"SHA1";
chooseInvoiceReq.cardSign = @"";
chooseInvoiceReq.nonceStr = @"";
[WXApi sendReq:chooseInvoiceReq completion:^(BOOL done) {
result(@(done));
}];
}
- (void)registerApp:(FlutterMethodCall *)call result:(FlutterResult)result {
NSNumber* doOnIOS =call.arguments[@"iOS"];
if (![doOnIOS boolValue]) {
result(@NO);
return;
}
NSString *appId = call.arguments[@"appId"];
if ([FluwxStringUtil isBlank:appId]) {
result([FlutterError errorWithCode:@"invalid app id" message:@"are you sure your app id is correct ? " details:appId]);
return;
}
NSString *universalLink = call.arguments[@"universalLink"];
if ([FluwxStringUtil isBlank:universalLink]) {
result([FlutterError errorWithCode:@"invalid universal link" message:@"are you sure your universal link is correct ? " details:universalLink]);
return;
}
BOOL isWeChatRegistered = [WXApi registerApp:appId universalLink:universalLink];
// If registration fails, we can return immediately
if(!isWeChatRegistered){
result(@(isWeChatRegistered));
_isRunning = NO;
return;
}
// Otherwise, since WXApi is now registered successfully,
// we can (and should) immediately handle the previously cached `app:openURL` event (if any)
if (_cachedOpenUrlRequest != nil) {
_cachedOpenUrlRequest();
_cachedOpenUrlRequest = nil;
}
// Set `_isRunning` after calling `_cachedOpenUrlRequest` to ensure that
// the `onReq` triggered by this call to `_cachedOpenUrlRequest` will
// be stored in `_attemptToResumeMsgFromWxRunnable` which can be obtained
// by triggering `attemptToResumeMsgFromWx`.
//
// At the same time, this also coincides with the approach on the Android side:
// cold start events are cached and triggered through `attemptToResumeMsgFromWx`
_isRunning = isWeChatRegistered;
result(@(isWeChatRegistered));
}
- (void)checkWeChatInstallation:(FlutterMethodCall *)call result:(FlutterResult)result {
result(@([WXApi isWXAppInstalled]));
}
- (void)openWeChatCustomerServiceChat:(FlutterMethodCall *)call result:(FlutterResult)result {
NSString *url = call.arguments[@"url"];
NSString *corpId = call.arguments[@"corpId"];
WXOpenCustomerServiceReq *req = [[WXOpenCustomerServiceReq alloc] init];
req.corpid = corpId; //企业ID
req.url = url; //客服URL
return [WXApi sendReq:req completion:^(BOOL success) {
result(@(success));
}];
}
- (void)checkSupportOpenBusinessView:(FlutterMethodCall *)call result:(FlutterResult)result {
if(![WXApi isWXAppInstalled]){
result([FlutterError errorWithCode:@"WeChat Not Installed" message:@"Please install the WeChat first" details:nil]);
}else {
result(@(true));
}
}
#ifndef NO_PAY
- (void)handlePayment:(FlutterMethodCall *)call result:(FlutterResult)result {
NSNumber *timestamp = call.arguments[@"timeStamp"];
NSString *partnerId = call.arguments[@"partnerId"];
NSString *prepayId = call.arguments[@"prepayId"];
NSString *packageValue = call.arguments[@"packageValue"];
NSString *nonceStr = call.arguments[@"nonceStr"];
UInt32 timeStamp = [timestamp unsignedIntValue];
NSString *sign = call.arguments[@"sign"];
[FluwxDelegate defaultManager].extData = call.arguments[@"extData"];
NSString * appId = call.arguments[@"appId"];
PayReq *req = [[PayReq alloc] init];
req.openID = (appId == (id) [NSNull null]) ? nil : appId;
req.partnerId = partnerId;
req.prepayId = prepayId;
req.nonceStr = nonceStr;
req.timeStamp = timeStamp;
req.package = packageValue;
req.sign = sign;
[WXApi sendReq:req completion:^(BOOL done) {
result(@(done));
}];
}
- (void)handleHongKongWalletPayment:(FlutterMethodCall *)call result:(FlutterResult)result {
NSString *partnerId = call.arguments[@"prepayId"];
WXOpenBusinessWebViewReq *req = [[WXOpenBusinessWebViewReq alloc] init];
req.businessType = 1;
NSMutableDictionary *queryInfoDic = [NSMutableDictionary dictionary];
[queryInfoDic setObject:partnerId forKey:@"token"];
req.queryInfoDic = queryInfoDic;
[WXApi sendReq:req completion:^(BOOL done) {
result(@(done));
}];
}
#endif
- (void)handleLaunchMiniProgram:(FlutterMethodCall *)call result:(FlutterResult)result {
NSString *userName = call.arguments[@"userName"];
NSString *path = call.arguments[@"path"];
// WXMiniProgramType *miniProgramType = call.arguments[@"miniProgramType"];
NSNumber *typeInt = call.arguments[@"miniProgramType"];
WXMiniProgramType miniProgramType = WXMiniProgramTypeRelease;
if ([typeInt isEqualToNumber:@1]) {
miniProgramType = WXMiniProgramTypeTest;
} else if ([typeInt isEqualToNumber:@2]) {
miniProgramType = WXMiniProgramTypePreview;
}
WXLaunchMiniProgramReq *launchMiniProgramReq = [WXLaunchMiniProgramReq object];
launchMiniProgramReq.userName = userName;
launchMiniProgramReq.path = (path == (id) [NSNull null]) ? nil : path;
launchMiniProgramReq.miniProgramType = miniProgramType;
[WXApi sendReq:launchMiniProgramReq completion:^(BOOL done) {
result(@(done));
}];
}
- (void)handleSubscribeWithCall:(FlutterMethodCall *)call result:(FlutterResult)result {
NSDictionary *params = call.arguments;
NSString *appId = [params valueForKey:@"appId"];
NSNumber *scene = [params valueForKey:@"scene"];
NSString *templateId = [params valueForKey:@"templateId"];
NSString *reserved = [params valueForKey:@"reserved"];
WXSubscribeMsgReq *req = [WXSubscribeMsgReq new];
#if __LP64__
req.scene = [scene unsignedIntValue];
#else
req.scene = [scene unsignedLongValue];
#endif
req.templateId = templateId;
req.reserved = reserved;
req.openID = appId;
[WXApi sendReq:req completion:^(BOOL done) {
result(@(done));
}];
}
- (void)handleAutoDeductWithCall:(FlutterMethodCall *)call result:(FlutterResult)result {
NSMutableDictionary *paramsFromDart = [NSMutableDictionary dictionaryWithDictionary:call.arguments];
[paramsFromDart removeObjectForKey:@"businessType"];
WXOpenBusinessWebViewReq *req = [[WXOpenBusinessWebViewReq alloc] init];
NSNumber *businessType = call.arguments[@"businessType"];
req.businessType = [businessType unsignedIntValue];
req.queryInfoDic = paramsFromDart;
[WXApi sendReq:req completion:^(BOOL done) {
result(@(done));
}];
}
- (void)handleautoDeductV2:(FlutterMethodCall *)call result:(FlutterResult)result {
NSMutableDictionary *paramsFromDart = call.arguments[@"queryInfo"];
// [paramsFromDart removeObjectForKey:@"businessType"];
WXOpenBusinessWebViewReq *req = [[WXOpenBusinessWebViewReq alloc] init];
NSNumber *businessType = call.arguments[@"businessType"];
req.businessType = [businessType unsignedIntValue];
req.queryInfoDic = paramsFromDart;
[WXApi sendReq:req completion:^(BOOL done) {
result(@(done));
}];
}
- (void)handleOpenBusinessView:(FlutterMethodCall *)call result:(FlutterResult)result {
NSDictionary *params = call.arguments;
WXOpenBusinessViewReq *req = [WXOpenBusinessViewReq object];
NSString *businessType = [params valueForKey:@"businessType"];
NSString *query = [params valueForKey:@"query"];
req.businessType = businessType;
req.query = query;
req.extInfo = @"{\"miniProgramType\":0}";
[WXApi sendReq:req completion:^(BOOL done) {
result(@(done));
}];
}
- (void)handelGetExtMsgWithCall:(FlutterMethodCall *)call result:(FlutterResult)result {
result([FluwxDelegate defaultManager].extMsg);
[FluwxDelegate defaultManager].extMsg=nil;
}
// Deprecated since iOS 9
// See https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623073-application?language=objc
// Use `application:openURL:options:` instead.
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
// Since flutter has minimum iOS version requirement of 11.0, we don't need to change the implementation here.
return [WXApi handleOpenURL:url delegate:self];
}
// Deprecated since iOS 9
// See https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622964-application?language=objc
// Use `application:openURL:options:` instead.
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
// Since flutter has minimum iOS version requirement of 11.0, we don't need to change the implementation here.
return [WXApi handleOpenURL:url delegate:self];
}
// Available on iOS 9.0 and later
// See https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application?language=objc
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *, id> *)options {
// ↓ previous solution -- according to document, this may fail if the WXApi hasn't registered yet.
// return [WXApi handleOpenURL:url delegate:self];
if (_isRunning) {
// registered -- directly handle open url request by WXApi
return [WXApi handleOpenURL:url delegate:self];
}else {
// unregistered -- cache open url request and handle it once WXApi is registered
__weak typeof(self) weakSelf = self;
_cachedOpenUrlRequest = ^() {
__strong typeof(weakSelf) strongSelf = weakSelf;
[WXApi handleOpenURL:url delegate:strongSelf];
};
// simply return YES to indicate that we can handle open url request later
return YES;
}
}
#ifndef SCENE_DELEGATE
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nonnull))restorationHandler{
// TODO: (if need) cache userActivity and handle it once WXApi is registered
return [WXApi handleOpenUniversalLink:userActivity delegate:self];
}
#endif
#ifdef SCENE_DELEGATE
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity API_AVAILABLE(ios(13.0)){
// TODO: (if need) cache userActivity and handle it once WXApi is registered
[WXApi handleOpenUniversalLink:userActivity delegate:self];
}
#endif
- (void)handleOpenUrlCall:(FlutterMethodCall *)call
result:(FlutterResult)result {
OpenWebviewReq *req = [[OpenWebviewReq alloc] init];
req.url = call.arguments[@"url"];
[WXApi sendReq:req
completion:^(BOOL success){
result(@(success));
}];
}
- (void)handleOpenRankListCall:(FlutterMethodCall *)call
result:(FlutterResult)result {
OpenRankListReq *req = [[OpenRankListReq alloc] init];
[WXApi sendReq:req
completion:^(BOOL success){
result(@(success));
}];
}
- (BOOL)handleOpenURL:(NSNotification *)aNotification {
if (handleOpenURLByFluwx) {
NSString *aURLString = [aNotification userInfo][@"url"];
NSURL *aURL = [NSURL URLWithString:aURLString];
return [WXApi handleOpenURL:aURL delegate:self];
} else {
return NO;
}
}
- (void)logToFlutterWithDetail:(NSString *) detail {
if(_channel != nil){
NSDictionary *result = @{
@"detail":detail
};
[_channel invokeMethod:@"wechatLog" arguments:result];
}
}
- (void)handleShare:(FlutterMethodCall *)call result:(FlutterResult)result {
if ([@"shareText" isEqualToString:call.method]) {
[self shareText:call result:result];
} else if ([@"shareImage" isEqualToString:call.method]) {
[self shareImage:call result:result];
} else if ([@"shareWebPage" isEqualToString:call.method]) {
[self shareWebPage:call result:result];
} else if ([@"shareMusic" isEqualToString:call.method]) {
[self shareMusic:call result:result];
} else if ([@"shareVideo" isEqualToString:call.method]) {
[self shareVideo:call result:result];
} else if ([@"shareMiniProgram" isEqualToString:call.method]) {
[self shareMiniProgram:call result:result];
} else if ([@"shareFile" isEqualToString:call.method]) {
[self shareFile:call result:result];
}
}
- (void)shareText:(FlutterMethodCall *)call result:(FlutterResult)result {
NSString *text = call.arguments[@"source"];
NSNumber *scene = call.arguments[fluwxKeyScene];
[self sendText:text InScene:[self intToWeChatScene:scene] completion:^(BOOL done) {
result(@(done));
}];
}
- (void)shareImage:(FlutterMethodCall *)call result:(FlutterResult)result {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_async(globalQueue, ^{
NSDictionary *sourceImage = call.arguments[keySource];
NSData *sourceImageData = [self getNsDataFromWeChatFile:sourceImage];
UIImage *thumbnailImage = [self getCommonThumbnail:call];
UIImage *realThumbnailImage;
if (thumbnailImage == nil) {
NSString *suffix = sourceImage[@"suffix"];
BOOL isPNG = [self isPNG:suffix];
BOOL compress = [call.arguments[fluwxKeyCompressThumbnail] boolValue];
realThumbnailImage = [self getThumbnailFromNSData:sourceImageData size:defaultThumbnailSize isPNG:isPNG compress:compress];
} else {
realThumbnailImage = thumbnailImage;
}
dispatch_async(dispatch_get_main_queue(), ^{
NSNumber *scene = call.arguments[fluwxKeyScene];
[self sendImageData:sourceImageData
TagName:call.arguments[fluwxKeyMediaTagName]
MessageExt:call.arguments[fluwxKeyMessageExt]
Action:call.arguments[fluwxKeyMessageAction]
ThumbImage:realThumbnailImage
InScene:[self intToWeChatScene:scene]
title:call.arguments[fluwxKeyTitle]
description:call.arguments[fluwxKeyDescription]
MsgSignature:call.arguments[fluwxKeyMsgSignature]
completion:^(BOOL done) {
result(@(done));
}
];
});
});
}
- (void)shareWebPage:(FlutterMethodCall *)call result:(FlutterResult)result {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_async(globalQueue, ^{
UIImage *thumbnailImage = [self getCommonThumbnail:call];
dispatch_async(dispatch_get_main_queue(), ^{
NSString *webPageUrl = call.arguments[@"webPage"];
NSNumber *scene = call.arguments[fluwxKeyScene];
[self sendLinkURL:webPageUrl
TagName:call.arguments[fluwxKeyMediaTagName]
Title:call.arguments[fluwxKeyTitle]
Description:call.arguments[fluwxKeyDescription]
ThumbImage:thumbnailImage
MessageExt:call.arguments[fluwxKeyMessageExt]
MessageAction:call.arguments[fluwxKeyMessageAction]
InScene:[self intToWeChatScene:scene]
MsgSignature:call.arguments[fluwxKeyMsgSignature]
completion:^(BOOL done) {
result(@(done));
}];
});
});
}
- (void)shareMusic:(FlutterMethodCall *)call result:(FlutterResult)result {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_async(globalQueue, ^{
UIImage *thumbnailImage = [self getCommonThumbnail:call];
dispatch_async(dispatch_get_main_queue(), ^{
NSNumber *scene = call.arguments[fluwxKeyScene];
[self sendMusicURL:call.arguments[@"musicUrl"]
dataURL:call.arguments[@"musicDataUrl"]
MusicLowBandUrl:call.arguments[@"musicLowBandUrl"]
MusicLowBandDataUrl:call.arguments[@"musicLowBandDataUrl"]
Title:call.arguments[fluwxKeyTitle]
Description:call.arguments[fluwxKeyDescription]
ThumbImage:thumbnailImage
MessageExt:call.arguments[fluwxKeyMessageExt]
MessageAction:call.arguments[fluwxKeyMessageAction]
TagName:call.arguments[fluwxKeyMediaTagName]
InScene:[self intToWeChatScene:scene]
MsgSignature:call.arguments[fluwxKeyMsgSignature]
completion:^(BOOL done) {
result(@(done));
}
];
});
});
}
- (void)shareVideo:(FlutterMethodCall *)call result:(FlutterResult)result {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_async(globalQueue, ^{
UIImage *thumbnailImage = [self getCommonThumbnail:call];
dispatch_async(dispatch_get_main_queue(), ^{
NSNumber *scene = call.arguments[fluwxKeyScene];
[self sendVideoURL:call.arguments[@"videoUrl"]
VideoLowBandUrl:call.arguments[@"videoLowBandUrl"]
Title:call.arguments[fluwxKeyTitle]
Description:call.arguments[fluwxKeyDescription]
ThumbImage:thumbnailImage
MessageExt:call.arguments[fluwxKeyMessageExt]
MessageAction:call.arguments[fluwxKeyMessageAction]
TagName:call.arguments[fluwxKeyMediaTagName]
InScene:[self intToWeChatScene:scene]
MsgSignature:call.arguments[fluwxKeyMsgSignature]
completion:^(BOOL done) {
result(@(done));
}];
});
});
}
- (void)shareFile:(FlutterMethodCall *)call result:(FlutterResult)result {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_async(globalQueue, ^{
NSDictionary *sourceFile = call.arguments[keySource];
UIImage *thumbnailImage = [self getCommonThumbnail:call];
NSString *fileExtension;
NSString *suffix = sourceFile[keySuffix];
fileExtension = suffix;
if ([suffix hasPrefix:@"."]) {
NSRange range = NSMakeRange(0, 1);
fileExtension = [suffix stringByReplacingCharactersInRange:range withString:@""];
}
NSData *data = [self getNsDataFromWeChatFile:sourceFile];
dispatch_async(dispatch_get_main_queue(), ^{
NSNumber *scene = call.arguments[fluwxKeyScene];
[self sendFileData:data
fileExtension:fileExtension
Title:call.arguments[fluwxKeyTitle]
Description:call.arguments[fluwxKeyDescription]
ThumbImage:thumbnailImage
InScene:[self intToWeChatScene:scene]
MsgSignature:call.arguments[fluwxKeyMsgSignature]
completion:^(BOOL success) {
result(@(success));
}];
});
});
}
- (void)shareMiniProgram:(FlutterMethodCall *)call result:(FlutterResult)result {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_async(globalQueue, ^{
UIImage *thumbnailImage = [self getCommonThumbnail:call];
NSData *hdImageData = nil;
NSDictionary *hdImagePath = call.arguments[@"hdImagePath"];
if (hdImagePath != (id) [NSNull null]) {
NSData *imageData = [self getNsDataFromWeChatFile:hdImagePath];
BOOL compress = [call.arguments[fluwxKeyCompressThumbnail] boolValue];
hdImageData = [self getThumbnailDataFromNSData:imageData size:120 * 1024 compress:compress];
// UIImage *uiImage = [self getThumbnailFromNSData:imageData size:120 * 1024 isPNG:isPNG compress:compress];
// if (isPNG) {
// hdImageData = UIImagePNGRepresentation(uiImage);
// } else {
// hdImageData = UIImageJPEGRepresentation(uiImage, 1);
// }
}
dispatch_async(dispatch_get_main_queue(), ^{
NSNumber *scene = call.arguments[fluwxKeyScene];
NSNumber *typeInt = call.arguments[@"miniProgramType"];
WXMiniProgramType miniProgramType = WXMiniProgramTypeRelease;
if ([typeInt isEqualToNumber:@1]) {
miniProgramType = WXMiniProgramTypeTest;
} else if ([typeInt isEqualToNumber:@2]) {
miniProgramType = WXMiniProgramTypePreview;
}
[self sendMiniProgramWebpageUrl:call.arguments[@"webPageUrl"]
userName:call.arguments[@"userName"]
path:call.arguments[@"path"]
title:call.arguments[fluwxKeyTitle]
Description:call.arguments[fluwxKeyDescription]
ThumbImage:thumbnailImage
hdImageData:hdImageData
withShareTicket:[call.arguments[@"withShareTicket"] boolValue]
miniProgramType:miniProgramType
MessageExt:call.arguments[fluwxKeyMessageExt]
MessageAction:call.arguments[fluwxKeyMessageAction]
TagName:call.arguments[fluwxKeyMediaTagName]
InScene:[self intToWeChatScene:scene]
MsgSignature:call.arguments[fluwxKeyMsgSignature]
completion:^(BOOL done) {
result(@(done));
}
];
});
});
}
- (UIImage *)getCommonThumbnail:(FlutterMethodCall *)call {
NSDictionary *thumbnail = call.arguments[fluwxKeyThumbnail];
if (thumbnail == nil || thumbnail == (id) [NSNull null]) {
return nil;
}
NSString *suffix = thumbnail[@"suffix"];
NSNumber *compress = call.arguments[fluwxKeyCompressThumbnail];
NSData *thumbnailData = [self getNsDataFromWeChatFile:thumbnail];
UIImage *thumbnailImage = [self getThumbnailFromNSData:thumbnailData size:defaultThumbnailSize isPNG:[self isPNG:suffix] compress:[compress boolValue]];
return thumbnailImage;
}
//enum ImageSchema {
// NETWORK,
// ASSET,
// FILE,
// BINARY,
//}
- (NSData *)getNsDataFromWeChatFile:(NSDictionary *)weChatFile {
NSNumber *schema = weChatFile[@"schema"];
if ([schema isEqualToNumber:@0]) {
NSString *source = weChatFile[keySource];
NSURL *imageURL = [NSURL URLWithString:source];
//下载图片
return [NSData dataWithContentsOfURL:imageURL];
} else if ([schema isEqualToNumber:@1]) {
NSString *source = weChatFile[keySource];
return [NSData dataWithContentsOfFile:[self readFileFromAssets:source]];
} else if ([schema isEqualToNumber:@2]) {
NSString *source = weChatFile[keySource];
return [NSData dataWithContentsOfFile:source];
} else if ([schema isEqualToNumber:@3]) {
FlutterStandardTypedData *imageData = weChatFile[@"source"];
return imageData.data;
} else {
return nil;
}
}
- (UIImage *)getThumbnailFromNSData:(NSData *)data size:(NSUInteger)size isPNG:(BOOL)isPNG compress:(BOOL)compress {
UIImage *uiImage = [UIImage imageWithData:data];
if (compress)
return [ThumbnailHelper compressImage:uiImage toByte:size isPNG:isPNG];
else
return uiImage;
}
- (NSData *)getThumbnailDataFromNSData:(NSData *)data size:(NSUInteger)size compress:(BOOL)compress {
if (compress) {
return [ThumbnailHelper compressImageData:data toByte:size];
} else {
return data;
}
}
- (NSString *)readFileFromAssets:(NSString *)imagePath {
NSArray *array = [self formatAssets:imagePath];
NSString *key;
if ([FluwxStringUtil isBlank:array[1]]) {
key = [_fluwxRegistrar lookupKeyForAsset:array[0]];
} else {
key = [_fluwxRegistrar lookupKeyForAsset:array[0] fromPackage:array[1]];
}
return [[NSBundle mainBundle] pathForResource:key ofType:nil];
}
- (NSArray *)formatAssets:(NSString *)originPath {
NSString *path = nil;
NSString *packageName = @"";
NSString *pathWithoutSchema = originPath;
NSInteger indexOfPackage = [pathWithoutSchema lastIndexOfString:@"?package="];
if (indexOfPackage != JavaNotFound) {
path = [pathWithoutSchema substringFromIndex:0 toIndex:indexOfPackage];
NSInteger begin = indexOfPackage + [fluwxKeyPackage length];
packageName = [pathWithoutSchema substringFromIndex:begin toIndex:[pathWithoutSchema length]];
} else {
path = pathWithoutSchema;
}
return @[path, packageName];
}
- (BOOL)isPNG:(NSString *)suffix {
return [@".png" equals:suffix];
}
- (enum WXScene)intToWeChatScene:(NSNumber *)value {
// enum WeChatScene { SESSION, TIMELINE, FAVORITE }
if ([value isEqual:@0]) {
return WXSceneSession;
} else if ([value isEqual:@1]) {
return WXSceneTimeline;
} else if ([value isEqual:@2]) {
return WXSceneFavorite;
} else {
return WXSceneSession;
}
}
- (void)managerDidRecvLaunchFromWXReq:(LaunchFromWXReq *)request {
[FluwxDelegate defaultManager].extMsg = request.message.messageExt;
// LaunchFromWXReq *launchFromWXReq = (LaunchFromWXReq *)request;
//
// if (_isRunning) {
// [FluwxDelegate defaultManager].extMsg = request.message.messageExt;
// } else {
// __weak typeof(self) weakSelf = self;
// _initialWXReqRunnable = ^() {
// __strong typeof(weakSelf) strongSelf = weakSelf;
// [FluwxDelegate defaultManager].extMsg = request.message.messageExt
// };
// }
}
- (void)onResp:(BaseResp *)resp {
if ([resp isKindOfClass:[SendMessageToWXResp class]]) {
SendMessageToWXResp *messageResp = (SendMessageToWXResp *) resp;
NSDictionary *result = @{
description: messageResp.description == nil ? @"" : messageResp.description,
errStr: messageResp.errStr == nil ? @"" : messageResp.errStr,
errCode: @(messageResp.errCode),
type: @(messageResp.type),
country: messageResp.country == nil ? @"" : messageResp.country,
lang: messageResp.lang == nil ? @"" : messageResp.lang};
if(_channel != nil){
[_channel invokeMethod:@"onShareResponse" arguments:result];
}
} else if ([resp isKindOfClass:[SendAuthResp class]]) {
SendAuthResp *authResp = (SendAuthResp *) resp;
NSDictionary *result = @{
description: authResp.description == nil ? @"" : authResp.description,
errStr: authResp.errStr == nil ? @"" : authResp.errStr,
errCode: @(authResp.errCode),
type: @(authResp.type),
country: authResp.country == nil ? @"" : authResp.country,
lang: authResp.lang == nil ? @"" : authResp.lang,
@"code": [FluwxStringUtil nilToEmpty:authResp.code],
@"state": [FluwxStringUtil nilToEmpty:authResp.state]
};
if(_channel != nil){
[_channel invokeMethod:@"onAuthResponse" arguments:result];
}
} else if ([resp isKindOfClass:[AddCardToWXCardPackageResp class]]) {
} else if ([resp isKindOfClass:[WXChooseCardResp class]]) {
} else if ([resp isKindOfClass:[WXChooseInvoiceResp class]]) {
//TODO 处理发票返回,并回调Dart
WXChooseInvoiceResp *chooseInvoiceResp = (WXChooseInvoiceResp *) resp;
NSArray *array = chooseInvoiceResp.cardAry;
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:array.count];
for (int i = 0; i< array.count; i++) {
WXInvoiceItem *item = array[i];
NSDictionary *dict = @{@"app_id":item.appID, @"encrypt_code":item.encryptCode, @"card_id":item.cardId};
[mutableArray addObject:dict];
}
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:mutableArray options:NSJSONWritingPrettyPrinted error: &error];
NSString *cardItemList = @"";
if ([jsonData length] && error == nil) {
cardItemList = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
NSDictionary *result = @{
description: chooseInvoiceResp.description == nil ? @"" : chooseInvoiceResp.description,
errStr: chooseInvoiceResp.errStr == nil ? @"" : chooseInvoiceResp.errStr,
errCode: @(chooseInvoiceResp.errCode),
type: @(chooseInvoiceResp.type),
@"cardItemList":cardItemList
};
if(_channel != nil){
[_channel invokeMethod:@"onOpenWechatInvoiceResponse" arguments:result];
}
} else if ([resp isKindOfClass:[WXSubscribeMsgResp class]]) {
WXSubscribeMsgResp *subscribeMsgResp = (WXSubscribeMsgResp *) resp;
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSString *openid = subscribeMsgResp.openId;
if(openid != nil && openid != NULL && ![openid isKindOfClass:[NSNull class]]){
result[@"openid"] = openid;
}
NSString *templateId = subscribeMsgResp.templateId;
if(templateId != nil && templateId != NULL && ![templateId isKindOfClass:[NSNull class]]){
result[@"templateId"] = templateId;
}
NSString *action = subscribeMsgResp.action;
if(action != nil && action != NULL && ![action isKindOfClass:[NSNull class]]){
result[@"action"] = action;
}
NSString *reserved = subscribeMsgResp.action;
if(reserved != nil && reserved != NULL && ![reserved isKindOfClass:[NSNull class]]){
result[@"reserved"] = reserved;
}
UInt32 scene = subscribeMsgResp.scene;
result[@"scene"] = @(scene);
if(_channel != nil){
[_channel invokeMethod:@"onSubscribeMsgResp" arguments:result];
}
} else if ([resp isKindOfClass:[WXLaunchMiniProgramResp class]]) {
WXLaunchMiniProgramResp *miniProgramResp = (WXLaunchMiniProgramResp *) resp;
NSDictionary *commonResult = @{
description: miniProgramResp.description == nil ? @"" : miniProgramResp.description,
errStr: miniProgramResp.errStr == nil ? @"" : miniProgramResp.errStr,
errCode: @(miniProgramResp.errCode),
type: @(miniProgramResp.type),
};
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithDictionary:commonResult];
if (miniProgramResp.extMsg != nil) {
result[@"extMsg"] = miniProgramResp.extMsg;
}
// @"extMsg":miniProgramResp.extMsg == nil?@"":miniProgramResp.extMsg
if(_channel != nil){
[_channel invokeMethod:@"onLaunchMiniProgramResponse" arguments:result];
}
} else if ([resp isKindOfClass:[WXInvoiceAuthInsertResp class]]) {
} else if ([resp isKindOfClass:[WXOpenBusinessWebViewResp class]]) {
WXOpenBusinessWebViewResp *businessResp = (WXOpenBusinessWebViewResp *) resp;
NSDictionary *result = @{
description: [FluwxStringUtil nilToEmpty:businessResp.description],
errStr: [FluwxStringUtil nilToEmpty:resp.errStr],
errCode: @(businessResp.errCode),
type: @(businessResp.type),
@"resultInfo": [FluwxStringUtil nilToEmpty:businessResp.result],
@"businessType": @(businessResp.businessType),
};
if(_channel != nil){
[_channel invokeMethod:@"onWXOpenBusinessWebviewResponse" arguments:result];
}
} else if ([resp isKindOfClass:[WXOpenCustomerServiceResp class]])
{
WXOpenCustomerServiceResp *customerResp = (WXOpenCustomerServiceResp *) resp;
NSDictionary *result = @{
description: [FluwxStringUtil nilToEmpty:customerResp.description],
errStr: [FluwxStringUtil nilToEmpty:resp.errStr],
errCode: @(customerResp.errCode),
type: @(customerResp.type),
@"extMsg":[FluwxStringUtil nilToEmpty:customerResp.extMsg]
};
if(_channel != nil){
[_channel invokeMethod:@"onWXOpenBusinessWebviewResponse" arguments:result];
}
// 相关错误信息
}else if ([resp isKindOfClass:[WXOpenBusinessViewResp class]])
{
WXOpenBusinessViewResp *openBusinessViewResp = (WXOpenBusinessViewResp *) resp;
NSDictionary *result = @{
description: [FluwxStringUtil nilToEmpty:openBusinessViewResp.description],
errStr: [FluwxStringUtil nilToEmpty:resp.errStr],
errCode: @(openBusinessViewResp.errCode),
@"businessType":openBusinessViewResp.businessType,
type: @(openBusinessViewResp.type),
@"extMsg":[FluwxStringUtil nilToEmpty:openBusinessViewResp.extMsg]
};
if(_channel != nil){
[_channel invokeMethod:@"onOpenBusinessViewResponse" arguments:result];
}
// 相关错误信息
}
#ifndef NO_PAY
else if ([resp isKindOfClass:[WXPayInsuranceResp class]]) {
} else if ([resp isKindOfClass:[PayResp class]]) {
PayResp *payResp = (PayResp *) resp;
NSDictionary *result = @{
description: [FluwxStringUtil nilToEmpty:payResp.description],
errStr: [FluwxStringUtil nilToEmpty:resp.errStr],
errCode: @(payResp.errCode),
type: @(payResp.type),
@"extData": [FluwxStringUtil nilToEmpty:[FluwxDelegate defaultManager].extData],
@"returnKey": [FluwxStringUtil nilToEmpty:payResp.returnKey],
};
[FluwxDelegate defaultManager].extData = nil;
if(_channel != nil){
[_channel invokeMethod:@"onPayResponse" arguments:result];
}
} else if ([resp isKindOfClass:[WXNontaxPayResp class]]) {
}
#endif
}
- (void)onReq:(BaseReq *)req {
if ([req isKindOfClass:[GetMessageFromWXReq class]]) {
} else if ([req isKindOfClass:[ShowMessageFromWXReq class]]) {
// ShowMessageFromWXReq -- android spec
ShowMessageFromWXReq *showMessageFromWXReq = (ShowMessageFromWXReq *) req;
WXMediaMessage *wmm = showMessageFromWXReq.message;
NSMutableDictionary *result = [NSMutableDictionary dictionary];
[result setValue:wmm.messageAction forKey:@"messageAction"];
[result setValue:wmm.messageExt forKey:@"extMsg"];
[result setValue:showMessageFromWXReq.lang forKey:@"lang"];
[result setValue:showMessageFromWXReq.country forKey:@"country"];
// Cache extMsg for later use (by calling 'getExtMsg')
[FluwxDelegate defaultManager].extMsg= wmm.messageExt;
if (_isRunning) {
[_channel invokeMethod:@"onWXShowMessageFromWX" arguments:result];
} else {
__weak typeof(self) weakSelf = self;
_attemptToResumeMsgFromWxRunnable = ^() {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf->_channel invokeMethod:@"onWXShowMessageFromWX" arguments:result];
};
}
} else if ([req isKindOfClass:[LaunchFromWXReq class]]) {
// ShowMessageFromWXReq -- ios spec
LaunchFromWXReq *launchFromWXReq = (LaunchFromWXReq *) req;
WXMediaMessage *wmm = launchFromWXReq.message;
NSMutableDictionary *result = [NSMutableDictionary dictionary];
[result setValue:wmm.messageAction forKey:@"messageAction"];
[result setValue:wmm.messageExt forKey:@"extMsg"];
[result setValue:launchFromWXReq.lang forKey:@"lang"];
[result setValue:launchFromWXReq.country forKey:@"country"];
// Cache extMsg for later use (by calling 'getExtMsg')
[FluwxDelegate defaultManager].extMsg= wmm.messageExt;
if (_isRunning) {
[_channel invokeMethod:@"onWXLaunchFromWX" arguments:result];
} else {
__weak typeof(self) weakSelf = self;
_attemptToResumeMsgFromWxRunnable = ^() {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf->_channel invokeMethod:@"onWXLaunchFromWX" arguments:result];
};
}
}
}
- (void)sendText:(NSString *)text
InScene:(enum WXScene)scene
completion:(void (^ __nullable)(BOOL success))completion {
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.scene = scene;
req.bText = YES;
req.text = text;
[WXApi sendReq:req
completion:completion];
}
- (void)sendImageData:(NSData *)imageData
TagName:(NSString *)tagName
MessageExt:(NSString *)messageExt
Action:(NSString *)action
ThumbImage:(UIImage *)thumbImage
InScene:(enum WXScene)scene
title:(NSString *)title
description:(NSString *)description
MsgSignature:(NSString *)msgSignature
completion:(void (^ __nullable)(BOOL success))completion {
WXImageObject *ext = [WXImageObject object];
ext.imageData = imageData;
WXMediaMessage *message = [self messageWithTitle:(title == (id) [NSNull null]) ? nil : title
Description:(description == (id) [NSNull null]) ? nil : description
Object:ext
MessageExt:(messageExt == (id) [NSNull null]) ? nil : messageExt
MessageAction:(action == (id) [NSNull null]) ? nil : action
ThumbImage:thumbImage
MediaTag:(tagName == (id) [NSNull null]) ? nil : tagName
MsgSignature:(msgSignature == (id) [NSNull null]) ? nil : msgSignature
];;
SendMessageToWXReq *req = [self requestWithText:nil
OrMediaMessage:message
bText:NO
InScene:scene];
[WXApi sendReq:req completion:completion];
}
- (void)sendLinkURL:(NSString *)urlString
TagName:(NSString *)tagName
Title:(NSString *)title
Description:(NSString *)description
ThumbImage:(UIImage *)thumbImage
MessageExt:(NSString *)messageExt
MessageAction:(NSString *)messageAction
InScene:(enum WXScene)scene
MsgSignature:(NSString *)msgSignature
completion:(void (^ __nullable)(BOOL success))completion {
WXWebpageObject *ext = [WXWebpageObject object];
ext.webpageUrl = urlString;
WXMediaMessage *message = [self messageWithTitle:(title == (id) [NSNull null]) ? nil :title
Description:(description == (id) [NSNull null]) ? nil : description
Object:ext
MessageExt:(messageExt == (id) [NSNull null]) ? nil : messageExt
MessageAction:(messageAction == (id) [NSNull null]) ? nil : messageAction
ThumbImage:thumbImage
MediaTag:(tagName == (id) [NSNull null]) ? nil : tagName
MsgSignature:(msgSignature == (id) [NSNull null]) ? nil : msgSignature
];
SendMessageToWXReq *req = [self requestWithText:nil
OrMediaMessage:message
bText:NO
InScene:scene];
[WXApi sendReq:req completion:completion];
}
- (void)sendMusicURL:(NSString *)musicURL
dataURL:(NSString *)dataURL
MusicLowBandUrl:(NSString *)musicLowBandUrl
MusicLowBandDataUrl:(NSString *)musicLowBandDataUrl
Title:(NSString *)title
Description:(NSString *)description
ThumbImage:(UIImage *)thumbImage
MessageExt:(NSString *)messageExt
MessageAction:(NSString *)messageAction
TagName:(NSString *)tagName
InScene:(enum WXScene)scene
MsgSignature:(NSString *)msgSignature
completion:(void (^ __nullable)(BOOL success))completion {
WXMusicObject *ext = [WXMusicObject object];
if ([FluwxStringUtil isBlank:musicURL]) {
ext.musicLowBandUrl = musicLowBandUrl;
ext.musicLowBandDataUrl = (musicLowBandDataUrl == (id) [NSNull null]) ? nil : musicLowBandDataUrl;
} else {
ext.musicUrl = musicURL;
ext.musicDataUrl = (dataURL == (id) [NSNull null]) ? nil : dataURL;
}
WXMediaMessage *message = [self messageWithTitle:(title == (id) [NSNull null]) ? nil : title
Description:description
Object:ext
MessageExt:(messageExt == (id) [NSNull null]) ? nil : messageExt
MessageAction:(messageAction == (id) [NSNull null]) ? nil : messageAction
ThumbImage:thumbImage
MediaTag:(tagName == (id) [NSNull null]) ? nil : tagName
MsgSignature:(msgSignature == (id) [NSNull null]) ? nil : msgSignature
];
SendMessageToWXReq *req = [self requestWithText:nil
OrMediaMessage:message
bText:NO
InScene:scene];
[WXApi sendReq:req completion:completion];
}
- (void)sendVideoURL:(NSString *)videoURL
VideoLowBandUrl:(NSString *)videoLowBandUrl
Title:(NSString *)title
Description:(NSString *)description
ThumbImage:(UIImage *)thumbImage
MessageExt:(NSString *)messageExt
MessageAction:(NSString *)messageAction
TagName:(NSString *)tagName
InScene:(enum WXScene)scene
MsgSignature:(NSString *)msgSignature
completion:(void (^ __nullable)(BOOL success))completion {
WXMediaMessage *message = [WXMediaMessage message];
message.title = (title == (id) [NSNull null]) ? nil : title;
message.description = (description == (id) [NSNull null]) ? nil : description;
message.messageExt = (messageExt == (id) [NSNull null]) ? nil : messageExt;
message.messageAction = (messageAction == (id) [NSNull null]) ? nil : messageAction;
message.mediaTagName = (tagName == (id) [NSNull null]) ? nil : tagName;
[message setThumbImage:thumbImage];
WXVideoObject *ext = [WXVideoObject object];
if ([FluwxStringUtil isBlank:videoURL]) {
ext.videoLowBandUrl = videoLowBandUrl;
} else {
ext.videoUrl = videoURL;
}
message.mediaObject = ext;
SendMessageToWXReq *req = [self requestWithText:nil
OrMediaMessage:message
bText:NO
InScene:scene];
[WXApi sendReq:req completion:completion];
}
- (void)sendEmotionData:(NSData *)emotionData
ThumbImage:(UIImage *)thumbImage
InScene:(enum WXScene)scene
MsgSignature:(NSString *)msgSignature
completion:(void (^ __nullable)(BOOL success))completion {
WXMediaMessage *message = [WXMediaMessage message];
[message setThumbImage:thumbImage];
WXEmoticonObject *ext = [WXEmoticonObject object];
ext.emoticonData = emotionData;
message.mediaObject = ext;
NSString *signature = (msgSignature == (id) [NSNull null]) ? nil : msgSignature;
if (signature != nil) {
message.msgSignature = signature;
}
SendMessageToWXReq *req = [self requestWithText:nil
OrMediaMessage:message
bText:NO
InScene:scene];
[WXApi sendReq:req completion:completion];
}
- (void)sendFileData:(NSData *)fileData
fileExtension:(NSString *)extension
Title:(NSString *)title
Description:(NSString *)description
ThumbImage:(UIImage *)thumbImage
InScene:(enum WXScene)scene
MsgSignature:(NSString *)msgSignature
completion:(void (^ __nullable)(BOOL success))completion {
WXMediaMessage *message = [WXMediaMessage message];
message.title = title;
message.description = description;
[message setThumbImage:thumbImage];
WXFileObject *ext = [WXFileObject object];
ext.fileExtension = extension;
ext.fileData = fileData;
message.mediaObject = ext;
NSString *signature = (msgSignature == (id) [NSNull null]) ? nil : msgSignature;
if (signature != nil) {
message.msgSignature = signature;
}
SendMessageToWXReq *req = [self requestWithText:nil
OrMediaMessage:message
bText:NO
InScene:scene];
[WXApi sendReq:req completion:completion];
}
- (void)sendMiniProgramWebpageUrl:(NSString *)webpageUrl
userName:(NSString *)userName
path:(NSString *)path
title:(NSString *)title
Description:(NSString *)description
ThumbImage:(UIImage *)thumbImage
hdImageData:(NSData *)hdImageData
withShareTicket:(BOOL)withShareTicket
miniProgramType:(WXMiniProgramType)programType
MessageExt:(NSString *)messageExt
MessageAction:(NSString *)messageAction
TagName:(NSString *)tagName
InScene:(enum WXScene)scene
MsgSignature:(NSString *)msgSignature
completion:(void (^ __nullable)(BOOL success))completion {
WXMiniProgramObject *ext = [WXMiniProgramObject object];
ext.webpageUrl = (webpageUrl == (id) [NSNull null]) ? nil : webpageUrl;
ext.userName = (userName == (id) [NSNull null]) ? nil : userName;
ext.path = (path == (id) [NSNull null]) ? nil : path;
ext.hdImageData = (hdImageData == (id) [NSNull null]) ? nil : hdImageData;
ext.withShareTicket = withShareTicket;
ext.miniProgramType = programType;
WXMediaMessage *message = [self messageWithTitle:(title == (id) [NSNull null]) ? nil : title
Description:(description == (id) [NSNull null]) ? nil : description
Object:ext
MessageExt:(messageExt == (id) [NSNull null]) ? nil : messageExt
MessageAction:(messageAction == (id) [NSNull null]) ? nil : messageAction
ThumbImage:thumbImage
MediaTag:(tagName == (id) [NSNull null]) ? nil : tagName
MsgSignature:(msgSignature == (id) [NSNull null]) ? nil : msgSignature
];
SendMessageToWXReq *req = [self requestWithText:nil
OrMediaMessage:message
bText:NO
InScene:scene];
[WXApi sendReq:req completion:completion];
}
- (void)sendAppContentData:(NSData *)data
ExtInfo:(NSString *)info
ExtURL:(NSString *)url
Title:(NSString *)title
Description:(NSString *)description
MessageExt:(NSString *)messageExt
MessageAction:(NSString *)action
ThumbImage:(UIImage *)thumbImage
InScene:(enum WXScene)scene
MsgSignature:(NSString *)msgSignature
completion:(void (^ __nullable)(BOOL success))completion {
WXAppExtendObject *ext = [WXAppExtendObject object];
ext.extInfo = info;
ext.url = url;
ext.fileData = data;
WXMediaMessage *message = [self messageWithTitle:title
Description:description
Object:ext
MessageExt:messageExt
MessageAction:action
ThumbImage:thumbImage
MediaTag:nil
MsgSignature:(msgSignature == (id) [NSNull null]) ? nil : msgSignature
];
SendMessageToWXReq *req = [self requestWithText:nil
OrMediaMessage:message
bText:NO
InScene:scene];
[WXApi sendReq:req completion:completion];
}
- (void)addCardsToCardPackage:(NSArray *)cardIds cardExts:(NSArray *)cardExts
completion:(void (^ __nullable)(BOOL success))completion {
NSMutableArray *cardItems = [NSMutableArray array];
for (NSString *cardId in cardIds) {
WXCardItem *item = [[WXCardItem alloc] init];
item.cardId = cardId;
item.appID = @"wxf8b4f85f3a794e77";
[cardItems addObject:item];
}
for (NSInteger index = 0; index < cardItems.count; index++) {
WXCardItem *item = cardItems[index];
NSString *ext = cardExts[index];
item.extMsg = ext;
}
AddCardToWXCardPackageReq *req = [[AddCardToWXCardPackageReq alloc] init];
req.cardAry = cardItems;
[WXApi sendReq:req completion:completion];
}
- (void)chooseCard:(NSString *)appid
cardSign:(NSString *)cardSign
nonceStr:(NSString *)nonceStr
signType:(NSString *)signType
timestamp:(UInt32)timestamp
completion:(void (^ __nullable)(BOOL success))completion {
WXChooseCardReq *chooseCardReq = [[WXChooseCardReq alloc] init];
chooseCardReq.appID = appid;
chooseCardReq.cardSign = cardSign;
chooseCardReq.nonceStr = nonceStr;
chooseCardReq.signType = signType;
chooseCardReq.timeStamp = timestamp;
[WXApi sendReq:chooseCardReq completion:completion];
}
- (void)sendAuthRequestScope:(NSString *)scope
State:(NSString *)state
OpenID:(NSString *)openID
InViewController:(UIViewController *)viewController
completion:(void (^ __nullable)(BOOL success))completion {
SendAuthReq *req = [[SendAuthReq alloc] init];
req.scope = scope; // @"post_timeline,sns"
req.state = state;
req.openID = openID;
return [WXApi sendAuthReq:req
viewController:viewController
delegate:self
completion:completion];
}
- (void)sendAuthRequestScope:(NSString *)scope
State:(NSString *)state
OpenID:(NSString *)openID
NonAutomatic:(BOOL)nonAutomatic
completion:(void (^)(BOOL))completion {
SendAuthReq *req = [[SendAuthReq alloc] init];
req.scope = scope; // @"post_timeline,sns"
req.state = state;
req.openID = openID;
req.nonautomatic = nonAutomatic;
[WXApi sendReq:req completion:completion];
}
- (void)openUrl:(NSString *)url
completion:(void (^ __nullable)(BOOL success))completion {
OpenWebviewReq *req = [[OpenWebviewReq alloc] init];
req.url = url;
[WXApi sendReq:req completion:completion];
}
- (void)chooseInvoice:(NSString *)appid
cardSign:(NSString *)cardSign
nonceStr:(NSString *)nonceStr
signType:(NSString *)signType
timestamp:(UInt32)timestamp
completion:(void (^ __nullable)(BOOL success))completion {
WXChooseInvoiceReq *chooseInvoiceReq = [[WXChooseInvoiceReq alloc] init];
chooseInvoiceReq.appID = appid;
chooseInvoiceReq.cardSign = cardSign;
chooseInvoiceReq.nonceStr = nonceStr;
chooseInvoiceReq.signType = signType;
// chooseCardReq.cardType = @"INVOICE";
chooseInvoiceReq.timeStamp = timestamp;
// chooseCardReq.canMultiSelect = 1;
[WXApi sendReq:chooseInvoiceReq completion:completion];
}
- (void)openCustomerService:(NSString *)url CorpId:(NSString *)corpId completion:(void (^)(BOOL))completion {
WXOpenCustomerServiceReq *req = [[WXOpenCustomerServiceReq alloc] init];
req.corpid = corpId; //企业ID
req.url = url; //客服URL
[WXApi sendReq:req completion:completion];
}
- (void)handleAuthByPhoneLogin:(FlutterMethodCall *)call result:(FlutterResult)result {
UIViewController *vc = UIApplication.sharedApplication.keyWindow.rootViewController;
SendAuthReq *authReq = [[SendAuthReq alloc] init];
authReq.scope = call.arguments[@"scope"];
authReq.state = (call.arguments[@"state"] == (id) [NSNull null]) ? nil : call.arguments[@"state"];
[WXApi sendAuthReq:authReq viewController:vc delegate:self completion:^(BOOL success) {
result(@(success));
}];
}
- (void)handleAuth:(FlutterMethodCall *)call result:(FlutterResult)result {
NSString *openId = call.arguments[@"openId"];
[self sendAuthRequestScope:call.arguments[@"scope"]
State:(call.arguments[@"state"] == (id) [NSNull null]) ? nil : call.arguments[@"state"]
OpenID:(openId == (id) [NSNull null]) ? nil : openId
NonAutomatic:[call.arguments[@"nonAutomatic"] boolValue]
completion:^(BOOL done) {
result(@(done));
}];
}
- (void)authByQRCode:(FlutterMethodCall *)call result:(FlutterResult)result {
NSString *appId = call.arguments[@"appId"];
NSString *scope = call.arguments[@"scope"];
NSString *nonceStr = call.arguments[@"nonceStr"];
NSString *timeStamp = call.arguments[@"timeStamp"];
NSString *signature = call.arguments[@"signature"];
NSString *schemeData = (call.arguments[@"schemeData"] == (id) [NSNull null]) ? nil : call.arguments[@"schemeData"];
BOOL done = [_qrauth Auth:appId nonceStr:nonceStr timeStamp:timeStamp scope:scope signature:signature schemeData:schemeData];
result(@(done));
}
- (void)stopAuthByQRCode:(FlutterMethodCall *)call result:(FlutterResult)result {
BOOL done = [_qrauth StopAuth];
result(@(done));
}
- (void)onQrcodeScanned {
if(_channel != nil){
[_channel invokeMethod:@"onQRCodeScanned" arguments:@{@"errCode": @0}];
}
}
- (void)onAuthGotQrcode:(UIImage *)image {
NSData *imageData = UIImagePNGRepresentation(image);
// if (imageData == nil) {
// imageData = UIImageJPEGRepresentation(image, 1);
// }
if(_channel != nil){
[_channel invokeMethod:@"onAuthGotQRCode" arguments:@{@"errCode": @0, @"qrCode": imageData}];
}
}
- (void)onAuthFinish:(int)errCode AuthCode:(nullable NSString *)authCode {
NSDictionary *errorCode = @{@"errCode": @(errCode)};
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithDictionary:errorCode];
if (authCode != nil) {
result[@"authCode"] = authCode;
}
if(_channel != nil){
[_channel invokeMethod:@"onAuthByQRCodeFinished" arguments:result];
}
}
- (WXMediaMessage *)messageWithTitle:(NSString *)title
Description:(NSString *)description
Object:(id)mediaObject
MessageExt:(NSString *)messageExt
MessageAction:(NSString *)action
ThumbImage:(UIImage *)thumbImage
MediaTag:(NSString *)tagName
MsgSignature:(NSString *)msgSignature {
WXMediaMessage *message = [WXMediaMessage message];
message.title = title;
message.description = description;
message.mediaObject = mediaObject;
message.messageExt = messageExt;
message.messageAction = action;
message.mediaTagName = tagName;
if(msgSignature != nil ){
message.msgSignature = msgSignature;
}
[message setThumbImage:thumbImage];
return message;
}
- (SendMessageToWXReq *)requestWithText:(NSString *)text
OrMediaMessage:(WXMediaMessage *)message
bText:(BOOL)bText
InScene:(enum WXScene)scene {
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.bText = bText;
req.scene = scene;
if (bText)
req.text = text;
else
req.message = message;
return req;
}
@end
//
// Created by mo on 2020/3/7.
//
#import <Foundation/Foundation.h>
@interface FluwxStringUtil : NSObject
+ (BOOL)isBlank:(NSString *)string;
+ (NSString *)nilToEmpty:(NSString *)string;
@end
\ No newline at end of file
//
// Created by mo on 2020/3/7.
//
#import <fluwx/FluwxStringUtil.h>
@implementation FluwxStringUtil
+ (BOOL)isBlank:(NSString *)string {
if (string == nil) {
return YES;
}
if ([string isKindOfClass:[NSNull class]]) {
return YES;
}
return [[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0;
}
+ (NSString *)nilToEmpty:(NSString *)string {
return string == nil ? @"" : string;
}
@end
//
// Created by mo on 2020/3/8.
//
#import <Foundation/Foundation.h>
#define JavaNotFound -1
@interface NSString (Wrapper)
/** Return the char value at the specified index. */
- (unichar)charAt:(int)index;
/**
* Compares two strings lexicographically.
* the value 0 if the argument string is equal to this string;
* a value less than 0 if this string is lexicographically less than the string argument;
* and a value greater than 0 if this string is lexicographically greater than the string argument.
*/
- (int)compareTo:(NSString *)anotherString;
- (int)compareToIgnoreCase:(NSString *)str;
- (BOOL)contains:(NSString *)str;
- (BOOL)startsWith:(NSString *)prefix;
- (BOOL)endsWith:(NSString *)suffix;
- (BOOL)equals:(NSString *)anotherString;
- (BOOL)equalsIgnoreCase:(NSString *)anotherString;
- (int)indexOfChar:(unichar)ch;
- (int)indexOfChar:(unichar)ch fromIndex:(int)index;
- (int)indexOfString:(NSString *)str;
- (int)indexOfString:(NSString *)str fromIndex:(int)index;
- (int)lastIndexOfChar:(unichar)ch;
- (int)lastIndexOfChar:(unichar)ch fromIndex:(int)index;
- (int)lastIndexOfString:(NSString *)str;
- (int)lastIndexOfString:(NSString *)str fromIndex:(int)index;
- (NSString *)substringFromIndex:(NSInteger)beginIndex
toIndex:(NSInteger)endIndex;
- (NSString *)toLowerCase;
- (NSString *)toUpperCase;
- (NSString *)trim;
- (NSString *)replaceAll:(NSString *)origin with:(NSString *)replacement;
- (NSArray *)split:(NSString *)separator;
@end
//
// Created by mo on 2020/3/8.
//
#import <fluwx/NSStringWrapper.h>
@implementation NSString (Wrapper)
/** Java-like method. Returns the char value at the specified index. */
- (unichar)charAt:(int)index {
return [self characterAtIndex:index];
}
/**
* Java-like method. Compares two strings lexicographically.
* the value 0 if the argument string is equal to this string;
* a value less than 0 if this string is lexicographically less than the string argument;
* and a value greater than 0 if this string is lexicographically greater than the string argument.
*/
- (int)compareTo:(NSString *)anotherString {
return (int)[self compare:anotherString];
}
/** Java-like method. Compares two strings lexicographically, ignoring case differences. */
- (int)compareToIgnoreCase:(NSString *)str {
return (int)[self compare:str options:NSCaseInsensitiveSearch];
}
/** Java-like method. Returns true if and only if this string contains the specified sequence of char values. */
- (BOOL)contains:(NSString *)str {
NSRange range = [self rangeOfString:str];
return (range.location != NSNotFound);
}
- (BOOL)startsWith:(NSString *)prefix {
return [self hasPrefix:prefix];
}
- (BOOL)endsWith:(NSString *)suffix {
return [self hasSuffix:suffix];
}
- (BOOL)equals:(NSString *)anotherString {
return [self isEqualToString:anotherString];
}
- (BOOL)equalsIgnoreCase:(NSString *)anotherString {
return [[self toLowerCase] equals:[anotherString toLowerCase]];
}
- (int)indexOfChar:(unichar)ch {
return [self indexOfChar:ch fromIndex:0];
}
- (int)indexOfChar:(unichar)ch fromIndex:(int)index {
int len = (int)self.length;
for (int i = index; i < len; ++i) {
if (ch == [self charAt:i]) {
return i;
}
}
return JavaNotFound;
}
- (int)indexOfString:(NSString *)str {
NSRange range = [self rangeOfString:str];
if (range.location == NSNotFound) {
return JavaNotFound;
}
return (int)range.location;
}
- (int)indexOfString:(NSString *)str fromIndex:(int)index {
NSRange fromRange = NSMakeRange(index, self.length - index);
NSRange range = [self rangeOfString:str options:NSLiteralSearch range:fromRange];
if (range.location == NSNotFound) {
return JavaNotFound;
}
return (int)range.location;
}
- (int)lastIndexOfChar:(unichar)ch {
int len = (int)self.length;
for (int i = len - 1; i >= 0; --i) {
if ([self charAt:i] == ch) {
return i;
}
}
return JavaNotFound;
}
- (int)lastIndexOfChar:(unichar)ch fromIndex:(int)index {
int len = (int)self.length;
if (index >= len) {
index = len - 1;
}
for (int i = index; i >= 0; --i) {
if ([self charAt:i] == ch) {
return index;
}
}
return JavaNotFound;
}
- (int)lastIndexOfString:(NSString *)str {
NSRange range = [self rangeOfString:str options:NSBackwardsSearch];
if (range.location == NSNotFound) {
return JavaNotFound;
}
return (int)range.location;
}
- (int)lastIndexOfString:(NSString *)str fromIndex:(int)index {
NSRange fromRange = NSMakeRange(0, index);
NSRange range = [self rangeOfString:str options:NSBackwardsSearch range:fromRange];
if (range.location == NSNotFound) {
return JavaNotFound;
}
return (int)range.location;
}
- (NSString *)substringFromIndex:(NSInteger)beginIndex
toIndex:(NSInteger)endIndex {
if (endIndex <= beginIndex) {
return @"";
}
NSRange range = NSMakeRange(beginIndex, endIndex - beginIndex);
return [self substringWithRange:range];
}
- (NSString *)toLowerCase {
return [self lowercaseString];
}
- (NSString *)toUpperCase {
return [self uppercaseString];
}
- (NSString *)trim {
return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
- (NSString *)replaceAll:(NSString *)origin with:(NSString *)replacement {
return [self stringByReplacingOccurrencesOfString:origin withString:replacement];
}
- (NSArray *)split:(NSString *)separator {
return [self componentsSeparatedByString:separator];
}
@end
//
// Created by mo on 2020/3/7.
//
#import <Foundation/Foundation.h>
@interface ThumbnailHelper : NSObject
+ (UIImage *)compressImage:(UIImage *)image toByte:(NSUInteger)maxLength isPNG:(BOOL)isPNG;
/// NSData 压缩后转NSData
/// @param imageData 来源data
/// @param maxLength 压缩目标值,压缩结果在maxLength的0.9~1之间
+ (NSData *)compressImageData:(NSData *)imageData toByte:(NSUInteger)maxLength;
@end
//
// Created by mo on 2020/3/7.
//
#import "ThumbnailHelper.h"
@implementation ThumbnailHelper
+ (NSData *)compressImageData:(NSData *)imageData toByte:(NSUInteger)maxLength {
// Compress by quality
CGFloat compression = 1;
NSData *data = imageData;
NSLog(@"压缩前 %lu %lu ",(unsigned long)data.length,maxLength);
if (data.length < maxLength) return data;
UIImage *image = [UIImage imageWithData:imageData];
CGFloat max = 1;
CGFloat min = 0;
for (int i = 0; i < 6; ++i) {
compression = (max + min) / 2;
data = UIImageJPEGRepresentation(image, compression);
if (data.length < maxLength * 0.9) {
min = compression;
} else if (data.length > maxLength) {
max = compression;
} else {
break;
}
}
NSLog(@"压缩第一次 %lu %lu ",(unsigned long)data.length,maxLength);
if (data.length < maxLength) return data;
UIImage *resultImage;
resultImage = [UIImage imageWithData:data];
// Compress by size
NSUInteger lastDataLength = 0;
while (data.length > maxLength && data.length != lastDataLength) {
lastDataLength = data.length;
CGFloat ratio = (CGFloat) maxLength / data.length;
CGSize size = CGSizeMake((NSUInteger) (resultImage.size.width * sqrtf(ratio)),
(NSUInteger) (resultImage.size.height * sqrtf(ratio))); // Use NSUInteger to prevent white blank
UIGraphicsBeginImageContext(size);
[resultImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
data = UIImageJPEGRepresentation(resultImage, compression);
}
NSLog(@"压缩第二次%lu %lu ",(unsigned long)data.length,maxLength);
return data;
}
+ (UIImage *)compressImage:(UIImage *)image toByte:(NSUInteger)maxLength isPNG:(BOOL)isPNG {
// Compress by quality
CGFloat compression = 1;
NSData *data = UIImageJPEGRepresentation(image, compression);
if (data.length < maxLength) return image;
CGFloat max = 1;
CGFloat min = 0;
for (int i = 0; i < 6; ++i) {
compression = (max + min) / 2;
data = UIImageJPEGRepresentation(image, compression);
if (data.length < maxLength * 0.9) {
min = compression;
} else if (data.length > maxLength) {
max = compression;
} else {
break;
}
}
UIImage *resultImage;
if (isPNG) {
NSData *tmp = UIImagePNGRepresentation([UIImage imageWithData:data]);
resultImage = [UIImage imageWithData:tmp];
} else {
resultImage = [UIImage imageWithData:data];
}
if (data.length < maxLength) return resultImage;
// Compress by size
NSUInteger lastDataLength = 0;
while (data.length > maxLength && data.length != lastDataLength) {
lastDataLength = data.length;
CGFloat ratio = (CGFloat) maxLength / data.length;
CGSize size = CGSizeMake((NSUInteger) (resultImage.size.width * sqrtf(ratio)),
(NSUInteger) (resultImage.size.height * sqrtf(ratio))); // Use NSUInteger to prevent white blank
UIGraphicsBeginImageContext(size);
[resultImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
data = UIImageJPEGRepresentation(resultImage, compression);
}
return resultImage;
}
- (UIImage *)scaleFromImage:(UIImage *)image width:(CGSize)newSize {
CGSize imageSize = image.size;
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;
if (width <= newSize.width && height <= newSize.height) {
return image;
}
if (width == 0 || height == 0) {
return image;
}
CGFloat widthFactor = newSize.width / width;
CGFloat heightFactor = newSize.height / height;
CGFloat scaleFactor = (widthFactor < heightFactor ? widthFactor : heightFactor);
CGFloat scaledWidth = width * scaleFactor;
CGFloat scaledHeight = height * scaleFactor;
CGSize targetSize = CGSizeMake(scaledWidth, scaledHeight);
UIGraphicsBeginImageContext(targetSize);
[image drawInRect:CGRectMake(0, 0, scaledWidth, scaledHeight)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
@end
//
// FluwxDelegate.h
// Pods
//
// Created by Mo on 2022/3/6.
//
#import <Foundation/Foundation.h>
@interface FluwxDelegate : NSObject
@property (strong,nonatomic)NSString *extMsg;
@property (strong,nonatomic)NSString *extData;
+ (instancetype)defaultManager;
- (void)registerWxAPI:(NSString *)appId universalLink:(NSString *)universalLink;
@end
#import <Flutter/Flutter.h>
@interface FluwxPlugin : NSObject<FlutterPlugin>
@end
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint fluwx.podspec` to validate before publishing.
#
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint fluwx.podspec` to validate before publishing.
#
pubspec = YAML.load_file(File.join('..', 'pubspec.yaml'))
library_version = pubspec['version'].gsub('+', '-')
current_dir = Dir.pwd
calling_dir = File.dirname(__FILE__)
project_dir = calling_dir.slice(0..(calling_dir.index('/.symlinks')))
symlinks_index = calling_dir.index('/ios/.symlinks')
if !symlinks_index
symlinks_index = calling_dir.index('/.ios/.symlinks')
end
flutter_project_dir = calling_dir.slice(0..(symlinks_index))
puts Psych::VERSION
psych_version_gte_500 = Gem::Version.new(Psych::VERSION) >= Gem::Version.new('5.0.0')
if psych_version_gte_500 == true
cfg = YAML.load_file(File.join(flutter_project_dir, 'pubspec.yaml'), aliases: true)
else
cfg = YAML.load_file(File.join(flutter_project_dir, 'pubspec.yaml'))
end
logging_status = "WECHAT_LOGGING=0"
if cfg['fluwx'] && cfg['fluwx']['debug_logging'] == true
logging_status = 'WECHAT_LOGGING=1'
else
logging_status = 'WECHAT_LOGGING=0'
end
scene_delegate = ''
if cfg['fluwx'] && cfg['fluwx']['ios'] && cfg['fluwx']['ios']['scene_delegate'] == true
scene_delegate = 'SCENE_DELEGATE=1'
else
scene_delegate = ''
end
if cfg['fluwx'] && cfg['fluwx']['ios'] && cfg['fluwx']['ios']['no_pay'] == true
fluwx_subspec = 'no_pay'
else
fluwx_subspec = 'pay'
end
Pod::UI.puts "using sdk with #{fluwx_subspec}"
app_id = ''
if cfg['fluwx'] && cfg['fluwx']['app_id']
app_id = cfg['fluwx']['app_id']
end
ignore_security = ''
if cfg['fluwx'] && cfg['fluwx']['ios'] && cfg['fluwx']['ios']['ignore_security'] == true
ignore_security = '-i'
end
Pod::UI.puts "ignore_security: #{ignore_security}"
universal_link = ''
if cfg['fluwx'] && (cfg['fluwx']['ios'] && cfg['fluwx']['ios']['universal_link'])
universal_link = cfg['fluwx']['ios']['universal_link']
end
Pod::UI.puts "app_id: #{app_id} universal_link: #{universal_link}"
system("ruby #{current_dir}/wechat_setup.rb #{ignore_security} -a #{app_id} -u #{universal_link} -p #{project_dir} -n Runner.xcodeproj")
Pod::Spec.new do |s|
s.name = 'fluwx'
s.version = '0.0.1'
s.summary = 'The capability of implementing WeChat SDKs in Flutter. With Fluwx, developers can use WeChatSDK easily, such as sharing, payment, lanuch mini program and etc.'
s.description = <<-DESC
The capability of implementing WeChat SDKs in Flutter. With Fluwx, developers can use WeChatSDK easily, such as sharing, payment, lanuch mini program and etc.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
s.platform = :ios, '11.0'
s.static_framework = true
s.default_subspec = fluwx_subspec
pod_target_xcconfig = {
'OTHER_LDFLAGS' => '$(inherited) -ObjC -all_load'
}
s.subspec 'pay' do |sp|
sp.dependency 'WechatOpenSDK-XCFramework','~> 2.0.2'
pod_target_xcconfig["GCC_PREPROCESSOR_DEFINITIONS"] = "$(inherited) #{logging_status} #{scene_delegate}"
sp.pod_target_xcconfig = pod_target_xcconfig
end
s.subspec 'no_pay' do |sp|
sp.dependency 'OpenWeChatSDKNoPay','~> 2.0.2+2'
sp.frameworks = 'CoreGraphics', 'Security', 'WebKit'
sp.libraries = 'c++', 'z', 'sqlite3.0'
pod_target_xcconfig["GCC_PREPROCESSOR_DEFINITIONS"] = "$(inherited) NO_PAY=1 #{logging_status} #{scene_delegate}"
sp.pod_target_xcconfig = pod_target_xcconfig
end
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
end
#
# Reference documentations
# https://github.com/firebase/flutterfire/blob/master/packages/firebase_crashlytics/firebase_crashlytics/ios/crashlytics_add_upload_symbols
# https://github.com/MagicalWater/Base-APP-Env/blob/master/fastlane/actions/xcode_parse.rb
#
require 'xcodeproj'
require 'plist'
require 'optparse'
require 'uri'
# Dictionary to hold command line arguments
options_dict = {}
# Parse command line arguments into options_dict
OptionParser.new do |options|
options.banner = "Setup the Wechat to an Xcode target."
options.on("-p", "--projectDirectory=DIRECTORY", String, "Directory of the Xcode project") do |dir|
options_dict[:project_dir] = dir
end
options.on("-n", "--projectName=NAME", String, "Name of the Xcode project (ex: Runner.xcodeproj)") do |name|
options_dict[:project_name] = name
end
options.on("-i", "--ignoreSecurity", "Ignore modifying NSAppTransportSecurity") do |opts|
options_dict[:ignore_security] = true
end
options.on("-a", "--appId=APPID", String, "App ID for Wechat") do |opts|
options_dict[:app_id] = opts
end
options.on("-u", "--universalLink=UNIVERSALLINK", String, "Universal Link for Wechat") do |opts|
options_dict[:universal_link] = opts
end
end.parse!
# Minimum required arguments are a project directory and project name
unless (options_dict[:project_dir] and options_dict[:project_name])
abort("Must provide a project directory and project name.\n")
end
# Path to the Xcode project to modify
project_path = File.join(options_dict[:project_dir], options_dict[:project_name])
unless (File.exist?(project_path))
abort("Project at #{project_path} does not exist. Please check paths manually.\n");
end
# Actually open and modify the project
project = Xcodeproj::Project.open(project_path)
project.targets.each do |target|
if target.name == "Runner"
app_id = options_dict[:app_id]
universal_link = options_dict[:universal_link]
applinks = ''
if (!app_id.nil? && !app_id.empty?)
applinks = "applinks:#{URI.parse(universal_link).host}"
end
sectionObject = {}
project.objects.each do |object|
if object.uuid == target.uuid
sectionObject = object
break
end
end
sectionObject.build_configurations.each do |config|
infoplist = config.build_settings["INFOPLIST_FILE"]
if !infoplist
abort("INFOPLIST_FILE is not exist\n")
end
infoplistFile = File.join(options_dict[:project_dir], infoplist)
if !File.exist?(infoplistFile)
abort("#{infoplist} is not exist\n")
end
result = Plist.parse_xml(infoplistFile, marshal: false)
if !result
result = {}
end
urlTypes = result["CFBundleURLTypes"]
if !urlTypes
urlTypes = []
result["CFBundleURLTypes"] = urlTypes
end
isUrlTypeExist = urlTypes.any? { |urlType| urlType["CFBundleURLSchemes"] && (urlType["CFBundleURLSchemes"].include? app_id) }
if !app_id.nil? && !app_id.empty? && !isUrlTypeExist
print("writing app id\n ")
urlTypes << {
"CFBundleTypeRole": "Editor",
"CFBundleURLName": "weixin",
"CFBundleURLSchemes": [ app_id ]
}
File.write(infoplistFile, Plist::Emit.dump(result))
end
queriesSchemes = result["LSApplicationQueriesSchemes"]
if !queriesSchemes
queriesSchemes = []
result["LSApplicationQueriesSchemes"] = queriesSchemes
end
wechatQueriesSchemes = ["weixin", "weixinULAPI", "weixinURLParamsAPI"]
if wechatQueriesSchemes.any? { |queriesScheme| !(queriesSchemes.include? queriesScheme) }
wechatQueriesSchemes.each do |queriesScheme|
if !(queriesSchemes.include? queriesScheme)
queriesSchemes << queriesScheme
end
end
File.write(infoplistFile, Plist::Emit.dump(result))
end
if !options_dict[:ignore_security]
security = result["NSAppTransportSecurity"]
if !security
security = {}
result["NSAppTransportSecurity"] = security
end
if security["NSAllowsArbitraryLoads"] != true
security["NSAllowsArbitraryLoads"] = true
File.write(infoplistFile, Plist::Emit.dump(result))
end
if security["NSAllowsArbitraryLoadsInWebContent"] != true
security["NSAllowsArbitraryLoadsInWebContent"] = true
File.write(infoplistFile, Plist::Emit.dump(result))
end
end
end
sectionObject.build_configurations.each do |config|
codeSignEntitlements = config.build_settings["CODE_SIGN_ENTITLEMENTS"]
if !codeSignEntitlements
codeSignEntitlements = "Runner/Runner.entitlements"
config.build_settings["CODE_SIGN_ENTITLEMENTS"] = codeSignEntitlements
project.save()
end
codeSignEntitlementsFile = File.join(options_dict[:project_dir], codeSignEntitlements)
if !File.exist?(codeSignEntitlementsFile)
content = Plist::Emit.dump({})
File.write(codeSignEntitlementsFile, content)
end
runnerTargetMainGroup = project.main_group.find_subpath('Runner', false)
isRefExist = runnerTargetMainGroup.files.any? { |file| file.path.include? File.basename(codeSignEntitlementsFile) }
if !isRefExist
runnerTargetMainGroup.new_reference(File.basename(codeSignEntitlementsFile))
project.save()
end
result = Plist.parse_xml(codeSignEntitlementsFile, marshal: false)
if !result
result = {}
end
domains = result["com.apple.developer.associated-domains"]
if !domains
domains = []
result["com.apple.developer.associated-domains"] = domains
end
isApplinksExist = domains.include? applinks
if !isApplinksExist
domains << applinks
File.write(codeSignEntitlementsFile, Plist::Emit.dump(result))
end
end
end
end
/*
* Copyright (c) 2023. OpenFlutter Project
*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. The ASF licenses this
* file to you under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
/// Fluwx is a powerful plugin for WeChatSDK.
/// easy to use.
///
/// A open sou;rce project authorized by [OpenFlutter](https://github.com/OpenFlutter).
library fluwx;
export 'src/fluwx.dart';
export 'src/foundation/arguments.dart';
export 'src/foundation/cancelable.dart' hide FluwxCancelableImpl;
export 'src/response/wechat_response.dart';
export 'src/wechat_enums.dart';
export 'src/wechat_file.dart' hide FileSchema;
/*
* Copyright (c) 2023. OpenFlutter Project
*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. The ASF licenses this
* file to you under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import 'dart:async';
import 'foundation/arguments.dart';
import 'foundation/cancelable.dart';
import 'method_channel/fluwx_platform_interface.dart';
import 'response/wechat_response.dart';
class Fluwx {
late final WeakReference<void Function(WeChatResponse event)>
responseListener;
final List<WeChatResponseSubscriber> _responseListeners = [];
Fluwx() {
responseListener = WeakReference((event) {
for (var listener in _responseListeners) {
listener(event);
}
});
final target = responseListener.target;
if (target != null) {
FluwxPlatform.instance.responseEventHandler.listen(target);
}
}
Future<bool> get isWeChatInstalled =>
FluwxPlatform.instance.isWeChatInstalled;
/// Open given target. See [OpenType] for more details
Future<bool> open({required OpenType target}) {
return FluwxPlatform.instance.open(target);
}
/// It's ok if you register multi times.
/// [appId] is not necessary.
/// if [doOnIOS] is true ,fluwx will register WXApi on iOS.
/// if [doOnAndroid] is true, fluwx will register WXApi on Android.
/// [universalLink] is required if you want to register on iOS.
Future<bool> registerApi({
required String appId,
bool doOnIOS = true,
bool doOnAndroid = true,
String? universalLink,
}) async {
return FluwxPlatform.instance.registerApi(
appId: appId,
doOnAndroid: doOnAndroid,
doOnIOS: doOnIOS,
universalLink: universalLink);
}
/// Share your requests to WeChat.
/// This depends on the actual type of [what].
Future<bool> share(WeChatShareModel what) async {
return FluwxPlatform.instance.share(what);
}
/// Login by WeChat.See [AuthType] for more details.
Future<bool> authBy({required AuthType which}) async {
return FluwxPlatform.instance.authBy(which);
}
/// Stop QR service
Future<bool> stopAuthByQRCode() => FluwxPlatform.instance.stopAuthByQRCode();
/// please read * [official docs](https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter3_2.shtml).
Future<bool> autoDeduct({required AutoDeduct data}) async {
return FluwxPlatform.instance.autoDeduct(data);
}
Future<bool> get isSupportOpenBusinessView async =>
await FluwxPlatform.instance.isSupportOpenBusinessView;
Future<String?> getExtMsg() async {
return FluwxPlatform.instance.getExtMsg();
}
/// Pay with WeChat. See [PayType]
Future<bool> pay({required PayType which}) async {
return FluwxPlatform.instance.pay(which);
}
/// Try to reload data from cold boot. For example, the app is launched by mini program and
/// we can get ext message by calling this.
Future<void> attemptToResumeMsgFromWx() async {
return FluwxPlatform.instance.attemptToResumeMsgFromWx();
}
/// Only works on iOS in debug mode.
/// Check if your app can work with WeChat correctly.
/// Please make sure [registerApi] returns true before self check.
Future<void> selfCheck() async {
return FluwxPlatform.instance.selfCheck();
}
/// Subscribe responses from WeChat
@Deprecated("use [addSubscriber] instead")
FluwxCancelable subscribeResponse(WeChatResponseSubscriber listener) {
return addSubscriber(listener);
}
/// Add a subscriber to subscribe responses from WeChat
FluwxCancelable addSubscriber(WeChatResponseSubscriber listener) {
_responseListeners.add(listener);
return FluwxCancelableImpl(onCancel: () {
removeSubscriber(listener);
});
}
/// Unsubscribe responses from WeChat
@Deprecated("use [removeSubscriber] instead")
unsubscribeResponse(WeChatResponseSubscriber listener) {
removeSubscriber(listener);
}
/// remove your subscriber from WeChat
removeSubscriber(WeChatResponseSubscriber listener) {
_responseListeners.remove(listener);
}
/// remove all existing
clearSubscribers() {
_responseListeners.clear();
}
}
/*
* Copyright (c) 2023. OpenFlutter Project
*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. The ASF licenses this
* file to you under the Apache License, Version 2.0 (the 'License'); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
// In order to *not* need this ignore, consider extracting the "web" version
// of your plugin as a separate package, instead of inlining it in the same
// package as the core of your plugin.
// ignore: avoid_web_libraries_in_flutter
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'method_channel/fluwx_platform_interface.dart';
/// A web implementation of the FluwxPlatform of the Fluwx plugin.
class FluwxWeb extends FluwxPlatform {
/// Constructs a FluwxWeb
FluwxWeb();
static void registerWith(Registrar registrar) {
FluwxPlatform.instance = FluwxWeb();
}
}
/*
* Copyright (c) 2023. OpenFlutter Project
*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. The ASF licenses this
* file to you under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import '../wechat_enums.dart';
import '../wechat_file.dart';
part 'auth_type.dart';
part 'auto_deduct.dart';
part 'open_type.dart';
part 'pay_type.dart';
part 'share_models.dart';
mixin _Argument {
Map<String, dynamic> get arguments => {};
}
part of 'arguments.dart';
sealed class AuthType with _Argument {}
/// The WeChat-Login is under Auth-2.0
/// This method login with native WeChat app.
/// For users without WeChat app, please use [QRCode] instead
/// This method only supports getting AuthCode,this is first step to login with WeChat
/// Once AuthCode got, you need to request Access_Token
/// For more information please visit:
/// * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419317851&token=
class NormalAuth extends AuthType {
final String scope;
final String state;
final bool nonAutomatic;
NormalAuth(
{required this.scope, this.state = 'state', this.nonAutomatic = false})
: assert(scope.trim().isNotEmpty);
@override
Map<String, dynamic> get arguments =>
{'scope': scope, 'state': state, 'nonAutomatic': nonAutomatic};
}
/// Sometimes WeChat is not installed on users's devices.However we can
/// request a QRCode so that we can get AuthCode by scanning the QRCode
/// All required params must not be null or empty
/// [schemeData] only works on iOS
/// see * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=215238808828h4XN&token=&lang=zh_CN
class QRCode extends AuthType {
final String appId;
final String scope;
final String nonceStr;
final String timestamp;
final String signature;
final String? schemeData;
QRCode({
required this.appId,
required this.scope,
required this.nonceStr,
required this.timestamp,
required this.signature,
this.schemeData,
}) : assert(appId.isNotEmpty),
assert(scope.isNotEmpty),
assert(nonceStr.isNotEmpty),
assert(timestamp.isNotEmpty),
assert(signature.isNotEmpty);
@override
Map<String, dynamic> get arguments => {
'appId': appId,
'scope': scope,
'nonceStr': nonceStr,
'timeStamp': timestamp,
'signature': signature,
'schemeData': schemeData
};
}
/// Currently only support iOS
class PhoneLogin extends AuthType {
final String scope;
final String state;
PhoneLogin({
required this.scope,
this.state = 'state',
});
@override
Map<String, dynamic> get arguments => {'scope': scope, 'state': state};
}
part of 'arguments.dart';
class AutoDeduct with _Argument {
final Map<String, String> queryInfo;
final int businessType;
final Map<String, dynamic> _detailData;
bool get isV2 => _detailData.isEmpty;
AutoDeduct.detail({
required String appId,
required String mchId,
required String planId,
required String contractCode,
required String requestSerial,
required String contractDisplayAccount,
required String notifyUrl,
required String version,
required String sign,
required String timestamp,
String returnApp = '3',
this.businessType = 12,
}) : queryInfo = {},
_detailData = {
'appid': appId,
'mch_id': mchId,
'plan_id': planId,
'contract_code': contractCode,
'request_serial': requestSerial,
'contract_display_account': contractDisplayAccount,
'notify_url': notifyUrl,
'version': version,
'sign': sign,
'timestamp': timestamp,
'return_app': returnApp,
'businessType': businessType
};
AutoDeduct.custom({required this.queryInfo, this.businessType = 12})
: _detailData = {};
@override
Map<String, dynamic> get arguments => isV2
? {'queryInfo': queryInfo, 'businessType': businessType}
: _detailData;
}
/*
* Copyright (c) 2023. OpenFlutter Project
*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. The ASF licenses this
* file to you under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import '../response/wechat_response.dart';
typedef WeChatResponseSubscriber = Function(WeChatResponse response);
mixin FluwxCancelable {
cancel();
}
class FluwxCancelableImpl implements FluwxCancelable {
final Function onCancel;
FluwxCancelableImpl({required this.onCancel});
@override
cancel() {
onCancel();
}
}
part of 'arguments.dart';
sealed class OpenType with _Argument {}
/// Just open WeChat app.
class WeChatApp extends OpenType {}
/// Open WeChat browser with given url.
class Browser extends OpenType {
final String url;
Browser(this.url);
@override
Map<String, dynamic> get arguments => {'url': url};
}
/// Rank list
class RankList extends OpenType {}
/// see * https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter6_2_1.shtml
class BusinessView extends OpenType {
final String businessType;
final String query;
BusinessView({required this.businessType, required this.query});
@override
Map<String, dynamic> get arguments =>
{"businessType": businessType, "query": query};
}
class Invoice extends OpenType {
final String appId;
final String cardType;
final String locationId;
final String cardId;
final String canMultiSelect;
Invoice(
{required this.appId,
required this.cardType,
this.locationId = "",
this.cardId = "",
this.canMultiSelect = "1"});
@override
Map<String, dynamic> get arguments => {
"appId": appId,
"cardType": cardType,
"locationId": locationId,
"cardId": cardId,
"canMultiSelect": canMultiSelect
};
}
class CustomerServiceChat extends OpenType {
final String corpId;
final String url;
CustomerServiceChat({required this.corpId, required this.url});
@override
Map<String, dynamic> get arguments => {"corpId": corpId, "url": url};
}
/// open mini-program
/// see [WXMiniProgramType]
class MiniProgram extends OpenType {
final String username;
final String? path;
final WXMiniProgramType miniProgramType;
MiniProgram({
required this.username,
this.path,
this.miniProgramType = WXMiniProgramType.release,
}) : assert(username.trim().isNotEmpty);
@override
Map<String, dynamic> get arguments => {
'userName': username,
'path': path,
'miniProgramType': miniProgramType.value
};
}
/// See *https://developers.weixin.qq.com/doc/oplatform/Mobile_App/One-time_subscription_info.html for more detail
class SubscribeMessage extends OpenType {
final String appId;
final int scene;
final String templateId;
final String? reserved;
SubscribeMessage({
required this.appId,
required this.scene,
required this.templateId,
this.reserved,
});
@override
Map<String, dynamic> get arguments => {
'appId': appId,
'scene': scene,
'templateId': templateId,
'reserved': reserved,
};
}
part of 'arguments.dart';
sealed class PayType with _Argument {}
/// request payment with WeChat.
/// Read the official document for more detail.
/// [timestamp] is int because [timestamp] will be mapped to Unit32.
class Payment extends PayType {
final String appId;
final String partnerId;
final String prepayId;
final String packageValue;
final String nonceStr;
final int timestamp;
final String sign;
final String? signType;
final String? extData;
Payment({
required this.appId,
required this.partnerId,
required this.prepayId,
required this.packageValue,
required this.nonceStr,
required this.timestamp,
required this.sign,
this.signType,
this.extData,
});
@override
Map<String, dynamic> get arguments => {
'appId': appId,
'partnerId': partnerId,
'prepayId': prepayId,
'packageValue': packageValue,
'nonceStr': nonceStr,
'timeStamp': timestamp,
'sign': sign,
'signType': signType,
'extData': extData,
};
}
/// request Hong Kong Wallet payment with WeChat.
/// Read the official document for more detail.
class HongKongWallet extends PayType {
final String prepayId;
HongKongWallet({required this.prepayId});
@override
Map<String, dynamic> get arguments => {
'prepayId': prepayId,
};
}
/*
* Copyright (c) 2023. OpenFlutter Project
*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. The ASF licenses this
* file to you under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
part of 'arguments.dart';
const String _scene = "scene";
const String _source = "source";
const String _thumbnail = "thumbnail";
const String _title = "title";
const String _description = "description";
const String _messageExt = "messageExt";
const String _mediaTagName = "mediaTagName";
const String _messageAction = "messageAction";
const String _compressThumbnail = "compressThumbnail";
const String _msgSignature = "msgSignature";
sealed class WeChatShareModel with _Argument {}
/// [source] the text you want to send to WeChat
/// [scene] the target you want to send
class WeChatShareTextModel extends WeChatShareModel {
WeChatShareTextModel(
this.source, {
this.scene = WeChatScene.session,
this.mediaTagName,
this.messageAction,
this.messageExt,
this.msgSignature,
String? description,
String? title,
}) : title = title ?? source,
description = description ?? source;
final String source;
final WeChatScene scene;
final String? messageExt;
final String? messageAction;
final String? mediaTagName;
final String? title;
final String? description;
final String? msgSignature;
@override
Map<String, dynamic> get arguments => {
_scene: scene.index,
_source: source,
_messageExt: messageExt,
_messageAction: messageAction,
_mediaTagName: mediaTagName,
_title: title,
_description: description,
_msgSignature: msgSignature
};
}
/// the default value is [MINI_PROGRAM_TYPE_RELEASE]
/// [hdImagePath] only works on iOS, not sure the relationship
/// between [thumbnail] and [hdImagePath].
class WeChatShareMiniProgramModel extends WeChatShareModel {
WeChatShareMiniProgramModel(
{required this.webPageUrl,
this.miniProgramType = WXMiniProgramType.release,
required this.userName,
this.path = "/",
this.title,
this.description,
this.withShareTicket = false,
required this.thumbnail,
this.hdImagePath,
this.mediaTagName,
this.messageAction,
this.messageExt,
this.compressThumbnail = true,
this.msgSignature})
: assert(webPageUrl.isNotEmpty),
assert(userName.isNotEmpty),
assert(path.isNotEmpty);
final String webPageUrl;
final WXMiniProgramType miniProgramType;
final String userName;
final String path;
final WeChatImage? hdImagePath;
final String? title;
final String? description;
final WeChatImage thumbnail;
final bool withShareTicket;
final String? messageExt;
final String? messageAction;
final String? mediaTagName;
final bool compressThumbnail;
final String? msgSignature;
@override
Map<String, dynamic> get arguments => {
'webPageUrl': webPageUrl,
"miniProgramType": miniProgramType.value,
"userName": userName,
"path": path,
"title": title,
_description: description,
"withShareTicket": withShareTicket,
_thumbnail: thumbnail.toMap(),
"hdImagePath": hdImagePath?.toMap(),
_messageAction: messageAction,
_mediaTagName: mediaTagName,
_compressThumbnail: compressThumbnail,
_msgSignature: msgSignature
};
}
/// [source] the image you want to send to WeChat
/// [scene] the target you want to send
/// [thumbnail] the preview of your image, will be created from [scene] if null.
class WeChatShareImageModel extends WeChatShareModel {
WeChatShareImageModel(this.source,
{WeChatImage? thumbnail,
this.title,
this.scene = WeChatScene.session,
this.description,
this.mediaTagName,
this.messageAction,
this.messageExt,
this.compressThumbnail = true,
this.msgSignature})
: thumbnail = thumbnail ?? source;
final WeChatImage source;
final WeChatImage thumbnail;
final String? title;
final WeChatScene scene;
final String? description;
final String? messageExt;
final String? messageAction;
final String? mediaTagName;
final bool compressThumbnail;
final String? msgSignature;
@override
Map<String, dynamic> get arguments => {
_scene: scene.index,
_source: source.toMap(),
_thumbnail: thumbnail.toMap(),
_title: title,
_description: description,
_messageAction: messageAction,
_mediaTagName: mediaTagName,
_compressThumbnail: compressThumbnail,
_msgSignature: msgSignature
};
}
/// if [musicUrl] and [musicLowBandUrl] are both provided,
/// only [musicUrl] will be used.
class WeChatShareMusicModel extends WeChatShareModel {
WeChatShareMusicModel(
{this.musicUrl,
this.musicLowBandUrl,
this.title = "",
this.description = "",
this.musicDataUrl,
this.musicLowBandDataUrl,
this.thumbnail,
this.mediaTagName,
this.messageAction,
this.messageExt,
this.scene = WeChatScene.session,
this.compressThumbnail = true,
this.msgSignature})
: assert(musicUrl != null || musicLowBandUrl != null);
final String? musicUrl;
final String? musicDataUrl;
final String? musicLowBandUrl;
final String? musicLowBandDataUrl;
final WeChatImage? thumbnail;
final String? title;
final String? description;
final WeChatScene scene;
final String? messageExt;
final String? messageAction;
final String? mediaTagName;
final bool compressThumbnail;
final String? msgSignature;
@override
Map<String, dynamic> get arguments => {
_scene: scene.index,
"musicUrl": musicUrl,
"musicDataUrl": musicDataUrl,
"musicLowBandUrl": musicLowBandUrl,
"musicLowBandDataUrl": musicLowBandDataUrl,
_thumbnail: thumbnail?.toMap(),
_title: title,
_description: description,
_messageAction: messageAction,
_mediaTagName: mediaTagName,
_compressThumbnail: compressThumbnail,
_msgSignature: msgSignature
};
}
/// if [videoUrl] and [videoLowBandUrl] are both provided,
/// only [videoUrl] will be used.
class WeChatShareVideoModel extends WeChatShareModel {
WeChatShareVideoModel(
{this.scene = WeChatScene.session,
this.videoUrl,
this.videoLowBandUrl,
this.title = "",
this.description = "",
this.thumbnail,
this.mediaTagName,
this.messageAction,
this.messageExt,
this.compressThumbnail = true,
this.msgSignature})
: assert(videoUrl != null || videoLowBandUrl != null),
assert(thumbnail != null);
final String? videoUrl;
final String? videoLowBandUrl;
final WeChatImage? thumbnail;
final String? title;
final String? description;
final WeChatScene scene;
final String? messageExt;
final String? messageAction;
final String? mediaTagName;
final bool compressThumbnail;
final String? msgSignature;
@override
Map<String, dynamic> get arguments => {
_scene: scene.index,
"videoUrl": videoUrl,
"videoLowBandUrl": videoLowBandUrl,
_thumbnail: thumbnail?.toMap(),
_title: title,
_description: description,
_messageAction: messageAction,
_mediaTagName: mediaTagName,
_compressThumbnail: compressThumbnail,
_msgSignature: msgSignature
};
}
/// [webPage] url you want to send to wechat
/// [thumbnail] logo of your website
class WeChatShareWebPageModel extends WeChatShareModel {
WeChatShareWebPageModel(
this.webPage, {
this.title = "",
String? description,
this.thumbnail,
this.scene = WeChatScene.session,
this.mediaTagName,
this.messageAction,
this.messageExt,
this.msgSignature,
this.compressThumbnail = true,
}) : assert(webPage.isNotEmpty),
description = description ?? webPage;
final String webPage;
final WeChatImage? thumbnail;
final String title;
final String description;
final WeChatScene scene;
final String? messageExt;
final String? messageAction;
final String? mediaTagName;
final bool compressThumbnail;
final String? msgSignature;
@override
Map<String, dynamic> get arguments => {
_scene: scene.index,
"webPage": webPage,
_thumbnail: thumbnail?.toMap(),
_title: title,
_messageAction: messageAction,
_mediaTagName: mediaTagName,
_description: description,
_compressThumbnail: compressThumbnail,
_msgSignature: msgSignature
};
}
/// [source] the file you want to share, [source.suffix] is necessary on iOS.
/// [scene] can't be [WeChatScene.TIMELINE], otherwise, sharing nothing.
/// send files to WeChat
class WeChatShareFileModel extends WeChatShareModel {
WeChatShareFileModel(
this.source, {
this.title = "",
this.description = "",
this.thumbnail,
this.scene = WeChatScene.session,
this.mediaTagName,
this.messageAction,
this.messageExt,
this.msgSignature,
this.compressThumbnail = true,
});
final WeChatFile source;
final WeChatImage? thumbnail;
final String title;
final String description;
final WeChatScene scene;
final String? messageExt;
final String? messageAction;
final String? mediaTagName;
final bool compressThumbnail;
final String? msgSignature;
@override
Map<String, dynamic> get arguments => {
_scene: scene.index,
_source: source.toMap(),
_thumbnail: thumbnail?.toMap(),
_title: title,
_description: description,
_messageAction: messageAction,
_mediaTagName: mediaTagName,
_compressThumbnail: compressThumbnail,
_msgSignature: msgSignature
};
}
/*
* Copyright (c) 2023. OpenFlutter Project
*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. The ASF licenses this
* file to you under the Apache License, Version 2.0 (the 'License'); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import '../foundation/arguments.dart';
import '../response/wechat_response.dart';
import 'fluwx_platform_interface.dart';
/// An implementation of [FluwxPlatform] that uses method channels.
class MethodChannelFluwx extends FluwxPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('com.jarvanmo/fluwx');
MethodChannelFluwx() {
methodChannel.setMethodCallHandler(_methodHandler);
}
final Map<Type, String> _shareModelMethodMapper = {
WeChatShareTextModel: 'shareText',
WeChatShareImageModel: 'shareImage',
WeChatShareMusicModel: 'shareMusic',
WeChatShareVideoModel: 'shareVideo',
WeChatShareWebPageModel: 'shareWebPage',
WeChatShareMiniProgramModel: 'shareMiniProgram',
WeChatShareFileModel: 'shareFile',
};
final StreamController<WeChatResponse> _responseEventHandler =
StreamController.broadcast();
/// Response answers from WeChat after sharing, payment etc.
@override
Stream<WeChatResponse> get responseEventHandler =>
_responseEventHandler.stream;
Future _methodHandler(MethodCall methodCall) {
if (methodCall.method == "wechatLog") {
_printLog(methodCall.arguments);
} else {
final response = WeChatResponse.create(
methodCall.method,
methodCall.arguments,
);
_responseEventHandler.add(response);
}
return Future.value();
}
_printLog(Map data) {
debugPrint("FluwxLog: ${data["detail"]}");
}
/// [true] if WeChat installed, otherwise [false].
/// Please add WeChat to the white list in order use this method on IOS.
@override
Future<bool> get isWeChatInstalled async {
return await methodChannel.invokeMethod('isWeChatInstalled');
}
/// The OpenType has various types, which are:
/// WeChatApp
/// Browser
/// RankList
/// BusinessView
/// Invoice
/// CustomerServiceChat
/// MiniProgram
/// SubscribeMessage
/// How to use:
///  Fluwx().open(
///  target: CustomerServiceChat(
///    url: 'URL',
///    corpId: 'ID',
///    ),
/// );
@override
Future<bool> open(OpenType target) async {
switch (target) {
case WeChatApp():
return await methodChannel.invokeMethod('openWXApp') ?? false;
case Browser():
return await methodChannel.invokeMethod('openUrl') ?? false;
case RankList():
return await methodChannel.invokeMethod("openRankList") ?? false;
case BusinessView():
return await methodChannel.invokeMethod(
"openBusinessView", target.arguments) ??
false;
case Invoice():
return await methodChannel.invokeMethod(
"openWeChatInvoice", target.arguments) ??
false;
case CustomerServiceChat():
return await methodChannel.invokeMethod(
"openWeChatCustomerServiceChat", target.arguments) ??
false;
case MiniProgram():
return await methodChannel.invokeMethod(
'launchMiniProgram', target.arguments) ??
false;
case SubscribeMessage():
return await methodChannel.invokeMethod(
'subscribeMsg', target.arguments);
}
}
@override
Future<bool> registerApi({
required String appId,
bool doOnIOS = true,
bool doOnAndroid = true,
String? universalLink,
}) async {
if (doOnIOS && Platform.isIOS) {
if (universalLink == null ||
universalLink.trim().isEmpty ||
!universalLink.startsWith('https')) {
throw ArgumentError.value(
universalLink,
"You're trying to use illegal universal link, see "
'https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html '
'for more detail',
);
}
}
return await methodChannel.invokeMethod('registerApp', {
'appId': appId,
'iOS': doOnIOS,
'android': doOnAndroid,
'universalLink': universalLink
});
}
/// Get ext Message
@override
Future<String?> getExtMsg() {
return methodChannel.invokeMethod('getExtMsg');
}
/// see [_shareModelMethodMapper] for detail.
@override
Future<bool> share(WeChatShareModel what) async {
if (_shareModelMethodMapper.containsKey(what.runtimeType)) {
final channelName = _shareModelMethodMapper[what.runtimeType];
if (channelName == null) {
throw ArgumentError.value(
'${what.runtimeType} method channel not found',
);
}
return await methodChannel.invokeMethod(channelName, what.arguments);
}
return Future.error('no method mapper found[${what.runtimeType}]');
}
@override
Future<bool> authBy(AuthType which) async {
switch (which) {
case NormalAuth():
return await methodChannel.invokeMethod(
'sendAuth',
which.arguments,
);
case QRCode():
return await methodChannel.invokeMethod(
'authByQRCode', which.arguments) ??
false;
case PhoneLogin():
return await methodChannel.invokeMethod(
'authByPhoneLogin', which.arguments);
}
}
@override
Future<bool> pay(PayType which) async {
switch (which) {
case Payment():
return await methodChannel.invokeMethod(
'payWithFluwx', which.arguments);
case HongKongWallet():
return await methodChannel.invokeMethod(
'payWithHongKongWallet', which.arguments);
}
}
@override
Future<bool> autoDeduct(AutoDeduct data) async {
return await methodChannel.invokeMethod(
data.isV2 ? "autoDeductV2" : 'autoDeduct', data.arguments) ??
false;
}
/// stop [authWeChatByQRCode]
@override
Future<bool> stopAuthByQRCode() async {
return await methodChannel.invokeMethod('stopAuthByQRCode');
}
///Only works on iOS in debug mode.
@override
Future<void> selfCheck() async {
return await methodChannel.invokeMethod('selfCheck');
}
@override
Future<void> attemptToResumeMsgFromWx() async {
return await methodChannel.invokeMethod("attemptToResumeMsgFromWx");
}
@override
Future<bool> get isSupportOpenBusinessView async =>
await methodChannel.invokeMethod("checkSupportOpenBusinessView");
}
/*
* Copyright (c) 2023. OpenFlutter Project
*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. The ASF licenses this
* file to you under the Apache License, Version 2.0 (the 'License'); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import 'package:fluwx/fluwx.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'fluwx_method_channel.dart';
abstract class FluwxPlatform extends PlatformInterface {
/// Constructs a FluwxPlatform.
FluwxPlatform() : super(token: _token);
static final Object _token = Object();
static FluwxPlatform _instance = MethodChannelFluwx();
/// The default instance of [FluwxPlatform] to use.
///
/// Defaults to [MethodChannelFluwx].
static FluwxPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [FluwxPlatform] when
/// they register themselves.
static set instance(FluwxPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Stream<WeChatResponse> get responseEventHandler {
throw UnimplementedError('responseEventHandler has not been implemented.');
}
Future<bool> get isWeChatInstalled {
throw UnimplementedError('isWeChatInstalled has not been implemented.');
}
Future<bool> open(OpenType target) {
throw UnimplementedError('open() has not been implemented.');
}
Future<bool> registerApi({
required String appId,
bool doOnIOS = true,
bool doOnAndroid = true,
String? universalLink,
}) {
throw UnimplementedError('registerWxApi() has not been implemented.');
}
Future<String?> getExtMsg() {
throw UnimplementedError('getExtMsg() has not been implemented.');
}
Future<bool> share(WeChatShareModel what) {
throw UnimplementedError('share() has not been implemented.');
}
Future<bool> sendAuth(
{required String scope,
String state = 'state',
bool nonAutomatic = false}) {
throw UnimplementedError('sendAuth() has not been implemented.');
}
Future<bool> authByPhoneLogin({
required String scope,
String state = 'state',
}) async {
throw UnimplementedError('authByPhoneLogin() has not been implemented.');
}
Future<bool> authByQRCode({
required String appId,
required String scope,
required String nonceStr,
required String timestamp,
required String signature,
String? schemeData,
}) async {
throw UnimplementedError('authByQRCode() has not been implemented.');
}
Future<bool> stopAuthByQRCode() async {
throw UnimplementedError(
'stopWeChatAuthByQRCode() has not been implemented.');
}
Future<bool> pay(PayType which) {
throw UnimplementedError('pay() has not been implemented.');
}
Future<bool> autoDeduct(AutoDeduct data) {
throw UnimplementedError('autoDeduct() has not been implemented.');
}
Future<bool> authBy(AuthType which) {
throw UnimplementedError('authBy() has not been implemented.');
}
Future<void> attemptToResumeMsgFromWx() {
throw UnimplementedError('attemptToResumeMsgFromWx() has not been implemented.');
}
Future<void> selfCheck(){
throw UnimplementedError('selfCheck() has not been implemented.');
}
Future<bool> get isSupportOpenBusinessView async {
throw UnimplementedError(
'isSupportOpenBusinessView() has not been implemented.');
}
}
/*
* Copyright (c) 2020. OpenFlutter Project
*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. The ASF licenses this
* file to you under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import 'dart:typed_data';
const String _errCode = 'errCode';
const String _errStr = 'errStr';
typedef _WeChatResponseInvoker = WeChatResponse Function(Map argument);
Map<String, _WeChatResponseInvoker> _nameAndResponseMapper = {
'onShareResponse': (Map argument) => WeChatShareResponse.fromMap(argument),
'onAuthResponse': (Map argument) => WeChatAuthResponse.fromMap(argument),
'onLaunchMiniProgramResponse': (Map argument) =>
WeChatLaunchMiniProgramResponse.fromMap(argument),
'onPayResponse': (Map argument) => WeChatPaymentResponse.fromMap(argument),
'onSubscribeMsgResp': (Map argument) =>
WeChatSubscribeMsgResponse.fromMap(argument),
'onWXOpenBusinessWebviewResponse': (Map argument) =>
WeChatOpenBusinessWebviewResponse.fromMap(argument),
'onAuthByQRCodeFinished': (Map argument) =>
WeChatAuthByQRCodeFinishedResponse.fromMap(argument),
'onAuthGotQRCode': (Map argument) =>
WeChatAuthGotQRCodeResponse.fromMap(argument),
'onQRCodeScanned': (Map argument) =>
WeChatQRCodeScannedResponse.fromMap(argument),
'onWXShowMessageFromWX': (Map argument) =>
WeChatShowMessageFromWXRequest.fromMap(argument),
'onWXOpenCustomerServiceChatResponse': (Map argument) =>
WeChatOpenCustomerServiceChatResponse.fromMap(argument),
"onOpenBusinessViewResponse": (Map argument) =>
WeChatOpenBusinessViewResponse.fromMap(argument),
"onOpenWechatInvoiceResponse": (Map argument) =>
WeChatOpenInvoiceResponse.fromMap(argument),
"onWXLaunchFromWX": (Map argument) =>
WeChatLaunchFromWXRequest.fromMap(argument),
};
sealed class WeChatResponse {
WeChatResponse._(this.errCode, this.errStr);
/// Create response from the response pool.
factory WeChatResponse.create(String name, Map argument) {
var result = _nameAndResponseMapper[name];
if (result == null) {
throw ArgumentError("Can't found instance of $name");
}
return result(argument);
}
final int? errCode;
final String? errStr;
bool get isSuccessful => errCode == 0;
}
class WeChatOpenInvoiceResponse extends WeChatResponse {
String? cardItemList;
WeChatOpenInvoiceResponse.fromMap(Map map)
: cardItemList = map["cardItemList"],
super._(map[_errCode], map[_errStr]);
}
class WeChatShareResponse extends WeChatResponse {
WeChatShareResponse.fromMap(Map map)
: type = map['type'],
super._(map[_errCode], map[_errStr]);
final int type;
}
class WeChatAuthResponse extends WeChatResponse {
WeChatAuthResponse.fromMap(Map map)
: type = map['type'],
country = map['country'],
lang = map['lang'],
code = map['code'],
state = map['state'],
super._(map[_errCode], map[_errStr]);
final int type;
final String? country;
final String? lang;
final String? code;
final String? state;
@override
bool operator ==(other) {
return other is WeChatAuthResponse &&
code == other.code &&
country == other.country &&
lang == other.lang &&
state == other.state;
}
@override
int get hashCode =>
super.hashCode + errCode.hashCode &
1345 + errStr.hashCode &
15 + (code ?? '').hashCode &
1432;
}
class WeChatLaunchMiniProgramResponse extends WeChatResponse {
WeChatLaunchMiniProgramResponse.fromMap(Map map)
: type = map['type'],
extMsg = map['extMsg'],
super._(map[_errCode], map[_errStr]);
final int? type;
final String? extMsg;
}
class WeChatPaymentResponse extends WeChatResponse {
WeChatPaymentResponse.fromMap(Map map)
: type = map['type'],
extData = map['extData'],
super._(map[_errCode], map[_errStr]);
final int type;
final String? extData;
}
class WeChatOpenCustomerServiceChatResponse extends WeChatResponse {
WeChatOpenCustomerServiceChatResponse.fromMap(Map map)
: extMsg = map['extMsg'],
super._(map[_errCode], map[_errStr]);
final String? extMsg;
}
class WeChatOpenBusinessViewResponse extends WeChatResponse {
final String? extMsg;
final String? openid;
final String? businessType;
final int? type;
WeChatOpenBusinessViewResponse.fromMap(Map map)
: extMsg = map["extMsg"],
openid = map["openid"],
businessType = map["businessType"],
type = map["type"],
super._(map[_errCode], map[_errStr]);
}
class WeChatSubscribeMsgResponse extends WeChatResponse {
WeChatSubscribeMsgResponse.fromMap(Map map)
: openid = map['openid'],
templateId = map['templateId'],
action = map['action'],
reserved = map['reserved'],
scene = map['scene'],
super._(map[_errCode], map[_errStr]);
final String? openid;
final String? templateId;
final String? action;
final String? reserved;
final int scene;
}
class WeChatOpenBusinessWebviewResponse extends WeChatResponse {
WeChatOpenBusinessWebviewResponse.fromMap(Map map)
: type = map['type'],
businessType = map['businessType'],
resultInfo = map['resultInfo'],
super._(map[_errCode], map[_errStr]);
final int? type;
final int? businessType;
final String resultInfo;
}
class WeChatAuthByQRCodeFinishedResponse extends WeChatResponse {
WeChatAuthByQRCodeFinishedResponse.fromMap(Map map)
: authCode = map['authCode'],
qrCodeErrorCode = (_authByQRCodeErrorCodes[_errCode] ??
AuthByQRCodeErrorCode.unknown),
super._(map[_errCode], map[_errStr]);
final String? authCode;
final AuthByQRCodeErrorCode? qrCodeErrorCode;
}
///[qrCode] in memory.
class WeChatAuthGotQRCodeResponse extends WeChatResponse {
WeChatAuthGotQRCodeResponse.fromMap(Map map)
: qrCode = map['qrCode'],
super._(map[_errCode], map[_errStr]);
final Uint8List? qrCode;
}
class WeChatQRCodeScannedResponse extends WeChatResponse {
WeChatQRCodeScannedResponse.fromMap(Map map)
: super._(map[_errCode], map[_errStr]);
}
// 获取微信打开App时携带的参数
class WeChatShowMessageFromWXRequest extends WeChatResponse {
final String? country;
final String? lang;
final String? messageAction;
final String? description;
WeChatShowMessageFromWXRequest.fromMap(Map map)
: extMsg = map['extMsg'],
country = map['country'],
messageAction = map['messageAction'],
description = map["description"],
lang = map["lang"],
super._(0, '');
final String? extMsg;
}
class WeChatLaunchFromWXRequest extends WeChatResponse {
final String? country;
final String? lang;
final String? messageAction;
WeChatLaunchFromWXRequest.fromMap(Map map)
: extMsg = map['extMsg'],
country = map['country'],
messageAction = map['messageAction'],
lang = map["lang"],
super._(0, '');
final String? extMsg;
}
enum AuthByQRCodeErrorCode {
ok, // WechatAuth_Err_OK(0)
normalErr, // WechatAuth_Err_NormalErr(-1)
networkErr, // WechatAuth_Err_NetworkErr(-2)
jsonDecodeErr, // WechatAuth_Err_JsonDecodeErr(-3), WechatAuth_Err_GetQrcodeFailed on iOS
cancel, // WechatAuth_Err_Cancel(-4)
timeout, // WechatAuth_Err_Timeout(-5)
authStopped, // WechatAuth_Err_Auth_Stopped(-6), Android only
unknown
}
const Map<int, AuthByQRCodeErrorCode> _authByQRCodeErrorCodes = {
0: AuthByQRCodeErrorCode.ok,
-1: AuthByQRCodeErrorCode.normalErr,
-2: AuthByQRCodeErrorCode.networkErr,
-3: AuthByQRCodeErrorCode.jsonDecodeErr,
-4: AuthByQRCodeErrorCode.cancel,
-5: AuthByQRCodeErrorCode.authStopped
};
/*
* Copyright (c) 2023. OpenFlutter Project
*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. The ASF licenses this
* file to you under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
/// [WXMiniProgramType.release]正式版
/// [WXMiniProgramType.test]测试版
/// [WXMiniProgramType.preview]预览版
enum WXMiniProgramType {
release(0),
test(1),
preview(2);
final int value;
const WXMiniProgramType(this.value);
}
/// [WeChatScene.session]会话
/// [WeChatScene.timeline]朋友圈
/// [WeChatScene.favorite]收藏
enum WeChatScene { session, timeline, favorite }
/*
* Copyright (c) 2023. OpenFlutter Project
*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. The ASF licenses this
* file to you under the Apache License, Version 2.0 (the 'License'); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import 'dart:io';
import 'dart:typed_data';
const String defaultSuffixJpeg = '.jpeg';
const String defaultSuffixTxt = '.txt';
class WeChatImage extends WeChatFile {
WeChatImage.network(
String source, {
String suffix = defaultSuffixJpeg,
}) : super.network(source, suffix: suffix);
WeChatImage.asset(
String source, {
String suffix = defaultSuffixJpeg,
}) : super.asset(source, suffix: suffix);
WeChatImage.file(
File source, {
String suffix = defaultSuffixJpeg,
}) : super.file(source, suffix: suffix);
WeChatImage.binary(
Uint8List source, {
String suffix = defaultSuffixJpeg,
}) : super.binary(source, suffix: suffix);
}
class WeChatFile {
/// [source] must begin with http or https
WeChatFile.network(
String this.source, {
String? suffix,
}) : assert(source.startsWith('http')),
schema = FileSchema.network,
suffix = source.readSuffix(suffix, defaultSuffixTxt);
///[source] path of the image, like '/asset/image.pdf?package=flutter',
///the query param package in [source] only available when you want to specify the package of image
WeChatFile.asset(
String this.source, {
String? suffix,
}) : assert(source.trim().isNotEmpty),
schema = FileSchema.asset,
suffix = source.readSuffix(suffix, defaultSuffixTxt);
WeChatFile.file(
File source, {
String suffix = defaultSuffixTxt,
}) : source = source.path,
schema = FileSchema.file,
suffix = source.path.readSuffix(suffix, defaultSuffixTxt);
WeChatFile.binary(
Uint8List this.source, {
this.suffix = defaultSuffixTxt,
}) : assert(suffix.trim().isNotEmpty),
schema = FileSchema.binary;
final dynamic source;
final FileSchema schema;
final String suffix;
Map toMap() => {'source': source, 'schema': schema.index, 'suffix': suffix};
}
/// Types of image, usually there are for types listed below.
///
/// [network] is online images.
/// [asset] is flutter asset image.
/// [binary] is binary image, shall be be [Uint8List]
/// [file] is local file, usually not comes from flutter asset.
enum FileSchema { network, asset, file, binary }
extension _FileSuffix on String {
/// returns [suffix] if [suffix] not blank.
/// If [suffix] is blank, then try to read from url
/// if suffix in url not found, then return jpg as default.
String readSuffix(String? suffix, String defaultSuffix) {
if (suffix != null && suffix.trim().isNotEmpty) {
if (suffix.startsWith('.')) {
return suffix;
} else {
return '.$suffix';
}
}
var path = Uri.parse(this).path;
var index = path.lastIndexOf('.');
if (index >= 0) {
return path.substring(index);
}
return defaultSuffix;
}
}
name: fluwx
description: The capability of implementing WeChat SDKs in Flutter. With Fluwx, developers can use WeChatSDK easily, such as sharing, payment, lanuch mini program and etc.
version: 4.4.0
homepage: https://github.com/OpenFlutter/fluwx
environment:
sdk: '>=3.0.0 <4.0.0'
flutter: ">=3.3.0"
dependencies:
flutter:
sdk: flutter
flutter_web_plugins:
sdk: flutter
plugin_platform_interface: ^2.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
# which should be registered in the plugin registry. This is required for
# using method channels.
# The Android 'package' specifies package in which the registered class is.
# This is required for using method channels on Android.
# The 'ffiPlugin' specifies that native code should be built and bundled.
# This is required for using `dart:ffi`.
# All these are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
platforms:
android:
package: com.jarvan.fluwx
pluginClass: FluwxPlugin
ios:
pluginClass: FluwxPlugin
web:
pluginClass: FluwxWeb
fileName: src/fluwx_web.dart
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fluwx/src/method_channel/fluwx_method_channel.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
MethodChannelFluwx platform = MethodChannelFluwx();
const MethodChannel channel = MethodChannel('fluwx');
setUp(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
channel,
(MethodCall methodCall) async {
return '42';
},
);
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
});
test('getPlatformVersion', () async {
});
}
import 'package:flutter_test/flutter_test.dart';
import 'package:fluwx/fluwx.dart';
import 'package:fluwx/src/method_channel/fluwx_method_channel.dart';
import 'package:fluwx/src/method_channel/fluwx_platform_interface.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
class MockFluwxPlatform
with MockPlatformInterfaceMixin
implements FluwxPlatform {
@override
Future<String?> getExtMsg() {
// TODO: implement getExtMsg
throw UnimplementedError();
}
@override
Future<bool> get isWeChatInstalled => Future.value(false);
@override
Future<bool> registerApi(
{required String appId,
bool doOnIOS = true,
bool doOnAndroid = true,
String? universalLink}) {
// TODO: implement registerWxApi
throw UnimplementedError();
}
@override
Future<bool> sendAuth(
{required String scope,
String state = 'state',
bool nonAutomatic = false}) {
// TODO: implement sendAuth
throw UnimplementedError();
}
@override
Future<bool> share(WeChatShareModel what) {
// TODO: implement share
throw UnimplementedError();
}
@override
Future<bool> stopAuthByQRCode() {
// TODO: implement stopWeChatAuthByQRCode
throw UnimplementedError();
}
@override
Stream<WeChatResponse> get responseEventHandler => throw UnimplementedError();
@override
Future<bool> open(OpenType target) {
throw UnimplementedError();
}
@override
Future<bool> authBy(AuthType which) {
// TODO: implement authBy
throw UnimplementedError();
}
@override
Future<bool> authByPhoneLogin({required String scope, String state = 'state'}) {
// TODO: implement authByPhoneLogin
throw UnimplementedError();
}
@override
Future<bool> authByQRCode({required String appId, required String scope, required String nonceStr, required String timestamp, required String signature, String? schemeData}) {
// TODO: implement authByQRCode
throw UnimplementedError();
}
@override
Future<bool> autoDeduct(AutoDeduct data) {
// TODO: implement autoDeduct
throw UnimplementedError();
}
@override
// TODO: implement isSupportOpenBusinessView
Future<bool> get isSupportOpenBusinessView => throw UnimplementedError();
@override
Future<bool> pay(PayType which) {
// TODO: implement pay
throw UnimplementedError();
}
@override
Future<void> attemptToResumeMsgFromWx() {
// TODO: implement attemptToResumeMsgFromWx
throw UnimplementedError();
}
@override
Future<void> selfCheck() {
// TODO: implement selfCheck
throw UnimplementedError();
}
}
void main() {
final FluwxPlatform initialPlatform = FluwxPlatform.instance;
test('$MethodChannelFluwx is the default instance', () {
expect(initialPlatform, isInstanceOf<MethodChannelFluwx>());
});
test('isWeChatInstalled', () async {
Fluwx fluwxPlugin = Fluwx();
MockFluwxPlatform fakePlatform = MockFluwxPlatform();
FluwxPlatform.instance = fakePlatform;
expect(await fluwxPlugin.isWeChatInstalled, false);
});
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论