update flutter uikit to 2.0.0

This commit is contained in:
anonymous 2023-05-06 17:28:05 +08:00
parent 57d836fcb6
commit d93b15a35f
206 changed files with 13599 additions and 6475 deletions

View File

@ -1,3 +1,158 @@
## 2.0.0
If you are upgrading from version 1.7.0, please refer to the changelog of all 2.0.0-preview versions, ranging from preview.1 to preview.7.
The main feature of this new 2.0.0 version is Desktop Support. Tencent Cloud Chat UIKit now supports all platforms, including iOS, Android, Web, Windows, and macOS, which has resulted in significant changes to the codebase. The UI has been improved to adapt to screens of various widths, with different layouts for both wide and narrow screens.
In addition, there are some significant changes compared to version 2.0.0-preview.7.
### New Features
* Added drag and drop support for multiple files in `TIMUIKitChat`, allowing direct sending.
* Introduced functionality to open files or their containing folder (using `Finder` on `macOS` or `Explorer` on `Windows`) for file messages via the message operation tooltip menu on desktop.
* Implemented text selection and copying in messages on desktop.
* Added group joining application processing on Desktop.
* Introduced `isAutoReportRead` to `TIMUIKitChatConfig` for controlling read status reporting.
### Improvements
* Enhanced group members selection panel for mentioning someone in a group chat.
* Refined image display ratio on Desktop.
* The Reply or Quote button is now labeled as `Reply` when `isAtWhenReply` is set to true, and `Quote` otherwise.
* @ member tags can now be deleted at once.
### Bug Fixes
* Fixed UI layout issue causing the `translate` button to display on two lines.
* Addressed an issue causing the mute status not to change when switching to another conversation.
* Fixed several issues causing bugs when opening files.
* Resolved an issue causing secondary confirmation modal UI layout to be over-width on Desktop.
* Fixed an issue causing UI layout errors on the profile page.
* Addressed an issue where the `chatMessageItemFromSelfBgColor` configuration did not work.
* Fixed an issue preventing files from being opened when the path contained Chinese characters on Windows.
* Resolved an issue preventing images from being pasted and sent directly with Ctrl + V on Windows.
* Fixed an issue causing errors in the muting members list.
## 2.0.0-preview.7
### New Features
* Added `additionalMessageToolTips` to `ToolTipsConfig`. This new property allows developers to add additional message operation tooltip items, apart from the default ones. The previous `additionalItemBuilder` has been replaced by this new property. With `additionalMessageToolTips`, developers only need to specify the data for the tooltip items, rather than providing a whole widget. This makes usage easier, as you no longer need to worry about the UI display.
* Added `isPreloadMessagesAfterInit` to `TIMUIKitConfig`, allows determines whether TUIKit should preload some messages after initialization for faster message display.
### Improvements
* Message operation menu shows when long-pressing messages will not show if nothing operation item includes and do not use message sticker reaction module.
* Renamed `desktopMessageHoverBar` to `additionalDesktopMessageHoverBarItem` in `TIMUIKitChatConfig` to control only the addition of extra operation items displayed on the hover bar of messages on desktop (macOS, Windows, and desktop version of Web), without affecting the default ones. Previously, it controlled the entire message hover bar, including covering the default items.
* Renamed `showWideScreenModalFunc` to `showDesktopModalFunc` in `TIMUIKitConfig` for better clarity.
* Upgraded several dependencies to their latest versions, including `ffi` to ^2.0.1, `file_picker` to ^5.2.9 and `device_info_plus` to ^8.2.0.
* Added support for the new permission authorization schema on Android 13 and `targetSdkVersion` greater than 33.
* Corrected the `textHight` to `textHeight` in `TIMUIKitChatConfig`, and modified the default value to 1.3.
### Bug Fixes
* Fixed an issue where the `showVideoCall` and `showVoiceCall` configuration options were not working.
* Fixed potential `Windows` platform deployment prohibition issue.
* Fixed an issue that may cause `setLocalCustomData` to be triggered repeatedly.
## 2.0.0-preview.6
### Improvements
* Permission requests now feature a gray translucent overlay for secondary confirmations on first-time requests, which was reintroduced after being removed in version 2.0.0-preview.4. Additionally, the overlay can now be successfully hidden once the permission authorization is complete.".
* Time Divider on Message List: The default 12-hour display has been changed to a 24-hour display.
* Message translation now targets the language of TUIKit instead of relying on the system language directly. The language of TUIKit can be set as the system language automatically or defined by the user. For more information, please refer to this documentation: https://www.tencentcloud.com/document/product/1047/52154.
* Optimized the animation for message text input area.
### Bug Fixes
* Fixed an issue where the `Voice Call` and `Video Call` buttons were not working in group chat.
* Fixed several null-safety issues.
* Fixed a layout problem for the message operation menu when not using the message sticker reaction module.
* Addressed a problem where the time ago display was not correct on the conversation item.
* Fixed an issue where stickers could not be clicked in some cases.
* Resolved an overflow error that occurred when opening the sticker panel.
## 2.0.0-preview.5
### New Features
* New Chat Configuration: `isAllowLongPressAvatarToAt`. This option controls whether users are allowed to mention another user in the group by long-pressing on their avatar.
### Improvements
* Improved tool bar configuration on desktop: The tool bar can now be customized using `desktopControlBarConfig` for embedded default items and `additionalDesktopControlBarItems` for additional tool items. These configurations come from TIMUIKitChatConfig.
* Renamed the `wideMessageHoverBar` configuration option to `desktopMessageHoverBar` for better clarity.
* Eliminated the dependency on `fluttertoast`. All necessary customer reminders are now triggered through the `onTUIKitCallbackListener` info callback in your project. For more information, please see: https://www.tencentcloud.com/document/product/1047/50054#how-do-i-get-an-api-call-error.2Fflutter-layer-error.2Fpop-up-prompt-message.3F.3Ca-id.3D.22callback.22.3E.3C.2Fa.3E.
* Eliminated other six unnecessary dependency packages to reduce the size and improve performance.
* Improved the clarity of the `sendMessage` function in `TIMUIKitChatController` by replacing the use of `convID` to represent both `userID` and `groupID` with separate parameters.
### Bug Fixes
* Fixed an issue where the message operation menu may show inaccurately when the message is too long.
* Fixed a problem where the message operation menu had the potential to be too wide for certain types of messages, causing display issues.
* Corrected an issue where the button to remove group members was not functioning correctly.
* Addressed a problem where the message item could exceed the pixel limit and appear too wide.
* Fixed a bug where certain JSON decoding operations could potentially fail.
* Fixed an issue with sound messages on iOS devices playing only through earpiece instead of speaker by default.
## 2.0.0-preview.4
### New Features
* New Chat Configuration: `TIMUIKitChatConfig` now includes `offlinePushInfo`, which allows for customization of the entire `offlinePushInfo` for each message. This field has a higher priority than the previous separate configuration fields for this object.
* New Color Configuration: Added `appbarTextColor` and `appbarBgColor` to configure the color for the Appbar. Also added `selectPanelBgColor` and `selectPanelTextIconColor` to configure the color of the messages multi-select panel.
### Improvements
* Improved Group Management: Muting members on Work Group is now not allowed.
* Improved Avatar: Ensured that the avatar can be as small as possible while still covering the entire target box.
* Permission Requests: Removed the gray translucent overlay for secondary confirmations on first-time permission requests.
### Bug Fixes
* Fixed an issue where the color defined by `chatBgColor` could not cover the entire chat screen when messages did not cover the whole page.
* Fixed an issue where the history message list could not be scrolled in some cases.
* Fixed an issue where the ratio of sending messages was incorrect, resulting in the wrong position of the read status label on the left.
* Fixed an issue where loading messages could fail when the number of messages equaled the specified count.
## 2.0.0-preview.3
### New Features
* Integrated Callkit: The Calls button no longer needs to be added to `MorePanelConfig`. If `tencent_calls_uikit` is installed, the Video Call and Voice Call buttons will be displayed automatically.
* Paste Images on Desktop: Users can now paste an image on the text field on Desktop to send it.
* Screenshot Capture on Desktop: Users can now capture a screenshot on Desktop and send it.
### Improvements
* Improved Compatibility: The TUIKit is now compatible with Flutter versions 3.0.0 to 3.7.7.
### Bug Fixes
* Fixed an issue where the `businessID` type may not be correct.
* Fixed an issue where the `chatMessageItemFromSelfBgColor` configuration was not taking effect.
## 2.0.0-preview.2
### New Features
* Added support for opening files locally from file messages.
## 2.0.0-preview.1
### New Features
* Desktop Support: Tencent Cloud Chat UIKit now supports all platforms, including iOS, Android, Web, Windows, and macOS, resulting in significant changes to the codebase. The UI has been enhanced to adapt to screens of various widths, with different layouts for both wide and narrow screens.
* Information Copy: The ability to copy information, such as Group ID, from the screen has been added.
### Improvements
* Improved group management logic, with non-administrators no longer able to access the management interface.
* Optimized cursor positioning when sending messages.
* Improved and optimized scrollbar functionality.
* Enhanced clickable URL support in messages, with URLs now supporting both with and without the "https://" prefix.
## 1.7.0+1
* Fix: An issue that caused errors on mentioning all members.

220
LICENSE
View File

@ -1,201 +1,25 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Copyright © 2013-2023 Tencent Cloud. All Rights Reserved.
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Definitions.
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
"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 [2023] [Tencent]
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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

745
README.md
View File

@ -63,123 +63,167 @@ More languages:
<br>
![](https://qcloudimg.tencent-cloud.cn/raw/193ec650f17da6bb33edf5df5d978091.png)
![](https://qcloudimg.tencent-cloud.cn/image/document/8fd972397eba8f56c8f294c8b042794c.jpg)
<p align="center">
TUIKit has Chat SDK, UI components and basic business logic inside. You can choose our pure Chat SDK <a href="https://pub.dev/packages/tencent-cloud-chat-sdk">tencent-cloud-chat-sdk</a> if you tend to build the UI yourself.
</p>
<a target="_blank" href="https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html"><button type="button" class="button-9" role="button">Official Documentation</button></a>
<a target="_blank" href="https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html"><button type="button" class="button-9" role="button">
Official Documentation</button></a>
<br>
## Experience DEMO
## Preview Version Release Notes
You can experience our Chat and Voice/Video Call modules via the following demos.
This version is a major update with version number 2.0.0-preview series, which is not backward
compatible. Tencent Cloud Chat UIKit has been extended from mobile-only (iOS/Android/mobile web) to
support all platforms, including iOS/Android/Web/Windows/macOS, resulting in significant changes to
the codebase.
**Those following versions of demo has been build by the same Flutter project with our SDKs and extensions.**
Therefore, users should evaluate the compatibility complexity of their business logic before
upgrading, while new users can use this version without any impact.
![](https://qcloudimg.tencent-cloud.cn/raw/89234f5032d4f6f8d89a8b439ca97ca2.png)
The documentation for the new version is still being improved, and users can refer to the sample app
source code at at https://github.com/TencentCloud/chat-demo-flutter.
**Also, taking a look of the screenshots of TUIKit from [here](https://www.tencentcloud.com/document/product/1047/50059?from=pub) are suggested.**
## Check Out Our Sample Apps
Experience our Chat and Voice/Video Call modules by trying out our sample apps.
**These apps have been created using the same Flutter project as our SDKs and extensions.**
| Platform | Link | Remark |
|---------|---------|---------|
| Android / iOS | <img src="https://qcloudimg.tencent-cloud.cn/raw/e791bd503ae267aa51234ad66e347f10.png" width="140px" alt="Tencent Chat Logo" /> | Scan to download app for both Android and iOS. Automatically identifies platform. |
| Web | <img src="https://qcloudimg.tencent-cloud.cn/raw/7908cf6f3c16e4059f8f21229d70a918.png" width="140px" alt="Tencent Chat Logo" /> | Supports both desktop and mobile browsers and automatically adjusts its layout accordingly. Same website as link below. |
| Web | [Visit Now](https://comm.qq.com/flutter/#/) | Supports both desktop and mobile browsers and automatically adjusts its layout accordingly. Same website as previous QR code. |
| macOS | [Download Now](https://comm.qq.com/im_demo_download/macos_flutter.dmg) | The macOS version of our sample app. Control-click the app icon, then choose "Open" from the shortcut menu. |
| Windows | [Download Now](https://comm.qq.com/im_demo_download/windows_flutter.appx) | The Windows version of our sample app, which is a UWP (Universal Windows Platform) application. |
| Linux | Coming Soon... | Will be available later this year. |
**Take a look at the screenshots of
TUIKit [here](https://www.tencentcloud.com/document/product/1047/50059?from=pub) to get an idea of
what to expect.**
## Introduction to TUIKit
[TUIKit](https://www.tencentcloud.com/document/product/1047/50059?from=pub) is a set of official UI components for Tencent Cloud Chat SDK, with chat business logic around it. It provides components such as the conversation, chat, relationship chain, and group.
Tencent Cloud Chat SDK comes
with [TUIKit](https://www.tencentcloud.com/document/product/1047/50059?from=pub), which is an
official set of UI components that have chat business logic built-in. TUIKit includes components
like conversation, chat, relationship chain, and group.
You can use these UI components to build your APP with the In-APP chat module quickly and easily.
Developers can use these UI components to quickly and easily add In-APP chat modules to their mobile
applications.
![img](https://qcloudimg.tencent-cloud.cn/raw/f140dd76be01a65abfb7e6ba2bf50ed5.png)
Currently, Flutter [TUIKit](https://www.tencentcloud.com/document/product/1047/50059?from=pub) contains the following main components:
Currently, Flutter [TUIKit](https://www.tencentcloud.com/document/product/1047/50059?from=pub)
contains the following main components:
- [TIMUIKitCore](https://comm.qq.com/im/doc/flutter/en/TUIKit/TIMUIKitCore/readme.html): Core entry
- [TIMUIKitConversation](https://www.tencentcloud.com/document/product/1047/50059?from=pub#timuikitconversation): Conversation list
- [TIMUIKitChat](https://www.tencentcloud.com/document/product/1047/50059?from=pub#timuikitchat): Chat module, includes historical message list and message sending area, with some other features like message reaction and URL preview, etc.
- [TIMUIKitContact](https://www.tencentcloud.com/document/product/1047/50059?from=pub#relationship-chain-components): Contacts list
- [TIMUIKitProfile](https://www.tencentcloud.com/document/product/1047/50059?from=pub#timuikitprofile): User profile and relationship management
- [TIMUIKitGroupProfile](https://www.tencentcloud.com/document/product/1047/50059?from=pub#timuikitgroupprofile): Group profile and management
- [TIMUIKitGroup](https://www.tencentcloud.com/document/product/1047/50059?from=pub#relationship-chain-components): The list of group self joined
- [TIMUIKitBlackList](https://www.tencentcloud.com/document/product/1047/50059?from=pub#relationship-chain-components): The list of user been blocked
- [TIMUIKitNewContact](https://www.tencentcloud.com/document/product/1047/50059?from=pub#relationship-chain-components): New contacts application list
- [TIMUIKitSearch](https://www.tencentcloud.com/document/product/1047/50036?from=pub): Search globally
- [TIMUIKitSearchMsgDetail](https://www.tencentcloud.com/document/product/1047/50036?from=pub): Search in specific conversation
- [TIMUIKitConversation](https://www.tencentcloud.com/document/product/1047/50059?from=pub#timuikitconversation):
Conversation list
- [TIMUIKitChat](https://www.tencentcloud.com/document/product/1047/50059?from=pub#timuikitchat):
Chat module, includes historical message list and message sending area, with some other features
like message reaction and URL preview, etc.
- [TIMUIKitContact](https://www.tencentcloud.com/document/product/1047/50059?from=pub#relationship-chain-components):
Contacts list
- [TIMUIKitProfile](https://www.tencentcloud.com/document/product/1047/50059?from=pub#timuikitprofile):
User profile and relationship management
- [TIMUIKitGroupProfile](https://www.tencentcloud.com/document/product/1047/50059?from=pub#timuikitgroupprofile):
Group profile and management
- [TIMUIKitGroup](https://www.tencentcloud.com/document/product/1047/50059?from=pub#relationship-chain-components):
The list of group self joined
- [TIMUIKitBlackList](https://www.tencentcloud.com/document/product/1047/50059?from=pub#relationship-chain-components):
The list of user been blocked
- [TIMUIKitNewContact](https://www.tencentcloud.com/document/product/1047/50059?from=pub#relationship-chain-components):
New contacts application list
- [TIMUIKitSearch](https://www.tencentcloud.com/document/product/1047/50036?from=pub): Search
globally
- [TIMUIKitSearchMsgDetail](https://www.tencentcloud.com/document/product/1047/50036?from=pub):
Search in specific conversation
Also, there are some other useful components and widgets, that can help to build your APP, and meet your business needs, including group entry application list and group member list, etc.
In addition to these components, there are other useful components and widgets available to help
developers meet their business needs, such as group entry application list and group member list.
For the source code of the project in the figure above, see [im-flutter-uikit](https://github.com/TencentCloud/tc-chat-demo-flutter). The project is open source and can be used directly.
For the source code of the project shown in the image above, please refer
to [chat-demo-flutter](https://github.com/TencentCloud/chat-demo-flutter). This project is open
source and can be directly used by developers.
## Supported Platforms
## Compatible Platforms
The platforms are compatible with the deployment of our Chat UIKit.
- Android
- iOS
- Web(After version of 0.1.4)
- Web (version 0.1.4 and later)
- Windows (version 2.0.0 and later)
- macOS (version 2.0.0 and later)
## Get Started
**[Please refer this documents](https://www.tencentcloud.com/document/product/1047/45907?from=pub), for a completed and detailed get started guide.**
Please refer to [this document](https://www.tencentcloud.com/document/product/1047/45907?from=pub) for a complete and detailed guide on getting started.
## Directions
The following guide describes how to use Flutter TUIKit to build a simple Chat APP quickly.
The following guide describes how to quickly build a simple chat application using Flutter TUIKit.
**You may refer to the appendix, if willing to know about the detail and parameter for each widgets.**
Refer to the appendix if you want to learn about the details and parameters of each widget.
> If you tend to add this Flutter [TUIKit](https://www.tencentcloud.com/document/product/1047/50059?from=pub) to your existing application directly, you may refer to [this documentation](https://www.tencentcloud.com/document/product/1047/51456). Add the Flutter module to your existing app, coding once and deploying to all platforms. It could reduce your workload, to adding chat and call modules to your existing, to a large extent.
> If you want to directly add Flutter TUIKit to your existing application, refer to [this document](https://www.tencentcloud.com/document/product/1047/51456). You can add the Flutter module to your existing application, code once, and deploy to all platforms. This can significantly reduce the workload of adding chat and call modules to your existing application.
### Step 0. Create two accounts for testing
### Step 0: Create two accounts for testing
[Signed up](https://www.tencentcloud.com/document/product/378/17985?from=pub) and [log in](https://www.tencentcloud.com/document/product/378/36004?from=pub) to the [Tencent IM console](https://console.tencentcloud.com/im?from=pub).
Sign up and log in to the [Tencent Cloud Chat console](https://console.tencentcloud.com/im?from=pub).
[Create an application](https://www.tencentcloud.com/document/product/1047/34577?from=pub) and enter in.
Create an application and enter it.
Select [Auxiliary Tools](https://console.tencentcloud.com/im-detail/tool-usersig?from=pub) > UserSig Generation and Verification on the left sidebar. Generate two pairs of "UserID" and the corresponding "UserSig", and copy the "key" information. [Refer to here.](https://www.tencentcloud.com/document/product/1047/34580?from=pub#usersig-generation-and-verification)
Select Auxiliary Tools > UserSig Generation and Verification on the left sidebar. Generate two pairs of "UserID" and the corresponding "UserSig," and copy the "key" information. Refer to [this document](https://www.tencentcloud.com/document/product/1047/34580?from=pub#usersig-generation-and-verification).
Tips: You may create "user1" and "user2" here.
Tips: You can create "user1" and "user2" here.
> Note
> Note:
>
> The correct `UserSig` distribution method is to integrate the calculation code of `UserSig` into your server and provide an application-oriented API. When `UserSig` is needed, your application can send a request to the business server for a dynamic `UserSig`. For more information, see [How do I calculate UserSig on the server?](https://www.tencentcloud.com/document/product/1047/34385?from=pub).
> The correct way to distribute `UserSig` is to integrate the calculation code for `UserSig` into your server and provide an application-oriented API. When `UserSig` is needed, your application can send a request to the business server for a dynamic `UserSig.` For more information, see [How do I calculate UserSig on the server?](https://www.tencentcloud.com/document/product/1047/34385?from=pub).
### Step 1. Create a Flutter app and add permission configuration
### Step 1: Create a Flutter app and add permission configuration
Quickly create a Flutter APP by referring to [Flutter documentation](https://docs.flutter.dev/get-started/install).
Create a Flutter app quickly by following the [Flutter documentation](https://docs.flutter.dev/get-started/install).
TUIKit needs the permissions of shooting/album/recording/network for basic messaging function. You need to declare in the permission manually to use the relevant capabilities normally.
TUIKit needs the permissions of shooting/album/recording/network for basic messaging functions. You need to declare these permissions manually to use the relevant capabilities normally.
#### Android
Open `android/app/src/main/AndroidManifest.xml`, add the following lines between `<manifest>` and `</manifest>`.
Open `android/app/src/main/AndroidManifest.xml` and add the following lines between `<manifest>` and `</manifest>`.
```xml
<uses-permission
android:name="android.permission.INTERNET"/>
<uses-permission
android:name="android.permission.RECORD_AUDIO"/>
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission
android:name="android.permission.VIBRATE"/>
<uses-permission
android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission
android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
```
#### iOS
Open `ios/Podfile`, add the following lines to the end of the file.
Open `ios/Podfile` and add the following lines to the end of the file.
```pod
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
config.build_settings['ENABLE_BITCODE'] = 'NO'
config.build_settings["ONLY_ACTIVE_ARCH"] = "NO"
end
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
@ -192,7 +236,7 @@ post_install do |installer|
end
```
### Step 2. Install dependencies
### Step 2: Install dependencies
Add `tencent_cloud_chat_uikit` under `dependencies` in the `pubspec.yaml` file, or run the following command:
@ -200,15 +244,15 @@ Add `tencent_cloud_chat_uikit` under `dependencies` in the `pubspec.yaml` file,
flutter pub add tencent_cloud_chat_uikit
```
It supports Android and iOS as default, if you are also willing to use it on the Web, please refer to the following guide.
It supports Android and iOS by default. If you also want to use it on the web, refer to the following guide.
#### Web Supports
#### Web Support
Version of 0.1.4 or later are required for web supports.
Version 0.1.4 or later is required to support web.
> If your existing Flutter project does not support Web, run `flutter create .` in the project root directory to add web support.
If your existing Flutter project does not support web, run `flutter create .` in the project root directory to add web support.
Install JS dependencies to `web/` by `npm` or `yarn`.
Install JavaScript dependencies to `web/` using `npm` or `yarn`.
```shell
cd web
@ -220,7 +264,7 @@ npm i tim-js-sdk
npm i tim-upload-plugin
```
Open `web/index.html` , add the following two lines between `<head>` and `</head>` to import them.
Open `web/index.html` and add the following two lines between `<head>` and `</head>` to import them.
```html
<script src="./node_modules/tim-upload-plugin/index.js"></script>
@ -229,35 +273,35 @@ Open `web/index.html` , add the following two lines between `<head>` and `</head
![](https://qcloudimg.tencent-cloud.cn/raw/a4d25e02c546e0878ba59fcda87f9c76.png)
### Step 3. Initialize TUIKit
### Step 3: Initialize TUIKit
Initialize the TUIKit after you app starts.You only need to perform the initialization once for the project to start.
Initialize TUIKit when your app starts. You only need to perform the initialization once for the project to start.
Get the instance of TUIKit first by `TIMUIKitCore.getInstance()`, followed by initializing it, `init()`, with your 'sdkAppID'.
Get the instance of TUIKit first using `TIMUIKitCore.getInstance()`, followed by initializing it with your `sdkAppID`.
```dart
/// main.dart
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
final CoreServicesImpl _coreInstance = TIMUIKitCore.getInstance();
@override
void initState() {
_coreInstance.init(
sdkAppID: 0, // Replace 0 with the SDKAppID of your IM application
@override
void initState() {
_coreInstance.init(
sdkAppID: 0, // Replace 0 with the SDKAppID of your Tencent Cloud Chat application
loglevel: LogLevelEnum.V2TIM_LOG_DEBUG,
listener: V2TimSDKListener());
super.initState();
}
}
super.initState();
}}
```
> **You may also better to register a callback function for `onTUIKitCallbackListener` here, please refer to the appendix.**
> **You may also want to register a callback function for `onTUIKitCallbackListener` here. Refer to the appendix.**
### Step 4. Get the signature and log in
### Step 4: Get the signature and log in
Now, you can log in one of the testing accounts, generated on Step 0, to start the IM module.
You can now log in one of the testing accounts generated in Step 0 to start the Tencent Cloud Chat module.
Log in by `_coreInstance.login` .
Log in using `_coreInstance.login`.
```dart
/// main.dart
@ -267,16 +311,16 @@ final CoreServicesImpl _coreInstance = TIMUIKitCore.getInstance();
_coreInstance.login(userID: userID, userSig: userSig);
```
Caveat: Importing UserSig to your application is ONLY for Debugging purposes and cannot be applied for the Release version. Before publishing your app, you should generate your UserSig from your server. Refers to: <https://www.tencentcloud.com/document/product/1047/34385?from=pub>
Note: Importing UserSig to your application is only for debugging purposes and cannot be used for the release version. Before publishing your app, you should generate your UserSig from your server. Refer to: <https://www.tencentcloud.com/document/product/1047/34385?from=pub>
### Step 5. Implementing the conversation list page
## Step 5. Implementing the conversation list page
You can take the conversation (channel) list page as the homepage of your Chat module, covering the conversation with all users and groups that have chat records.
You can use the conversation (channel) list page as the homepage of your Chat module, which includes all conversations with users and groups that have chat records.
<img src="https://qcloudimg.tencent-cloud.cn/raw/a27b131d555b1158d150bd9b337c1d9d.png" style="zoom:50%;"/>
```markdown
![Conversation List Page](https://qcloudimg.tencent-cloud.cn/raw/a27b131d555b1158d150bd9b337c1d9d.png)
You can create a `Conversation` class, with `TIMUIKitConversation` on its `body`, to render the conversation list.
The only parameter you need to provide at least is `onTapItem` callback, aimed at navigating to the Chat page for each conversation. The `Chat` class will be introduced in the next step.
You can create a `Conversation` class, with `TIMUIKitConversation` as its body, to render the conversation list. You only need to provide the `onTapItem` callback, which allows users to navigate to the Chat page for each conversation. In the next step, we'll introduce the `Chat` class.
```dart
import 'package:flutter/material.dart';
@ -284,6 +328,7 @@ import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class Conversation extends StatelessWidget {
const Conversation({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
@ -298,9 +343,10 @@ class Conversation extends StatelessWidget {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Chat(
selectedConversation: selectedConv,
),
builder: (context) =>
Chat(
selectedConversation: selectedConv,
),
));
},
),
@ -308,15 +354,14 @@ class Conversation extends StatelessWidget {
}
}
```
## Step 6. Implementing the chat page
### Step 6. Implementing the chat page
The chat page consists of the main message list and a message sending bar at the bottom.
The chat page is composed of the main historical message list and a message sending bar at the bottom.
```markdown
![Chat Page](https://qcloudimg.tencent-cloud.cn/raw/09b8b9b54fd0caa47069544343eba461.jpg)
![](https://qcloudimg.tencent-cloud.cn/raw/09b8b9b54fd0caa47069544343eba461.jpg)
You can create a `Chat` class, with `TIMUIKitChat` on its `body`, to render the chat page.
It is recommended to provide a `onTapAvatar` callback function, for navigating to the profile page for current contact, which will be introduced in the next step.
You can create a `Chat` class, with `TIMUIKitChat` as its body, to render the chat page. We recommend providing an `onTapAvatar` callback function to navigate to the profile page for the current contact, which we'll introduce in the next step.
```dart
import 'package:flutter/material.dart';
@ -324,42 +369,43 @@ import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class Chat extends StatelessWidget {
final V2TimConversation selectedConversation;
const Chat({Key? key, required this.selectedConversation}) : super(key: key);
String? _getConvID() {
return selectedConversation.type == 1
? selectedConversation.userID
: selectedConversation.groupID;
}
@override
Widget build(BuildContext context) {
return TIMUIKitChat(
conversationID: _getConvID() ?? '', // groupID or UserID
conversationType: selectedConversation.type ?? 1, // Conversation type
conversationShowName: selectedConversation.showName ?? "", // Conversation display name
conversationID: _getConvID() ?? '',
conversationType: selectedConversation.type ?? 1,
conversationShowName: selectedConversation.showName ?? "",
onTapAvatar: (_) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserProfile(userID: userID),
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserProfile(userID: userID),
));
}, // Callback for the clicking of the message sender profile photo. This callback can be used with `TIMUIKitProfile`.
},
);
}
}
```
## Step 7. Implementing the user profile page
### Step 7. Implementing the user profile page
This page shows the profile of a specific user and maintains the relationship between the current logged-in user and the other user.
This page can show the profile of a specific user and maintain the relationship between the current login user and it.
```markdown
![User Profile Page](https://qcloudimg.tencent-cloud.cn/raw/03e88da6f1d63f688d2a8ee446da43ff.png)
![](https://qcloudimg.tencent-cloud.cn/raw/03e88da6f1d63f688d2a8ee446da43ff.png)
You can create a `UserProfile` class, with `TIMUIKitProfile` as its body, to render the user profile page.
Here, you can create a `UserProfile` class, with `TIMUIKitProfile` on its `body`, to render the profile page.
The only parameter you have to provide is `userID`, while this component automatically generates the profile and relationship maintenance page based on the existence of friendship.
The only parameter you have to provide at least is 'userID', while this component can generate the profile and relationship maintenance page based on the existence of friendship automatically.
> TIPS
>
> Please give priority to use `profileWidgetBuilder`, to customize some profile widgets, with `profileWidgetsOrder`, determine the vertical sequence, if you tend to customize this page. If this method could not meet your business needs, you may consider using `builder` instead.
> **TIP**: Please use `profileWidgetBuilder` first to customize some profile widgets and determine their vertical sequence using `profileWidgetsOrder` if you want to customize this page. If this method cannot meet your business needs, you may consider using `builder` instead.
```dart
import 'package:flutter/material.dart';
@ -367,34 +413,35 @@ import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class UserProfile extends StatelessWidget {
final String userID;
const UserProfile({required this.userID, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
"Message",
style: TextStyle(color: Colors.black),
),
),
body: TIMUIKitProfile(
userID: widget.userID,
),
title: const Text(
"Message",
style: TextStyle(color: Colors.black),
),
),
body: TIMUIKitProfile(
userID: widget.userID,
),
);
}
}
```
Now, your app can send/receive messages, show the conversation list, and deal with the contact friendship.
You can use others components from TUIKit continually to implement the complete Chat module quickly and easily.
Now your app can send and receive messages, display the conversation list, and manage contact friendships. You can use other components from TUIKit to quickly and easily implement the complete Chat module.
## FAQs
### Do I need to integrate Chat SDK after integrating TUIKit?
No. You don't need to integrate Chat SDK again. If you want to use Chat SDK related APIs, you can get them via `TIMUIKitCore.getSDKInstance()`. This method is recommended to ensure Chat SDK version consistency.
No. You don't need to integrate Chat SDK again. If you want to use Chat SDK related APIs, you can
get them via `TIMUIKitCore.getSDKInstance()`. This method is recommended to ensure Chat SDK version
consistency.
### Why did force quit occur when I sent voice, image, file or other messages?
@ -404,11 +451,13 @@ Refers to Step 1 above.
### What should I do if clicking Build And Run for an Android device triggers an error, stating no available device is found?
Check that the device is not occupied by other resources. Alternatively, click Build to generate an APK package, drag it to the simulator, and run it.
Check that the device is not occupied by other resources. Alternatively, click Build to generate an
APK package, drag it to the simulator, and run it.
### What should I do if an error occurs during the first run for an iOS device?
If an error occurs after the configuration, click **Product > Clean Build Folder** , clean the product, and run `pod install` or `flutter run` again.
If an error occurs after the configuration, click **Product > Clean Build Folder** , clean the
product, and run `pod install` or `flutter run` again.
![](https://qcloudimg.tencent-cloud.cn/raw/d495b2e8be86dac4b430e8f46a15cef4.png)
@ -416,33 +465,33 @@ If an error occurs after the configuration, click **Product > Clean Build Folder
![](https://qcloudimg.tencent-cloud.cn/raw/1ffcfe39a18329c86849d7d3b34b9a0e.png)
Turn on Airplane Mode on your Apple Watch, and go to **Settings > Bluetooth** on your iPhone to turn off Bluetooth.
Turn on Airplane Mode on your Apple Watch, and go to **Settings > Bluetooth** on your iPhone to turn
off Bluetooth.
Restart Xcode (if opened) and run `flutter run` again.
### Issue with Flutter environment?
If you want to check the Flutter environment, run `flutter doctor` to detect whether the Flutter environment is ready.
### What should I do when an error occurs on an Android device after TUIKit is imported into the application automatically generated by Flutter?
![](https://qcloudimg.tencent-cloud.cn/raw/d95efdd4ae50f13f38f4c383ca755ae7.png)
1. Open `android\app\src\main\AndroidManifest.xml` and complete `xmlns:tools="http://schemas.android.com/tools" / android:label="@string/android_label" / tools:replace="android:label"` as follows.
1. Open `android\app\src\main\AndroidManifest.xml` and
complete `xmlns:tools="http://schemas.android.com/tools" / android:label="@string/android_label" / tools:replace="android:label"`
as follows.
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="Replace it with your Android package name"
xmlns:tools="http://schemas.android.com/tools">
<application
android:label="@string/android_label"
tools:replace="android:label"
android:icon="@mipmap/ic_launcher" // Specify an icon path
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true">
<application android:label="@string/android_label" tools:replace="android:label"
android:icon="@mipmap/ic_launcher"
// Specify an icon path
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true">
```
2. Open `android\app\build.gradle` and complete `minSdkVersion` and `targetSdkVersion` in `defaultConfig`.
2. Open `android\app\build.gradle` and complete `minSdkVersion` and `targetSdkVersion`
in `defaultConfig`.
```gradle
defaultConfig {
@ -456,422 +505,8 @@ defaultConfig {
## Contact Us
Please do not hesitate to contact us in the following place, if you have any further questions or tend to learn more about the use cases.
- Telegram Group: <https://t.me/+1doS9AUBmndhNGNl>
- WhatsApp Group: <https://chat.whatsapp.com/Gfbxk7rQBqc8Rz4pzzP27A>
- QQ Group: 788910197, chat in Chinese
Our Website: <https://www.tencentcloud.com/products/im?from=pub>
---
## Appendix: Overview for each widgets
### TIMUIKitCore
`TIMUIKitCore` provides two static methods, including `getInstance` and `getSDKInstance`
- `getInstance`: Used for get the instance of `CoreServicesImpl`.
- `getSDKInstance`: Used for get the instance of Chat SDK.
`CoreServicesImpl` is the main class of `TUIKit` , providing the methods includes initialization, logging in and out, getting user information, etc.
```dart
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
final CoreServicesImpl _coreInstance = TIMUIKitCore.getInstance();
final V2TIMManager _sdkInstance = TIMUIKitCore.getSDKInstance();
// init
_coreInstance.init(
language: LanguageEnum?, // Specify the displaying language among English / Chinese, Traditional / Chinese, Simplified. If this field is not provided, the default is the system language
onTUIKitCallbackListener: ValueChanged<TIMCallback>, // The callback listener for information from TUIKit, includes errors from SDK API/ the info needs to reminds users/ errors from Flutter. You can reminds users up to your business needs, the description below for details.
sdkAppID: 0, // sdkAppID from Tencent IM console
loglevel: LogLevelEnum.V2TIM_LOG_DEBUG,
listener: V2TimSDKListener());
// unInit
_coreInstance.unInit();
// login
_coreInstance.login(
userID: 0, // user ID
userSig: "" // [How do I calculate UserSig on the server?](https://www.tencentcloud.com/document/product/1047/34385?from=pub)
)
// logout
_coreInstance.logout();
// getUsersInfo
_coreInstance.getUsersInfo(userIDList: ["123", "456"]);
// setOfflinePushConfig
_coreInstance.setOfflinePushConfig(
businessID: businessID, // The business from Tencent IM console, for each platform of devices
token: token, // The token from manufactors when registering the offline push
)
// setSelfInfo
_coreInstance.setSelfInfo(userFullInfo: userFullInfo) // set self userinfo
// setTheme
_coreInstance.setTheme(TUITheme theme: theme) // set theme color
/*
TUITheme(
// Primary color
final Color? primaryColor;
// Secondary color
final Color? secondaryColor;
// Info color, for secondary operations or prompts
final Color? infoColor;
// Weak background color, lighter than the main background color, used to fill gaps or shadows
final Color? weakBackgroundColor;
// Weak divider line color, for dividing lines or borders
final Color? weakDividerColor;
// Weak text color
final Color? weakTextColor;
// Dark text color
final Color? darkTextColor;
// Used for AppBar or Panels
final Color? lightPrimaryColor;
// Text color
final Color? textColor;
// Warning color for dangerous operation
final Color? cautionColor;
// Group owner identification color
final Color? ownerColor;
// Group administrator identification color
final Color? adminColor;)
*/
```
#### `onTUIKitCallbackListener`
This listener is used to get information including: errors form SDK API / errors form Flutter / some remind information that may need to pop up to prompt users.
Determine the type by `TIMCallbackType`.
> You may refer to our [DEMO](https://github.com/TencentCloud/tc-chat-demo-flutter/lib/src/pages/app.dart) for the codes in this part, and modifying up to your business needs.
##### Errors form SDK API(`TIMCallbackType.API_ERROR`)
In this scenario, SDK API original `errorMsg` and `errorCode` are provided.
[Error codes listed here](https://www.tencentcloud.com/document/product/1047/34348?from=pub)
#### Errors form Flutter(`TIMCallbackType.FLUTTER_ERROR`)
This error is captured by listening Flutter natively throwing an exception, providing `stackTrace` (from `FlutterError.onError`) or `catchError` (from try-catch) when the error occurs.
#### Remind information(`TIMCallbackType.INFO`)
It is suggest to pup up to prompt users for this kind of messages.
Provide the `infoCode` info code to help you determine the current scene, and provide the default prompt recommendation `infoRecommendText`.
You can directly pop up our recommendations, or you can customize the recommendations according to the scene code. The language of recommendation text is adaptive according to the system language or the language you specified, do not judge the scene according to the recommendation language.
The rules for info code are as follows:
The info code consists of seven digits, the first five digits determine the components of the scene, and the last two digits determine the specific performance of the scene.
| The first five digits | Corresponding widget |
| ---------- | ---------------------- |
| 66601 | `TIMUIKitAddFriend` |
| 66602 | `TIMUIKitAddGroup` |
| 66603 | `TIMUIKitBlackList` |
| 66604 | `TIMUIKitChat` |
| 66605 | `TIMUIKitContact` |
| 66606 | `TIMUIKitConversation` |
| 66607 | `TIMUIKitGroup` |
| 66608 | `TIMUIKitGroupProfile` |
| 66609 | `TIMUIKitNewContact` |
| 66610 | `TIMUIKitGroupProfile` |
| 66611 | `TIMUIKitNewContact` |
| 66612 | `TIMUIKitProfile` |
| 66613 | `TIMUIKitSearch` |
| 66614 | General Widget |
All info codes are listed below:
| `infoCode` | Recommendation prompt `infoRecommendText` | Scene description |
| ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 6660101 | Contact request sent | User requests to add another user as a contact. |
| 6660102 | This user is your contact. | When a user applies to add another user who is already a contact, the callback of `onTapAlreadyFriendsItem` is triggered. |
| 6660201 | Group request sent | Users apply to join a group chat that requires the approval of the administrator.|
| 6660202 | You are already in this group | When a user applies to join a group, it is determined that the user is already a member of the current group, triggering the callback of `onTapExistGroup`. |
| 6660401 | Failed to locate the original message | When the user needs to jump to the @ message or reference the message, the target message is not found in the message list. |
| 6660402 | Video saved successfully | After clicking on the video message in the message list, and chooses to save the video. |
| 6660403 | Failed to save the video | After clicking on the video message in the message list, and chooses to save the video. |
| 6660404 | Message too short | The user sent an overly short voice message. |
| 6660405 | Sending failed. The video cannot exceed 100 MB. | The user attempted to send a video larger than 100MB. |
| 6660406 | Image saved successfully | After clicking on the image in the message list, the user chooses to save the picture. |
| 6660407 | Failed to save the image | After clicking on the image in the message list, the user chooses to save the picture. |
| 6660408 | Copied | The user chooses to copy the text message in the pop-up window. |
| 6660409 | Not implemented | The user selects a non-implemented function in the pop-up. window |
| 6660410 | You are receiving other files | When the user clicks the download file message, the previous download task has not yet been completed. |
| 6660411 | Receiving | User clicks to download file message. |
| 6660412 | Video is available with .mp4 only | The user sent a video message in non-mp4 format |
| 6660413 | Added to download queue and waiting | Added to the queue to be downloaded, while other files are downloading |
| 6661001 | Modification failed due to network disconnection | When users try to modify group data in a non-network environment. |
| 6661002 | Failed to view the group members due to network disconnection | When users try to modify group data in a non-network environment. |
| 6661003 | Admin role canceled successfully | The user removes the other users from the administrator in the group. |
| 6661201 | Modification failed due to network disconnection | When a user tries to modify his or her contact information without a network environment. |
| 6661202 | Added successfully | Add other users as contact on the profile page and automatically add them successfully without verification. |
| 6661203 | Request sent successfully | Add other users as contact on the profile page, and the other user's settings need to be verified. |
| 6661204 | The user is blocked | Add other users as contacts on the profile page, who are on their own blocklist. |
| 6661205 | Added failed | Add other users as contact on the profile page, but failed, probably because the other party is forbidden to add contact. |
| 6661206 | Deleted successfully | Delete other users as contact on the profile page and succeed. |
| 6661207 | Deleted failed | Delete other users as contact on the profile page. Failed. |
| 6661401 | The input cannot be empty | When the user is entering information, an empty string is entered. |
| 6661402 | Please provide a life cycle hook navigating back to home or other pages. | When users quit the group or dissolve the group, they did not provide a way to return to the home page. |
| 6661403 | Insufficient disk storage space, it is recommended to clean up to obtain a better experience | After the login is successful, the device storage space will be automatically detected. If there is less than 1GB, it will be prompted. |
### TIMUIKitConversation
`TIMUIKitConversation` shows the conversation list.
The corresponding controller: `TIMUIKitConversationController` is also provided.
```dart
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
final TIMUIKitConversationController _controller =
TIMUIKitConversationController();
void _handleOnConvItemTaped(V2TimConversation? selectedConv) {
// You can jump to the chat interface here.
}
List<ConversationItemSlidablePanel> _itemSlidableBuilder(
V2TimConversation conversationItem) {
return [
ConversationItemSlidablePanel(
onPressed: (context) {
_clearHistory(conversationItem);
},
backgroundColor: hexToColor("006EFF"),
foregroundColor: Colors.white,
label: 'Clear conversaation',
autoClose: true,
),
ConversationItemSlidablePanel(
onPressed: (context) {
_pinConversation(conversationItem);
},
backgroundColor: hexToColor("FF9C19"),
foregroundColor: Colors.white,
label: conversationItem.isPinned! ? 'unpined' : 'pinned',
)
];
}
TIMUIKitConversation(
lifeCycle: ConversationLifeCycle(), // The lifecycle hook
onTapItem: _handleOnConvItemTaped, // Callback of clicking conversation, can navigating to chat page
itemSlidableBuilder: _itemSlidableBuilder, // Operation items for conversation Item sliding to the left, conversation topping, etc.
controller: _controller, // Conversation component controller, through which you can get conversation data, set conversation data, pin conversation to top and other operations
itembuilder: (conversationItem) {} // Used to customize the conversation item. Can be combined with TIMUIKitConversationController to implement business logic.
conversationCollector: (conversation) {} // Conversation collector, which can customize whether the conversation is displayed
lastMessageBuilder: (V2TimMessage, List<V2TimGroupAtInfo?>) {} // Customize the second line of the conversation item, which is generally used to show the last message
)
```
---
### TIMUIKitChat
`TIMUIKitChat` is the main chat component that provides the display of message list and the ability to send messages.
It also supports custom display of various message types.
Additionally, it can be combined with `TIMUIKitChatController` to realize local storage and pre-rendering of messages, etc.
Currently supported message parsing:
- Text message.
- Image message.
- Video message.
- Voice message.
- Group message.
- Merge message.
- File message.
```dart
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
TIMUIKitChat(
lifeCycle: ChatLifeCycle(), // Lifecycle hook for TIMUIKitChat
conversationID: "", // User ID or Group ID
conversationType: ConversationType, // 1 is c2c chat, 2 is group chat
conversationShowName: "",
appBarActions: [], // appBar operation item, which can be used to jump to the page of group details and personal details, etc.
onTapAvatar: _onTapAvatar, // callback function for clicking the avatar, which can be used to jump to the user profile.
messageItemBuilder: (MessageItemBuilder) {
// Message item layout constructor, you can choose to customize part of the message type or message row layout.
},
extraTipsActionItemBuilder: (message) {
// The configuration for the menu, opend by long pressed messages
},
morePanelConfig: MorePanelConfig(), // The config for more panel area
appBarConfig: AppBar(), // The config for AppBar
mainHistoryListConfig: ListView(), // Additional config for ListView of historical message list
textFieldHintText: "", // The hint of inputTextField
draftText: "", // The draft of inputting message
initFindingMsg: 0, // The message been jumped
config: TIMUIKitChatConfig(), // The config for the whole TIMUIKitChat
onDealWithGroupApplication: (String groupID){
// Navigating to the pages for the page of [TIMUIKitGroupApplicationList] or other pages to handle joining group application for specific group
}
)
```
---
### TIMUIKitProfile
`TIMUIKitProfile` shows the detail information for a user, and manage the relationship.
```dart
TIMUIKitProfile(
userID: "",
controller: TIMUIKitProfileController(), // Profile Controller
profileWidgetBuilder: ProfileWidgetBuilder(), // Customized some kinds of item.
profileWidgetsOrder: List<ProfileWidgetEnum>, // Determine the vertical sequence for those profile widgets.
builder: (
BuildContext context,
V2TimFriendInfo friendInfo,
V2TimConversation conversation,
int friendType,
bool isMute) {
// Customized the whole page. `profileWidgetBuilder` and `profileWidgetsOrder` will no longer works if you define this.
},
lifeCycle: ProfileLifeCycle(),// Lifecycle hook for TIMUIKitProfile
)
```
---
### TIMUIKitGroupProfile
`TIMUIKitGroupProfile` shows the details of a group and can manage this group.
```dart
TIMUIKitGroupProfile(
groupID: "",
profileWidgetBuilder: GroupProfileWidgetBuilder(), // Customized some kinds of item.
profileWidgetsOrder: List<GroupProfileWidgetEnum>, // Determine the vertical sequence for those profile widgets.
builder: (BuildContext context, V2TimGroupInfo groupInfo, List<V2TimGroupMemberFullInfo?> groupMemberList){
// Customized the whole page. `profileWidgetBuilder` and `profileWidgetsOrder` will no longer works if you define this.
},
lifeCycle: GroupProfileLifeCycle, // Lifecycle hook for TIMUIKitGroupProfile
)
```
---
### TIMUIKitBlackList
`TIMUIKitBlackList` shows the list of blocked users.
```dart
TIMUIKitBlackList(
onTapItem: (_) {}, // Callback of clicking the item
emptyBuilder: () {} // The builder when no user listed
itemBuilder: () {} // Customized the user item
lifeCycle: BlockListLifeCycle(), // Lifecycle hook for TIMUIKitBlackList
)
```
---
### TIMUIKitGroup
`TIMUIKitGroup` shows the list of joined group.
```dart
TIMUIKitGroup(
onTapItem: (_) {}, // Callback of clicking the item
emptyBuilder: () {} // The builder when no group listed
itemBuilder: () {} // Customized the group item
)
```
---
### TIMUIKitContact
`TIMUIKitContact` shows the list of contacts.
```dart
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
TIMUIKitContact(
lifeCycle: FriendListLifeCycle(), // Lifecycle hook for TIMUIKitContact
topList: [
TopListItem(name: "New Contact", id: "newContact"),
TopListItem(name: "Group", id: "groupList"),
TopListItem(name: "Blocklist", id: "blackList")
], // Top list array
topListItemBuilder: _topListBuilder, // The builder for top list array
onTapItem: (item) { }, // Callback of clicking a contact
emptyBuilder: (context) => const Center(
child: Text("No cantact"),
), // The builder when no contact listed
);
```
### TIMUIKitSearch
`TIMUIKitSearch` is a global search widget. Global search supports search for "contacts" / "groups" / "chat records".
`TIMUIKitSearchMsgDetail` is an intra-conversation search component that can search for chat records for a specific conversation.
```dart
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
// Search globally
TIMUIKitSearch(
onTapConversation: _handleOnConvItemTapedWithPlace, // Function(V2TimConversation, V2TimMessage? message), navigating to specific message from specific conversation
onEnterConversation: (V2TimConversation conversation, String initKeyword){}, // Navigating to search in specific conversation. Please navigate to TIMUIKitSearchMsgDetail manually
);
// Search inside a specific conversation
TIMUIKitSearchMsgDetail(
currentConversation: conversation!,
onTapConversation: onTapConversation,
keyword: initKeyword ?? "",
);
```
## What's more
In addition to Flutter SDK, we have numerous SDKs that covering all platforms. The following platforms can communicate with each other and provide services across devices and platforms.
| Platform | Introduction | Demo | Download | UI Components library |
| --- | --- | --- | --- | --- |
| Android | Compatible with JDK 1.6 and Android SDK version 14 and later | [Get](https://www.tencentcloud.com/document/product/1047/34279) | [Get](https://github.com/TencentCloud/TIMSDK/tree/master/Android/IMSDK) | [Get](https://www.tencentcloud.com/document/product/1047/50062) |
| iOS | Compatible with iOS 8.0 and later | [Get](https://www.tencentcloud.com/document/product/1047/34279) | [Get](https://github.com/TencentCloud/TIMSDK/tree/master/iOS/IMSDK) | [Get](https://www.tencentcloud.com/document/product/1047/50062) |
| Mac | Compatible with OS X 10.10 and later | - | [Get](https://github.com/TencentCloud/TIMSDK/tree/master/Mac/IMSDK) | - |
| Windows | C and C++ are included. Compatible with Windows 7, Windows 8 and 8.1, and Windows 10. Both 32-bit and 64-bit programs can be connected | - | [Get](https://github.com/TencentCloud/TIMSDK/tree/master/Windows/IMSDK) | - |
| Web | Supports Internet Explorer 11+, Chrome 7+, Firefox 3.6+, Opera 12+ and Safari 6+ | [Get](https://www.tencentcloud.com/document/product/1047/34279) | [Get](https://www.npmjs.com/package/tim-js-sdk) | [Get](https://www.tencentcloud.com/document/product/1047/50061) |
| Unity | Supports 2020.2.7f1c1 or later | - | [Get](https://www.tencentcloud.com/document/product/1047/46263) | - |
| Flutter | Supports Flutter 2 & dart 2.12 and later, deploying to Android, iOS, Web, macOS and Windows. | [Get](https://www.tencentcloud.com/document/product/1047/34279) | Here | [Get](https://pub.dev/packages/tencent_cloud_chat_uikit) |
| Electron | Electron SDK | - | [Get](https://github.com/tencentyun/im_electron_demo) | - |
## Contact Us
Please do not hesitate to contact us in the following place, if you have any further questions or tend to learn more about the use cases.
Please do not hesitate to contact us in the following place, if you have any further questions or
tend to learn more about the use cases.
- Telegram Group: <https://t.me/+1doS9AUBmndhNGNl>
- WhatsApp Group: <https://chat.whatsapp.com/Gfbxk7rQBqc8Rz4pzzP27A>

View File

@ -1,509 +0,0 @@
# 社交场景最佳实践方案
社交模块是目前主流应用程序最常见的功能之一。有了社交模块,用户在您的应用内,可以自由的交流互动,并添加好友,关注其他用户等等。
这可在很大程度上,促进您应用程序的活跃度,吸引用户留存,获取更多新用户,并可拓展您应用的业务范围。让更多用户花更多时间在您的应用程序上。
例如,
- 交友软件,其核心便是社交聊天模块,用于匹配对话及用户关系链维护,让更多的用户相聚与相识;
- 音乐软件,可用社交模块让乐迷及粉丝群体实时沟通,打造音乐社区文化;
- 教育软件,可用社交模块打通 "学校-教师-家长" 循环,促进家校互动,形成家校社三合力,更大程度发挥教育影响作用,保证教育的一致性与连贯性;
- 电子游戏特别是RPG类型内置的聊天模块让玩家能在线组局一起作战并组建工会创造游戏内社区提升游戏社交属性丰富体验提升活跃度
- 医疗软件,聊天及社群模块让患者间得以互助交流,一起战胜病魔,走出病情,让大家看到希望;
- 导航软件,堵车交流群,让归心似箭的旅行者们,交流前方最新情报,不再无聊干等,发发段子调侃一下,为拥堵的出行,带来一些希望与欢乐。
因此,社交聊天模块可谓是您应用程序不可或缺的能力。
本文将以腾讯云IM为基础梳理现有应用在接入社交场景过程中常见需求给出解决实现方案。以及可能遇到的问题、需要注意的细节点等希望能帮助客户朋友们快速的理解业务、实现需求。
>? 本文档主要介绍社交场景的通用SDK实现方案文中示例截图来自于我们的[Flutter TUIKit](https://cloud.tencent.com/document/product/269/70747)您可根据需要选用我们提供的全部平台SDK或TUIKit。
## 用户
腾讯云IM支持托管维护用户信息与用户资料。您可直接将您应用的用户资料存储与我们的服务内并通过相关API进行读取/更新/维护操作。
对于社交场景,常见用户资料可分为基本信息资料和其他信息资料。
|基本信息|其他信息|
|---|---|
|用户名,性别,生日,所在地,个性签名,昵称等|其他社交模块内需要的资料|
### 导入现有用户数据
如果您需要给您现有应用添加社交能力。可直接使用我们的服务端API快速将您现有用户数据完整导入至腾讯云IM中。
导入完成后,现有用户可直接使用其原有身份数据,和其他用户发起会话,一起聊天,开启社交之旅。
[导入多个账号 - 服务端API](https://cloud.tencent.com/document/product/269/4919)
### 用户在线状态
腾讯云IM支持自动上报并让其他用户获取[在线状态信息](https://cloud.tencent.com/document/product/269/3665)。
状态包括:前台运行状态 / 后台运行状态 / 未登录状态。
利用这一能力,您可让用户看到其他用户的在线状态,增强互动性。
此外,您还可使用这一能力,针对您的业务场景,做许多功能拓展。例如,交友软件,能够优先推荐匹配在线的用户,让聊天可进行的更顺畅,缘分更快相聚。
| 会话列表用户在线状态 | 通讯录用户在线状态 |
|---------|---------|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/0353c92f612c22d11352b19d03c9c44c.png" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/4c0bcc45cab028507df6675e1d242cb9.jpg" /> |
#### 获取用户在线状态
在客户端上, 您可调用 [`getUserStatus`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMManager/getUserStatus.html) 方法,[批量查询其他用户的在线状态](https://cloud.tencent.com/document/product/269/75511#.E6.9F.A5.E8.AF.A2.E7.94.A8.E6.88.B7.E7.8A.B6.E6.80.81)。
此外,在服务端上,也可[通过REST API获取用户状态](https://cloud.tencent.com/document/product/269/2566)。
#### 订阅用户在线状态变更
其他用户的在线状态总是实时在变化的,您可在客户端上,调用 [`subscribeUserStatus`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMManager/subscribeUserStatus.html) 方法,[批量订阅其他用户的在线状态](https://cloud.tencent.com/document/product/269/75511#.E8.AE.A2.E9.98.85.E7.94.A8.E6.88.B7.E7.8A.B6.E6.80.81)。当发生变化时将通过回调函数通知您您可根据其修改界面UI并完成其他您的业务操作。
## 好友
好友管理,又称关系链管理,是社交场景的基础。众多会话/聊天特性,都要依赖于好友关系状态。有了好友关系链能力,众多的用户才能得以串联起来,互动形成整体。
例如微信/QQ只允许好友间发起一对一单聊交友软件则常常可在非好友的情况下进行有限度的聊天在线娱乐社区软件则常常不需要好友关系即可会话。
因此,您需要根据您的应用使用场景,确定好友及关系链管理的用法。
| 通讯录 |
|---------|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/4c0bcc45cab028507df6675e1d242cb9.jpg" /> |
### 好友关系
腾讯云IM支持单个用户添加最多3000个好友。
#### 好友关系类型
好友关系类别包含单向好友和双向好友。
- 双向好友:用户 A 的好友表中有用户 BB 的好友表中也有 A。
- 单向好友:用户 A 的好友表中有用户 B但 B 的好友表中却没有 A。
#### 添加好友验证方式
一回合加好友:如果帐号 A 设置的加好友验证方式是 [AllowType_Type_AllowAny](https://cloud.tencent.com/document/product/269/1500#.E6.A0.87.E9.85.8D.E8.B5.84.E6.96.99.E5.AD.97.E6.AE.B5),那么任何人添加 A 为好友都可直接添加成功,这种一个请求就添加好友成功的场景称作一回合加好友。
两回合加好友:如果帐号 A 设置的加好友验证方式是 [AllowType_Type_NeedConfirm](https://cloud.tencent.com/document/product/269/1500#.E6.A0.87.E9.85.8D.E8.B5.84.E6.96.99.E5.AD.97.E6.AE.B5),那么任何人添加 AA 都会收到一个请求加好友验证消息,这是第一个回合,然后 A 对这个请求加好友验证消息进行同意操作时,这是第二个回合,这种需要验证的加好友场景就被称为两回合加好友。
### 非好友发消息
对于某些场景,需要非好友关系也能发送消息。例如对于交友软件,常常允许匹配到的陌生人,发送若干条消息打招呼。
这需要您在[腾讯云IM的控制台](https://console.cloud.tencent.com/im/login-message),关闭 “好友关系检查” 功能。
![](https://qcloudimg.tencent-cloud.cn/raw/a7d79a4f4af1301879683f1ff99dcdd8.png)
如果您需要针对陌生人发消息的数量加以限制,可在您的业务层实现。发送若干条消息后,不再允许发送即可。
## 群组
有的时候,仅一对一单聊无法满足您的社交场景要求。
例如对于音乐类型app优质的乐迷群及粉丝群有助于打造音乐社区文化吸引用户留存。
### 群组类型
腾讯云IM支持多种类型的群为便于理解在这里以常见的群聊举例。
#### 微信群 - 好友工作群 Work
Work群类似普通微信群。创建后仅支持已在群内的好友邀请加群且无需被邀请方同意或群主审批。
该群适合用于打造好友间互动拉入,产生的群。
#### QQ群 - 陌生人社交群 Public
Public群类似QQ群。创建后群主可指定群管理员用户搜索群 ID 发起加群申请后,需要群主或管理员审批通过(可选)才能入群。
该群适合用于打造兴趣社区用户在您的app中发现好玩的兴趣群组可按需主动加入。
#### 会议群 - Meeting
创建后可随意进出,且支持查看入群前消息。
适合用于音视频会议场景、在线教育场景等与实时音视频产品结合的场景。
#### 直播群 - AVChatRoom
创建后可随意进出,没有群成员数量上限,但不支持历史消息存储。
适合与直播产品结合,用于弹幕聊天场景。
#### Discord - 社群 Community
创建后可随意进出最多支持10w人支持历史消息存储用户搜索群 ID 发起加群申请后,无需管理员审批即可进群。[详情可查看此文档](https://cloud.tencent.com/document/product/269/83870)。
### 群资料
群资料主要包括 群组本身的资料 和 群成员资料。
#### 群组本身的资料
群组资料是指单个群组维度的属性,包括群名称、简介、公告、群主等,以及群组维度自定义字段。
##### 获取群资料
在客户端上,可调用 [`getGroupsInfo`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMGroupManager/getGroupsInfo.html) 方法,获取特定群组资料详情。
##### 修改群资料
可修改群组名称、群组简介、群组公告、群组头像、群名片,修改加群选项、群纬度自定义字段、用户群内身份、群成员维度自定义字段和接收群消息选项等信息。
在客户端上,可调用 [`setGroupInfo`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMGroupManager/setGroupInfo.html) 方法,修改特定群组资料详情。
#### 群成员资料
群成员资料主要包括,特定成员,在群内的备注名/角色/禁言状态/自定义字段信息。在强社交场景的群内,会非常实用。
##### 获取群成员资料
可通过客户端 [`getGroupMembersInfo`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMGroupManager/getGroupMembersInfo.html) 方法或 [`getGroupMemberList`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMGroupManager/getGroupMemberList.html) 方法获取此信息。
##### 修改群成员资料
不同的群成员资料调用不同的API方法修改。[详情可查看此处](https://cloud.tencent.com/document/product/269/37411#.E7.BE.A4.E6.88.90.E5.91.98)。
## 会话
一个会话,您可理解为同某个特定用户的单聊,或一个群聊的消息集合。当用户创建了一个单聊或群聊,当其中有消息的收发时,对应的会话就随之创建。
在腾讯云IM层面每个会话都是一个 [`V2TIMConversation`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimConversation.html) 类的实例,包括了 `会话类型 / 会话ID / 用户ID / 群ID / 显示名称 / 头像 / 最后一条消息 / 草稿 / 群聊类型 / 消息接收方式 / 群 @ 信息列表 / 是否置顶 / 标记列表 / 所属分组信息 / 自定义数据` 信息。
### 会话列表
会话列表,您可以理解成微信软件的首页。即,所有会话的集合。方便用户找到目标会话。
会话列表功能主要分为获取会话列表、处理会话列表更新。
| 会话列表 |
|---------|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/0353c92f612c22d11352b19d03c9c44c.png" /> |
#### 获取会话列表
您可在客户端上调用 [`getConversationList`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/getConversationList.html)。该接口获取的是本地缓存的会话如果服务器会话有更新SDK 内部会自动同步,然后在 [`V2TIMConversationListener`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Listener/V2TimConversationListener.html) 通知回调。
如果您的应用场景会产生较多的会话数,考虑到加载效率、网络省流,我们建议您采用分页拉取的方式。每次分页拉取的数量建议不超过 100 个。[具体方案可参考此处。](https://cloud.tencent.com/document/product/269/75366#.E5.88.86.E9.A1.B5.E6.8B.89.E5.8F.96)
#### 会话列表实时更新
当会话信息发生变化,例如收到一条新消息/设置消息草稿/出现一个新的会话,都会导致会话列表发生更新。
如果需要实时获取更新信息,需要通过客户端的 [`addConversationListener`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/addConversationListener.html) 方法添加会话监听器。收到更新触发后更改UI。
### 会话草稿
在发送消息时,可能会遇到消息尚未编辑完,就要切换至其它聊天窗口的情况。
这些未编辑完的消息可通过 [`setConversationDraft`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/setConversationDraft.html) 接口保存,以便于下次回到这个聊天界面时,通过 [`V2TIMConversation`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimConversation.html) 对象的 [`draftText`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimConversation.html#drafttext) 字段,获取到尚未编辑完的内容,继续编辑。
此类草稿信息,仅保存在本地。
### 置顶会话
会话置顶指的是把单聊或者群聊会话固定在会话列表的最顶部,不会被其他会话更新挤到底部,方便用户查找。
在社交场景中,用户常常需要将一些重要的人或群置顶。这在我们使用微信的过程中,很普遍。
置顶状态会存储在服务器,切换终端设备后,置顶状态会同步到新设备上。
| 置顶的会话,注意最上方第一条 |
|---------|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/0353c92f612c22d11352b19d03c9c44c.png" /> |
置顶会话,通过客户端 [`pinConversation`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/pinConversation.html) 即可。
调用 [`getConversationList`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/getConversationList.html) 获取会话列表时,该接口返回的会话列表中,置顶的会话在前,未置顶的会话在后。您可以通过 [`V2TIMConversation`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimConversation.html) 对象的 [`isPinned`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimConversation.html#ispinned) 字段,检查会话有没有置顶。
### 会话标记
在某些社交场景下,您可能需要对会话进行标记,例如 "会话标星"、"会话折叠"、"会话隐藏"、“会话标记未读”。
直接在客户端调用 [`markConversation`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/markConversation.html) 方法,即可标记,或取消标记一条会话。
标记后,在后续通过 [`getConversationListByFilter`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMConversationManager/getConversationListByFilter.html) 方法请求会话列表时,可按照标记,过滤获取指定会话。
## 消息
消息是社交模块的灵魂。众多各种类型的消息,组成了一个个会话,使得用户与用户之间,紧密的串联在一起。
腾讯云IM中一对一单聊消息与群聊消息用法在大部分场景中都类似下面着重介绍几点。
### 消息类型
腾讯云IM支持多种类型的消息如下
| 功能类型 | 功能描述 |
| ------------ | ------------------------------------------------------------ |
| 文本消息 | 消息内容是普通文本 |
| 表情消息 | 表情消息为开发者自定义,可传入表情资源路径 |
| 地理位置消息 | 消息内容为地理位置标题、经度、纬度信息 |
| 图片消息 | 消息内容为图片的 URL 地址、尺寸、图片大小等信息最大支持大小为28M的图片 |
| 语音消息 | 消息内容为语音文件的 URL 地址、大小、时长等信息最大支持大小为28M的语音文件 |
| 文件消息 | 消息内容为文件的 URL 地址、大小、格式等信息格式不限最大支持大小为100M的文件 |
| 短视频消息 | 消息内容为短视频文件的 URL 地址、时长、大小、格式等信息最大支持大小为100M的短视频文件 |
| 自定义消息 | 开发者自定义的消息类型,例如红包消息、石头剪刀布等形式的消息 |
### 消息回复
回复一条消息,既支持使用文字内容,发一条新消息,引用原消息;也支持使用[Emoji表情回应](https://cloud.tencent.com/document/product/269/85906)。
| 引用回复文本 | [表情回应](https://cloud.tencent.com/document/product/269/85906) |
|---------|---------|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/cf6ded385d08f6ce4e3fde708e7dd588.png" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/0a7532c8c59a40fd4d548bfc64e1d119.png" /> |
#### 引用回复文本
此方案效果和微信中,长按一条消息,选择 “引用”,效果一致。
引用消息实际上在腾讯云IM SDK层面也是一条普通文本消息。文本消息的主体则是回复的文字内容。
为了展示原消息的引用,需要在发送文本消息的时候,将原消息的信息,传入新消息的 [`cloudCustomData`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#cloudcustomdata) 字段中。例如我们的TUIKit为了实现这个功能传入了如下JSON。
```json
"messageReply": {
"messageID": 原消息的ID,
"messageAbstract": 原消息的描述,用于显示在消息列表气泡中,
"messageSender": 原消息的发送者,建议使用备注名或昵称,
"messageType": 原消息类型,
"version": 协议版本
}
```
在消息列表中展示时,从 [`cloudCustomData`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#cloudcustomdata) 字段中提取出上述JSON信息直接用于拼接展示 `"${messageSender}: ${messageAbstract}"` 即可。
如需支持点击展示引用消息的区域跳转至被引用原始消息。可根据上述JSON中的 `messageID` 字段,在消息列表中,找到这条消息,跳转即可。如果消息当前不存在于数组中,可直接调用 [`getHistoryMessageList`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/getHistoryMessageList.html),参数传入 `messageID`,获取本条消息及其上下文消息。
#### 表情回应
在回复特定的单条消息时你不仅可以直接引用原消息并回复还可使用Emoji表情回应高效表达“好的”、“赞同”、“很棒”、“哭笑不得”、“加油”等多种信息大大降低沟通成本解决多人聊天中消息冗杂的问题。
通常,若干个用户,可对同一条消息,或多条消息,点击一个或若干个回应表情。[在显示上](https://cloud.tencent.com/document/product/269/85906#.E5.B1.95.E7.A4.BA.E8.A1.A8.E6.83.85.E5.9B.9E.E5.BA.94),这些回应信息,常常承载在不同的气泡中,以表情为首,后面跟着若干个名字。如本章节图片所示。
这些名称需要支持点击并跳转至用户Profile详情页中。若名字过多还需要加以折叠[通过新窗口详情页展示](https://cloud.tencent.com/document/product/269/85906#.E5.B1.95.E7.A4.BA.E8.A1.A8.E6.83.85.E5.9B.9E.E5.BA.94)。
[发送表情回应](https://cloud.tencent.com/document/product/269/85906#.E5.8F.91.E9.80.81.E8.A1.A8.E6.83.85.E5.9B.9E.E5.BA.94),则可放置于消息的长按菜单中。
| 发送表情回应 [TUIKit](https://cloud.tencent.com/document/product/269/85906) | 表情回应详情 [TUIKit](https://cloud.tencent.com/document/product/269/85906) |
|---------|---------|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/0a7532c8c59a40fd4d548bfc64e1d119.png" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/1f0930f86d405fc6e79bb607b762dbc8.png" /> |
**下面介绍实施细节:**
表情回应的数据,存储于消息的 [`cloudCustomData`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#cloudcustomdata) 字段中可以如下JSON格式示例。其中的 `key` 字段如采用Emoji Unicode表情字符可直接传入 Unicode若采用图片小表情可传入路径或文件名。
```json
messageReactions: [
{
key: "表情名称1",
users: ["用户1", "用户2", ...]
},
{
key: "表情名称2",
users: ["用户1", "用户2", ...]
},
...
]
```
本部分代码可根据您的需求,加以修改。
展示时,渲染遍历上述结构体即可。
发送表情回应,则直接在客户端调用 [`modifyMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/modifyMessage.html) 方法,修改消息本身的 [`cloudCustomData`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#cloudcustomdata) 字段即可。将当前用户头像,添加或从上述结构体中移除,完成回应或取消回应。
调用方法修改后,所有用户的 [`V2TimAdvancedMsgListener`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Listener/V2TimAdvancedMsgListener.html) => [`onRecvMessageModified`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Callback/OnRecvMessageModified.html) 监听器会触发您可依此修改消息UI展示最新表情回应内容。
### 删除消息
删除消息分为两种:删除本地消息和删除云端消息。
删除云端消息会在删除本地消息的基础上,同步删除云端存储的消息,且无法恢复。
删除本地消息,在客户端调用 [`deleteMessageFromLocalStorage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/deleteMessageFromLocalStorage.html) 方法。需要注意的是,如果程序卸载重装,依旧能获取到被删除的消息。
删除云端存储的消息,在客户端调用 [`deleteMessages`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/deleteMessages.html) 方法。此方法不支持多端同步,即无法自动删除,其他设备上,已经存在的消息。
### 搜索消息
搜索能力是社交场景中的重要一环。
用户们常常需要,在特定会话中,亦或是全局中,快速准确搜索到某条消息。此外,也可作为社交活动运营工具,增加相关内容的引导,简洁高效。
您可在客户端,调用 [`searchLocalMessages`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/searchLocalMessages.html) 方法,并传入以 [`V2TIMMessageSearchParam`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessageSearchParam.html) 对象封装的关键词信息,即可完成搜索。
如果您希望在全部会话范围内搜索,只需要将 [`V2TIMMessageSearchParam`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessageSearchParam.html) 中的 [`conversationID`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessageSearchParam.html#conversationid) 设置为空或者不设置即可。
| 全局搜索 | 会话内搜索 |
|---------|---------|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/2a79909010aad73f0fe9cfec33c66857.png" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/739aaa8f9df01b0646aa3e66faceea5b.png" /> |
### 转发消息
在日常生活聊天或工作场景中,将一个会话中的消息,合并或逐条转发至另一个会话,是个非常高频且基础的操作。
| 合并转发消息 | 合并消息详细内容 |
|---------|---------|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/61833485e037eb9bef71a618744e233a.png" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/07c14e870093f514755303ca9efe34a9.png" /> |
逐条转发消息,需要先在客户端调用 [`createForwardMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/createForwardMessage.html) 方法创建一条和原消息内容完全一样的转发消息,再调用 [`sendMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/sendMessage.html) 方法把转发消息发送出去。
合并转发消息,同样需要先创建后转发。需要额外注意的是,在客户端调用 [`createMergerMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/createMergerMessage.html) 方法创建一条合并消息时,需要设置原始消息列表,合并消息标题、合并消息摘要等信息。
若想转发至多个接收者,遍历调用 [`sendMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/sendMessage.html) 方法即可。
对于接收者端,若想展示上方右侧图片的合并消息详情,需要当用户点击合并消息的时候再调用 [`downloadMergerMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/downloadMergerMessage.html) 方法下载合并消息列表 UI 展示。
### 撤回消息
消息撤回是目前社交软件中必备的功能。
发送方可撤回一条已经发送成功的消息。默认情况下,发送者只能撤回 2 分钟以内的消息,此配置可按需修改。
撤回方在客户端,调用 [`revokeMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/revokeMessage.html) 方法,接收方会收到消息撤回通知 [`onRecvMessageRevoked`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Callback/OnRecvMessageRevokedCallback.html)。通知中包含了撤回消息的 msgID您可根据这个 msgID 判断 UI 层是哪一条消息撤回了,然后把对应的消息气泡切换成 "消息已被撤回" 状态。
### 消息翻译
对于国际化的聊天场景,消息翻译功能必不可少,可大大提升跨语言交流效率。社交场景中,大型群聊内,有不同语言的交流存在,是非常之常见的。
| 消息翻译 |
|---------|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/678b9d364dcd0e3f3924d66ef2ae9a1c.png" /> |
对于文本类型的消息,您可在客户端上调用 [`translateText`](https://im.sdk.qcloud.com/doc/zh-cn/classcom_1_1tencent_1_1imsdk_1_1v2_1_1V2TIMMessageManager.html#a1e1806c27bc7b76a3b816492ed9cbe5c) 方法,将待翻译文本列表和目标语言传至我们的服务端。原语言可由您自行判断,也可由我们判断。
翻译结果返回后,我们建议您将其通过客户端的 [`setLocalCustomData`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/setLocalCustomData.html) 方法,存放于消息的 [`localCustomData`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#localcustomdata) 字段中,以便于后续直接展示,避免用户重复翻译,多次发送翻译请求。
我们支持众多语言的互相翻译,[所有支持的语言可查看此处](https://cloud.tencent.com/document/product/269/85380#.E6.96.87.E6.9C.AC.E7.BF.BB.E8.AF.91.E8.AF.AD.E8.A8.80.E6.94.AF.E6.8C.81)。
### 消息已读回执
单聊和群聊均支持消息已读回执功能,操作步骤一致。
| 单聊,[TUIKit](https://cloud.tencent.com/document/product/269/85905)中以文字承载 | 群聊,[TUIKit](https://cloud.tencent.com/document/product/269/85905)中以圆圈承载 |
|---------|---------|
| <img style="width:200px" src="https://qcloudimg.tencent-cloud.cn/raw/fe8aac1b9c3d38aaa81362fa66bd1276.png" /> | <img style="width:200px" src="https://qcloudimg.tencent-cloud.cn/raw/3540692e8d43871d445a1a643fa74b50.png" /> |
是否启用此功能,可根据您的社交业务需求决定。
例如对于类似微信的熟人社交,已读回执的用处可能不是非常大;但是对于陌生人交友场景,已读回执则十分重要,帮助用户来确认,对方是否愿意跟自己聊下去,是否已读不回;对于工作聊天场景,群已读回执还能发挥更大的作用,可便捷看到群内哪些人已读哪些人未读,帮助发送者确认信息传递效率。
**具体用法如下:**
发送端创建消息后,先通过消息对象 [`V2TIMMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html) 的 [`needReadReceipt`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#needreadreceipt) 字段设置这条消息需要已读回执,再发送消息到会话中。
接收端收到消息后,根据消息对象 [`V2TIMMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html) 的 [`needReadReceipt`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Message/V2TimMessage.html#needreadreceipt) 字段判断消息是否需要已读回执。如果需要,当用户查看消息后,调用 `sendMessageReadReceipts` 方法发送消息已读回执。
接收端发送消息已读回执后,发送端可在 [`V2TIMAdvancedMsgListener`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Class/Listener/V2TimAdvancedMsgListener.html) 的 [`onRecvMessageReadReceipts`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Callback/OnRecvMessageReadReceipts.html) 中收到已读回执通知,在通知中更新 UI例如更新某条消息为 “2 人已读”。
此外,发送端也可主动请求消息已读回执信息。发送端从其他界面进入消息列表后,先请求获取历史消息,再调用 [`getMessageReadReceipts`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/getMessageReadReceipts.html) 方法请求获取消息已读回执信息。
群聊场景的消息已读回执,通常需要能够查看详情,显示群内哪些人已读,哪些人未读。当用户点击已读回执角标时,可调用 [`getGroupMessageReadMemberList`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/getGroupMessageReadMemberList.html) 方法分页拉取消息已读或未读群成员列表。
| 已读 群成员 | 未读 群成员 |
|---------|---------|
| <img style="width:200px" src="https://qcloudimg.tencent-cloud.cn/raw/16f2c468b375024f5e70345d657d0f73.png" /> | <img style="width:200px" src="https://qcloudimg.tencent-cloud.cn/raw/916906e7bfb688719e6e96af8540a9c8.png" /> |
### 群内@消息
相信大家已经很熟悉,在群聊交流过程中,如果需要提及或提醒某些群成员,我们可直接 @ 他们。所有的社交聊天软件,都有这个基础功能。
当用户输入 @ 字符后,弹出群成员选择界面。选择完需要 @ 的成员后以 “@A @B @C......” 形式显示在输入框,并可继续编辑消息内容,完成消息发送。
| 监听 @ 字符选择群成员 | 编辑群 @ 消息发送 | 收到群 @ 消息 |
|---------|---------|---------|
| <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/40ff8252c910c955a581ea498463d82a.png" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/71d01c4845b9bbab37585f4968d84ca4.png" /> | <img style="width:250px" src="https://qcloudimg.tencent-cloud.cn/raw/ecfeb41da226a6f8d7162bc935f3aaf6.png" /> |
>? 图一:在聊天界面监听到输入框输入 "@" 字符后,可跳转到群成员选择界面,选择需要 @ 的群成员。
>图二:在群成员选择完成后,重新返回聊天界面,继续编辑群 @ 消息发送。
>图三:如果有消息 @ 我,自己会收到会话更新,可在会话 Cell 展示 “有人@我” 信息。
由于实现方案内容较多,[您可查看此文档](https://cloud.tencent.com/document/product/269/75349),获取详情。
### 消息漫游
如果用户有多台设备,或者同时使用电脑和手机登录您的应用程序,用户们希望看到,无论在哪一端,历史消息都能尽可能完整。能从提供的历史消息上下文中,快速无障碍的加入聊天,满足社交场景高频率聊天的要求。
为了保证交流的连续性与流畅性我们提供一套消息漫游存储能力即用户更换终端的情况下也可以获取到跟其他用户或者某个群的聊天记录可以达到类似QQ软件的效果。
默认情况下单聊消息和群聊消息有7天漫游超过漫游时长的消息会被删除。此外还支持在控制台修改消息漫游时长延长消息漫游时长是增值服务。
以下截图演示了消息在手机和电脑之间漫游。*图片来自Flutter TUIKit一套代码完成电脑桌面端/Web端/移动端应用的开发。*
| 电脑端 | 手机移动端 |
|---------|---------|
| <img style="width:600px" src="https://qcloudimg.tencent-cloud.cn/raw/c0e7e53a523390afa016e3c3c16d28dd.png" /> | <img style="width:200px" src="https://qcloudimg.tencent-cloud.cn/raw/a9d407a28d09f04602327b9fa12f5a08.png" /> |
### 更多丰富的消息形态
我们默认提供的消息类型,可满足您大部分的聊天场景需求。但是对于社交软件来说,仅有这些还远远不够。
红包/送礼物/投票/发送匹配度/闪照等等一系列创新玩法让您app的社交场景模块变得更加丰富多彩。
因此,您可使用我们提供的**自定义消息**能力,来发挥您的想象力,尽情创造激动人心的玩法及贴合您业务需求的功能。
发送自定义消息分两步:
- 调用 [`createCustomMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/createCustomMessage.html) 创建自定义消息,传入消息体。
- 调用 [`sendMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/sendMessage.html) 发送消息。
消息体中您可以JSON格式传入任何符合您需求的数据。例如包含一个字段控制消息形态类型还包含一个字段控制消息当前数据。
如果是投票/红包等类型的消息,如果您想实时更新投票数据/红包领取信息,可将此类信息放在消息体中,在客户端上,通过 [`modifyMessage`](https://comm.qq.com/im/doc/flutter/zh/SDKAPI/Api/V2TIMMessageManager/modifyMessage.html) 方法实时修改。
## 更多能力
### 内容审核
在社交场景中,用户很可能会发送不合规的消息。特别是陌生人交友软件,黄色不良内容消息更是频频出现。诱导性暗示图片/裸聊等令人反感的内容,不仅严重损害了用户们的身心健康,更很有可能违法并导致应用被监管部门查封。
我们支持内容审核(反垃圾信息)功能,可针对不安全、不适宜的内容进行自动识别、处理,为您的产品体验和业务安全保驾护航。
目前有以下三种内容审核方式。
| 方式 | 介绍 |
|---------|---------|
| [本地审核](https://cloud.tencent.com/document/product/269/79139#bdsh) | 在客户端本地检测由即时通信 SDK 发送的文本内容,支持对已配置的敏感词进行拦截或者替换处理。此功能通过在 IM 开启服务并配置词库的方式实现。 |
| [云端审核](https://cloud.tencent.com/document/product/269/79139#ydsh) | 在服务端检测由单聊、群聊、资料场景中产生的文本、图片、音频、视频内容,支持针对不同场景的不同内容分别配置审核策略,并对识别出的不安全内容进行拦截。 |
| [第三方回调服务](https://cloud.tencent.com/document/product/269/79139#dsf) | 如果您已接入第三方内容审核服务,您可以使用 第三方回调配置 来实现。 |
您可按需使用如上三种内容审核工具,保证业务安全运行。
### 离线推送
社交场景下,用户需要随时都能够得知最新的消息,以加快聊天效率,促进社交关系的形成。
由于移动端设备的性能与电量有限,当 App 处于后台时,为了避免维持长连接而导致的过多资源消耗,我们推荐您使用各厂商提供的系统级推送通道来进行消息通知。系统级的推送通道相比第三方推送拥有更稳定的系统级长连接,可以做到随时接受推送消息,且资源消耗大幅降低。
我们目前原生支持的厂商系统有苹果iOS/Google FCM/OPPO/VIVO/华为/小米/魅族/荣耀。
理论上集成系统原生的离线推送需要手动对接各个厂商的SDK手动注册服务/获取Token/承载点击回调页面,非常之复杂。
因此我们提供了如下几款离线推送插件封装了上述厂商的原生SDK大大降低了使用上手成本。您可直接按照文档配置。开箱即用。
- [Native](https://cloud.tencent.com/document/product/269/74285)
- [Flutter](https://cloud.tencent.com/document/product/269/74605)
- [uni-app](https://cloud.tencent.com/document/product/269/79124)
![](https://qcloudimg.tencent-cloud.cn/raw/58f36a132a9fa46b84b12070777b8e9b.png)
### 音视频通话
许多时候,仅靠文字和图片还是不足以抒发我们内心的情感,可能打字聊天一小时,也比不是直接打一通视频电话来的爽快。
特别是对于社交场景下的用户们,他们一定有很多想法想要交流与诉说。
因此,我们也强烈推荐您,再集成我们的音视频通话能力,一步到位,完成一对一或多人群组的音频/视频通话,并且支持离线唤起能力。
[详情可查看此文档](https://cloud.tencent.com/document/product/269/84296)。
![](https://qcloudimg.tencent-cloud.cn/raw/08f914b45857743fd05dfaa28e2adb72.png)
以上就是使用腾讯云IM实现社交场景常见需求的解决方案。快来使用腾讯云IM打造属于您的社交产品吧

View File

@ -1,10 +1,36 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
# This file should be version controlled.
version:
revision: 7e9793dee1b85a243edd0e06cb1658e98b077561
revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
- platform: linux
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
- platform: macos
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
- platform: windows
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
example/imsdk_C.mmap2 Executable file

Binary file not shown.

Binary file not shown.

BIN
example/imsdk_api_report Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,2 @@
<EFBFBD>
<EFBFBD>

View File

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@ -1,17 +1,18 @@
PODS:
- camera (0.0.1):
- audioplayers_darwin (0.0.1):
- Flutter
- connectivity_plus (0.0.1):
- camera_avfoundation (0.0.1):
- Flutter
- ReachabilitySwift
- DKImagePickerController/Core (4.3.3):
- disk_space (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.4):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.3)
- DKImagePickerController/PhotoGallery (4.3.3):
- DKImagePickerController/ImageDataManager (4.3.4)
- DKImagePickerController/PhotoGallery (4.3.4):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.3)
- DKImagePickerController/Resource (4.3.4)
- DKPhotoGallery (0.0.17):
- DKPhotoGallery/Core (= 0.0.17)
- DKPhotoGallery/Model (= 0.0.17)
@ -35,11 +36,18 @@ PODS:
- DKPhotoGallery/Resource (0.0.17):
- SDWebImage
- SwiftyGif
- fc_native_video_thumbnail_for_us (0.0.1):
- Flutter
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- flutter_plugin_record (0.0.1):
- flutter_image_compress (1.0.0):
- Flutter
- Mantle
- SDWebImage
- SDWebImageWebPCoder
- flutter_plugin_record_plus (0.0.1):
- Flutter
- fluttertoast (0.0.2):
- Flutter
@ -52,69 +60,98 @@ PODS:
- Flutter
- image_picker_ios (0.0.1):
- Flutter
- libwebp (1.2.1):
- libwebp/demux (= 1.2.1)
- libwebp/mux (= 1.2.1)
- libwebp/webp (= 1.2.1)
- libwebp/demux (1.2.1):
- libwebp (1.2.4):
- libwebp/demux (= 1.2.4)
- libwebp/mux (= 1.2.4)
- libwebp/webp (= 1.2.4)
- libwebp/demux (1.2.4):
- libwebp/webp
- libwebp/mux (1.2.1):
- libwebp/mux (1.2.4):
- libwebp/demux
- libwebp/webp (1.2.1)
- open_file (0.0.1):
- Flutter
- libwebp/webp (1.2.4)
- Mantle (2.2.0):
- Mantle/extobjc (= 2.2.0)
- Mantle/extobjc (2.2.0)
- package_info_plus (0.4.5):
- Flutter
- path_provider_ios (0.0.1):
- pasteboard (0.0.1):
- Flutter
- "permission_handler (5.1.0+2)":
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.0.4):
- Flutter
- photo_manager (2.0.0):
- Flutter
- FlutterMacOS
- ReachabilitySwift (5.0.0)
- SDWebImage (5.12.5):
- SDWebImage/Core (= 5.12.5)
- SDWebImage/Core (5.12.5)
- shared_preferences_ios (0.0.1):
- ReactiveObjC (3.1.1)
- SDWebImage (5.15.5):
- SDWebImage/Core (= 5.15.5)
- SDWebImage/Core (5.15.5)
- SDWebImageWebPCoder (0.11.0):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.15)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite (0.0.2):
- Flutter
- FMDB (>= 2.7.5)
- SwiftyGif (5.4.3)
- tencent_im_sdk_plugin (1.0.5):
- SwiftyGif (5.4.4)
- tencent_cloud_chat_sdk (5.1.2):
- Flutter
- HydraAsync
- TXIMSDK_Plus_iOS (= 6.1.2155.1)
- TXIMSDK_Plus_iOS (= 7.1.3925)
- tencent_cloud_uikit_core (0.0.1):
- Flutter
- TUICore (= 7.1.3925)
- tencent_open_file (0.0.1):
- Flutter
- Toast (4.0.0)
- TXIMSDK_Plus_iOS (6.1.2155.1)
- TUICore (7.1.3925):
- ReactiveObjC
- SDWebImage
- TUICore/ImSDK_Plus (= 7.1.3925)
- TUICore/Base (7.1.3925):
- ReactiveObjC
- SDWebImage
- TUICore/ImSDK_Plus (7.1.3925):
- ReactiveObjC
- SDWebImage
- TUICore/Base
- TXIMSDK_Plus_iOS (= 7.1.3925)
- TXIMSDK_Plus_iOS (7.1.3925)
- url_launcher_ios (0.0.1):
- Flutter
- video_player_avfoundation (0.0.1):
- Flutter
- video_thumbnail (0.0.1):
- Flutter
- libwebp
- wakelock (0.0.1):
- Flutter
DEPENDENCIES:
- camera (from `.symlinks/plugins/camera/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`)
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
- disk_space (from `.symlinks/plugins/disk_space/ios`)
- fc_native_video_thumbnail_for_us (from `.symlinks/plugins/fc_native_video_thumbnail_for_us/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_plugin_record (from `.symlinks/plugins/flutter_plugin_record/ios`)
- flutter_image_compress (from `.symlinks/plugins/flutter_image_compress/ios`)
- flutter_plugin_record_plus (from `.symlinks/plugins/flutter_plugin_record_plus/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- open_file (from `.symlinks/plugins/open_file/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- permission_handler (from `.symlinks/plugins/permission_handler/ios`)
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)
- tencent_im_sdk_plugin (from `.symlinks/plugins/tencent_im_sdk_plugin/ios`)
- tencent_cloud_chat_sdk (from `.symlinks/plugins/tencent_cloud_chat_sdk/ios`)
- tencent_cloud_uikit_core (from `.symlinks/plugins/tencent_cloud_uikit_core/ios`)
- tencent_open_file (from `.symlinks/plugins/tencent_open_file/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
- video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
SPEC REPOS:
@ -124,83 +161,104 @@ SPEC REPOS:
- FMDB
- HydraAsync
- libwebp
- ReachabilitySwift
- Mantle
- ReactiveObjC
- SDWebImage
- SDWebImageWebPCoder
- SwiftyGif
- Toast
- TUICore
- TXIMSDK_Plus_iOS
EXTERNAL SOURCES:
camera:
:path: ".symlinks/plugins/camera/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
audioplayers_darwin:
:path: ".symlinks/plugins/audioplayers_darwin/ios"
camera_avfoundation:
:path: ".symlinks/plugins/camera_avfoundation/ios"
disk_space:
:path: ".symlinks/plugins/disk_space/ios"
fc_native_video_thumbnail_for_us:
:path: ".symlinks/plugins/fc_native_video_thumbnail_for_us/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_plugin_record:
:path: ".symlinks/plugins/flutter_plugin_record/ios"
flutter_image_compress:
:path: ".symlinks/plugins/flutter_image_compress/ios"
flutter_plugin_record_plus:
:path: ".symlinks/plugins/flutter_plugin_record_plus/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
image_gallery_saver:
:path: ".symlinks/plugins/image_gallery_saver/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
open_file:
:path: ".symlinks/plugins/open_file/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_ios:
:path: ".symlinks/plugins/path_provider_ios/ios"
permission_handler:
:path: ".symlinks/plugins/permission_handler/ios"
pasteboard:
:path: ".symlinks/plugins/pasteboard/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
photo_manager:
:path: ".symlinks/plugins/photo_manager/ios"
shared_preferences_ios:
:path: ".symlinks/plugins/shared_preferences_ios/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/ios"
tencent_im_sdk_plugin:
:path: ".symlinks/plugins/tencent_im_sdk_plugin/ios"
tencent_cloud_chat_sdk:
:path: ".symlinks/plugins/tencent_cloud_chat_sdk/ios"
tencent_cloud_uikit_core:
:path: ".symlinks/plugins/tencent_cloud_uikit_core/ios"
tencent_open_file:
:path: ".symlinks/plugins/tencent_open_file/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/ios"
video_thumbnail:
:path: ".symlinks/plugins/video_thumbnail/ios"
wakelock:
:path: ".symlinks/plugins/wakelock/ios"
SPEC CHECKSUMS:
camera: 9993f92f2c793e87b65e35f3a23c70582afb05b1
connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
DKImagePickerController: 72fd378f244cef3d27288e0aebf217a4467e4012
audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40
camera_avfoundation: 07c77549ea54ad95d8581be86617c094a46280d9
disk_space: e94d34bbdf77954adfb39e60bde9cc5c7233eda6
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
flutter_plugin_record: 562ded56f3a109d769e72c3ef52ef20d835493d4
fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037
fc_native_video_thumbnail_for_us: 69559e6500bff0f6340f044ec0847366fa6f6233
file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_image_compress: 5a5e9aee05b6553048b8df1c3bc456d0afaac433
flutter_plugin_record_plus: 79b8e13ee7ed9a94f6c067018653599528cee1fc
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
HydraAsync: 8d589bd725b0224f899afafc9a396327405f8063
image_gallery_saver: 259eab68fb271cfd57d599904f7acdc7832e7ef2
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc
open_file: 02eb5cb6b21264bd3a696876f5afbfb7ca4f4b7d
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
permission_handler: ccb20a9fad0ee9b1314a52b70b76b473c5f8dab0
pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SDWebImage: 0905f1b7760fc8ac4198cae0036600d67478751e
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
ReactiveObjC: 011caa393aa0383245f2dcf9bf02e86b80b36040
SDWebImage: fd7e1a22f00303e058058278639bf6196ee431fe
SDWebImageWebPCoder: 295a6573c512f54ad2dd58098e64e17dcf008499
shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
tencent_im_sdk_plugin: c68993c62fd0198cd47132a055a06c9cef33b8e3
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
tencent_cloud_chat_sdk: 17f2ddd7de43495312603e7c9dac04d76352e246
tencent_cloud_uikit_core: 2c4ccb41c33b45b5c69750b9774fa389fc20cdb2
tencent_open_file: 1261db508715b8f43ef3b7e31c90824838038165
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
TXIMSDK_Plus_iOS: 2b0e9440eacdb49f385c90a23ad6558013f0cac6
TUICore: edc9e0911f6a04224620d7098ff9d7f4b1f0291a
TXIMSDK_Plus_iOS: 3edf95acc3dff794287ea858b5205ed6f4dd339f
url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
video_thumbnail: c4e2a3c539e247d4de13cd545344fd2d26ffafd1
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
PODFILE CHECKSUM: 7368163408c647b7eb699d0d788ba6718e18fb8d
COCOAPODS: 1.11.3
COCOAPODS: 1.12.0

View File

@ -3,7 +3,6 @@
import 'package:example/TIMUIKitGroupProfileExample.dart';
import 'package:example/TIMUIKitProfileExample.dart';
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class TIMUIKitChatExample extends StatelessWidget {
@ -12,21 +11,60 @@ class TIMUIKitChatExample extends StatelessWidget {
const TIMUIKitChatExample({Key? key, this.selectedConversation})
: super(key: key);
String? _getConversationID() {
if(selectedConversation != null){
return selectedConversation!.type == 1
? selectedConversation!.userID
: selectedConversation!.groupID;
}
return null;
Widget renderCustomStickerPanel({
sendTextMessage,
sendFaceMessage,
deleteText,
addCustomEmojiText,
addText,
List<CustomEmojiFaceData> defaultCustomEmojiStickerList = const [],
double? height,
double? width
}) {
final defaultEmojiList =
defaultCustomEmojiStickerList.map((customEmojiPackage) {
return CustomStickerPackage(
name: customEmojiPackage.name,
baseUrl: "assets/custom_face_resource/${customEmojiPackage.name}",
isEmoji: customEmojiPackage.isEmoji,
isDefaultEmoji: true,
stickerList: customEmojiPackage.list
.asMap()
.keys
.map((idx) =>
CustomSticker(index: idx, name: customEmojiPackage.list[idx]))
.toList(),
menuItem: CustomSticker(
index: 0,
name: customEmojiPackage.icon,
));
}).toList();
return StickerPanel(
sendTextMsg: sendTextMessage,
sendFaceMsg: (index, data) =>
sendFaceMessage(index + 1, (data.split("/")[3]).split("@")[0]),
deleteText: deleteText,
addText: addText,
addCustomEmojiText: addCustomEmojiText,
customStickerPackageList: [
...defaultEmojiList,
]);
}
@override
Widget build(BuildContext context) {
return TIMUIKitChat(
conversation: selectedConversation ??
V2TimConversation(
conversationID: "c2c_10040818",
userID: "10040818",
showName: "Test Chat",
type: 1),
customStickerPanel: renderCustomStickerPanel,
config: const TIMUIKitChatConfig(
// 使
isAllowClickAvatar: true,
isUseDefaultEmoji: true,
isAllowLongPressMessage: true,
isShowReadingStatus: true,
isShowGroupReadingStatus: true,
@ -38,12 +76,6 @@ class TIMUIKitChatExample extends StatelessWidget {
GroupReceiptAllowType.public
],
),
conversation: selectedConversation ??
V2TimConversation(
conversationID: "c2c_10040818",
userID: "10040818",
showName: "Test Chat",
type: 1),
appBarConfig: AppBar(
actions: [
IconButton(

View File

@ -10,8 +10,6 @@ class TIMUIKitSearchExample extends StatelessWidget {
Widget build(BuildContext context) {
return TIMUIKitSearch(
onTapConversation: (conv, message) {
print(conv.toJson());
print(message!.toJson());
},
onEnterConversation: (V2TimConversation conversation, String keyword) {},
);

View File

@ -39,7 +39,7 @@ class MyApp extends StatelessWidget {
// is not restarted.
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Tencent IM UIKit'),
home: const MyHomePage(title: 'Tencent Cloud Chat UIKit'),
);
}
}
@ -67,7 +67,7 @@ class _MyHomePageState extends State<MyHomePage> {
}
String getUserID() {
return const String.fromEnvironment('LOGINUSERID', defaultValue: "");
return const String.fromEnvironment('LOGINUSERID', defaultValue: "10045363");
}
String getSecret() {

1
example/linux/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
flutter/ephemeral

View File

@ -0,0 +1,138 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.10)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "example")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.example")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Define the application target. To change its name, change BINARY_NAME above,
# not the value here, or `flutter run` will no longer work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()

View File

@ -0,0 +1,88 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)

View File

@ -0,0 +1,27 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <audioplayers_linux/audioplayers_linux_plugin.h>
#include <desktop_drop/desktop_drop_plugin.h>
#include <pasteboard/pasteboard_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
g_autoptr(FlPluginRegistrar) pasteboard_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin");
pasteboard_plugin_register_with_registrar(pasteboard_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}

View File

@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View File

@ -0,0 +1,27 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux
desktop_drop
pasteboard
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

6
example/linux/main.cc Normal file
View File

@ -0,0 +1,6 @@
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}

View File

@ -0,0 +1,104 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "example");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "example");
}
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
nullptr));
}

View File

@ -0,0 +1,18 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_

7
example/macos/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# Flutter-related
**/Flutter/ephemeral/
**/Pods/
# Xcode-related
**/dgph
**/xcuserdata/

View File

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@ -0,0 +1,34 @@
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
import audioplayers_darwin
import desktop_drop
import device_info_plus_macos
import fc_native_video_thumbnail_for_us
import package_info_plus_macos
import pasteboard
import path_provider_foundation
import photo_manager
import shared_preferences_foundation
import sqflite
import url_launcher_macos
import wakelock_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FcNativeVideoThumbnailPlugin.register(with: registry.registrar(forPlugin: "FcNativeVideoThumbnailPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
PhotoManagerPlugin.register(with: registry.registrar(forPlugin: "PhotoManagerPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin"))
}

40
example/macos/Podfile Normal file
View File

@ -0,0 +1,40 @@
platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_macos_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_macos_build_settings(target)
end
end

View File

@ -0,0 +1,68 @@
PODS:
- FlutterMacOS (1.0.0)
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- package_info_plus_macos (0.0.1):
- FlutterMacOS
- path_provider_macos (0.0.1):
- FlutterMacOS
- photo_manager (2.0.0):
- Flutter
- FlutterMacOS
- shared_preferences_macos (0.0.1):
- FlutterMacOS
- sqflite (0.0.2):
- FlutterMacOS
- FMDB (>= 2.7.5)
- url_launcher_macos (0.0.1):
- FlutterMacOS
- wakelock_macos (0.0.1):
- FlutterMacOS
DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`)
- package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`)
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
- photo_manager (from `Flutter/ephemeral/.symlinks/plugins/photo_manager/macos`)
- shared_preferences_macos (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- wakelock_macos (from `Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos`)
SPEC REPOS:
trunk:
- FMDB
EXTERNAL SOURCES:
FlutterMacOS:
:path: Flutter/ephemeral
package_info_plus_macos:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos
path_provider_macos:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
photo_manager:
:path: Flutter/ephemeral/.symlinks/plugins/photo_manager/macos
shared_preferences_macos:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos
sqflite:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
wakelock_macos:
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos
SPEC CHECKSUMS:
FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9
PODFILE CHECKSUM: 0d3963a09fc94f580682bd88480486da345dc3f0
COCOAPODS: 1.11.3

View File

@ -0,0 +1,632 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objects = {
/* Begin PBXAggregateTarget section */
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
buildPhases = (
33CC111E2044C6BF0003C045 /* ShellScript */,
);
dependencies = (
);
name = "Flutter Assemble";
productName = FLX;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
BCA39B6D459F9CBDE3F985BB /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AB45B5BD0777F55845AF08A /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
remoteInfo = FLX;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
33CC110E2044A8840003C045 /* Bundle Framework */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Bundle Framework";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
5E1960EFBF841FBF2ED9CB42 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
613D89C488B7E8A9AABFE80A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
6AB45B5BD0777F55845AF08A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9055C8A896F8B9969E9A9A75 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
33CC10EA2044A3C60003C045 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
BCA39B6D459F9CBDE3F985BB /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
33BA886A226E78AF003329D5 /* Configs */ = {
isa = PBXGroup;
children = (
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
);
path = Configs;
sourceTree = "<group>";
};
33CC10E42044A3C60003C045 = {
isa = PBXGroup;
children = (
33FAB671232836740065AC1E /* Runner */,
33CEB47122A05771004F2AC0 /* Flutter */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
35CB9EEBE37DA168A0AC063E /* Pods */,
);
sourceTree = "<group>";
};
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* example.app */,
);
name = Products;
sourceTree = "<group>";
};
33CC11242044D66E0003C045 /* Resources */ = {
isa = PBXGroup;
children = (
33CC10F22044A3C60003C045 /* Assets.xcassets */,
33CC10F42044A3C60003C045 /* MainMenu.xib */,
33CC10F72044A3C60003C045 /* Info.plist */,
);
name = Resources;
path = ..;
sourceTree = "<group>";
};
33CEB47122A05771004F2AC0 /* Flutter */ = {
isa = PBXGroup;
children = (
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
);
path = Flutter;
sourceTree = "<group>";
};
33FAB671232836740065AC1E /* Runner */ = {
isa = PBXGroup;
children = (
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
33E51914231749380026EE4D /* Release.entitlements */,
33CC11242044D66E0003C045 /* Resources */,
33BA886A226E78AF003329D5 /* Configs */,
);
path = Runner;
sourceTree = "<group>";
};
35CB9EEBE37DA168A0AC063E /* Pods */ = {
isa = PBXGroup;
children = (
613D89C488B7E8A9AABFE80A /* Pods-Runner.debug.xcconfig */,
9055C8A896F8B9969E9A9A75 /* Pods-Runner.release.xcconfig */,
5E1960EFBF841FBF2ED9CB42 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
6AB45B5BD0777F55845AF08A /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
33CC10EC2044A3C60003C045 /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
DF30845ABEF3338382F8E5CB /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
2F97E070B313FE8332FEB41C /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
33CC11202044C79F0003C045 /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* example.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
33CC10E52044A3C60003C045 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
33CC10EC2044A3C60003C045 = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Sandbox = {
enabled = 1;
};
};
};
33CC111A2044C6BA0003C045 = {
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Manual;
};
};
};
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 33CC10E42044A3C60003C045;
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
33CC10EC2044A3C60003C045 /* Runner */,
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
33CC10EB2044A3C60003C045 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
2F97E070B313FE8332FEB41C /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
};
33CC111E2044C6BF0003C045 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Flutter/ephemeral/FlutterInputs.xcfilelist,
);
inputPaths = (
Flutter/ephemeral/tripwire,
);
outputFileListPaths = (
Flutter/ephemeral/FlutterOutputs.xcfilelist,
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
DF30845ABEF3338382F8E5CB /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
33CC10E92044A3C60003C045 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
33CC10F52044A3C60003C045 /* Base */,
);
name = MainMenu.xib;
path = Runner;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
338D0CE9231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Profile;
};
338D0CEA231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Profile;
};
338D0CEB231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Profile;
};
33CC10F92044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
33CC10FA2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
33CC10FC2044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
33CC10FD2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Release;
};
33CC111C2044C6BA0003C045 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
33CC111D2044C6BA0003C045 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10F92044A3C60003C045 /* Debug */,
33CC10FA2044A3C60003C045 /* Release */,
338D0CE9231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10FC2044A3C60003C045 /* Debug */,
33CC10FD2044A3C60003C045 /* Release */,
338D0CEA231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC111C2044C6BA0003C045 /* Debug */,
33CC111D2044C6BA0003C045 /* Release */,
338D0CEB231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "example.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "example.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "example.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "example.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,9 @@
import Cocoa
import FlutterMacOS
@NSApplicationMain
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}

View File

@ -0,0 +1,68 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,343 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
<connections>
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="APP_NAME" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About APP_NAME" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="EPT-qC-fAb">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/>
</menuItem>
</items>
<point key="canvasLocation" x="142" y="-258"/>
</menu>
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask"/>
</view>
</window>
</objects>
</document>

View File

@ -0,0 +1,14 @@
// Application-level settings for the Runner target.
//
// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
// future. If not, the values below would default to using the project name when this becomes a
// 'flutter create' template.
// The application's name. By default this is also the title of the Flutter window.
PRODUCT_NAME = example
// The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = com.example.example
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved.

View File

@ -0,0 +1,2 @@
#include "../../Flutter/Flutter-Debug.xcconfig"
#include "Warnings.xcconfig"

View File

@ -0,0 +1,2 @@
#include "../../Flutter/Flutter-Release.xcconfig"
#include "Warnings.xcconfig"

View File

@ -0,0 +1,13 @@
WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
GCC_WARN_UNDECLARED_SELECTOR = YES
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
CLANG_WARN_PRAGMA_PACK = YES
CLANG_WARN_STRICT_PROTOTYPES = YES
CLANG_WARN_COMMA = YES
GCC_WARN_STRICT_SELECTOR_MATCH = YES
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
GCC_WARN_SHADOW = YES
CLANG_WARN_UNREACHABLE_CODE = YES

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>$(PRODUCT_COPYRIGHT)</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -0,0 +1,15 @@
import Cocoa
import FlutterMacOS
class MainFlutterWindow: NSWindow {
override func awakeFromNib() {
let flutterViewController = FlutterViewController.init()
let windowFrame = self.frame
self.contentViewController = flutterViewController
self.setFrame(windowFrame, display: true)
RegisterGeneratedPlugins(registry: flutterViewController)
super.awakeFromNib()
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "569ddca58d535e601dd1584afa117710abc999d036c0cd2c51777fb257df78e8"
sha256: "0c80aeab9bc807ab10022cd3b2f4cf2ecdf231949dc1ddd9442406a003f19201"
url: "https://pub.dev"
source: hosted
version: "53.0.0"
version: "52.0.0"
adaptive_action_sheet:
dependency: transitive
description:
@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: analyzer
sha256: "10927c4b7c7c88b1adbca278c3d5531db92e2f4b4abf04e2919a800af965f3f5"
sha256: cd8ee83568a77f3ae6b913a36093a1c9b1264e7cb7f834d9ddd2311dade9c1f4
url: "https://pub.dev"
source: hosted
version: "5.5.0"
version: "5.4.0"
archive:
dependency: "direct main"
description:
@ -37,10 +37,10 @@ packages:
dependency: transitive
description:
name: args
sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440"
sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.3.2"
async:
dependency: transitive
description:
@ -49,14 +49,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.10.0"
azlistview:
audioplayers:
dependency: transitive
description:
name: azlistview
sha256: "93e865f11777a271b439f0d6b00799c0797e9daeec2e082a2e01373809c4b90d"
name: audioplayers
sha256: "16451eab798b23ad9307aef6f9ca62bb8fb06542af8810eead0d236d3fd40a42"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
audioplayers_android:
dependency: transitive
description:
name: audioplayers_android
sha256: b2c833e6f718b6b030454e329931229afafe9327fdb002874dd544dc8bf2484d
url: "https://pub.dev"
source: hosted
version: "2.0.0"
audioplayers_darwin:
dependency: transitive
description:
name: audioplayers_darwin
sha256: e7a3c8759bf11ecfe4b20df338bf9f3d37c7719a5761c46a3833aba0ceeaacff
url: "https://pub.dev"
source: hosted
version: "3.0.1"
audioplayers_linux:
dependency: transitive
description:
name: audioplayers_linux
sha256: e95b65e1f4d4764601dac5e65f8d8186fc29401043ab020f1dacec483d708707
url: "https://pub.dev"
source: hosted
version: "1.0.4"
audioplayers_platform_interface:
dependency: transitive
description:
name: audioplayers_platform_interface
sha256: "178581a44cb685fd798d2108111d2e98cca3400e30b9c3a05546f124fb37f600"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
audioplayers_web:
dependency: transitive
description:
name: audioplayers_web
sha256: "859ba09be2a57e57a787273f18c8cf0d9b61383870c5ee4b5632fe9adbc37edf"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
audioplayers_windows:
dependency: transitive
description:
name: audioplayers_windows
sha256: "622e01c4c357c2aaf1b956c3a0f89d97c3cb40315c03f16e3b6c2a31ff9c38bc"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
azlistview_all_platforms:
dependency: transitive
description:
name: azlistview_all_platforms
sha256: "47ce2204863e0c3e481ca2a3813096d9818b153f1f677e839503e33d36e97993"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
boolean_selector:
dependency: transitive
description:
@ -109,10 +165,10 @@ packages:
dependency: transitive
description:
name: camera_android
sha256: e491c836147f60dd8a54cf3895fd2960e13b21b78a9d15b563a1b6c70894f142
sha256: "4cef01e8e78fe27c809a429bf74352ab94ab76b0c980e3ec708f1414614e3d9f"
url: "https://pub.dev"
source: hosted
version: "0.10.4"
version: "0.10.3"
camera_avfoundation:
dependency: transitive
description:
@ -125,10 +181,10 @@ packages:
dependency: transitive
description:
name: camera_platform_interface
sha256: b632be28e61d00a233f67d98ea90fd7041956f27a1c65500188ee459be60e15f
sha256: "0eedd642d905ca24f1c483fe9ea0d0e7287b86a402845c28d24df28cc7b0ee6e"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.3.4"
camera_web:
dependency: transitive
description:
@ -225,6 +281,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
desktop_drop:
dependency: transitive
description:
name: desktop_drop
sha256: "4ca4d960f4b11c032e9adfd2a0a8ac615bc3fddb4cbe73dcf840dd8077582186"
url: "https://pub.dev"
source: hosted
version: "0.4.1"
device_info_plus:
dependency: transitive
description:
name: device_info_plus
sha256: b809c4ed5f7fcdb325ccc70b80ad934677dc4e2aa414bf46859a42bfdfafcbb6
url: "https://pub.dev"
source: hosted
version: "4.1.3"
device_info_plus_linux:
dependency: transitive
description:
name: device_info_plus_linux
sha256: "77a8b3c4af06bc46507f89304d9f49dfc64b4ae004b994532ed23b34adeae4b3"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
device_info_plus_macos:
dependency: transitive
description:
name: device_info_plus_macos
sha256: "37961762fbd46d3620c7b69ca606671014db55fc1b7a11e696fd90ed2e8fe03d"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
device_info_plus_platform_interface:
dependency: transitive
description:
name: device_info_plus_platform_interface
sha256: "83fdba24fcf6846d3b10f10dfdc8b6c6d7ada5f8ed21d62ea2909c2dfa043773"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
device_info_plus_web:
dependency: transitive
description:
name: device_info_plus_web
sha256: "5890f6094df108181c7a29720bc23d0fd6159f17d82787fac093d1fefcaf6325"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
device_info_plus_windows:
dependency: transitive
description:
name: device_info_plus_windows
sha256: "23a2874af0e23ee6e3a2a0ebcecec3a9da13241f2cb93a93a44c8764df123dd7"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
diff_match_patch:
dependency: transitive
description:
name: diff_match_patch
sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4"
url: "https://pub.dev"
source: hosted
version: "0.4.1"
disk_space:
dependency: transitive
description:
@ -245,10 +365,10 @@ packages:
dependency: transitive
description:
name: extended_image
sha256: a6b738d9b8d5513be72c545cc3e9c5c451fbee77c8db3cbec7c32ae85b82fb93
sha256: "5854d0d05ee0c687d1852af9db05f15cfe058520fa56f417075705c5bce965d4"
url: "https://pub.dev"
source: hosted
version: "6.4.1"
version: "6.4.0"
extended_image_library:
dependency: transitive
description:
@ -273,14 +393,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.12.6"
fc_native_video_thumbnail_for_us:
dependency: transitive
description:
name: fc_native_video_thumbnail_for_us
sha256: db6fa2998195ef5eadac690ae58d6a909ddb5b0283ebbbf9ae4e0e8f99a54902
url: "https://pub.dev"
source: hosted
version: "0.4.8+1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "13a6ccf6a459a125b3fcdb6ec73bd5ff90822e071207c663bfd1f70062d51d18"
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "2.0.1"
file:
dependency: transitive
description:
@ -293,10 +421,10 @@ packages:
dependency: transitive
description:
name: file_picker
sha256: "704259669b5e9cb24e15c11cfcf02caf5f20d30901b3916d60b6d1c2d647035f"
sha256: b85eb92b175767fdaa0c543bf3b0d1f610fe966412ea72845fe5ba7801e763ff
url: "https://pub.dev"
source: hosted
version: "4.6.1"
version: "5.2.10"
file_utils:
dependency: transitive
description:
@ -350,19 +478,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
flutter_localizations:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_markdown:
dependency: transitive
description:
name: flutter_markdown
sha256: "7b25c10de1fea883f3c4f9b8389506b54053cd00807beab69fd65c8653a2711f"
sha256: "818cf6c28377ba2c91ed283c96fd712e9c175dd2d2488eb7fc93b6afb9ad2e08"
url: "https://pub.dev"
source: hosted
version: "0.6.14"
version: "0.6.13+1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@ -387,14 +510,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
flutter_spinkit:
dependency: transitive
description:
name: flutter_spinkit
sha256: "77a2117c0517ff909221f3160b8eb20052ab5216107581168af574ac1f05dff8"
url: "https://pub.dev"
source: hosted
version: "5.1.0"
flutter_svg:
dependency: transitive
description:
@ -481,18 +596,18 @@ packages:
dependency: transitive
description:
name: image_picker
sha256: d39cc12402dab8365fe5b5370e64694ae0223d675c36b15ff0490b7cc3d32551
sha256: f98d76672d309c8b7030c323b3394669e122d52b307d2bbd8d06bd70f5b2aabe
url: "https://pub.dev"
source: hosted
version: "0.8.6+2"
version: "0.8.6+1"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
sha256: "385f12ee9c7288575572c7873a332016ec45ebd092e1c2f6bd421b4a9ad21f1d"
sha256: b1cbfec0f5aef427a18eb573f5445af8c9c568626bf3388553e40c263d3f7368
url: "https://pub.dev"
source: hosted
version: "0.8.5+6"
version: "0.8.5+5"
image_picker_for_web:
dependency: transitive
description:
@ -505,10 +620,10 @@ packages:
dependency: transitive
description:
name: image_picker_ios
sha256: "884ed71165bc01ffe1b0b7813e6fa17e1e9442da974656f99b79a292371303d6"
sha256: "39c013200046d14c58b71dc4fa3d00e425fc9c699d589136cd3ca018727c0493"
url: "https://pub.dev"
source: hosted
version: "0.8.6+8"
version: "0.8.6+6"
image_picker_platform_interface:
dependency: transitive
description:
@ -585,10 +700,10 @@ packages:
dependency: transitive
description:
name: markdown
sha256: b3c60dee8c2af50ad0e6e90cceba98e47718a6ee0a7a6772c77846a0cc21f78b
sha256: c2b81e184067b41d0264d514f7cdaa2c02d38511e39d6521a1ccc238f6d7b3f2
url: "https://pub.dev"
source: hosted
version: "7.0.1"
version: "6.0.1"
matcher:
dependency: transitive
description:
@ -649,10 +764,10 @@ packages:
dependency: transitive
description:
name: package_info_plus
sha256: "7a6114becdf042be2b0777d77ace954d4a205644171a1cbd8712976b9bbb837c"
sha256: f62d7253edc197fe3c88d7c2ddab82d68f555e778d55390ccc3537eca8e8d637
url: "https://pub.dev"
source: hosted
version: "1.4.2"
version: "1.4.3+1"
package_info_plus_linux:
dependency: transitive
description:
@ -689,10 +804,18 @@ packages:
dependency: transitive
description:
name: package_info_plus_windows
sha256: a6040b8695c82f8dd3c3d4dfab7ef96fbe9c67aac21b9bcf5db272589ef84441
sha256: "79524f11c42dd9078b96d797b3cf79c0a2883a50c4920dc43da8562c115089bc"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
version: "2.1.0"
pasteboard:
dependency: transitive
description:
name: pasteboard
sha256: "1c8b6a8b3f1d12e55d4e9404433cda1b4abe66db6b17bc2d2fb5965772c04674"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
path:
dependency: transitive
description:
@ -745,10 +868,10 @@ packages:
dependency: transitive
description:
name: path_provider_linux
sha256: "2e32f1640f07caef0d3cb993680f181c79e54a3827b997d5ee221490d131fbd9"
sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379
url: "https://pub.dev"
source: hosted
version: "2.1.8"
version: "2.1.7"
path_provider_platform_interface:
dependency: transitive
description:
@ -761,10 +884,10 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: a34ecd7fb548f8e57321fd8e50d865d266941b54e6c3b7758cf8f37c24116905
sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6
url: "https://pub.dev"
source: hosted
version: "2.0.7"
version: "2.1.6"
pedantic:
dependency: transitive
description:
@ -829,14 +952,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.5.2"
photo_view:
dependency: transitive
description:
name: photo_view
sha256: "8036802a00bae2a78fc197af8a158e3e2f7b500561ed23b4c458107685e645bb"
url: "https://pub.dev"
source: hosted
version: "0.14.0"
platform:
dependency: transitive
description:
@ -909,14 +1024,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
scrollable_positioned_list:
scrollable_positioned_list_for_us:
dependency: transitive
description:
name: scrollable_positioned_list
sha256: "9566352ab9ba05794ee6c8864f154afba5d36c5637d0e3e32c615ba4ceb92772"
name: scrollable_positioned_list_for_us
sha256: b5bcbb35114902c004a4f98f2dbd5b0a5a7f80a0144a8b46297601e38fa5383d
url: "https://pub.dev"
source: hosted
version: "0.2.3"
version: "0.4.2"
shared_preferences:
dependency: transitive
description:
@ -1062,33 +1177,41 @@ packages:
dependency: transitive
description:
name: tencent_cloud_chat_sdk
sha256: "32b7e40c5a1682b3ee85daa73504af3d0d4e60a93472b279591e769b15b4861c"
sha256: "765a93262a41080e155ce5b8a6ca20147a81c7d306f7f87444077c5eaae87e08"
url: "https://pub.dev"
source: hosted
version: "5.0.9"
version: "5.1.5"
tencent_cloud_chat_uikit:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "1.7.0"
version: "2.0.0+1"
tencent_cloud_uikit_core:
dependency: transitive
description:
name: tencent_cloud_uikit_core
sha256: "829dfde0c4fbdae019ba233f7f2c299e7cbd18c3ae20ecfe3ab4a43084a33064"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
tencent_extended_text:
dependency: transitive
description:
name: tencent_extended_text
sha256: cf0d283c01a9e63f75666d8b5b1cabd463e18e51802bf1d093d7a65bd369b3d4
sha256: "27a2f7ee58ada480e295102471f1733a7402178a239d0c80a7aa33a134c641ef"
url: "https://pub.dev"
source: hosted
version: "1.0.2+1"
version: "1.0.2"
tencent_extended_text_field:
dependency: transitive
description:
name: tencent_extended_text_field
sha256: daa10f3775bfac1cc841b34275c2746ced7764f3b77222a93edb4c13bad1209b
sha256: d311c240983dbf78e31b58f91e425920a40d6564942813e692a3419bf5c9deb0
url: "https://pub.dev"
source: hosted
version: "1.0.1+2"
version: "1.0.1"
tencent_extended_text_library:
dependency: transitive
description:
@ -1101,26 +1224,34 @@ packages:
dependency: transitive
description:
name: tencent_im_base
sha256: b5917ff0bae7c31d52f54932349fc400d3752719a1c5a2f0209258f85c7a6c07
sha256: "516356a80f43b94a6c0719b54e4c641cb1f164830b2b3e887d175ae862ebab3f"
url: "https://pub.dev"
source: hosted
version: "1.0.26"
version: "1.0.51"
tencent_im_sdk_plugin_desktop:
dependency: "direct main"
description:
name: tencent_im_sdk_plugin_desktop
sha256: "5fe5ab0765183185fe4a1f94ce1fdc6ab0a450b8522011806549678edb52130d"
url: "https://pub.dev"
source: hosted
version: "0.1.13"
tencent_im_sdk_plugin_platform_interface:
dependency: transitive
description:
name: tencent_im_sdk_plugin_platform_interface
sha256: "7aff6a8495eae3efc3aed1ed944bab7a0ce7eb1035d09b3f3d7974bcb3d8b137"
sha256: "04043582f1af698b4abe12d53cd0f043466228fae712677688988d8ff7bfc1f1"
url: "https://pub.dev"
source: hosted
version: "0.3.12"
version: "0.3.19"
tencent_im_sdk_plugin_web:
dependency: "direct main"
description:
name: tencent_im_sdk_plugin_web
sha256: d83a2370398948bccdc3fa4aae33840bcee62d78faa288823a02d9e0e4ab677f
sha256: "6ddf543c6fb4a2c220c5cec3b9247b4353c3dc0f276e76b81c800ad4c92f9bb5"
url: "https://pub.dev"
source: hosted
version: "0.3.7"
version: "0.3.9"
tencent_keyboard_visibility:
dependency: transitive
description:
@ -1133,10 +1264,10 @@ packages:
dependency: transitive
description:
name: tencent_open_file
sha256: "98cbffe55e3245a308fbf76e18c9a0f808e534c624bc1a1cc0a00bd63a418290"
sha256: "01f94f618da42e5593bbad0657fcd13cfc1c2360cca805d8cdfefe898cbe5429"
url: "https://pub.dev"
source: hosted
version: "4.0.9+1"
version: "4.0.10"
tencent_super_tooltip:
dependency: transitive
description:
@ -1149,10 +1280,10 @@ packages:
dependency: transitive
description:
name: tencent_wechat_camera_picker
sha256: "6a6fd12d61ad2ef17273a226a165fe0b5e3ef5c7e49779de38503e4f4b6e3ef1"
sha256: "8f95b435c7a12a9221f00fe4354fb9c0f9313d79cc09ddb5902b7b418185092d"
url: "https://pub.dev"
source: hosted
version: "3.6.5+2"
version: "3.6.5+1"
term_glyph:
dependency: transitive
description:
@ -1173,18 +1304,18 @@ packages:
dependency: transitive
description:
name: tim_ui_kit_sticker_plugin
sha256: cd5d6e2380deaf73e420602bbfe3da36c0f3253adc531f76e139efec80671175
sha256: "2a825d33076f319f6c1c87d58e2b0d650c9284ae4afd8efdc206f3e6f3582e64"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "2.0.1"
transparent_image:
dependency: transitive
description:
name: transparent_image
sha256: e8991d955a2094e197ca24c645efec2faf4285772a4746126ca12875e54ca02f
sha256: e566a616922a781489f4d91cc939b1b3203b6e4a093317805f2f82f0bb0f8dec
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.0"
tuple:
dependency: transitive
description:
@ -1205,26 +1336,26 @@ packages:
dependency: transitive
description:
name: universal_html
sha256: b5061c64c7c863c12e46279e032976f1c274f927fb3589b52b5928dcd2d52f7c
sha256: "5ff50b7c14d201421cf5230ec389a0591c4deb5c817c9d7ccca3b26fe5f31e34"
url: "https://pub.dev"
source: hosted
version: "2.0.9"
version: "2.0.8"
universal_io:
dependency: transitive
description:
name: universal_io
sha256: "06866290206d196064fd61df4c7aea1ffe9a4e7c4ccaa8fcded42dd41948005d"
sha256: "79f78ddad839ee3aae3ec7c01eb4575faf0d5c860f8e5223bc9f9c17f7f03cef"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.0.4"
url_launcher:
dependency: transitive
description:
name: url_launcher
sha256: e8f2efc804810c0f2f5b485f49e7942179f56eabcfe81dce3387fec4bb55876b
sha256: "698fa0b4392effdc73e9e184403b627362eb5fbf904483ac9defbb1c2191d809"
url: "https://pub.dev"
source: hosted
version: "6.1.9"
version: "6.1.8"
url_launcher_android:
dependency: transitive
description:
@ -1237,10 +1368,10 @@ packages:
dependency: transitive
description:
name: url_launcher_ios
sha256: "0a5af0aefdd8cf820dd739886efb1637f1f24489900204f50984634c07a54815"
sha256: bb328b24d3bccc20bdf1024a0990ac4f869d57663660de9c936fb8c043edefe3
url: "https://pub.dev"
source: hosted
version: "6.1.0"
version: "6.0.18"
url_launcher_linux:
dependency: transitive
description:
@ -1337,14 +1468,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.13"
video_thumbnail:
dependency: transitive
description:
name: video_thumbnail
sha256: "3455c189d3f0bb4e3fc2236475aa84fe598b9b2d0e08f43b9761f5bc44210016"
url: "https://pub.dev"
source: hosted
version: "0.5.3"
wakelock:
dependency: transitive
description:
@ -1381,10 +1504,10 @@ packages:
dependency: transitive
description:
name: wakelock_windows
sha256: "108b1b73711f1664ee462e73af34a9286ff496e27d4d8371e2fb4da8fde4cdac"
sha256: "857f77b3fe6ae82dd045455baa626bc4b93cb9bb6c86bf3f27c182167c3a5567"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.2.1"
watcher:
dependency: transitive
description:
@ -1405,18 +1528,18 @@ packages:
dependency: transitive
description:
name: win32
sha256: c0e3a4f7be7dae51d8f152230b86627e3397c1ba8c3fa58e63d44a9f3edc9cef
sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4
url: "https://pub.dev"
source: hosted
version: "2.6.1"
version: "3.1.4"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1
sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "0.2.0+3"
xml:
dependency: transitive
description:
@ -1434,5 +1557,5 @@ packages:
source: hosted
version: "3.1.1"
sdks:
dart: ">=2.19.0 <4.0.0"
dart: ">=2.19.0 <3.0.0"
flutter: ">=3.7.0"

View File

@ -36,8 +36,17 @@ dependencies:
cupertino_icons: ^1.0.2
tencent_cloud_chat_uikit:
path: ../../tim_ui_kit
tencent_im_sdk_plugin_web: ^0.3.2
tencent_im_sdk_plugin_web: ^0.3.9
archive: ^3.3.0
tencent_im_sdk_plugin_desktop: ^0.1.13
# dependency_overrides:
# tencent_cloud_chat_sdk:
# path: /Users/wangrunlin/Documents/GitHub/im-flutter-plugin/tencent_im_sdk_plugin
# tencent_im_sdk_plugin_desktop:
# path: /Users/wangrunlin/Documents/GitHub/im-flutter-plugin/tencent_im_sdk_plugin_desktop
# tencent_im_sdk_plugin_web:
# path: /Users/wangrunlin/Documents/GitHub/im-flutter-plugin/tencent_im_sdk_plugin_web
dev_dependencies:
flutter_test:

View File

@ -6,10 +6,22 @@
#include "generated_plugin_registrant.h"
#include <audioplayers_windows/audioplayers_windows_plugin.h>
#include <desktop_drop/desktop_drop_plugin.h>
#include <fc_native_video_thumbnail_for_us/fc_native_video_thumbnail_for_us_plugin_c_api.h>
#include <pasteboard/pasteboard_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
AudioplayersWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
DesktopDropPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
FcNativeVideoThumbnailForUsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FcNativeVideoThumbnailForUsPluginCApi"));
PasteboardPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PasteboardPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(

View File

@ -3,6 +3,10 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows
desktop_drop
fc_native_video_thumbnail_for_us
pasteboard
permission_handler_windows
url_launcher_windows
)

BIN
images/folder_open.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

BIN
images/open_in_new.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

BIN
images/video_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -8,10 +8,6 @@ import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
class TIMUIKitState<T extends StatefulWidget> extends TIMState<T> {
final CoreServicesImpl _coreServices = serviceLocator<CoreServicesImpl>();
@override
initState() {
super.initState();
}
@override
void onTIMCallback(TIMCallback callbackValue) {

View File

@ -1,13 +1,11 @@
import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
class TUIChatModelTools {
final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>();
final CoreServicesImpl _coreServices = serviceLocator<CoreServicesImpl>();
final MessageService _messageService = serviceLocator<MessageService>();
OfflinePushInfo buildMessagePushInfo(
V2TimMessage message, String convID, ConvType convType) {
@ -15,6 +13,14 @@ class TUIChatModelTools {
return "{\"conversationID\": \"$convID\"}";
}
if (globalModel.chatConfig.offlinePushInfo != null) {
final customData =
globalModel.chatConfig.offlinePushInfo!(message, convID, convType);
if(customData != null){
return customData;
}
}
String title = globalModel.chatConfig.notificationTitle;
// If user provides null, use default ext.
@ -109,35 +115,9 @@ class TUIChatModelTools {
});
if (targetIndex != null &&
targetIndex != -1 &&
targetIndex > -1 &&
currentHistoryMsgList.isNotEmpty) {
List<V2TimMessage> response;
if (currentHistoryMsgList.length > targetIndex + 2) {
response = await _messageService.getHistoryMessageList(
count: 1,
getType: HistoryMsgGetTypeEnum.V2TIM_GET_LOCAL_NEWER_MSG,
userID: conversationType == ConvType.c2c ? conversationID : null,
groupID: conversationType == ConvType.group ? conversationID : null,
lastMsgID: currentHistoryMsgList[targetIndex + 1].msgID);
} else {
response = await _messageService.getHistoryMessageList(
count: 5,
getType: HistoryMsgGetTypeEnum.V2TIM_GET_LOCAL_OLDER_MSG,
userID: conversationType == ConvType.c2c ? conversationID : null,
groupID: conversationType == ConvType.group ? conversationID : null,
lastMsgID: currentHistoryMsgList.length - 3 < 0
? null
: currentHistoryMsgList[currentHistoryMsgList.length - 3].msgID,
);
}
try {
return response.firstWhere((item) {
return item.msgID == msgID;
});
} catch (e) {
return null;
}
return currentHistoryMsgList[targetIndex];
} else {
return null;
}

View File

@ -2,10 +2,14 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/cupertino.dart';
// ignore: unnecessary_import
import 'package:flutter/foundation.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/chat_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_model_tools.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
@ -17,10 +21,7 @@ import 'package:tencent_cloud_chat_uikit/ui/constants/history_message_constant.d
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:uuid/uuid.dart';
enum LoadDirection {
previous,
latest
}
enum LoadDirection { previous, latest }
class TUIChatSeparateViewModel extends ChangeNotifier {
final FriendshipServices _friendshipServices =
@ -39,7 +40,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
ConvType? conversationType;
bool haveMoreData = false;
bool haveMoreLatestData = false;
String _currentSelectedMsgId = "";
String _currentPlayedMsgId = "";
String _editRevokedMsg = "";
GroupReceiptAllowType? _groupType;
List<V2TimMessage> _multiSelectedMessageList = [];
@ -51,11 +52,29 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
TIMUIKitChatConfig chatConfig = const TIMUIKitChatConfig();
ValueChanged<String>? setInputField;
String Function(V2TimMessage message)? abstractMessageBuilder;
Function(String userID)? onTapAvatar;
Function(String userID, TapDownDetails tapDetails)? onTapAvatar;
V2TimGroupMemberFullInfo? _currentChatUserInfo;
V2TimGroupInfo? _groupInfo;
String groupMemberListSeq = "0";
List<V2TimGroupMemberFullInfo?>? groupMemberList = [];
double atPositionX = 0.0;
double atPositionY = 0.0;
int _activeAtIndex = -1;
List<V2TimGroupMemberFullInfo?> _showAtMemberList = [];
int get activeAtIndex => _activeAtIndex;
set activeAtIndex(int value) {
_activeAtIndex = value;
notifyListeners();
}
List<V2TimGroupMemberFullInfo?> get showAtMemberList => _showAtMemberList;
set showAtMemberList(List<V2TimGroupMemberFullInfo?> value) {
_showAtMemberList = value;
notifyListeners();
}
V2TimGroupInfo? get groupInfo => _groupInfo;
@ -78,10 +97,10 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
notifyListeners();
}
String get currentSelectedMsgId => _currentSelectedMsgId;
String get currentPlayedMsgId => _currentPlayedMsgId;
set currentSelectedMsgId(String value) {
_currentSelectedMsgId = value;
set currentPlayedMsgId(String value) {
_currentPlayedMsgId = value;
notifyListeners();
}
@ -167,6 +186,12 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
globalModel.refreshGroupApplicationList();
getGroupInfo(groupID ?? convID);
loadGroupMemberList(groupID: groupID ?? convID);
} else {
_groupType = null;
isGroupExist = true;
_groupInfo = null;
groupMemberList?.clear();
notifyListeners();
}
if (conversationType == ConvType.c2c) {
final List<V2TimFriendInfoResult>? friendRes =
@ -208,36 +233,29 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
List<V2TimMessage> msgList = [];
haveMoreData = false;
final previousResponse = await _messageService.getHistoryMessageListWithComplete(
count: 20,
getType: HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_OLDER_MSG,
userID: conversationType == ConvType.c2c ? conversationID : null,
groupID: conversationType == ConvType.group ? conversationID : null,
lastMsgSeq: max(seq, 0)
);
final previousResponse =
await _messageService.getHistoryMessageListWithComplete(
count: 20,
getType: HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_OLDER_MSG,
userID: conversationType == ConvType.c2c ? conversationID : null,
groupID: conversationType == ConvType.group ? conversationID : null,
lastMsgSeq: max(seq, 0));
msgList = previousResponse?.messageList ?? [];
haveMoreData = !(previousResponse?.isFinished ?? false);
// final latestResponse = await _messageService.getHistoryMessageListWithComplete(
// count: 20,
// getType: HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_NEWER_MSG,
// userID: conversationType == ConvType.c2c ? conversationID : null,
// groupID: conversationType == ConvType.group ? conversationID : null,
// lastMsgSeq: max(seq - 1, 0)
// );
// msgList = [...(latestResponse?.messageList.reversed ?? []), ...msgList];
haveMoreLatestData = true;
globalModel.setMessageListPosition(conversationID, HistoryMessagePosition.notShowLatest);
globalModel.setMessageListPosition(
conversationID, HistoryMessagePosition.notShowLatest);
msgList = await lifeCycle?.didGetHistoricalMessageList(msgList) ?? msgList;
msgList.insert(0, V2TimMessage(
userID: '',
isSelf: false,
elemType: 101,
msgID: msgList[0].msgID,
seq: msgList[0].seq,
timestamp: 9999
));
msgList.insert(
0,
V2TimMessage(
userID: '',
isSelf: false,
elemType: 101,
msgID: msgList[0].msgID,
seq: msgList[0].seq,
timestamp: 9999));
globalModel.setMessageList(conversationID, msgList,
needResetNewMessageCount: false);
@ -253,99 +271,141 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
return haveMoreData;
}
Future<bool> loadData({
HistoryMsgGetTypeEnum? getType,
int lastMsgSeq = -1,
required int count,
String? lastMsgID,
LoadDirection direction = LoadDirection.previous,
//
Future<bool> loadChatRecord({
HistoryMsgGetTypeEnum? getType, //
int lastMsgSeq = -1, //
required int count, //
String? lastMsgID, // ID
LoadDirection direction =
LoadDirection.previous, // previous表示向上加载latest表示向下加载
}) async {
// TODO: 2.0QAQ
if(direction == LoadDirection.latest){
haveMoreLatestData = false;
}else{
haveMoreData = false;
}
final currentHistoryMsgList = globalModel.messageListMap[conversationID];
final response = await _messageService.getHistoryMessageListWithComplete(
try {
//
direction == LoadDirection.latest
? haveMoreLatestData = false
: haveMoreData = false;
//
final currentRecordList = globalModel.messageListMap[conversationID];
// MessageService获取聊天记录
final response = await _messageService.getHistoryMessageListWithComplete(
count: count,
getType: getType ?? (direction == LoadDirection.previous
? HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_OLDER_MSG
: HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_NEWER_MSG),
getType: getType ??
(direction == LoadDirection.previous
? HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_OLDER_MSG
: HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_NEWER_MSG),
userID: conversationType == ConvType.c2c ? conversationID : null,
groupID: conversationType == ConvType.group ? conversationID : null,
lastMsgID: lastMsgID,
lastMsgSeq: lastMsgSeq);
if (response == null) {
return false;
}
lastMsgSeq: lastMsgSeq,
);
if (lastMsgID != null && currentHistoryMsgList != null) {
List<V2TimMessage> messageList = response.messageList;
List<V2TimMessage> newList = [];
if(direction == LoadDirection.latest){
globalModel.receivedNewMessageCount = globalModel.receivedMessageListCount + messageList.length;
messageList = messageList.reversed.toList();
newList = [...messageList, ...currentHistoryMsgList];
}else{
newList = [...currentHistoryMsgList, ...messageList];
if (response == null) {
return false;
}
final List<V2TimMessage> msgList =
await lifeCycle?.didGetHistoricalMessageList(newList) ?? newList;
globalModel.setMessageList(conversationID, msgList,
needResetNewMessageCount: false);
} else {
List<V2TimMessage> messageList =
await lifeCycle?.didGetHistoricalMessageList(response.messageList) ??
response.messageList;
if (globalModel.loadingMessage[conversationID] != null) {
if (globalModel.loadingMessage[conversationID]!.isNotEmpty) {
if(direction == LoadDirection.previous){
messageList = [
...?globalModel.loadingMessage[conversationID],
...messageList
];
}else{
messageList = messageList.reversed.toList();
messageList = [
...messageList,
...?globalModel.loadingMessage[conversationID],
];
//
if (direction == LoadDirection.latest) {
haveMoreLatestData = !response.isFinished;
} else {
haveMoreData = !response.isFinished;
}
notifyListeners();
if (response.messageList.isEmpty) {
return false;
}
// lastMsgID判断是否为分页加载
if (lastMsgID != null && currentRecordList != null) {
List<V2TimMessage> messageList = response.messageList;
List<V2TimMessage> newList = [];
//
if (direction == LoadDirection.latest) {
globalModel.receivedNewMessageCount =
globalModel.receivedMessageListCount + messageList.length;
messageList = messageList.reversed.toList();
newList = _combineMessageList(messageList, currentRecordList);
} else {
newList = _combineMessageList(currentRecordList, messageList);
}
//
final List<V2TimMessage> msgList =
await lifeCycle?.didGetHistoricalMessageList(newList) ?? newList;
// model
globalModel.setMessageList(
conversationID,
msgList,
needResetNewMessageCount: false,
);
} else {
//
List<V2TimMessage> receivedList = await lifeCycle
?.didGetHistoricalMessageList(response.messageList) ??
response.messageList;
//
if (globalModel.loadingMessage[conversationID]?.isNotEmpty ?? false) {
if (direction == LoadDirection.previous) {
receivedList = _combineMessageList(
globalModel.messageListMap[conversationID]!, receivedList);
} else {
receivedList = receivedList.reversed.toList();
receivedList = _combineMessageList(
receivedList, globalModel.messageListMap[conversationID]!);
}
} else {
globalModel.loadingMessage.remove(conversationID);
}
}
globalModel.setMessageList(conversationID, messageList,
needResetNewMessageCount: false);
}
if (chatConfig.isShowGroupReadingStatus &&
conversationType == ConvType.group &&
response.messageList.isNotEmpty) {
_getMsgReadReceipt(response.messageList);
}
if (chatConfig.isReportGroupReadingStatus &&
conversationType == ConvType.group &&
response.messageList.isNotEmpty) {
_setMsgReadReceipt(response.messageList);
}
if(direction == LoadDirection.latest){
haveMoreLatestData = !response.isFinished;
if(!haveMoreLatestData){
globalModel.setMessageListPosition(conversationID, HistoryMessagePosition.inTwoScreen);
// model
globalModel.setMessageList(
conversationID,
receivedList,
needResetNewMessageCount: false,
);
}
}else{
haveMoreData = !response.isFinished;
//
if (chatConfig.isShowGroupReadingStatus &&
conversationType == ConvType.group &&
response.messageList.isNotEmpty) {
_getMsgReadReceipt(response.messageList);
}
if (chatConfig.isReportGroupReadingStatus &&
conversationType == ConvType.group &&
response.messageList.isNotEmpty) {
_setMsgReadReceipt(response.messageList);
}
//
if (direction == LoadDirection.latest && !haveMoreLatestData) {
globalModel.setMessageListPosition(
conversationID, HistoryMessagePosition.inTwoScreen);
}
notifyListeners();
return haveMoreData;
} catch (e) {
// ignore: avoid_print
print('loadChatRecord error: $e');
return false;
}
return haveMoreData;
}
//
List<V2TimMessage> _combineMessageList(
List<V2TimMessage> first, List<V2TimMessage> second) {
return [...first, ...second];
}
Future<bool> loadDataFromController({int? count}) {
return loadData(
return loadChatRecord(
count: count ?? HistoryMessageDartConstant.getCount, //20
);
}
@ -357,7 +417,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
_getMsgReadReceipt(List<V2TimMessage> message) async {
final msgID = message
.where((e) => (e.isSelf ?? false) && (e.needReadReceipt ?? false))
.where((e) => (e.isSelf ?? true) && (e.needReadReceipt ?? false))
.map((e) => e.msgID ?? '')
.toList();
if (msgID.isNotEmpty) {
@ -374,10 +434,26 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
}
}
translateText(V2TimMessage message) async {
final String originText = message.textElem?.text ?? "";
final String deviceLocale = TIM_getCurrentDeviceLocale();
final String targetMessage = deviceLocale.split("-")[0];
final translatedText =
await _messageService.translateText(originText, targetMessage);
final LocalCustomDataModel localCustomData = LocalCustomDataModel.fromMap(
json.decode(TencentUtils.checkString(message.localCustomData) ?? "{}"));
localCustomData.translatedText = translatedText;
message.localCustomData = json.encode(localCustomData.toMap());
globalModel.onMessageModified(message);
TencentImSDKPlugin.v2TIMManager.v2TIMMessageManager.setLocalCustomData(
msgID: message.msgID!, localCustomData: message.localCustomData ?? "");
}
_setMsgReadReceipt(List<V2TimMessage> message) async {
final msgIDList = List<String>.empty(growable: true);
for (var item in message) {
final isSelf = item.isSelf ?? false;
final isSelf = item.isSelf ?? true;
final needReadReceipt = item.needReadReceipt ?? false;
if (!isSelf && needReadReceipt && item.msgID != null) {
msgIDList.add(item.msgID!);
@ -397,7 +473,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
markMessageAsRead() async {
globalModel.unreadCountForConversation = 0;
// globalModel.receivedNewMessageCount = 0;
if (conversationType == ConvType.c2c) {
return _messageService.markC2CMessageAsRead(userID: conversationID);
}
@ -472,15 +547,17 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
}
}
Future<void> updateMessageFromController({required String msgID}) async {
V2TimMessage? newMessage = await tools.getExistingMessageByID(
msgID: msgID,
conversationType: conversationType ?? ConvType.c2c,
conversationID: conversationID);
Future<void> updateMessageFromController(
{required String msgID, V2TimMessage? message}) async {
V2TimMessage? newMessage = message ??
await tools.getExistingMessageByID(
msgID: msgID,
conversationType: conversationType ?? ConvType.c2c,
conversationID: conversationID);
if (newMessage != null) {
globalModel.onMessageModified(newMessage, conversationID);
} else {
loadData(
loadChatRecord(
count: HistoryMessageDartConstant.getCount,
);
}
@ -531,7 +608,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
})
: "",
);
if (isEditStatusMessage == false && globalModel.getMessageListPosition(conversationID) != HistoryMessagePosition.notShowLatest) {
if (isEditStatusMessage == false &&
globalModel.getMessageListPosition(conversationID) !=
HistoryMessagePosition.notShowLatest) {
globalModel.updateMessage(
sendMsgRes, convID, id, convType, groupType, setInputField);
}
@ -570,7 +649,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
}
}
if(globalModel.getMessageListPosition(conversationID) != HistoryMessagePosition.notShowLatest) {
if (globalModel.getMessageListPosition(conversationID) !=
HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList = [
lifeCycleMsg ?? messageInfoWithSender,
...currentHistoryMsgList
@ -608,15 +688,16 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
}
}
if(globalModel.getMessageListPosition(conversationID) != HistoryMessagePosition.notShowLatest) {
if (globalModel.getMessageListPosition(conversationID) !=
HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList = [
lifeCycleMsg ?? messageInfoWithSender,
...currentHistoryMsgList
];
globalModel.setMessageList(conversationID, currentHistoryMsgList);
notifyListeners();
}
return _sendMessage(
convID: convID,
id: textATMessageInfo.id as String,
@ -646,7 +727,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
return null;
}
}
if(globalModel.getMessageListPosition(conversationID) != HistoryMessagePosition.notShowLatest) {
if (globalModel.getMessageListPosition(conversationID) !=
HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList = [
lifeCycleMsg ?? messageInfoWithSender,
...currentHistoryMsgList
@ -654,6 +737,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
globalModel.setMessageList(conversationID, currentHistoryMsgList);
notifyListeners();
}
return _sendMessage(
convID: convID,
id: textMessageInfo.id as String,
@ -685,15 +769,17 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
return null;
}
}
if(globalModel.getMessageListPosition(conversationID) != HistoryMessagePosition.notShowLatest) {
if (globalModel.getMessageListPosition(conversationID) !=
HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList = [
lifeCycleMsg ?? messageInfoWithSender,
...currentHistoryMsgList
];
globalModel.setMessageList(conversationID, currentHistoryMsgList);
notifyListeners();
}
return _sendMessage(
convID: convID,
id: soundMessageInfo.id as String,
@ -735,16 +821,22 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
}
}
Future<V2TimValueCallback<V2TimMessage>?> sendReplyMessage(
{required String text,
required String convID,
required ConvType convType}) async {
Future<V2TimValueCallback<V2TimMessage>?> sendReplyMessage({
required String text,
required String convID,
required ConvType convType,
List<String>? atUserIDList,
}) async {
if (text.isEmpty) {
return null;
}
if (_repliedMessage != null) {
final V2TimMsgCreateInfoResult? textMessageInfo =
V2TimMsgCreateInfoResult? textMessageInfo =
await _messageService.createTextMessage(text: text);
if (atUserIDList != null && atUserIDList.isNotEmpty) {
textMessageInfo = await _messageService.createTextAtMessage(
text: text, atUserList: atUserIDList);
}
final V2TimMessage? messageInfo = textMessageInfo!.messageInfo;
final receiver = convType == ConvType.c2c ? convID : '';
final groupID = convType == ConvType.group ? convID : '';
@ -753,7 +845,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
: null;
if (messageInfo != null) {
V2TimMessage messageInfoWithSender =
tools.setUserInfoForMessage(messageInfo, messageInfo.id!);
tools.setUserInfoForMessage(messageInfo, textMessageInfo.id!);
final hasNickName = _repliedMessage?.nickName != null &&
_repliedMessage?.nickName != "";
final cloudCustomData = {
@ -843,10 +935,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
format: CompressFormat.jpeg, quality: 85);
image = result?.path;
}
} catch (e) {
// ignore: avoid_print
print(e);
}
// ignore: empty_catches
} catch (e) {}
}
final imageMessageInfo = await _messageService.createImageMessage(
imagePath: image ?? imagePath, inputElement: inputElement);
@ -863,15 +953,17 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
return null;
}
}
if(globalModel.getMessageListPosition(conversationID) != HistoryMessagePosition.notShowLatest){
currentHistoryMsgList = [
lifeCycleMsg ?? messageInfoWithSender,
...currentHistoryMsgList
];
globalModel.setMessageList(conversationID, currentHistoryMsgList);
notifyListeners();
if (globalModel.getMessageListPosition(conversationID) !=
HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList = [
lifeCycleMsg ?? messageInfoWithSender,
...currentHistoryMsgList
];
globalModel.setMessageList(conversationID, currentHistoryMsgList);
notifyListeners();
}
return _sendMessage(
convID: convID,
messageInfo: lifeCycleMsg ?? messageInfoWithSender,
@ -894,7 +986,9 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
List<V2TimMessage> currentHistoryMsgList = getOriginMessageList();
final videoMessageInfo = await _messageService.createVideoMessage(
videoPath: videoPath,
type: 'mp4',
type: videoPath != null
? videoPath.split(".")[videoPath.split(".").length - 1]
: 'mp4',
duration: duration,
inputElement: inputElement,
snapshotPath: snapshotPath);
@ -909,15 +1003,17 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
return null;
}
}
if(globalModel.getMessageListPosition(conversationID) != HistoryMessagePosition.notShowLatest) {
if (globalModel.getMessageListPosition(conversationID) !=
HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList = [
lifeCycleMsg ?? messageInfoWithSender,
...currentHistoryMsgList
];
globalModel.setMessageList(conversationID, currentHistoryMsgList);
notifyListeners();
}
return _sendMessage(
convID: convID,
messageInfo: lifeCycleMsg ?? messageInfoWithSender,
@ -954,15 +1050,17 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
return null;
}
}
if(globalModel.getMessageListPosition(conversationID) != HistoryMessagePosition.notShowLatest) {
if (globalModel.getMessageListPosition(conversationID) !=
HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList = [
lifeCycleMsg ?? messageInfoWithSender,
...currentHistoryMsgList
];
globalModel.setMessageList(conversationID, currentHistoryMsgList);
notifyListeners();
}
return _sendMessage(
convID: convID,
messageInfo: lifeCycleMsg ?? messageInfoWithSender,
@ -995,13 +1093,14 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
return null;
}
}
if(globalModel.getMessageListPosition(conversationID) != HistoryMessagePosition.notShowLatest) {
if (globalModel.getMessageListPosition(conversationID) !=
HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList = [
lifeCycleMsg ?? messageInfoWithSender,
...currentHistoryMsgList
];
globalModel.setMessageList(conversationID, currentHistoryMsgList);
notifyListeners();
}
return _sendMessage(
@ -1126,10 +1225,12 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
}
//
Future<V2TimValueCallback<V2TimMessage>?> reSendFailMessage(
{required V2TimMessage message,
required String convID,
required ConvType convType}) async {
Future<V2TimValueCallback<V2TimMessage>?> reSendFailMessage({
required V2TimMessage message,
required String convID,
required ConvType convType,
List<String>? atUserIDList,
}) async {
await deleteMsg(message.msgID ?? "",
id: message.id, webMessageInstance: message.messageFromWeb);
int messageType = message.elemType;
@ -1138,7 +1239,11 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
String text = message.textElem!.text!;
if (_repliedMessage != null) {
res = await sendReplyMessage(
text: text, convID: convID, convType: convType);
text: text,
convID: convID,
convType: convType,
atUserIDList: atUserIDList,
);
} else {
res = await sendTextMessage(
text: text, convID: convID, convType: convType);
@ -1204,7 +1309,8 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
}
}
if(globalModel.getMessageListPosition(conversationID) != HistoryMessagePosition.notShowLatest){
if (globalModel.getMessageListPosition(conversationID) !=
HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList = [
lifeCycleMsg ?? messageInfoWithSender,
...currentHistoryMsgList
@ -1213,7 +1319,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
notifyListeners();
}
return _sendMessage(
convID: convID,
id: textMessageInfo.id as String,
@ -1235,8 +1340,16 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
final messageInfoWithSender = messageInfo.sender == null
? tools.setUserInfoForMessage(messageInfo, messageInfo.id!)
: messageInfo;
currentHistoryMsgList = [messageInfoWithSender, ...currentHistoryMsgList];
globalModel.setMessageList(conversationID, currentHistoryMsgList);
if (globalModel.getMessageListPosition(conversationID) !=
HistoryMessagePosition.notShowLatest) {
currentHistoryMsgList = [
messageInfoWithSender,
...currentHistoryMsgList
];
globalModel.setMessageList(conversationID, currentHistoryMsgList);
}
return _sendMessage(
convID: conversationID,
id: messageInfo.id as String,
@ -1316,26 +1429,6 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
}
}
translateText(V2TimMessage message) async {
final String originText = message.textElem?.text ?? "";
final String deviceLocale =
WidgetsBinding.instance?.window.locale.toLanguageTag() ?? "en";
final String targetMessage = deviceLocale.split("-")[0];
final translatedText =
await _messageService.translateText(originText, targetMessage);
final LocalCustomDataModel localCustomData = LocalCustomDataModel.fromMap(
json.decode(TencentUtils.checkString(message.localCustomData) ?? "{}"));
localCustomData.translatedText = translatedText;
final result = await TencentImSDKPlugin.v2TIMManager.v2TIMMessageManager
.setLocalCustomData(
msgID: message.msgID!,
localCustomData: json.encode(localCustomData.toMap()));
if (result.code == 0 && TencentUtils.checkString(message.msgID) != null) {
updateMessageFromController(msgID: message.msgID!);
}
}
updateMultiSelectStatus(bool isSelect) {
_isMultiSelect = isSelect;
if (!isSelect) {

View File

@ -26,7 +26,7 @@ class TUIGroupProfileModel extends ChangeNotifier {
List<V2TimGroupMemberFullInfo?>? _groupMemberList;
String _groupMemberListSeq = "0";
V2TimGroupInfo? _groupInfo;
Function(String userID)? onClickUser;
Function(String userID, TapDownDetails? tapDetails)? onClickUser;
GroupProfileLifeCycle? get lifeCycle => _lifeCycle;
@ -180,7 +180,6 @@ class TUIGroupProfileModel extends ChangeNotifier {
setGroupNotification(String notification) async {
if (_groupInfo != null) {
_groupInfo?.notification = notification;
final response = await _groupServices.setGroupInfo(
info: V2TimGroupInfo.fromJson({
"groupID": _groupID,
@ -189,6 +188,7 @@ class TUIGroupProfileModel extends ChangeNotifier {
}));
if (response.code == 0) {
notifyListeners();
_groupInfo?.notification = notification;
}
}
}
@ -327,7 +327,7 @@ class TUIGroupProfileModel extends ChangeNotifier {
Future<V2TimCallback?> muteGroupMember(
String userID, bool isMute, int? serverTime) async {
final muteTime = serverTime != null ? serverTime + 9999 : 51556926 * 10;
const muteTime = 315360000;
final res = await _groupServices.muteGroupMember(
groupID: _groupID, userID: userID, seconds: isMute ? muteTime : 0);
if (res.code == 0) {
@ -335,7 +335,7 @@ class TUIGroupProfileModel extends ChangeNotifier {
_groupMemberList!.indexWhere((e) => e!.userID == userID);
if (targetIndex != -1) {
final targetElem = _groupMemberList![targetIndex];
targetElem?.muteUntil = isMute ? muteTime : 0;
targetElem?.muteUntil = isMute ? (serverTime ?? 0) + muteTime : 0;
_groupMemberList![targetIndex] = targetElem;
}
notifyListeners();

View File

@ -31,6 +31,11 @@ class TUIProfileViewModel extends ChangeNotifier {
return _userProfile;
}
set userProfile(UserProfile? value) {
_userProfile = value;
notifyListeners();
}
bool? get isDisturb {
return _isDisturb;
}
@ -48,6 +53,9 @@ class TUIProfileViewModel extends ChangeNotifier {
}
loadData({required String userID, bool isNeedConversation = true}) async {
if(userID.isEmpty){
return;
}
V2TimFriendInfo? friendUserInfo;
V2TimConversation? conversation;
final userInfoList =
@ -159,8 +167,6 @@ class TUIProfileViewModel extends ChangeNotifier {
if (res.code == 0) {
_userProfile?.friendInfo!.userProfile!.allowType = allowType;
notifyListeners();
} else {
print("${res.code},${res.desc}");
}
return res;
}
@ -175,8 +181,6 @@ class TUIProfileViewModel extends ChangeNotifier {
if (res.code == 0) {
_userProfile?.friendInfo!.userProfile!.gender = gender;
notifyListeners();
} else {
print("${res.code},${res.desc}");
}
return res;
@ -192,8 +196,6 @@ class TUIProfileViewModel extends ChangeNotifier {
if (res.code == 0) {
_userProfile?.friendInfo!.userProfile!.nickName = nickName;
notifyListeners();
} else {
print("${res.code},${res.desc}");
}
return res;
@ -208,8 +210,6 @@ class TUIProfileViewModel extends ChangeNotifier {
if (res.code == 0) {
_userProfile?.friendInfo!.userProfile!.selfSignature = selfSignature;
notifyListeners();
} else {
print("${res.code},${res.desc}");
}
return res;
}
@ -297,8 +297,6 @@ class TUIProfileViewModel extends ChangeNotifier {
});
notifyListeners();
} else {
print("${res.code},${res.desc}");
}
return res;
}

View File

@ -18,7 +18,12 @@ import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart';
enum ConvType { none, c2c, group }
enum HistoryMessagePosition { bottom, inTwoScreen, awayTwoScreen, notShowLatest }
enum HistoryMessagePosition {
bottom,
inTwoScreen,
awayTwoScreen,
notShowLatest
}
class CurrentConversation {
final String conversationID;
@ -37,6 +42,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
final Map<String, dynamic> _preloadImageMap = {};
final Map<String, HistoryMessagePosition> _historyMessagePositionMap = {};
final List<CurrentConversation> _currentConversationList = [];
Map<String, dynamic> get preloadImageMap => _preloadImageMap;
ChatLifeCycle? _lifeCycle;
@ -49,8 +55,9 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
late V2TimAdvancedMsgListener advancedMsgListener;
int _unreadCountForConversation = 0;
// use for generate a new sliver list to show received message list
int _receivedNewMessageCount = 0;
// use for generate a new sliver list to show recived messag list
int _recivedNewMessageCount = 0;
TIMUIKitChatConfig chatConfig = const TIMUIKitChatConfig();
List<V2TimGroupApplication>? _groupApplicationList;
String Function(V2TimMessage message)? _abstractMessageBuilder;
@ -58,7 +65,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
Map.from({}); // 0 normal 1 sending
final Map<String, bool> _c2cMessageFromUserActiveMap = Map.from({});
final Map<String, Timer> _c2cMessageActiveTimer = Map.from({});
bool _showC2cMessageEditStatus = true;
bool _showC2cMessageEditStaus = true;
final Map<String, Timer> _c2cMessageStatusShowTimer = Map.from({});
Map<String, List> loadingMessage = {};
@ -73,8 +80,8 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
onRecvNewMessage: (V2TimMessage newMsg) {
_onReceiveNewMsg(newMsg);
},
onSendMessageProgress: (V2TimMessage message, int progress) {
_onSendMessageProgress(message, progress);
onSendMessageProgress: (V2TimMessage messagae, int progress) {
_onSendMessageProgress(messagae, progress);
},
onRecvMessageReadReceipts: (List<V2TimMessageReceipt> receiptList) {
_onReceiveMessageReadReceipts(receiptList);
@ -87,12 +94,14 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
onMessageDownloadProgressCallback(messageProgress);
},
);
// Future.delayed(const Duration(milliseconds: 200)).then((value) => initMessageMapFromLocal());
}
bool get isDownloading => _isDownloading;
bool get hasWaiting => _waitingDownloadList.isNotEmpty;
Map<String, String> get currentDownLoad => _waitingDownloadList.first;
void addWaitingList(String msgID) {
print("add to waiting list success");
bool contains = false;
@ -114,43 +123,28 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
}
downloadFile() async {
bool hasWait = _waitingDownloadList.isNotEmpty;
print(_waitingDownloadList);
print(_isDownloading);
print(!hasWaiting);
if (_isDownloading || !hasWait) {
print("no download return");
if (_isDownloading || _waitingDownloadList.isEmpty) {
return;
}
Map<String, String> nextDownLoad = _waitingDownloadList.first;
String downLoadMsgID = nextDownLoad["msgID"] ?? "";
if (downLoadMsgID.isEmpty) {
final nextDownload = _waitingDownloadList.first;
final msgID = nextDownload["msgID"] ?? "";
if (msgID.isEmpty || _messageListProgressMap[msgID] == 100) {
return;
}
if (_messageListProgressMap[downLoadMsgID] == 100) {
return;
}
// await Dio().download(downLoadMsgUrl, downLoadMsgSavePath,
// onReceiveProgress: (recv, total) {
// if (total != -1) {
// // print((received / total * 100).toStringAsFixed(0) + "%");
// int progrss = (recv / total * 100).ceil();
// if (progrss > 1) {
// setMessageProgress(downLoadMsgID, progrss);
// }
// //you can build progressbar feature too
// }
// });
await _messageService.downloadMessage(
msgID: downLoadMsgID, messageType: 6, imageType: 0, isSnapshot: false);
_isDownloading = true;
// startDownloadProcess(context, theme, received);
await _messageService.downloadMessage(
msgID: msgID,
messageType: 6,
imageType: 0,
isSnapshot: false,
);
print("start another download");
// downloadFile();
}
int getReceived(msgID) {
int getRecevied(msgID) {
return messageListProgressMap[msgID] ?? 0;
}
@ -179,11 +173,11 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
}
int get receivedMessageListCount {
return _receivedNewMessageCount;
return _recivedNewMessageCount;
}
set receivedNewMessageCount(int value) {
_receivedNewMessageCount = value;
_recivedNewMessageCount = value;
}
int get unreadCountForConversation => _unreadCountForConversation;
@ -222,7 +216,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
}
setShowC2cEditStatus(bool show) {
_showC2cMessageEditStatus = show;
_showC2cMessageEditStaus = show;
}
/// set edit status from chats
@ -379,14 +373,14 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
_totalUnreadCount = 0;
_groupApplicationList?.clear();
_totalUnreadCount = 0;
_receivedNewMessageCount = 0;
_recivedNewMessageCount = 0;
_messageReadReceiptMap.clear();
_messageListProgressMap.clear();
notifyListeners();
}
clearRecivedNewMessageCount() {
_receivedNewMessageCount = 0;
_recivedNewMessageCount = 0;
}
_preLoadImage(List<V2TimMessage> msgList) {
@ -457,17 +451,18 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
_editStatusCheck(V2TimMessage msg) {
bool isStatusMessage = false;
if (msg.customElem != null && msg.groupID == null) {
if (msg.customElem != null &&
TencentUtils.checkString(msg.groupID) == null) {
V2TimCustomElem customElem = msg.customElem!;
String sender = msg.sender ?? "";
if (customElem.data!.isNotEmpty) {
try {
Map<String, dynamic>? data = json.decode(customElem.data ?? "");
if (data != null) {
String businessID = data["businessID"];
var businessID = data["businessID"];
int? userAction = data["userAction"];
String? actionParam = data["actionParam"];
if (businessID == "user_typing_status") {
if (businessID.toString() == "user_typing_status") {
int? typingStatus = data["typingStatus"];
if (sender != "") {
if (typingStatus != null) {
@ -501,7 +496,6 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
try {
Map<String, dynamic> data = json.decode(msg.cloudCustomData ?? "");
Map<String, dynamic>? messageFeature = data["messageFeature"];
print(data);
if (messageFeature != null) {
int needTyping = messageFeature["needTyping"];
if (needTyping == 1) {
@ -532,7 +526,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
}
sendEditStatusMessage(bool isEditing, String toUser) async {
if (!_showC2cMessageEditStatus) {
if (!_showC2cMessageEditStaus) {
return;
}
if (!(_c2cMessageFromUserActiveMap[toUser] ?? false)) {
@ -598,19 +592,27 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
}
_checkFromUserisActive(msgComing);
final convID = newMsg.userID ?? newMsg.groupID;
final convID = TencentUtils.checkString(newMsg.userID) ?? newMsg.groupID;
final convType = TencentUtils.checkString(newMsg.groupID) != null
? ConvType.group
: ConvType.c2c;
if (convID != null && convID == currentSelectedConv) {
final position = getMessageListPosition(convID);
if(position == HistoryMessagePosition.notShowLatest){
if (position == HistoryMessagePosition.notShowLatest) {
return;
}
if (position == HistoryMessagePosition.bottom &&
unreadCountForConversation == 0) {
markMessageAsRead(
convID: convID,
convType: currentSelectedConvType!,
);
_receivedNewMessageCount = 0;
_unreadCountForConversation = 0;
if (chatConfig.isAutoReportRead) {
Future.delayed(const Duration(seconds: 1), () {
markMessageAsRead(
convID: convID,
convType: convType,
);
});
}
_recivedNewMessageCount = 0;
final currentMsg = _messageListMap[convID] ?? [];
_messageListMap[convID] = [newMsg, ...currentMsg];
notifyListeners();
@ -619,14 +621,17 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
if (needReadReceipt &&
messageID != null &&
msgComing.groupID != null &&
chatConfig.isReportGroupReadingStatus) {
chatConfig.isReportGroupReadingStatus &&
chatConfig.isAutoReportRead) {
// only group message send message read receipt
sendMessageReadReceipts([messageID]);
Future.delayed(const Duration(seconds: 1), () {
sendMessageReadReceipts([messageID]);
});
}
} else {
if (convID == currentSelectedConv) {
unreadCountForConversation++;
_receivedNewMessageCount++;
_recivedNewMessageCount++;
final currentMsg = _messageListMap[convID] ?? [];
_messageListMap[convID] = [newMsg, ...currentMsg];
notifyListeners();
@ -667,7 +672,9 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
onMessageModified(V2TimMessage modifiedMessage, [String? convID]) async {
modifiedMessage.id = DateTime.now().millisecondsSinceEpoch.toString();
final activeMessageList = _messageListMap[convID ?? currentSelectedConv];
final String? exactId = TencentUtils.checkString(modifiedMessage.userID) ??
TencentUtils.checkString(modifiedMessage.groupID);
final activeMessageList = _messageListMap[convID ?? exactId];
if (activeMessageList == null || activeMessageList.isEmpty) {
return;
}
@ -675,7 +682,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
await _lifeCycle?.modifiedMessageWillMount(modifiedMessage) ??
modifiedMessage;
final msgID = newMsg.msgID;
_messageListMap[currentSelectedConv] = activeMessageList.map((item) {
_messageListMap[convID ?? exactId ?? ""] = activeMessageList.map((item) {
if (item.msgID == msgID) {
return newMsg;
}
@ -690,7 +697,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
final isNotEmpty = _messageListMap[convID]?.isNotEmpty;
if (isNotEmpty != null && isNotEmpty) {
_messageListMap[convID] = _messageListMap[convID]!.map((element) {
final isSelf = element.isSelf ?? false;
final isSelf = element.isSelf ?? true;
final isPeerRead = element.isPeerRead ?? false;
if (isSelf && !isPeerRead) {
element.isPeerRead = true;
@ -711,9 +718,8 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
}
}
notifyListeners();
} catch (e) {
print(e);
}
// ignore: empty_catches
} catch (e) {}
}
_onSendMessageProgress(V2TimMessage messagae, int progress) {
@ -721,11 +727,23 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
}
onMessageDownloadProgressCallback(
V2TimMessageDownloadProgress messageProgress) {
V2TimMessageDownloadProgress messageProgress) async {
if (messageProgress.isFinish) {
setMessageProgress(messageProgress.msgID, 100);
setFileMessageLocation(messageProgress.msgID, messageProgress.path);
downloadFile();
if (messageProgress.type == 0) {
final messages = await _messageService
.findMessages(messageIDList: [messageProgress.msgID]);
final V2TimMessage? message = messages?.first;
if (message != null) {
updateAsyncMessage(
message,
TencentUtils.checkString(message.userID) ??
TencentUtils.checkString(message.groupID) ??
"");
}
}
return;
}
if (messageProgress.isError) {
@ -761,11 +779,9 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
required String convID,
required ConvType convType,
}) async {
_unreadCountForConversation = 0;
if (convType == ConvType.c2c) {
return _messageService.markC2CMessageAsRead(userID: convID);
}
_messageService.markGroupMessageAsRead(groupID: convID);
}
@ -888,7 +904,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
{bool needResetNewMessageCount = true}) {
_messageListMap[conversationID] = messageList;
if (needResetNewMessageCount) {
_receivedNewMessageCount = 0;
_recivedNewMessageCount = 0;
}
notifyListeners();
}
@ -926,6 +942,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
if (chatConfig.isShowGroupReadingStatus &&
convType == ConvType.group &&
sendMsgRes.data?.msgID != null &&
((chatConfig.groupReadReceiptPermissionList != null &&
chatConfig.groupReadReceiptPermissionList!
.contains(groupType)) ||
@ -967,6 +984,7 @@ class TUIChatGlobalModel extends ChangeNotifier with TIMUIKitClass {
if (listWithTimestamp.isEmpty ||
(listWithTimestamp[listWithTimestamp.length - 1].timestamp !=
null &&
item.timestamp != null &&
(item.timestamp! -
listWithTimestamp[listWithTimestamp.length - 1]
.timestamp! >

View File

@ -1,6 +1,7 @@
// ignore_for_file: unnecessary_getters_setters
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/conversation_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
@ -29,6 +30,8 @@ List<T> removeDuplicates<T>(
}
class TUIConversationViewModel extends ChangeNotifier {
final TUISelfInfoViewModel selfInfoViewModel =
serviceLocator<TUISelfInfoViewModel>();
final ConversationService _conversationService =
serviceLocator<ConversationService>();
final FriendshipServices _friendshipServices =
@ -39,23 +42,16 @@ class TUIConversationViewModel extends ChangeNotifier {
late V2TimConversationListener _conversationListener;
List<V2TimConversation?> _conversationList = [];
static V2TimConversation? _selectedConversation;
bool _haveMoreData = true;
int _totalUnReadCount = 0;
String? _scrollToConversation;
final TUIChatGlobalModel globalChatModel =
serviceLocator<TUIChatGlobalModel>();
String _nextSeq = "0";
ConversationLifeCycle? _lifeCycle;
String? get scrollToConversation => _scrollToConversation;
set scrollToConversation(String? value) {
_scrollToConversation = value;
notifyListeners();
}
void clearScrollToConversation(){
_scrollToConversation = null;
}
List<V2TimConversation?> get conversationList {
if (PlatformUtils().isWeb) {
try {
@ -69,9 +65,8 @@ class TUIConversationViewModel extends ChangeNotifier {
.toList();
_conversationList.removeWhere((element) => element?.isPinned == true);
_conversationList = [...pinnedConversation, ..._conversationList];
// ignore: empty_catches
} catch (e) {
// ignore: avoid_print
print(e);
}
} else {
_conversationList.sort((a, b) => b!.orderkey!.compareTo(a!.orderkey!));
@ -79,6 +74,17 @@ class TUIConversationViewModel extends ChangeNotifier {
return _conversationList;
}
String? get scrollToConversation => _scrollToConversation;
set scrollToConversation(String? value) {
_scrollToConversation = value;
notifyListeners();
}
void clearScrollToConversation() {
_scrollToConversation = null;
}
bool get haveMoreData {
return _haveMoreData;
}
@ -98,6 +104,11 @@ class TUIConversationViewModel extends ChangeNotifier {
notifyListeners();
}
set selectedConversation(V2TimConversation? value) {
_selectedConversation = value;
notifyListeners();
}
V2TimConversation? get selectedConversation {
return _selectedConversation;
}
@ -125,7 +136,9 @@ class TUIConversationViewModel extends ChangeNotifier {
loadInitConversation() async {
await loadData(count: 40);
_chatGlobalModel.initMessageMapFromLocalDatabase(_conversationList);
if (selfInfoViewModel.globalConfig?.isPreloadMessagesAfterInit ?? true) {
_chatGlobalModel.initMessageMapFromLocalDatabase(_conversationList);
}
}
initConversation() async {
@ -165,6 +178,7 @@ class TUIConversationViewModel extends ChangeNotifier {
void setSelectedConversation(V2TimConversation conversation) {
_selectedConversation = conversation;
notifyListeners();
}
Future<V2TimCallback> pinConversation({
@ -182,6 +196,9 @@ class TUIConversationViewModel extends ChangeNotifier {
false) {
return null;
}
globalChatModel.setMessageList(convID, []);
if (convType == 1) {
return _messageService.clearC2CHistoryMessage(userID: convID);
} else {

View File

@ -1,5 +1,6 @@
// ignore_for_file: constant_identifier_names
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/data_services/friendShip/friendship_services.dart';
@ -91,10 +92,13 @@ class TUISearchViewModel extends ChangeNotifier {
type: KeywordListMatchType.V2TIM_KEYWORD_LIST_MATCH_TYPE_OR.index,
));
if (searchResult.code == 0 && searchResult.data != null) {
totalMsgInConversationCount = searchResult.data!.totalCount!;
final messageSearchResultItems = searchResult
.data!.messageSearchResultItems!
.firstWhereOrNull((element) => element.conversationID == conversationId);
totalMsgInConversationCount = messageSearchResultItems?.messageCount ?? 0;
currentMsgListForConversation = [
...currentMsgListForConversation,
...(searchResult.data!.messageSearchResultItems?[0].messageList ?? [])
...(messageSearchResultItems?.messageList ?? [])
];
}
notifyListeners();

View File

@ -1,7 +1,6 @@
// ignore_for_file: avoid_print
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_setting_model.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/listener_model/tui_group_listener_model.dart';
@ -157,7 +156,7 @@ class CoreServicesImpl with CoreServices {
onCallback = onTUIKitCallbackListener;
}
setGlobalConfig(config);
if (PlatformUtils().isIOS || PlatformUtils().isAndroid) {
if (!PlatformUtils().isWeb) {
didLoginSuccess();
}
}
@ -251,8 +250,8 @@ class CoreServicesImpl with CoreServices {
}
tuiFriendShipViewModel.userStatusList = currentUserStatusList;
// ignore: empty_catches
} catch (e) {
print(e);
}
}
@ -265,7 +264,7 @@ class CoreServicesImpl with CoreServices {
_userSig = userSig;
V2TimCallback result = await TencentImSDKPlugin.v2TIMManager
.login(userID: userID, userSig: userSig);
if (PlatformUtils().isIOS || PlatformUtils().isAndroid) {
if (!PlatformUtils().isWeb) {
didLoginSuccess();
}
if (result.code != 0) {
@ -294,7 +293,7 @@ class CoreServicesImpl with CoreServices {
}
});
if (!kIsWeb &&
if (PlatformUtils().isMobile &&
selfInfoViewModel.globalConfig?.isCheckDiskStorageSpace == true) {
final diskSpace = await DiskSpace.getFreeDiskSpace;
if (diskSpace != null && diskSpace < 1024) {

View File

@ -1,4 +1,6 @@
import 'package:flutter/cupertino.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_wide_modal_operation_key.dart';
import 'package:tencent_im_base/theme/tui_theme.dart';
class TIMUIKitConfig {
/// Control if show online status of friends or contacts.
@ -18,8 +20,34 @@ class TIMUIKitConfig {
/// The configuration of border radius for all the avatar shows in TUIKit.
final BorderRadius? defaultAvatarBorderRadius;
const TIMUIKitConfig({
/// You can use this function to customize the Modal that shows on desktop.
/// Do not specified or return `false` will use our default implementation.
final Future<bool> Function(
TUIKitWideModalOperationKey operationKey,
BuildContext context,
Widget Function(VoidCallback closeFunc) child,
TUITheme? theme,
double? width,
double? height,
Offset? offset,
String? initText,
BorderRadius? borderRadius,
bool? isDarkBackground,
String? title,
VoidCallback? onSubmit,
Widget? submitWidget,
VoidCallback? onConfirm,
VoidCallback? onCancel,
)? showDesktopModalFunc;
/// Determines whether TUIKit should preload some messages after initialization for faster message display,
/// with a default value of `true`, and backward-compatibility.
final bool isPreloadMessagesAfterInit;
const TIMUIKitConfig( {
this.defaultAvatarAssetPath,
this.showDesktopModalFunc,
this.isPreloadMessagesAfterInit = true,
this.defaultAvatarBorderRadius,
this.isCheckDiskStorageSpace = true,
this.isShowOnlineStatus = true,

View File

@ -0,0 +1,43 @@
enum TUIKitWideModalOperationKey{
/// You could use this value for your own Modal usage.
custom,
// The following values are used in TUIKit
conversationSecondaryMenu,
chooseCountry,
beforeSendScreenShot,
showUserProfileFromChat,
addNewContact,
showBlockedUsers,
chooseContacts,
addFriend,
addGroup,
chooseGroupType,
settings,
contactUs,
aboutUs,
showConditionsAndTerms,
secondaryClickUserAvatar,
forward,
messageReadDetails,
mergerMessageList,
chooseMentionedMembers,
chatHistory,
groupAddOpt,
setMute,
setUnmute,
setAdmins,
deleteAdmin,
groupMembersList,
addGroupMembers,
kickOffGroupMembers,
confirmDeleteMessages,
confirmClearChatHistory,
confirmExitGroup,
confirmDisbandGroup,
confirmGeneral,
unableToSendDueToFolders
}

View File

@ -105,6 +105,7 @@ class GroupServicesImpl extends GroupServices {
count: count,
offset: offset);
if (res.code != 0) {
_coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR,
errorMsg: res.desc,

View File

@ -127,14 +127,14 @@ class MessageServiceImpl extends MessageService {
lastMsgID: lastMsgID,
lastMsgSeq: lastMsgSeq,
messageTypeList: messageTypeList);
final reponseMessageList = res.data;
final responseMessageList = res.data;
if (res.code != 0) {
_coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR,
errorMsg: res.desc,
errorCode: res.code));
}
return reponseMessageList;
return responseMessageList;
}
@override
@ -812,4 +812,5 @@ class MessageServiceImpl extends MessageService {
}
return result.data?[text] ?? "";
}
}

File diff suppressed because one or more lines are too long

View File

@ -659,8 +659,34 @@
"k_1ui0gai": "搜索指定内容",
"k_003nvk2": "消息",
"k_03agld7": "群提示",
"k_0elt0kw": "添加群聊",
"k_0s3sgel": "移出黑名单",
"k_1qqgjra": "$option3条未读消息",
"k_0uubyjr": "以下为未读消息",
"k_16as7eq": "表情回应",
"k_003s12u": "回复",
"k_003s38r": "更多",
"k_002wkr3": "翻译",
"k_13g4hxv": "翻译完成",
"k_1qqgjra": "$option3条未读消息",
"k_0uubyjr": "以下为未读消息"
"k_003molk": "表情",
"k_165bbw6": "消息历史",
"k_13sqc0z": "清除消息",
"k_0glns86": "删除会话",
"k_13s99rx": "清空消息",
"k_11vsa3j": "退出群组",
"k_11vvszp": "解散群组",
"k_15i9w72": "群管理员",
"k_0p3espj": "设置备注名",
"k_118sw9v": "立即搜索",
"k_0h20hg5": "视频通话",
"k_0h22snw": "语音通话",
"k_003lz6t": "对方",
"k_1xf4yre": "发送给$option1",
"k_003por5": "截图",
"k_1rw7s82": " 访问相册中视频权限,以正常使用发送视频等功能。",
"k_003rcwm": "打开",
"k_1698c42": "在访达中打开",
"k_066fxsz": "查看文件夹",
"k_0k432y2": "无法发送,包含文件夹",
"k_002wb4y": "会话"
}

File diff suppressed because one or more lines are too long

View File

@ -32,12 +32,12 @@ export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/tim_uikit_searc
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_controller.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitAppBar/tim_uikit_appbar.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_item.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroup/tim_uikit_group_application_list.dart';
export 'package:tencent_im_base/tencent_im_base.dart';
export 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/models/link_preview_content.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitProfile/widget/tim_uikit_profile_userinfo_card.dart';
export 'package:tencent_cloud_chat_uikit/ui/widgets/column_menu.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitProfile/widget/tim_uikit_profile_userinfo_card/tim_uikit_profile_userinfo_card.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitConversation/tim_ui_kit_conversation_total_unread.dart';
// Enum
@ -52,6 +52,7 @@ export 'package:permission_handler/permission_handler.dart';
// Utils
export 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart';
export 'package:tencent_cloud_uikit_core/tencent_cloud_uikit_core.dart';
class TIMUIKitCore {
static CoreServicesImpl getInstance() {

View File

@ -17,17 +17,16 @@ class TIMUIKitChatController {
}
}
Future<bool> loadHistoryMessageList({
HistoryMsgGetTypeEnum getType =
HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_OLDER_MSG,
String? userID,
String? groupID,
int lastMsgSeq = -1,
required int count,
String? lastMsgID,
LoadDirection direction = LoadDirection.previous
}) async {
return await model?.loadData(
Future<bool> loadHistoryMessageList(
{HistoryMsgGetTypeEnum getType =
HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_OLDER_MSG,
String? userID,
String? groupID,
int lastMsgSeq = -1,
required int count,
String? lastMsgID,
LoadDirection direction = LoadDirection.previous}) async {
return await model?.loadChatRecord(
count: count,
getType: getType,
lastMsgID: lastMsgID,
@ -70,7 +69,6 @@ class TIMUIKitChatController {
/// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
Future<void> updateMessage(
{
/// The ID of the target conversation
String? convID,
@ -88,32 +86,47 @@ class TIMUIKitChatController {
return;
}
/// Send message;
///
/// Please provide `convType` and `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
/// Sends a message to the specified conversation, or to the current conversation specified on `TIMUIKitChat`.
/// `TIMUIKitChat`
/// You must provide `convType` and either `userID` or `groupID`, only if you use `TIMUIKitChat` without specifying a `TIMUIKitChatController`, you must provide these parameters.
/// `convType` `userID` `groupID`, 使 `TIMUIKitChat` `TIMUIKitChatController`
Future<V2TimValueCallback<V2TimMessage>?>? sendMessage({
required V2TimMessage? messageInfo,
/// The type of the target conversation
/// The type of the target conversation: either ConvType.group or ConvType.c2c. Required if using `TIMUIKitChat` without specifying a `TIMUIKitChatController`.
/// ConvType.group ConvType.c2c使 `TIMUIKitChat` `TIMUIKitChatController`
ConvType? convType,
/// The ID of the target conversation
String? convID,
/// The user ID of the target one-to-one conversation. Required if convType is ConvType.c2c.
/// ID convType ConvType.c2c
String? userID,
/// The method for updating the input field when message sending failed
/// The target group ID. Required if convType is ConvType.group.
/// ID convType ConvType.group
String? groupID,
/// A callback function to update the input field when message sending fails.
///
ValueChanged<String>? setInputField,
/// Offline push info
/// Offline push information.
/// 线
OfflinePushInfo? offlinePushInfo,
}) {
if (convID != null && convType != null) {
if (convType != null) {
/// Sends a message to the specified conversation.
assert((groupID == null) != (userID == null));
assert(groupID != null || convType != ConvType.group);
assert(userID != null || convType != ConvType.c2c);
return globalChatModel.sendMessageFromController(
messageInfo: messageInfo,
convType: convType,
convID: convID,
convID: (convType == ConvType.group ? groupID : userID) ?? "",
setInputField: setInputField,
offlinePushInfo: offlinePushInfo);
} else if (model != null) {
/// Sends a message to the current conversation specified on `TIMUIKitChat`. `TIMUIKitChat`
return model!.sendMessageFromController(
messageInfo: messageInfo, offlinePushInfo: offlinePushInfo);
}

View File

@ -9,6 +9,11 @@ class TIMUIKitConversationController {
return model.selectedConversation;
}
/// Set the selected conversation currently
set selectedConversation(V2TimConversation? conversation) {
model.selectedConversation = conversation;
}
/// Get the conversation list
List<V2TimConversation?> get conversationList {
return model.conversationList;

View File

@ -47,15 +47,23 @@ class TIMUIKitProfileController {
}
/// Show the text input bottom sheet
showTextInputBottomSheet(
BuildContext context,
String title,
String tips,
void Function(String) onSubmitted,
TUITheme theme
) {
showTextInputBottomSheet({
required BuildContext context,
required String title,
required String tips,
required Function(String text) onSubmitted,
required TUITheme theme,
Offset? initOffset,
String? initText,
}) {
TextInputBottomSheet.showTextInputBottomSheet(
context, title, tips, onSubmitted, theme);
context: context,
title: title,
tips: tips,
onSubmitted: onSubmitted,
theme: theme,
initOffset: initOffset,
initText: initText);
}
/// Load the profile data

View File

@ -6,4 +6,153 @@ class TencentUtils{
static String? checkString(String? text){
return (text != null && text.isEmpty) ? null : text;
}
static String? checkStringWithoutSpace(String? text){
if (text == null || text.trim().isEmpty || text.contains(' ')) {
return null;
}
return text;
}
static String getFileType(String fileType) {
switch (fileType) {
case "3gp":
return "video/3gpp";
case "torrent":
return "application/x-bittorrent";
case "kml":
return "application/vnd.google-earth.kml+xml";
case "gpx":
return "application/gpx+xml";
case "asf":
return "video/x-ms-asf";
case "avi":
return "video/x-msvideo";
case "bin":
case "class":
case "exe":
return "application/octet-stream";
case "bmp":
return "image/bmp";
case "c":
return "text/plain";
case "conf":
return "text/plain";
case "cpp":
return "text/plain";
case "doc":
return "application/msword";
case "docx":
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
case "xls":
case "csv":
return "application/vnd.ms-excel";
case "xlsx":
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
case "gif":
return "image/gif";
case "gtar":
return "application/x-gtar";
case "gz":
return "application/x-gzip";
case "h":
return "text/plain";
case "htm":
return "text/html";
case "html":
return "text/html";
case "jar":
return "application/java-archive";
case "java":
return "text/plain";
case "jpeg":
return "image/jpeg";
case "jpg":
return "image/jpeg";
case "js":
return "application/x-javascript";
case "log":
return "text/plain";
case "m3u":
return "audio/x-mpegurl";
case "m4a":
return "audio/mp4a-latm";
case "m4b":
return "audio/mp4a-latm";
case "m4p":
return "audio/mp4a-latm";
case "m4u":
return "video/vnd.mpegurl";
case "m4v":
return "video/x-m4v";
case "mov":
return "video/quicktime";
case "mp2":
return "audio/x-mpeg";
case "mp3":
return "audio/x-mpeg";
case "mp4":
return "video/mp4";
case "mpc":
return "application/vnd.mpohun.certificate";
case "mpe":
return "video/mpeg";
case "mpeg":
return "video/mpeg";
case "mpg":
return "video/mpeg";
case "mpg4":
return "video/mp4";
case "mpga":
return "audio/mpeg";
case "msg":
return "application/vnd.ms-outlook";
case "ogg":
return "audio/ogg";
case "pdf":
return "application/pdf";
case "png":
return "image/png";
case "pps":
return "application/vnd.ms-powerpoint";
case "ppt":
return "application/vnd.ms-powerpoint";
case "pptx":
return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
case "prop":
return "text/plain";
case "rc":
return "text/plain";
case "rmvb":
return "audio/x-pn-realaudio";
case "rtf":
return "application/rtf";
case "sh":
return "text/plain";
case "tar":
return "application/x-tar";
case "tgz":
return "application/x-compressed";
case "txt":
return "text/plain";
case "wav":
return "audio/x-wav";
case "wma":
return "audio/x-ms-wma";
case "wmv":
return "audio/x-ms-wmv";
case "wps":
return "application/vnd.ms-works";
case "xml":
return "text/plain";
case "z":
return "application/x-compress";
case "zip":
return "application/x-zip-compressed";
default:
return "*/*";
}
}
}

View File

@ -1,13 +1,12 @@
// ignore_for_file: unrelated_type_equality_checks, avoid_print
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/ui/constants/history_message_constant.dart';
import 'package:tencent_cloud_chat_uikit/ui/constants/time.dart';
import 'package:collection/collection.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart';
class MessageUtils {
// CallingData的方式和Trtc的方法一致
@ -101,9 +100,12 @@ class MessageUtils {
}
static String? _getOpUserNick(V2TimGroupMemberInfo? opUser) {
return TencentUtils.checkString(opUser?.friendRemark) ??
TencentUtils.checkString(opUser?.nickName) ??
TencentUtils.checkString(opUser?.userID) ?? "";
if(opUser == null){
return "";
}
return TencentUtils.checkString(opUser.friendRemark) ??
TencentUtils.checkString(opUser.nickName) ??
TencentUtils.checkString(opUser.userID);
}
static String? _getMemberNickName(V2TimGroupMemberInfo e) {
@ -155,21 +157,21 @@ class MessageUtils {
break;
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_INVITE:
final option5 =
memberList!.map((e) => _getMemberNickName(e!).toString()).join("");
memberList!.map((e) => _getMemberNickName(e!).toString()).join("");
final inviteUser = _getOpUserNick(operationMember);
displayMessage = '$inviteUser' +
TIM_t_para("邀请{{option5}}加入群组", "邀请$option5加入群组")(option5: option5);
break;
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_KICKED:
final option4 =
memberList!.map((e) => _getMemberNickName(e!).toString()).join("");
memberList!.map((e) => _getMemberNickName(e!).toString()).join("");
final kickUser = _getOpUserNick(operationMember);
displayMessage = '$kickUser' +
TIM_t_para("将{{option4}}踢出群组", "$option4踢出群组")(option4: option4);
break;
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_JOIN:
final option3 =
memberList!.map((e) => _getMemberNickName(e!).toString()).join("");
memberList!.map((e) => _getMemberNickName(e!).toString()).join("");
displayMessage = TIM_t_para("用户{{option3}}加入了群聊", "用户$option3加入了群聊")(
option3: option3);
break;
@ -186,7 +188,7 @@ class MessageUtils {
break;
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_SET_ADMIN:
final adminMember =
memberList!.map((e) => _getMemberNickName(e!).toString()).join("");
memberList!.map((e) => _getMemberNickName(e!).toString()).join("");
final opMember = _getOpUserNick(operationMember);
final option1 = adminMember;
displayMessage = '$opMember' +
@ -195,7 +197,7 @@ class MessageUtils {
break;
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_CANCEL_ADMIN:
final adminMember =
memberList!.map((e) => _getMemberNickName(e!).toString()).join("");
memberList!.map((e) => _getMemberNickName(e!).toString()).join("");
final opMember = _getOpUserNick(operationMember);
final option1 = adminMember;
displayMessage = '$opMember' +
@ -276,46 +278,14 @@ class MessageUtils {
}
}
static Future<String> getAbstractMessage(V2TimMessage message,
List<V2TimGroupMemberFullInfo?> groupMemberList) async {
final msgType = message.elemType;
switch (msgType) {
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
return handleCustomMessageString(message);
case MessageElemType.V2TIM_ELEM_TYPE_SOUND:
return TIM_t("[语音]");
case MessageElemType.V2TIM_ELEM_TYPE_TEXT:
return message.textElem!.text as String;
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
return TIM_t("[表情]");
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
final String? option2 = message.fileElem!.fileName ?? "";
return TIM_t_para("[文件] {{option2}}", "[文件] $option2")(
option2: option2);
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
return await MessageUtils.groupTipsMessageAbstract(
message.groupTipsElem!, groupMemberList);
case MessageElemType.V2TIM_ELEM_TYPE_IMAGE:
return TIM_t("[图片]");
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
return TIM_t("[视频]");
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
return TIM_t("[位置]");
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
return TIM_t("[聊天记录]");
default:
return TIM_t("未知消息");
}
}
static V2TimImage? getImageFromImgList(
List<V2TimImage?>? list, List<String> order) {
V2TimImage? img;
try {
for (String type in order) {
img = list?.firstWhere(
(e) =>
e?.type == HistoryMessageDartConstant.V2_TIM_IMAGE_TYPES[type],
(e) =>
e?.type == HistoryMessageDartConstant.V2_TIM_IMAGE_TYPES[type],
orElse: () => null);
}
} catch (e) {
@ -332,10 +302,10 @@ class MessageUtils {
final displayName = friendRemark.isNotEmpty
? friendRemark
: nameCard.isNotEmpty
? nameCard
: nickName.isNotEmpty
? nickName
: sender;
? nameCard
: nickName.isNotEmpty
? nickName
: sender;
return displayName.toString();
}

View File

@ -1,6 +1,7 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
@ -11,6 +12,7 @@ import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
class PermissionRequestInfo extends StatefulWidget {
final Function removeOverLay;
final int permissionType;
@ -32,14 +34,14 @@ class _PermissionRequestInfo extends TIMUIKitState<PermissionRequestInfo>
@override
void initState() {
super.initState();
WidgetsBinding.instance?.addObserver(this);
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
widget.removeOverLay();
super.dispose();
WidgetsBinding.instance?.removeObserver(this);
WidgetsBinding.instance.removeObserver(this);
}
@override
@ -65,11 +67,21 @@ class _PermissionRequestInfo extends TIMUIKitState<PermissionRequestInfo>
"icon": "images/chat_permission_icon_mic.png",
"text": TIM_t("为方便您发送语音消息、拍摄视频以及音视频通话,请允许我们使用麦克风进行录音。")
},
9: {
"name": TIM_t("相册"),
"icon": "images/chat_permission_icon_file.png",
"text": TIM_t("为方便您查看和选择相册里的图片视频发送给朋友,以及保存内容到设备,请允许我们访问您设备上的照片、媒体内容。")
},
15: {
"name": TIM_t("存储"),
"icon": "images/chat_permission_icon_file.png",
"text": TIM_t("为方便您查看和选择相册里的图片视频发送给朋友,以及保存内容到设备,请允许我们访问您设备上的照片、媒体内容。")
},
32: {
"name": TIM_t("相册"),
"icon": "images/chat_permission_icon_file.png",
"text": TIM_t("为方便您查看和选择相册里的图片视频发送给朋友,以及保存内容到设备,请允许我们访问您设备上的照片、媒体内容。")
},
}[widget.permissionType];
final option2 = permission?["name"] ?? "";
return Stack(
@ -128,6 +140,9 @@ class _PermissionRequestInfo extends TIMUIKitState<PermissionRequestInfo>
}
class Permissions {
static OverlayEntry? _entry;
static List<String> _names(BuildContext context) {
return <String>[
TIM_t("日历"),
@ -161,6 +176,10 @@ class Permissions {
'bluetoothScan',
'bluetoothAdvertise',
'bluetoothConnect',
'nearbyWifiDevices',
TIM_t("视频"),
'audio',
'scheduleExactAlarm'
];
}
@ -199,6 +218,10 @@ class Permissions {
'bluetoothScan',
'bluetoothAdvertise',
'bluetoothConnect',
'nearbyWifiDevices',
TIM_t(" 访问相册中视频权限,以正常使用发送视频等功能。"),
'audio',
'scheduleExactAlarm'
];
return _prefix + appName + _postfixList[value];
}
@ -220,7 +243,10 @@ class Permissions {
isShowPermissionPage,
);
if (shouldRequestPermission != null && shouldRequestPermission) {
return await Permission.byValue(value).request().isGranted;
final isGranted = await Permission.byValue(value).request().isGranted;
_entry?.remove();
_entry = null;
return isGranted;
}
return shouldRequestPermission ?? false;
}
@ -241,11 +267,13 @@ class Permissions {
static showPermissionRequestInfoDialog(BuildContext context, value) async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String appName = packageInfo.appName;
OverlayEntry? _entry;
final entry = OverlayEntry(builder: (context) {
return PermissionRequestInfo(
appName: appName,
removeOverLay: () => _entry?.remove(),
removeOverLay: () {
_entry?.remove();
_entry = null;
},
permissionType: value,
);
});
@ -269,6 +297,7 @@ class Permissions {
String appName = packageInfo.appName;
final option2 = _names(context)[value];
final permissionText = _permissionText(context, appName, value);
void closeDialog() {
Navigator.of(context).pop(false);
}

View File

@ -5,13 +5,23 @@ class PlatformUtils {
PlatformUtils._internal();
static late bool _isAndroid;
static late bool _isIos;
static late bool _isMobile;
static late bool _isWeb;
static late bool _isWindows;
static late bool _isMacOS;
static late bool _isLinux;
static late bool _isDesktop;
static bool _isInstantiation = false;
factory PlatformUtils() {
if (!_isInstantiation) {
_isAndroid = !kIsWeb && Platform.isAndroid;
_isIos = !kIsWeb && Platform.isIOS;
_isMobile = _isAndroid || _isIos;
_isWindows = !kIsWeb && Platform.isWindows;
_isMacOS = !kIsWeb && Platform.isMacOS;
_isLinux = !kIsWeb && Platform.isLinux;
_isDesktop = _isMacOS || _isWindows || _isLinux;
_isWeb = kIsWeb;
_isInstantiation = true;
}
@ -32,4 +42,20 @@ class PlatformUtils {
get isIOS {
return _isIos;
}
get isWindows {
return _isWindows;
}
get isMacOS {
return _isMacOS;
}
get isMobile {
return _isMobile;
}
bool get isDesktop => _isDesktop;
bool get isLinux => _isLinux;
}

View File

@ -0,0 +1,104 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:uuid/uuid.dart';
class ScreenshotHelper {
static Future<String?> captureScreen() async {
await requestScreenRecordingPermission();
String directory;
if(PlatformUtils().isWindows){
final String documentsDirectoryPath =
"${Platform.environment['USERPROFILE']}";
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String pkgName = packageInfo.packageName;
directory = p.join(documentsDirectoryPath, "Documents", ".TencentCloudChat",
pkgName, "screenshots");
}else{
final dic = await getApplicationSupportDirectory();
directory = dic.path;
}
const uuid = Uuid();
final fileName = 'screenshot_${uuid.v4()}.png';
final filePath = '$directory/$fileName';
if (Platform.isMacOS) {
// macOS平台上使用screencapture工具
final result = await Process.run(
'screencapture',
['-i', '-s', '-o', filePath],
);
if (result.exitCode == 0) {
return filePath;
} else {
return null;
}
} else if (Platform.isWindows) {
// Windows平台上使用snippingtool工具
final result = await Process.run(
'snippingtool',
['/clip', filePath],
);
if (result.exitCode == 0) {
return filePath;
} else {
return null;
}
} else {
//
return null;
}
}
static Future<bool> requestScreenRecordingPermission() async {
if (Platform.isMacOS) {
final result = await Process.run(
'sh', ['-c', 'echo ${Platform.environment['USER']}']);
final username = result.stdout.toString().trim();
const script =
'tell application "System Events" to return exists (processes where name is "ControlCenter")';
final process = await Process.run('osascript', ['-e', script]);
final isControlCenterRunning = process.stdout.toString().trim() == 'true';
if (!isControlCenterRunning) {
await Process.run('open', ['-a', 'ControlCenter']);
await Future.delayed(const Duration(seconds: 1));
}
final script2 = 'tell application "ControlCenter" to activate\n'
'tell application "System Events"\n'
' tell process "ControlCenter"\n'
' click menu item "Screen Recording" of menu "File" of menu bar 1\n'
' delay 0.5\n'
' keystroke "$username" & return\n'
' end tell\n'
'end tell\n';
await Process.run('osascript', ['-e', script2]);
await Future.delayed(const Duration(seconds: 1));
final isScreenRecordingEnabled = await SystemChannels.platform
.invokeMethod<bool>('isScreenRecordingEnabled');
return isScreenRecordingEnabled ?? false;
} else {
return true;
}
}
static Future<Size> getImageSize(String imagePath) async {
final bytes = await File(imagePath).readAsBytes();
final completer = Completer<Size>();
final imageStream =
Image.memory(bytes).image.resolve(ImageConfiguration.empty);
imageStream.addListener(ImageStreamListener((imageInfo, _) {
completer.complete(Size(
imageInfo.image.width.toDouble(), imageInfo.image.height.toDouble()));
}));
return completer.future;
}
}

View File

@ -2,20 +2,40 @@
import 'package:flutter/cupertino.dart';
enum ScreenType { Desktop, Tablet, Handset, Watch }
enum DeviceType { Desktop, Mobile }
class FormFactor {
static double desktop = 900;
static double tablet = 600;
static double handset = 300;
}
class ScreenUtils {
static ScreenType getFormFactor(BuildContext context) {
double deviceWidth = MediaQuery.of(context).size.shortestSide;
if (deviceWidth > FormFactor.desktop) return ScreenType.Desktop;
if (deviceWidth > FormFactor.tablet) return ScreenType.Tablet;
if (deviceWidth > FormFactor.handset) return ScreenType.Handset;
return ScreenType.Watch;
class TUIKitScreenUtils {
static DeviceType? deviceType;
static DeviceType getFormFactor([BuildContext? context]) {
if (deviceType != null) return deviceType!;
if(context != null){
double deviceWidth = MediaQuery.of(context).size.width;
double deviceHeight = MediaQuery.of(context).size.height;
if (deviceWidth > FormFactor.desktop || deviceWidth > deviceHeight * 1.1) {
deviceType = DeviceType.Desktop;
} else if (deviceWidth > FormFactor.handset) {
deviceType = DeviceType.Mobile;
}
return deviceType ?? DeviceType.Mobile;
}else{
return DeviceType.Mobile;
}
}
static Widget getDeviceWidget({
required Widget defaultWidget,
Widget? desktopWidget,
Widget? mobileWidget,
}) {
if (deviceType == DeviceType.Desktop) return desktopWidget ?? defaultWidget;
return mobileWidget ?? defaultWidget;
}
}

View File

@ -1,5 +1,5 @@
import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter_plugin_record_plus/const/play_state.dart';
import 'package:flutter_plugin_record_plus/const/response.dart';
import 'package:flutter_plugin_record_plus/index.dart';
@ -14,33 +14,37 @@ class SoundPlayer {
static final FlutterPluginRecord _recorder = FlutterPluginRecord();
static SoundInterruptListener? _soundInterruptListener;
static bool isInited = false;
static final AudioPlayer _audioPlayer = AudioPlayer();
static initSoundPlayer() {
if (!isInited) {
_recorder.init();
AudioPlayer.global.setGlobalAudioContext(const AudioContext());
isInited = true;
}
}
static play({required String url}) {
_recorder.stopPlay();
static Future<void> play({required String url}) async {
_audioPlayer.stop();
if (_soundInterruptListener != null) {
_soundInterruptListener!();
}
_recorder.playByPath(url, 'url');
await _audioPlayer.play(UrlSource(url));
}
static stop() {
_recorder.stopPlay();
_audioPlayer.stop();
}
static dispose() {
_audioPlayer.dispose();
_recorder.dispose();
}
static StreamSubscription<PlayState> playStateListener(
{required PlayStateListener listener}) =>
_recorder.responsePlayStateController.listen(listener);
static StreamSubscription<PlayerState> playStateListener(
{required void Function(PlayerState)? listener}) =>
_audioPlayer.onPlayerStateChanged.listen(listener);
static setSoundInterruptListener(SoundInterruptListener listener) {
_soundInterruptListener = listener;

View File

@ -11,18 +11,6 @@ class TimeAgo {
];
}
List<String> daysMap() {
return [
TIM_t("星期天"),
TIM_t("星期一"),
TIM_t("星期二"),
TIM_t("星期三"),
TIM_t("星期四"),
TIM_t("星期五"),
TIM_t("星期六")
];
}
List<String> weekdayMap() {
return [
'',
@ -36,7 +24,7 @@ class TimeAgo {
];
}
String getYearMounthDate(DateTime dateTime) {
String getYearMonthDate(DateTime dateTime) {
String month = dateTime.month.toString();
String date = dateTime.day.toString();
return dateTime.year.toString() +
@ -48,7 +36,7 @@ class TimeAgo {
date;
}
String getMounthDate(DateTime dateTime) {
String getMonthDate(DateTime dateTime) {
String month = dateTime.month.toString();
String date = dateTime.day.toString();
return (month.length == 1 ? '0' : '') +
@ -59,39 +47,33 @@ class TimeAgo {
}
String getTimeStringForChat(int timeStamp) {
final formatedTimeStamp = timeStamp * 1000;
final DateTime date =
DateTime.fromMillisecondsSinceEpoch(formatedTimeStamp);
final currentTimeStamp = DateTime.now().millisecondsSinceEpoch;
final DateTime date = DateTime.fromMillisecondsSinceEpoch(timeStamp * 1000);
final Duration duration = DateTime.now().difference(date);
final int diffDays = duration.inDays +
(duration.inMinutes >
DateTime.now()
.difference(DateTime(DateTime.now().year,
DateTime.now().month, DateTime.now().day))
.inMinutes
? 1
: 0);
final int diffMinutes = duration.inMinutes;
int diffDays = duration.inDays;
final diffMinutes = duration.inMinutes;
var res;
//
if (diffDays > 0 && diffDays < 7) {
final String formatTodayZero =
DateFormat('yyyyMMdd').format(DateTime.now());
final todayZero = DateTime.parse(formatTodayZero).millisecondsSinceEpoch;
final todayDiff = currentTimeStamp - todayZero;
final isTwoDay = todayDiff < (diffMinutes - diffDays * 1440) * 60000;
if (isTwoDay) {
diffDays = diffDays + 1;
}
if (diffDays <= 2) {
res = dayMap()[diffDays - 1];
} else {
res = daysMap()[date.weekday];
res = weekdayMap()[date.weekday];
}
} else if (diffDays >= 7) {
//
if (date.year == DateTime.now().year) {
res = getMounthDate(date);
res = getMonthDate(date);
} else {
res = getYearMounthDate(date);
res = getYearMonthDate(date);
}
} else {
if (diffMinutes > 1) {
@ -99,9 +81,9 @@ class TimeAgo {
final String option2 = diffMinutes.toString();
res = TIM_t_para("{{option2}} 分钟前", "$option2 分钟前")(option2: option2);
} else {
final prefix = date.hour > 12 ? TIM_t("下午") : TIM_t("上午");
final timeStr = DateFormat('hh:mm').format(date);
res = "$prefix $timeStr";
res =
"${date.hour}:${date.minute < 10 ? date.minute.toString() + "0" : date.minute}";
// res = "$prefix $timeStr";
}
} else {
res = TIM_t("现在");
@ -115,26 +97,27 @@ class TimeAgo {
var nowTime = DateTime.now();
nowTime = DateTime(nowTime.year, nowTime.month, nowTime.day);
var ftime = DateTime.fromMillisecondsSinceEpoch(timeStamp * 1000);
var preFix = ftime.hour >= 12 ? TIM_t("下午") : TIM_t("上午");
final timeStr = DateFormat('hh:mm').format(ftime);
// + / + (12)
// var preFix = ftime.hour >= 12 ? TIM_t("下午") : TIM_t("上午");
final timeStr =
DateFormat('HH:mm').format(ftime); // Use 'HH:mm' for 24-hour format
// + (24)
if (nowTime.year != ftime.year) {
return '${DateFormat('yyyy-MM-dd').format(ftime)} $preFix $timeStr';
return '${DateFormat('yyyy-MM-dd').format(ftime)} $timeStr';
}
// + / + (12
// + (24
if (ftime.isBefore(nowTime.subtract(const Duration(days: 6)))) {
return '${DateFormat('MM-dd').format(ftime)} $preFix $timeStr';
return '${DateFormat('MM-dd').format(ftime)} $timeStr';
}
// + / + (12
// + (24
if (ftime.isBefore(nowTime.subtract(const Duration(days: 1)))) {
return '${weekdayMap()[ftime.weekday]} $preFix $timeStr';
return '${weekdayMap()[ftime.weekday]} $timeStr';
}
// + / + (12)
// + (24)
if (nowTime.day != ftime.day) {
String option2 = '$preFix $timeStr';
String option2 = timeStr;
return TIM_t_para("昨天 {{option2}}", "昨天 $option2")(option2: option2);
}
// / + (12)
return '$preFix $timeStr';
// (24)
return timeStr;
}
}

View File

@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_wide_modal_operation_key.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
@ -20,11 +23,15 @@ class TIMUIKitAddFriend extends StatefulWidget {
/// The life cycle hooks for adding friends and contact business logic
final AddFriendLifeCycle? lifeCycle;
final VoidCallback? closeFunc;
const TIMUIKitAddFriend(
{Key? key,
this.isShowDefaultGroup = false,
this.lifeCycle,
required this.onTapAlreadyFriendsItem})
required this.onTapAlreadyFriendsItem,
this.closeFunc})
: super(key: key);
@override
@ -45,6 +52,9 @@ class _TIMUIKitAddFriendState extends TIMUIKitState<TIMUIKitAddFriend> {
Widget _searchResultItemBuilder(
V2TimUserFullInfo friendInfo, TUITheme theme) {
final isDesktopScreen =
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
final faceUrl = friendInfo.faceUrl ?? "";
final userID = friendInfo.userID ?? "";
final String showName =
@ -74,14 +84,32 @@ class _TIMUIKitAddFriendState extends TIMUIKitState<TIMUIKitAddFriend> {
return;
}
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SendApplication(
lifeCycle: widget.lifeCycle,
isShowDefaultGroup: widget.isShowDefaultGroup ?? false,
friendInfo: friendInfo,
model: _selfInfoViewModel)));
if (isDesktopScreen) {
if (widget.closeFunc != null) {
widget.closeFunc!();
}
TUIKitWidePopup.showPopupWindow(
operationKey: TUIKitWideModalOperationKey.addFriend,
context: context,
width: MediaQuery.of(context).size.width * 0.3,
height: MediaQuery.of(context).size.width * 0.4,
title: TIM_t("添加好友"),
child: (closeFuncSendApplication) => SendApplication(
lifeCycle: widget.lifeCycle,
isShowDefaultGroup: widget.isShowDefaultGroup ?? false,
friendInfo: friendInfo,
model: _selfInfoViewModel),
);
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SendApplication(
lifeCycle: widget.lifeCycle,
isShowDefaultGroup: widget.isShowDefaultGroup ?? false,
friendInfo: friendInfo,
model: _selfInfoViewModel)));
}
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
@ -89,9 +117,9 @@ class _TIMUIKitAddFriendState extends TIMUIKitState<TIMUIKitAddFriend> {
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 48,
height: 48,
margin: const EdgeInsets.only(right: 12),
width: isDesktopScreen ? 38 : 48,
height: isDesktopScreen ? 38 : 48,
margin: const EdgeInsets.only(right: 16),
child: Avatar(faceUrl: faceUrl, showName: showName),
),
Column(
@ -99,7 +127,9 @@ class _TIMUIKitAddFriendState extends TIMUIKitState<TIMUIKitAddFriend> {
children: [
Text(
showName,
style: TextStyle(color: theme.darkTextColor, fontSize: 18),
style: TextStyle(
color: theme.darkTextColor,
fontSize: isDesktopScreen ? 16 : 18),
),
const SizedBox(
height: 4,

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/add_friend_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart';
@ -53,26 +54,9 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
: userID) ??
"";
final option2 = widget.friendInfo.selfSignature ?? "";
return Scaffold(
appBar: AppBar(
title: Text(
TIM_t("添加好友"),
style: TextStyle(color: theme.white, fontSize: 17),
),
shadowColor: theme.white,
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
theme.lightPrimaryColor ?? CommonColor.lightPrimaryColor,
theme.primaryColor ?? CommonColor.primaryColor
]),
),
),
iconTheme: IconThemeData(
color: theme.white,
),
),
body: SingleChildScrollView(
Widget sendApplicationBody(){
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -95,7 +79,7 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
Text(
showName,
style:
TextStyle(color: theme.darkTextColor, fontSize: 18),
TextStyle(color: theme.darkTextColor, fontSize: 18),
),
const SizedBox(
height: 4,
@ -103,16 +87,16 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
Text(
"ID: $userID",
style:
TextStyle(fontSize: 13, color: theme.weakTextColor),
TextStyle(fontSize: 13, color: theme.weakTextColor),
),
const SizedBox(
height: 4,
),
Text(
if(TencentUtils.checkString(option2) != null)Text(
TIM_t_para("个性签名: {{option2}}", "个性签名: $option2")(
option2: option2),
style:
TextStyle(fontSize: 13, color: theme.weakTextColor),
TextStyle(fontSize: 13, color: theme.weakTextColor),
),
],
)
@ -184,19 +168,19 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
Container(
color: theme.white,
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
TIM_t("分组"),
style:
TextStyle(color: theme.darkTextColor, fontSize: 16),
TextStyle(color: theme.darkTextColor, fontSize: 16),
),
Text(
TIM_t("我的好友"),
style:
TextStyle(color: theme.darkTextColor, fontSize: 16),
TextStyle(color: theme.darkTextColor, fontSize: 16),
)
],
),
@ -213,7 +197,7 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
if (widget.lifeCycle?.shouldAddFriend != null &&
await widget.lifeCycle!.shouldAddFriend(userID, remark,
friendGroup, addWording, context) ==
friendGroup, addWording, context) ==
false) {
return;
}
@ -246,7 +230,29 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
)
],
),
);
}
return TUIKitScreenUtils.getDeviceWidget(
desktopWidget: Container(
padding: const EdgeInsets.only(top: 10),
color: theme.weakBackgroundColor,
child: sendApplicationBody(),
),
);
defaultWidget: Scaffold(
appBar: AppBar(
title: Text(
TIM_t("添加好友"),
style: TextStyle(color: theme.appbarTextColor, fontSize: 17),
),
shadowColor: theme.white,
backgroundColor: theme.appbarBgColor ??
theme.primaryColor,
iconTheme: IconThemeData(
color: theme.appbarTextColor,
),
),
body: sendApplicationBody(),
));
}
}

View File

@ -1,4 +1,7 @@
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_wide_modal_operation_key.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/add_group_life_cycle.dart';
@ -19,8 +22,10 @@ class TIMUIKitAddGroup extends StatefulWidget {
final Function(String groupID, V2TimConversation conversation)
onTapExistGroup;
final VoidCallback? closeFunc;
const TIMUIKitAddGroup(
{Key? key, this.lifeCycle, required this.onTapExistGroup})
{Key? key, this.lifeCycle, required this.onTapExistGroup, this.closeFunc})
: super(key: key);
@override
@ -67,6 +72,8 @@ class _TIMUIKitAddGroupState extends TIMUIKitState<TIMUIKitAddGroup> {
final groupID = groupInfo.groupID;
final showName = groupInfo.groupName ?? groupID;
final groupType = _getGroupType(groupInfo.groupType);
final isDesktopScreen =
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
return InkWell(
onTap: () async {
final V2TimConversation? groupConversation =
@ -76,16 +83,38 @@ class _TIMUIKitAddGroupState extends TIMUIKitState<TIMUIKitAddGroup> {
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("您已是群成员"),
infoCode: 6660202));
if (widget.closeFunc != null) {
widget.closeFunc!();
}
widget.onTapExistGroup(groupID, groupConversation);
return;
}
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SendJoinGroupApplication(
lifeCycle: widget.lifeCycle,
groupInfo: groupInfo,
)));
if(isDesktopScreen){
if (widget.closeFunc != null) {
widget.closeFunc!();
}
TUIKitWidePopup.showPopupWindow(
operationKey: TUIKitWideModalOperationKey.addGroup,
context: context,
width: MediaQuery.of(context).size.width * 0.3,
height: MediaQuery.of(context).size.width * 0.4,
title: TIM_t("添加群聊"),
child: (closeFuncSendApplication) => SendJoinGroupApplication(
lifeCycle: widget.lifeCycle,
groupInfo: groupInfo,
),
);
}else{
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SendJoinGroupApplication(
lifeCycle: widget.lifeCycle,
groupInfo: groupInfo,
)));
}
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
@ -93,9 +122,9 @@ class _TIMUIKitAddGroupState extends TIMUIKitState<TIMUIKitAddGroup> {
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 48,
height: 48,
margin: const EdgeInsets.only(right: 12),
width: isDesktopScreen ? 38 : 48,
height: isDesktopScreen ? 38 : 48,
margin: const EdgeInsets.only(right: 16),
child: Avatar(faceUrl: faceUrl, showName: showName),
),
Column(
@ -103,7 +132,7 @@ class _TIMUIKitAddGroupState extends TIMUIKitState<TIMUIKitAddGroup> {
children: [
Text(
showName,
style: const TextStyle(fontSize: 18),
style: TextStyle(fontSize: isDesktopScreen ? 16 : 18),
),
Text(
"ID: $groupID",

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/add_group_life_cycle.dart';
@ -74,26 +75,9 @@ class _SendJoinGroupApplicationState
final groupID = widget.groupInfo.groupID;
final showName = widget.groupInfo.groupName ?? groupID;
final option1 = _getGroupType(widget.groupInfo.groupType);
return Scaffold(
appBar: AppBar(
title: Text(
TIM_t("进群申请"),
style: TextStyle(color: theme.white, fontSize: 17),
),
shadowColor: theme.white,
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
theme.lightPrimaryColor ?? CommonColor.lightPrimaryColor,
theme.primaryColor ?? CommonColor.primaryColor
]),
),
),
iconTheme: IconThemeData(
color: theme.white,
),
),
body: SingleChildScrollView(
Widget sendGroupApplicationBody(){
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -116,7 +100,7 @@ class _SendJoinGroupApplicationState
Text(
showName,
style:
TextStyle(color: theme.darkTextColor, fontSize: 18),
TextStyle(color: theme.darkTextColor, fontSize: 18),
),
const SizedBox(
height: 4,
@ -124,7 +108,7 @@ class _SendJoinGroupApplicationState
Text(
"ID: $groupID",
style:
TextStyle(fontSize: 13, color: theme.weakTextColor),
TextStyle(fontSize: 13, color: theme.weakTextColor),
),
const SizedBox(
height: 4,
@ -133,7 +117,7 @@ class _SendJoinGroupApplicationState
TIM_t_para("群类型: {{option1}}", "群类型: $option1")(
option1: option1),
style:
TextStyle(fontSize: 12, color: theme.weakTextColor),
TextStyle(fontSize: 12, color: theme.weakTextColor),
),
],
)
@ -181,7 +165,26 @@ class _SendJoinGroupApplicationState
)
],
),
);
}
return TUIKitScreenUtils.getDeviceWidget(
desktopWidget: sendGroupApplicationBody(),
defaultWidget: Scaffold(
appBar: AppBar(
title: Text(
TIM_t("进群申请"),
style: TextStyle(color: theme.appbarTextColor, fontSize: 17),
),
shadowColor: theme.white,
backgroundColor: theme.appbarBgColor ??
theme.primaryColor,
iconTheme: IconThemeData(
color: theme.appbarTextColor,
),
),
);
body: sendGroupApplicationBody(),
));
}
}

View File

@ -7,6 +7,7 @@ import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_friendsh
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/avatar.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
@ -50,35 +51,33 @@ class _TIMUIKitBlackListState extends TIMUIKitState<TIMUIKitBlackList> {
final theme = Provider.of<TUIThemeViewModel>(context).theme;
final showName = _getShowName(friendInfo);
final faceUrl = friendInfo.userProfile?.faceUrl ?? "";
return Slidable(
endActionPane: ActionPane(motion: const DrawerMotion(), children: [
SlidableAction(
onPressed: (context) async {
await _friendshipViewModel
.deleteFromBlockList([friendInfo.userID]);
},
backgroundColor: theme.cautionColor ?? CommonColor.cautionColor,
foregroundColor: theme.white,
label: TIM_t("删除"),
autoClose: true,
)
]),
child: GestureDetector(
final isDesktopScreen =
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
Widget itemWidget() {
return Material(
color: theme.wideBackgroundColor,
child: InkWell(
onTap: () {
if (widget.onTapItem != null) {
widget.onTapItem!(friendInfo);
}
},
child: Container(
padding: const EdgeInsets.only(top: 10, left: 16),
padding: const EdgeInsets.only(top: 10, left: 16, right: 16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: theme.weakDividerColor ??
CommonColor.weakDividerColor))),
child: Row(
children: [
Container(
padding: const EdgeInsets.only(bottom: 12),
margin: const EdgeInsets.only(right: 12),
child: SizedBox(
height: 40,
width: 40,
height: isDesktopScreen ? 30 : 40,
width: isDesktopScreen ? 30 : 40,
child: Avatar(faceUrl: faceUrl, showName: showName),
),
),
@ -86,19 +85,45 @@ class _TIMUIKitBlackListState extends TIMUIKitState<TIMUIKitBlackList> {
child: Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(top: 10, bottom: 20),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: theme.weakDividerColor ??
CommonColor.weakDividerColor))),
child: Text(
showName,
style: TextStyle(color: theme.black, fontSize: 18),
style: TextStyle(
color: theme.black, fontSize: isDesktopScreen ? 14 : 18),
),
))
)),
if (isDesktopScreen)
OutlinedButton(
onPressed: () {
_friendshipViewModel
.deleteFromBlockList([friendInfo.userID]);
},
child: Text(
TIM_t("移出黑名单"),
style: TextStyle(color: theme.primaryColor),
))
],
),
),
),
);
}
return TUIKitScreenUtils.getDeviceWidget(
desktopWidget: itemWidget(),
defaultWidget: Slidable(
endActionPane: ActionPane(motion: const DrawerMotion(), children: [
SlidableAction(
onPressed: (context) async {
await _friendshipViewModel
.deleteFromBlockList([friendInfo.userID]);
},
backgroundColor: theme.cautionColor ?? CommonColor.cautionColor,
foregroundColor: theme.white,
label: TIM_t("删除"),
autoClose: true,
)
]),
child: itemWidget(),
));
}
@ -106,10 +131,6 @@ class _TIMUIKitBlackListState extends TIMUIKitState<TIMUIKitBlackList> {
return widget.itemBuilder ?? _itemBuilder;
}
@override
void initState() {
super.initState();
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {

View File

@ -8,8 +8,8 @@ enum MessageListTongueType {
none,
toLatest,
showUnread,
showPrevious,
atMe,
showPrevious,
atAll,
}
@ -36,9 +36,9 @@ class TIMUIKitHistoryMessageListTongue extends TIMUIKitStatelessWidget {
TIMUIKitHistoryMessageListTongue({
Key? key,
required this.previousCount,
required this.valueType,
required this.onClick,
required this.previousCount,
required this.unreadCount,
this.tongueItemBuilder,
this.atNum = "",

Some files were not shown because too many files have changed in this diff Show More