update flutter uikit to 2.0.0
155
CHANGELOG.md
|
|
@ -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
|
|
@ -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
|
|
@ -63,123 +63,167 @@ More languages:
|
|||
|
||||
<br>
|
||||
|
||||

|
||||

|
||||
|
||||
<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.
|
||||
|
||||

|
||||
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.
|
||||
|
||||

|
||||
|
||||
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
|
|||
|
||||

|
||||
|
||||
### 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
|
||||

|
||||
|
||||
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
|
||||

|
||||
|
||||

|
||||
|
||||
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
|
||||

|
||||
|
||||

|
||||
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.
|
||||
|
||||

|
||||
|
||||
|
|
@ -416,33 +465,33 @@ If an error occurs after the configuration, click **Product > Clean Build Folder
|
|||
|
||||

|
||||
|
||||
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?
|
||||
|
||||

|
||||
|
||||
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>
|
||||
|
|
|
|||
509
doc/社交场景最佳实践.md
|
|
@ -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 的好友表中有用户 B,B 的好友表中也有 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),那么任何人添加 A,A 都会收到一个请求加好友验证消息,这是第一个回合,然后 A 对这个请求加好友验证消息进行同意操作时,这是第二个回合,这种需要验证的加好友场景就被称为两回合加好友。
|
||||
|
||||
### 非好友发消息
|
||||
|
||||
对于某些场景,需要非好友关系也能发送消息。例如对于交友软件,常常允许匹配到的陌生人,发送若干条消息打招呼。
|
||||
|
||||
这需要您在[腾讯云IM的控制台](https://console.cloud.tencent.com/im/login-message),关闭 “好友关系检查” 功能。
|
||||
|
||||

|
||||
|
||||
如果您需要针对陌生人发消息的数量加以限制,可在您的业务层实现。发送若干条消息后,不再允许发送即可。
|
||||
|
||||
## 群组
|
||||
|
||||
有的时候,仅一对一单聊无法满足您的社交场景要求。
|
||||
|
||||
例如对于音乐类型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://cloud.tencent.com/document/product/269/84296)。
|
||||
|
||||

|
||||
|
||||
以上,就是使用腾讯云IM实现社交场景常见需求的解决方案。快来使用腾讯云IM打造属于您的社交产品吧~
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
<EFBFBD>
|
||||
<EFBFBD>
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) {},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
flutter/ephemeral
|
||||
|
|
@ -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()
|
||||
|
|
@ -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}
|
||||
)
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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_
|
||||
|
|
@ -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)
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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_
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Flutter-related
|
||||
**/Flutter/ephemeral/
|
||||
**/Pods/
|
||||
|
||||
# Xcode-related
|
||||
**/dgph
|
||||
**/xcuserdata/
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
|
@ -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"))
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 */;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 520 B |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
|
@ -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>
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#include "../../Flutter/Flutter-Debug.xcconfig"
|
||||
#include "Warnings.xcconfig"
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#include "../../Flutter/Flutter-Release.xcconfig"
|
||||
#include "Warnings.xcconfig"
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 440 B |
|
After Width: | Height: | Size: 374 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.0大版本得彻底改造下QAQ
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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! >
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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] ?? "";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": "会话"
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 "*/*";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 = "",
|
||||
|
|
|
|||