rename package folders
@@ -0,0 +1,60 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
coverage/
|
||||
coverage_helper_test.dart
|
||||
|
||||
# Web related
|
||||
lib/generated_plugin_registrant.dart
|
||||
|
||||
# Exceptions to above rules.
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
# See https://www.dartlang.org/guides/libraries/private-files
|
||||
|
||||
# Files and directories created by pub
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
# If you're building an application, you may want to check-in your pubspec.lock
|
||||
pubspec.lock
|
||||
|
||||
# Directory created by dartdoc
|
||||
# If you don't generate documentation locally you can remove this line.
|
||||
doc/api/
|
||||
|
||||
# Avoid committing generated Javascript files:
|
||||
*.dart.js
|
||||
*.info.json # Produced by the --dump-info flag.
|
||||
*.js # When generated by dart2js. Don't specify *.js if your
|
||||
# project includes source files written in JavaScript.
|
||||
*.js_
|
||||
*.js.deps
|
||||
*.js.map
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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.
|
||||
|
||||
version:
|
||||
revision: 659dc8129d4edb9166e9a0d600439d135740933f
|
||||
channel: beta
|
||||
|
||||
project_type: package
|
||||
@@ -0,0 +1,455 @@
|
||||
## 1.0.0-beta
|
||||
|
||||
- 🛑 **BREAKING** Renamed `Client` to less generic `StreamChatClient`
|
||||
- 🛑 **BREAKING** Segregated the persistence layer into separate package (stream_chat_persistence)[https://pub.dev/packages/stream_chat_persistence]
|
||||
- 🛑 **BREAKING** Moved `Client.backgroundKeepAlive` to (core package)[https://pub.dev/packages/stream_chat_core]
|
||||
- 🛑 **BREAKING** Moved `Client.showLocalNotification` to (core package)[https://pub.dev/packages/stream_chat_core] and renamed it to `StreamChatCore.onBackgroundEventReceived`
|
||||
- Removed `flutter` dependency. This is now a pure Dart package 🥳
|
||||
- Minor improvements and bugfixes
|
||||
|
||||
## 0.2.24+2
|
||||
|
||||
- Fix reconnection bug while using tokenProvider
|
||||
|
||||
## 0.2.24+1
|
||||
|
||||
- Stop ws reconnection after calling disconnect
|
||||
|
||||
## 0.2.24
|
||||
|
||||
- Create enum for push providers
|
||||
- Add merge helper functions in `Message` and `ChannelModel` for easier data manipulation
|
||||
|
||||
## 0.2.23+3
|
||||
|
||||
- Remove + notation from userAgent
|
||||
- Fix optimistic update for totalUnreadCount
|
||||
|
||||
## 0.2.23+2
|
||||
|
||||
- Do not throw an error when calling queryChannels without an active connection if the offline storage is enabled
|
||||
|
||||
## 0.2.23+1
|
||||
|
||||
- Throw an error when calling queryChannels without an active connection
|
||||
- Wait to establish a connection if calling queryChannels while connecting
|
||||
|
||||
## 0.2.23
|
||||
|
||||
- Add thread_participants in message model
|
||||
|
||||
## 0.2.22
|
||||
|
||||
- Add thread-less message reply feature (QuotedMessage)
|
||||
|
||||
## 0.2.21+2
|
||||
|
||||
- Fix but not throwing error during querychannels and persistance disabled
|
||||
- Fix reaction.updated event handling
|
||||
|
||||
## 0.2.21+1
|
||||
|
||||
- Fix error in the offline storage queryChannelCids query
|
||||
|
||||
## 0.2.21
|
||||
|
||||
- Fix channel.hide(clearHistory: true) not clearing local messages
|
||||
- Add banned field to member
|
||||
|
||||
## 0.2.20
|
||||
|
||||
- Return offline data only if the backend is unreachable. This avoids the glitch of the ChannelListView because we cannot sort by custom properties.
|
||||
|
||||
## 0.2.19
|
||||
|
||||
- Added message filters for `Client.search()`
|
||||
|
||||
## 0.2.18
|
||||
|
||||
- Correctly dispose resources when disposing the client state
|
||||
- Limit parallel queryChannels with same parameters to 1
|
||||
- Added `clearUser` parameter to `client.disconnect` to remove the user instance of the client
|
||||
|
||||
## 0.2.17+1
|
||||
|
||||
- Do not retry messages when server returns error
|
||||
|
||||
## 0.2.17
|
||||
|
||||
- Add shadow ban feature
|
||||
|
||||
## 0.2.16
|
||||
|
||||
- Listen for user.updated events
|
||||
|
||||
## 0.2.15+2
|
||||
|
||||
- Fix reaction score updates
|
||||
|
||||
## 0.2.15+1
|
||||
|
||||
- Listen to reaction.updated event
|
||||
|
||||
## 0.2.15
|
||||
|
||||
- Fix search message response
|
||||
|
||||
## 0.2.14
|
||||
|
||||
- Add event.extradata
|
||||
|
||||
## 0.2.13+1
|
||||
|
||||
- Let user change channel.extradata if the channel is not initialized yet
|
||||
|
||||
## 0.2.13
|
||||
|
||||
- Add parent_id to events for typing indicators in threads
|
||||
|
||||
## 0.2.12+2
|
||||
|
||||
- Fix error with reactions with null user
|
||||
|
||||
## 0.2.12
|
||||
|
||||
- Do not save channels in memory if not being watched. This was leading to some bugs in some specific use-cases.
|
||||
|
||||
## 0.2.11
|
||||
|
||||
- Fix user.name getter
|
||||
- Use detached loggers
|
||||
- Throw error while connecting if it comes from backend
|
||||
- Fix ws reconnection
|
||||
|
||||
## 0.2.10+2
|
||||
|
||||
- Fix bug with event filtering
|
||||
|
||||
## 0.2.10+1
|
||||
|
||||
- Add default limit to pagination
|
||||
|
||||
## 0.2.10
|
||||
|
||||
- Added `channel.state.unreadCountStream`
|
||||
|
||||
## 0.2.9
|
||||
|
||||
- Adding a message on `Channel.update` is now optional
|
||||
|
||||
## 0.2.8+1
|
||||
|
||||
- Fix retry logic
|
||||
|
||||
## 0.2.8
|
||||
|
||||
- Add missing event types
|
||||
- Fix local sorting on offline storage
|
||||
|
||||
## 0.2.7+1
|
||||
|
||||
- `Client.channel` returns an existing channel if available
|
||||
- Update message in the offline storage if attachment has expired (for the new CDN)
|
||||
- Fix `GetMessagesByIdResponse` format
|
||||
- Do not query messages if already existing in offline storage
|
||||
|
||||
## 0.2.6
|
||||
|
||||
- Experimental support for Flutter web and MacOs
|
||||
|
||||
## 0.2.5+2
|
||||
|
||||
- Cleaned up Serialization on extra_data
|
||||
|
||||
## 0.2.5+1
|
||||
|
||||
- Fix `channel.show` api call
|
||||
|
||||
## 0.2.5
|
||||
|
||||
- Add `channelType` and `channelId` properties to event object
|
||||
|
||||
## 0.2.4+2
|
||||
|
||||
- Fix query members messing channel state
|
||||
|
||||
## 0.2.4+1
|
||||
|
||||
- Do not resync if there is no channel in offlinestorage
|
||||
|
||||
## 0.2.4
|
||||
|
||||
- Add null-safety to ws disconnect
|
||||
- Add pagination parameters to queryUsers request
|
||||
|
||||
## 0.2.3+3
|
||||
|
||||
- Fix reaction add/remove logic
|
||||
|
||||
## 0.2.3+2
|
||||
|
||||
- Skip system messages during unreadCount computation
|
||||
|
||||
## 0.2.3+1
|
||||
|
||||
- Removed moor_ffi from dependencies in favor of moor/ffi
|
||||
|
||||
## 0.2.3
|
||||
|
||||
- Fix reject invite payload
|
||||
|
||||
- Add multi-tenant properties to channel and user
|
||||
|
||||
## 0.2.2+1
|
||||
|
||||
- Fix queryChannels payload
|
||||
|
||||
## 0.2.2
|
||||
|
||||
- Fix add/remove/invite members api calls
|
||||
|
||||
## 0.2.1
|
||||
|
||||
- Add `isMutedStream` to `Channel`
|
||||
- Add `isGroup` to `Channel`
|
||||
- Add `isDistinct` to `Channel`
|
||||
|
||||
## 0.2.0+2
|
||||
|
||||
- Fix search messages response class
|
||||
|
||||
## 0.2.0+1
|
||||
|
||||
- Fix offline members update
|
||||
- Add channel mutes
|
||||
- Fix default channel sort
|
||||
|
||||
## 0.2.0
|
||||
|
||||
- Add `lastMessage` getter to Channel.state
|
||||
- Add `isSystem` property to Message
|
||||
- Incremental websocket reconnection timeout
|
||||
- Add translate message api call
|
||||
- Add queryMembers api call
|
||||
- Add user list to client state
|
||||
- Synchronize channel members status
|
||||
- Add offline storage
|
||||
- Add push notifications helper functions
|
||||
|
||||
## 0.2.0-alpha+23
|
||||
|
||||
- Add `lastMessage` getter to `Channel.state`
|
||||
|
||||
## 0.2.0-alpha+22
|
||||
|
||||
- Add `isSystem` property to Message
|
||||
|
||||
## 0.2.0-alpha+21
|
||||
|
||||
- Incremental websocket reconnection timeout
|
||||
|
||||
## 0.2.0-alpha+20
|
||||
|
||||
- More robust offline storage insertions
|
||||
|
||||
## 0.2.0-alpha+19
|
||||
|
||||
- Add translate message api call
|
||||
- Add queryMembers api call
|
||||
|
||||
## 0.2.0-alpha+18
|
||||
|
||||
- Revert moor_ffi version to 0.5.0
|
||||
|
||||
## 0.2.0-alpha+17
|
||||
|
||||
- Add user list to client
|
||||
|
||||
- Synchronize channel members status
|
||||
|
||||
## 0.2.0-alpha+16
|
||||
|
||||
- Try QueryChannels when `resync` endpoint returns an error
|
||||
|
||||
## 0.2.0-alpha+15
|
||||
|
||||
- Fix receiving reactions
|
||||
|
||||
## 0.2.0-alpha+14
|
||||
|
||||
- Avoid sending local event for optimistic updates
|
||||
|
||||
## 0.2.0-alpha+13
|
||||
|
||||
- Fix offline on app first start up
|
||||
|
||||
## 0.2.0-alpha+12
|
||||
|
||||
- Fix retry mechanism in threads
|
||||
- Fix delete channel query
|
||||
|
||||
## 0.2.0-alpha+9
|
||||
|
||||
- Add retry mechanism and retry queue
|
||||
|
||||
## 0.2.0-alpha+8
|
||||
|
||||
- Add copyWith to Attachment
|
||||
|
||||
## 0.2.0-alpha+7
|
||||
|
||||
- Add channel deleted/updated event handling
|
||||
|
||||
## 0.2.0-alpha+6
|
||||
|
||||
- Align with stable release
|
||||
|
||||
## 0.2.0-alpha+5
|
||||
|
||||
- Rename client parameters
|
||||
|
||||
## 0.2.0-alpha+3
|
||||
|
||||
- Remove dependencies on notification service
|
||||
|
||||
- Expose some helping method for integrate offline storage with push notifications
|
||||
|
||||
## 0.2.0-alpha+2
|
||||
|
||||
- Fix unread count
|
||||
|
||||
## 0.2.0-alpha
|
||||
|
||||
- Offline storage
|
||||
|
||||
- Push notifications
|
||||
|
||||
- Minor bug fixes
|
||||
|
||||
## 0.1.30
|
||||
|
||||
- Add silent property to message
|
||||
|
||||
## 0.1.29
|
||||
|
||||
- Fix read event handling
|
||||
|
||||
## 0.1.28
|
||||
|
||||
- Fix bug clearing members when receiving a message
|
||||
|
||||
## 0.1.27
|
||||
|
||||
- Update dependencies
|
||||
|
||||
## 0.1.26
|
||||
|
||||
- Remove wrong `members` property from `ChannelModel`
|
||||
|
||||
## 0.1.25
|
||||
|
||||
- Fix online status
|
||||
|
||||
## 0.1.24
|
||||
|
||||
- Fix unread count
|
||||
|
||||
## 0.1.22
|
||||
|
||||
- Add mute/unmute channel
|
||||
|
||||
## 0.1.20
|
||||
|
||||
- Fix channel query path without id
|
||||
|
||||
## 0.1.19
|
||||
|
||||
- Fix loading message replies
|
||||
|
||||
## 0.1.18
|
||||
|
||||
- Export dio error
|
||||
|
||||
## 0.1.17
|
||||
|
||||
- Ignore current user typing events
|
||||
|
||||
- Add event types
|
||||
|
||||
## 0.1.16
|
||||
|
||||
- Fix message update
|
||||
|
||||
## 0.1.15
|
||||
|
||||
- Fix mentions handling
|
||||
|
||||
## 0.1.14
|
||||
|
||||
- Handle message modification and commands
|
||||
|
||||
## 0.1.13
|
||||
|
||||
- Add message.updated event handling
|
||||
|
||||
## 0.1.12
|
||||
|
||||
- Add export multipart_file from dio
|
||||
|
||||
## 0.1.11
|
||||
|
||||
- Add channel config checks
|
||||
|
||||
## 0.1.10
|
||||
|
||||
- Rename Channel.channelClients to channels
|
||||
|
||||
## 0.1.9
|
||||
|
||||
- Fix channel update on message delete
|
||||
|
||||
## 0.1.8
|
||||
|
||||
- Add delete message handling
|
||||
|
||||
## 0.1.7
|
||||
|
||||
- Add reaction handling
|
||||
|
||||
## 0.1.6
|
||||
|
||||
- Add initialized completer
|
||||
|
||||
- Update example
|
||||
|
||||
## 0.1.5
|
||||
|
||||
- Add `ClientState` and `ChannelClientState` classes to handle channel state updates using events
|
||||
|
||||
- Update example supporting threads
|
||||
|
||||
## 0.1.4
|
||||
|
||||
- Update some api with wrong or incomplete signatures
|
||||
|
||||
- Add documentation for public apis
|
||||
|
||||
## 0.1.2
|
||||
|
||||
- add websocket reconnection logic
|
||||
|
||||
- add token expiration mechanism
|
||||
|
||||
## 0.1.1
|
||||
|
||||
- add typing events handling
|
||||
|
||||
## 0.1.0
|
||||
|
||||
- a better example can be found in the example/ directory
|
||||
|
||||
- fix some api calls and add missing one
|
||||
|
||||
## 0.0.2
|
||||
|
||||
- first beta version
|
||||
@@ -0,0 +1,219 @@
|
||||
SOURCE CODE LICENSE AGREEMENT
|
||||
|
||||
IMPORTANT - READ THIS CAREFULLY BEFORE DOWNLOADING, INSTALLING, USING OR
|
||||
ELECTRONICALLY ACCESSING THIS PROPRIETARY PRODUCT.
|
||||
|
||||
THIS IS A LEGAL AGREEMENT BETWEEN STREAM.IO, INC. (“STREAM.IO”) AND THE
|
||||
BUSINESS ENTITY OR PERSON FOR WHOM YOU (“YOU”) ARE ACTING (“CUSTOMER”) AS THE
|
||||
LICENSEE OF THE PROPRIETARY SOFTWARE INTO WHICH THIS AGREEMENT HAS BEEN
|
||||
INCLUDED (THE “AGREEMENT”). YOU AGREE THAT YOU ARE THE CUSTOMER, OR YOU ARE AN
|
||||
EMPLOYEE OR AGENT OF CUSTOMER AND ARE ENTERING INTO THIS AGREEMENT FOR LICENSE
|
||||
OF THE SOFTWARE BY CUSTOMER FOR CUSTOMER’S BUSINESS PURPOSES AS DESCRIBED IN
|
||||
AND IN ACCORDANCE WITH THIS AGREEMENT. YOU HEREBY AGREE THAT YOU ENTER INTO
|
||||
THIS AGREEMENT ON BEHALF OF CUSTOMER AND THAT YOU HAVE THE AUTHORITY TO BIND
|
||||
CUSTOMER TO THIS AGREEMENT.
|
||||
|
||||
STREAM.IO IS WILLING TO LICENSE THE SOFTWARE TO CUSTOMER ONLY ON THE FOLLOWING
|
||||
CONDITIONS: (1) YOU ARE A CURRENT CUSTOMER OF STREAM.IO; (2) YOU ARE NOT A
|
||||
COMPETITOR OF STREAM.IO; AND (3) THAT YOU ACCEPT ALL THE TERMS IN THIS
|
||||
AGREEMENT. BY DOWNLOADING, INSTALLING, CONFIGURING, ACCESSING OR OTHERWISE
|
||||
USING THE SOFTWARE, INCLUDING ANY UPDATES, UPGRADES, OR NEWER VERSIONS, YOU
|
||||
REPRESENT, WARRANT AND ACKNOWLEDGE THAT (A) CUSTOMER IS A CURRENT CUSTOMER OF
|
||||
STREAM.IO; (B) CUSTOMER IS NOT A COMPETITOR OF STREAM.IO; AND THAT (C) YOU HAVE
|
||||
READ THIS AGREEMENT, UNDERSTAND THIS AGREEMENT, AND THAT CUSTOMER AGREES TO BE
|
||||
BOUND BY ALL THE TERMS OF THIS AGREEMENT.
|
||||
|
||||
IF YOU DO NOT AGREE TO ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT,
|
||||
STREAM.IO IS UNWILLING TO LICENSE THE SOFTWARE TO CUSTOMER, AND THEREFORE, DO
|
||||
NOT COMPLETE THE DOWNLOAD PROCESS, ACCESS OR OTHERWISE USE THE SOFTWARE, AND
|
||||
CUSTOMER SHOULD IMMEDIATELY RETURN THE SOFTWARE AND CEASE ANY USE OF THE
|
||||
SOFTWARE.
|
||||
|
||||
1. SOFTWARE. The Stream.io software accompanying this Agreement, may include
|
||||
Source Code, Executable Object Code, associated media, printed materials and
|
||||
documentation (collectively, the “Software”). The Software also includes any
|
||||
updates or upgrades to or new versions of the original Software, if and when
|
||||
made available to you by Stream.io. “Source Code” means computer programming
|
||||
code in human readable form that is not suitable for machine execution without
|
||||
the intervening steps of interpretation or compilation. “Executable Object
|
||||
Code" means the computer programming code in any other form than Source Code
|
||||
that is not readily perceivable by humans and suitable for machine execution
|
||||
without the intervening steps of interpretation or compilation. “Site” means a
|
||||
Customer location controlled by Customer. “Authorized User” means any employee
|
||||
or contractor of Customer working at the Site, who has signed a written
|
||||
confidentiality agreement with Customer or is otherwise bound in writing by
|
||||
confidentiality and use obligations at least as restrictive as those imposed
|
||||
under this Agreement.
|
||||
|
||||
2. LICENSE GRANT. Subject to the terms and conditions of this Agreement, in
|
||||
consideration for the representations, warranties, and covenants made by
|
||||
Customer in this Agreement, Stream.io grants to Customer, during the term of
|
||||
this Agreement, a personal, non-exclusive, non-transferable, non-sublicensable
|
||||
license to:
|
||||
|
||||
a. install and use Software Source Code on password protected computers at a Site,
|
||||
restricted to Authorized Users;
|
||||
|
||||
b. create derivative works, improvements (whether or not patentable), extensions
|
||||
and other modifications to the Software Source Code (“Modifications”) to build
|
||||
unique scalable newsfeeds, activity streams, and in-app messaging via Stream’s
|
||||
application program interface (“API”);
|
||||
|
||||
c. compile the Software Source Code to create Executable Object Code versions of
|
||||
the Software Source Code and Modifications to build such newsfeeds, activity
|
||||
streams, and in-app messaging via the API;
|
||||
|
||||
d. install, execute and use such Executable Object Code versions solely for
|
||||
Customer’s internal business use (including development of websites through
|
||||
which data generated by Stream services will be streamed (“Apps”));
|
||||
|
||||
e. use and distribute such Executable Object Code as part of Customer’s Apps; and
|
||||
|
||||
f. make electronic copies of the Software and Modifications as required for backup
|
||||
or archival purposes.
|
||||
|
||||
3. RESTRICTIONS. Customer is responsible for all activities that occur in
|
||||
connection with the Software. Customer will not, and will not attempt to: (a)
|
||||
sublicense or transfer the Software or any Source Code related to the Software
|
||||
or any of Customer’s rights under this Agreement, except as otherwise provided
|
||||
in this Agreement, (b) use the Software Source Code for the benefit of a third
|
||||
party or to operate a service; (c) allow any third party to access or use the
|
||||
Software Source Code; (d) sublicense or distribute the Software Source Code or
|
||||
any Modifications in Source Code or other derivative works based on any part of
|
||||
the Software Source Code; (e) use the Software in any manner that competes with
|
||||
Stream.io or its business; or (e) otherwise use the Software in any manner that
|
||||
exceeds the scope of use permitted in this Agreement. Customer shall use the
|
||||
Software in compliance with any accompanying documentation any laws applicable
|
||||
to Customer.
|
||||
|
||||
4. OPEN SOURCE. Customer and its Authorized Users shall not use any software or
|
||||
software components that are open source in conjunction with the Software
|
||||
Source Code or any Modifications in Source Code or in any way that could
|
||||
subject the Software to any open source licenses.
|
||||
|
||||
5. CONTRACTORS. Under the rights granted to Customer under this Agreement,
|
||||
Customer may permit its employees, contractors, and agencies of Customer to
|
||||
become Authorized Users to exercise the rights to the Software granted to
|
||||
Customer in accordance with this Agreement solely on behalf of Customer to
|
||||
provide services to Customer; provided that Customer shall be liable for the
|
||||
acts and omissions of all Authorized Users to the extent any of such acts or
|
||||
omissions, if performed by Customer, would constitute a breach of, or otherwise
|
||||
give rise to liability to Customer under, this Agreement. Customer shall not
|
||||
and shall not permit any Authorized User to use the Software except as
|
||||
expressly permitted in this Agreement.
|
||||
|
||||
6. COMPETITIVE PRODUCT DEVELOPMENT. Customer shall not use the Software in any way
|
||||
to engage in the development of products or services which could be reasonably
|
||||
construed to provide a complete or partial functional or commercial alternative
|
||||
to Stream.io’s products or services (a “Competitive Product”). Customer shall
|
||||
ensure that there is no direct or indirect use of, or sharing of, Software
|
||||
source code, or other information based upon or derived from the Software to
|
||||
develop such products or services. Without derogating from the generality of
|
||||
the foregoing, development of Competitive Products shall include having direct
|
||||
or indirect access to, supervising, consulting or assisting in the development
|
||||
of, or producing any specifications, documentation, object code or source code
|
||||
for, all or part of a Competitive Product.
|
||||
|
||||
7. LIMITATION ON MODIFICATIONS. Notwithstanding any provision in this Agreement,
|
||||
Modifications may only be created and used by Customer as permitted by this
|
||||
Agreement and Modification Source Code may not be distributed to third parties.
|
||||
Customer will not assert against Stream.io, its affiliates, or their customers,
|
||||
direct or indirect, agents and contractors, in any way, any patent rights that
|
||||
Customer may obtain relating to any Modifications for Stream.io, its
|
||||
affiliates’, or their customers’, direct or indirect, agents’ and contractors’
|
||||
manufacture, use, import, offer for sale or sale of any Stream.io products or
|
||||
services.
|
||||
|
||||
8. DELIVERY AND ACCEPTANCE. The Software will be delivered electronically pursuant
|
||||
to Stream.io standard download procedures. The Software is deemed accepted upon
|
||||
delivery.
|
||||
|
||||
9. IMPLEMENTATION AND SUPPORT. Stream.io has no obligation under this Agreement to
|
||||
provide any support or consultation concerning the Software.
|
||||
|
||||
10. TERM AND TERMINATION. The term of this Agreement begins when the Software is
|
||||
downloaded or accessed and shall continue until terminated. Either party may
|
||||
terminate this Agreement upon written notice. This Agreement shall
|
||||
automatically terminate if Customer is or becomes a competitor of Stream.io or
|
||||
makes or sells any Competitive Products. Upon termination of this Agreement for
|
||||
any reason, (a) all rights granted to Customer in this Agreement immediately
|
||||
cease to exist, (b) Customer must promptly discontinue all use of the Software
|
||||
and return to Stream.io or destroy all copies of the Software in Customer’s
|
||||
possession or control. Any continued use of the Software by Customer or attempt
|
||||
by Customer to exercise any rights under this Agreement after this Agreement
|
||||
has terminated shall be considered copyright infringement and subject Customer
|
||||
to applicable remedies for copyright infringement. Sections 2, 5, 6, 8 and 9
|
||||
shall survive expiration or termination of this Agreement for any reason.
|
||||
|
||||
11. OWNERSHIP. As between the parties, the Software and all worldwide intellectual
|
||||
property rights and proprietary rights relating thereto or embodied therein,
|
||||
are the exclusive property of Stream.io and its suppliers. Stream.io and its
|
||||
suppliers reserve all rights in and to the Software not expressly granted to
|
||||
Customer in this Agreement, and no other licenses or rights are granted by
|
||||
implication, estoppel or otherwise.
|
||||
|
||||
12. WARRANTY DISCLAIMER. USE OF THIS SOFTWARE IS ENTIRELY AT YOURS AND CUSTOMER’S
|
||||
OWN RISK. THE SOFTWARE IS PROVIDED “AS IS” WITHOUT ANY WARRANTY OF ANY KIND
|
||||
WHATSOEVER. STREAM.IO DOES NOT MAKE, AND HEREBY DISCLAIMS, ANY WARRANTY OF ANY
|
||||
KIND, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING WITHOUT
|
||||
LIMITATION, THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, TITLE, NON-INFRINGEMENT OF THIRD-PARTY RIGHTS, RESULTS, EFFORTS,
|
||||
QUALITY OR QUIET ENJOYMENT. STREAM.IO DOES NOT WARRANT THAT THE SOFTWARE IS
|
||||
ERROR-FREE, WILL FUNCTION WITHOUT INTERRUPTION, WILL MEET ANY SPECIFIC NEED
|
||||
THAT CUSTOMER HAS, THAT ALL DEFECTS WILL BE CORRECTED OR THAT IT IS
|
||||
SUFFICIENTLY DOCUMENTED TO BE USABLE BY CUSTOMER. TO THE EXTENT THAT STREAM.IO
|
||||
MAY NOT DISCLAIM ANY WARRANTY AS A MATTER OF APPLICABLE LAW, THE SCOPE AND
|
||||
DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER SUCH LAW.
|
||||
CUSTOMER ACKNOWLEDGES THAT IT HAS RELIED ON NO WARRANTIES OTHER THAN THE
|
||||
EXPRESS WARRANTIES IN THIS AGREEMENT.
|
||||
|
||||
13. LIMITATION OF LIABILITY. TO THE FULLEST EXTENT PERMISSIBLE BY LAW, STREAM.IO’S
|
||||
TOTAL LIABILITY FOR ALL DAMAGES ARISING OUT OF OR RELATED TO THE SOFTWARE OR
|
||||
THIS AGREEMENT, WHETHER IN CONTRACT, TORT (INCLUDING NEGLIGENCE) OR OTHERWISE,
|
||||
SHALL NOT EXCEED $100. IN NO EVENT WILL STREAM.IO BE LIABLE FOR ANY INDIRECT,
|
||||
CONSEQUENTIAL, EXEMPLARY, PUNITIVE, SPECIAL OR INCIDENTAL DAMAGES OF ANY KIND
|
||||
WHATSOEVER, INCLUDING ANY LOST DATA AND LOST PROFITS, ARISING FROM OR RELATING
|
||||
TO THE SOFTWARE EVEN IF STREAM.IO HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES. CUSTOMER ACKNOWLEDGES THAT THIS PROVISION REFLECTS THE AGREED UPON
|
||||
ALLOCATION OF RISK FOR THIS AGREEMENT AND THAT STREAM.IO WOULD NOT ENTER INTO
|
||||
THIS AGREEMENT WITHOUT THESE LIMITATIONS ON ITS LIABILITY.
|
||||
|
||||
14. General. Customer may not assign or transfer this Agreement, by operation of
|
||||
law or otherwise, or any of its rights under this Agreement (including the
|
||||
license rights granted to Customer) to any third party without Stream.io’s
|
||||
prior written consent, which consent will not be unreasonably withheld or
|
||||
delayed. Stream.io may assign this Agreement, without consent, including, but
|
||||
limited to, affiliate or any successor to all or substantially all its business
|
||||
or assets to which this Agreement relates, whether by merger, sale of assets,
|
||||
sale of stock, reorganization or otherwise. Any attempted assignment or
|
||||
transfer in violation of the foregoing will be null and void. Stream.io shall
|
||||
not be liable hereunder by reason of any failure or delay in the performance of
|
||||
its obligations hereunder for any cause which is beyond the reasonable control.
|
||||
All notices, consents, and approvals under this Agreement must be delivered in
|
||||
writing by courier, by electronic mail, or by certified or registered mail,
|
||||
(postage prepaid and return receipt requested) to the other party at the
|
||||
address set forth in the customer agreement between Stream.io and Customer and
|
||||
will be effective upon receipt or when delivery is refused. This Agreement will
|
||||
be governed by and interpreted in accordance with the laws of the State of
|
||||
Colorado, without reference to its choice of laws rules. The United Nations
|
||||
Convention on Contracts for the International Sale of Goods does not apply to
|
||||
this Agreement. Any action or proceeding arising from or relating to this
|
||||
Agreement shall be brought in a federal or state court in Denver, Colorado, and
|
||||
each party irrevocably submits to the jurisdiction and venue of any such court
|
||||
in any such action or proceeding. All waivers must be in writing. Any waiver or
|
||||
failure to enforce any provision of this Agreement on one occasion will not be
|
||||
deemed a waiver of any other provision or of such provision on any other
|
||||
occasion. If any provision of this Agreement is unenforceable, such provision
|
||||
will be changed and interpreted to accomplish the objectives of such provision
|
||||
to the greatest extent possible under applicable law and the remaining
|
||||
provisions will continue in full force and effect. Customer shall not violate
|
||||
any applicable law, rule or regulation, including those regarding the export of
|
||||
technical data. The headings of Sections of this Agreement are for convenience
|
||||
and are not to be used in interpreting this Agreement. As used in this
|
||||
Agreement, the word “including” means “including but not limited to.” This
|
||||
Agreement (including all exhibits and attachments) constitutes the entire
|
||||
agreement between the parties regarding the subject hereof and supersedes all
|
||||
prior or contemporaneous agreements, understandings and communication, whether
|
||||
written or oral. This Agreement may be amended only by a written document
|
||||
signed by both parties. The terms of any purchase order or similar document
|
||||
submitted by Customer to Stream.io will have no effect.
|
||||
@@ -0,0 +1,114 @@
|
||||
# Stream Chat Dart
|
||||
[](https://pub.dartlang.org/packages/stream_chat)
|
||||

|
||||

|
||||
[](https://codecov.io/gh/GetStream/stream-chat-dart)
|
||||
|
||||
stream-chat-dart is the official Dart client for Stream Chat, a service for building chat applications. This library can be used on any Dart project and on both mobile and web apps with Flutter.
|
||||
|
||||
You can sign up for a Stream account at https://getstream.io/chat/
|
||||
|
||||
## Getting started
|
||||
|
||||
### Add dependency
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
stream_chat: ^1.0.0-beta
|
||||
```
|
||||
|
||||
You should then run `flutter packages get`
|
||||
|
||||
## Example Project
|
||||
|
||||
There is a detailed Flutter example project in the `example` folder. You can directly run and play on it.
|
||||
|
||||
## Setup API Client
|
||||
|
||||
First you need to instantiate a chat client. The Chat client will manage API call, event handling and manage the websocket connection to Stream Chat servers. You should only create the client once and re-use it across your application.
|
||||
|
||||
```dart
|
||||
final client = StreamChatClient("stream-chat-api-key");
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
By default the Chat Client will write all messages with level Warn or Error to stdout.
|
||||
|
||||
#### Change Logging Level
|
||||
|
||||
During development you might want to enable more logging information, you can change the default log level when constructing the client.
|
||||
|
||||
```dart
|
||||
final client = StreamChatClient("stream-chat-api-key", logLevel: Level.INFO);
|
||||
```
|
||||
|
||||
#### Custom Logger
|
||||
|
||||
You can handle the log messages directly instead of have them written to stdout, this is very convenient if you use an error tracking tool or if you want to centralize your logs into one facility.
|
||||
|
||||
```dart
|
||||
myLogHandlerFunction = (LogRecord record) {
|
||||
// do something with the record (ie. send it to Sentry or Fabric)
|
||||
}
|
||||
|
||||
final client = StreamChatClient("stream-chat-api-key", logHandlerFunction: myLogHandlerFunction);
|
||||
```
|
||||
|
||||
### Offline storage
|
||||
|
||||
To add data persistance you can extend the class `ChatPersistenceClient` and pass an instance to the `StreamChatClient`.
|
||||
|
||||
```dart
|
||||
class CustomChatPersistentClient extends ChatPersistenceClient {
|
||||
...
|
||||
}
|
||||
|
||||
final client = StreamChatClient(
|
||||
apiKey ?? kDefaultStreamApiKey,
|
||||
logLevel: Level.INFO,
|
||||
)..chatPersistenceClient = CustomChatPersistentClient();
|
||||
```
|
||||
|
||||
We provide an official persistent client in the (stream_chat_persistence)[https://pub.dev/packages/stream_chat_persistence] package.
|
||||
|
||||
```dart
|
||||
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
|
||||
|
||||
final chatPersistentClient = StreamChatPersistenceClient(
|
||||
logLevel: Level.INFO,
|
||||
connectionMode: ConnectionMode.background,
|
||||
);
|
||||
|
||||
final client = StreamChatClient(
|
||||
apiKey ?? kDefaultStreamApiKey,
|
||||
logLevel: Level.INFO,
|
||||
)..chatPersistenceClient = chatPersistentClient;
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
### Code conventions
|
||||
|
||||
- Make sure that you run `dartfmt` before commiting your code
|
||||
- Make sure all public methods and functions are well documented
|
||||
|
||||
### Running tests
|
||||
|
||||
- run `flutter test`
|
||||
|
||||
### Releasing a new version
|
||||
|
||||
- update the package version on `pubspec.yaml` and `version.dart`
|
||||
|
||||
- add a changelog entry on `CHANGELOG.md`
|
||||
|
||||
- run `flutter pub publish` to publish the package
|
||||
|
||||
### Watch models and generate JSON code
|
||||
|
||||
JSON serialization relies on code generation; make sure to keep that running while you make changes to the library
|
||||
|
||||
```bash
|
||||
flutter pub run build_runner watch
|
||||
```
|
||||
@@ -0,0 +1,62 @@
|
||||
include: package:pedantic/analysis_options.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/**/*.g.dart
|
||||
- example/*
|
||||
- test/*
|
||||
|
||||
linter:
|
||||
rules:
|
||||
# these rules are documented on and in the same order as
|
||||
# the Dart Lint rules page to make maintenance easier
|
||||
# https://github.com/dart-lang/linter/blob/master/example/all.yaml
|
||||
# - always_declare_return_types
|
||||
# - always_specify_types
|
||||
# - annotate_overrides
|
||||
# - avoid_as
|
||||
- avoid_empty_else
|
||||
- avoid_init_to_null
|
||||
- avoid_return_types_on_setters
|
||||
- avoid_web_libraries_in_flutter
|
||||
- await_only_futures
|
||||
- camel_case_types
|
||||
- cancel_subscriptions
|
||||
- close_sinks
|
||||
# - comment_references # we do not presume as to what people want to reference in their dartdocs
|
||||
# - constant_identifier_names # https://github.com/dart-lang/linter/issues/204
|
||||
- control_flow_in_finally
|
||||
- empty_constructor_bodies
|
||||
- empty_statements
|
||||
- hash_and_equals
|
||||
- implementation_imports
|
||||
# - invariant_booleans
|
||||
# - iterable_contains_unrelated_type
|
||||
- library_names
|
||||
# - library_prefixes
|
||||
# - list_remove_unrelated_type
|
||||
# - literal_only_boolean_expressions
|
||||
- non_constant_identifier_names
|
||||
# - one_member_abstracts
|
||||
# - only_throw_errors
|
||||
# - overridden_fields
|
||||
- package_api_docs
|
||||
- package_names
|
||||
- package_prefixed_library_names
|
||||
- prefer_is_not_empty
|
||||
# - prefer_mixin # https://github.com/dart-lang/language/issues/32
|
||||
- public_member_api_docs
|
||||
- slash_for_doc_comments
|
||||
# - sort_constructors_first
|
||||
# - sort_unnamed_constructors_first
|
||||
# - super_goes_last # no longer needed w/ Dart 2
|
||||
- test_types_in_equals
|
||||
- throw_in_finally
|
||||
# - type_annotate_public_apis # subset of always_specify_types
|
||||
- type_init_formals
|
||||
# - unawaited_futures
|
||||
- unnecessary_brace_in_string_interps
|
||||
- unnecessary_getters_setters
|
||||
- unnecessary_statements
|
||||
- unrelated_type_equality_checks
|
||||
- valid_regexps
|
||||
@@ -0,0 +1,8 @@
|
||||
targets:
|
||||
$default:
|
||||
builders:
|
||||
json_serializable:
|
||||
options:
|
||||
explicit_to_json: true
|
||||
field_rename: snake
|
||||
any_map: true
|
||||
@@ -0,0 +1,41 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Web related
|
||||
lib/generated_plugin_registrant.dart
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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.
|
||||
|
||||
version:
|
||||
revision: 78910062997c3a836feee883712c241a5fd22983
|
||||
channel: stable
|
||||
|
||||
project_type: app
|
||||
@@ -0,0 +1,2 @@
|
||||
# Stream Chat Dart Example
|
||||
Please see `lib/` for example code.
|
||||
@@ -0,0 +1,11 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||
key.properties
|
||||
@@ -0,0 +1,63 @@
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.example.example"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.example">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,47 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.example">
|
||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||
In most cases you can leave this as-is, but you if you want to provide
|
||||
additional functionality it is fine to subclass or reimplement
|
||||
FlutterApplication and put your custom class here. -->
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:label="example"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<!-- Displays an Android View that continues showing the launch screen
|
||||
Drawable until Flutter paints its first frame, then this splash
|
||||
screen fades out. A splash screen is useful to avoid any visual
|
||||
gap between the end of Android's launch screen and the painting of
|
||||
Flutter's first frame. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||
android:resource="@drawable/launch_background"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.example.example
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@android:color/white</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.example">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,31 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.50'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.enableR8=true
|
||||
@@ -0,0 +1,6 @@
|
||||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
||||
@@ -0,0 +1,11 @@
|
||||
include ':app'
|
||||
|
||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||
def properties = new Properties()
|
||||
|
||||
assert localPropertiesFile.exists()
|
||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||
@@ -0,0 +1,32 @@
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>8.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,2 @@
|
||||
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,2 @@
|
||||
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.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,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>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.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">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</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 = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.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,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>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,13 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 564 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "[email protected]",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
@@ -0,0 +1,5 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>example</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@@ -0,0 +1,248 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
/// Create a new instance of [StreamChatClient] passing the apikey obtained from your
|
||||
/// project dashboard.
|
||||
final client = StreamChatClient('b67pax5b2wdq');
|
||||
|
||||
/// Set the current user. In a production scenario, this should be done using
|
||||
/// a backend to generate a user token using our server SDK.
|
||||
/// Please see the following for more information:
|
||||
/// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
|
||||
await client.setUser(
|
||||
User(
|
||||
id: 'cool-shadow-7',
|
||||
extraData: {
|
||||
'image':
|
||||
'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow',
|
||||
},
|
||||
),
|
||||
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo',
|
||||
);
|
||||
|
||||
/// Creates a channel using the type `messaging` and `godevs`.
|
||||
/// Channels are containers for holding messages between different members. To
|
||||
/// learn more about channels and some of our predefined types, checkout our
|
||||
/// our channel docs: https://getstream.io/chat/docs/initialize_channel/?language=dart
|
||||
final channel = client.channel('messaging', id: 'godevs');
|
||||
|
||||
/// `.watch()` is used to create and listen to the channel for updates. If the
|
||||
/// channel already exists, it will simply listen for new events.
|
||||
await channel.watch();
|
||||
|
||||
runApp(
|
||||
StreamExample(
|
||||
client: client,
|
||||
channel: channel,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Example using Stream's Low Level Dart client.
|
||||
class StreamExample extends StatelessWidget {
|
||||
/// To initialize this example, an instance of [client] and [channel] is required.
|
||||
const StreamExample({
|
||||
Key key,
|
||||
@required this.client,
|
||||
@required this.channel,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Instance of [StreamChatClient] we created earlier. This contains information about
|
||||
/// our application and connection state.
|
||||
final StreamChatClient client;
|
||||
|
||||
/// The channel we'd like to observe and participate.
|
||||
final Channel channel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Stream Chat Dart Example',
|
||||
home: HomeScreen(channel: channel),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Main screen of our application. The layout is comprised of an [AppBar]
|
||||
/// containing the channel name and a [MessageView] displaying recent messages.
|
||||
class HomeScreen extends StatelessWidget {
|
||||
/// [HomeScreen] is constructed using the [Channel] we defined earlier.
|
||||
const HomeScreen({Key key, @required this.channel}) : super(key: key);
|
||||
|
||||
/// Channel object containing the [Channel.id] we'd like to observe.
|
||||
final Channel channel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final messages = channel.state.channelStateStream;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Channel: ${channel.id}'),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: StreamBuilder<ChannelState>(
|
||||
stream: messages,
|
||||
builder: (
|
||||
BuildContext context,
|
||||
AsyncSnapshot<ChannelState> snapshot,
|
||||
) {
|
||||
if (snapshot.hasData && snapshot.data != null) {
|
||||
return MessageView(
|
||||
messages: snapshot.data.messages.reversed.toList(),
|
||||
channel: channel,
|
||||
);
|
||||
} else if (snapshot.hasError) {
|
||||
return const Center(
|
||||
child: Text(
|
||||
'There was an error loading messages. Please see logs.',
|
||||
),
|
||||
);
|
||||
}
|
||||
return const Center(
|
||||
child: SizedBox(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// UI used to display a list of recent messages and a [TextField] for sending
|
||||
/// new messages.
|
||||
class MessageView extends StatefulWidget {
|
||||
/// Message takes the latest list of messages and the current channel.
|
||||
const MessageView({
|
||||
Key key,
|
||||
@required this.messages,
|
||||
@required this.channel,
|
||||
}) : super(key: key);
|
||||
|
||||
/// List of messages sent in the given channel.
|
||||
final List<Message> messages;
|
||||
|
||||
/// Current channel being observed.
|
||||
final Channel channel;
|
||||
|
||||
@override
|
||||
_MessageViewState createState() => _MessageViewState();
|
||||
}
|
||||
|
||||
class _MessageViewState extends State<MessageView> {
|
||||
TextEditingController _controller;
|
||||
ScrollController _scrollController;
|
||||
|
||||
List<Message> get _messages => widget.messages;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = TextEditingController();
|
||||
_scrollController = ScrollController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Convenience method for scrolling the list view when a new message is sent.
|
||||
void _updateList() {
|
||||
_scrollController.animateTo(
|
||||
0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
itemCount: _messages.length,
|
||||
reverse: true,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = _messages[index];
|
||||
if (item.user.id == widget.channel.client.uid) {
|
||||
return Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(item.text),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(item.text),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter your message',
|
||||
),
|
||||
),
|
||||
),
|
||||
Material(
|
||||
type: MaterialType.circle,
|
||||
color: Colors.blue,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
// We can send a new message by calling `sendMessage` on
|
||||
// the current channel. After sending a message, the
|
||||
// TextField is cleared and the list view is scrolled
|
||||
// to show the new item.
|
||||
if (_controller.value.text.isNotEmpty) {
|
||||
await widget.channel.sendMessage(
|
||||
Message(text: _controller.value.text),
|
||||
);
|
||||
_controller.clear();
|
||||
_updateList();
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.send,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper extension for quickly retrieving the current user id from a [StreamChatClient].
|
||||
extension on StreamChatClient {
|
||||
String get uid => state.user.id;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
name: example
|
||||
description: A new Flutter project.
|
||||
|
||||
publish_to: 'none'
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: ">=2.7.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cupertino_icons: ^1.0.0
|
||||
stream_chat:
|
||||
path: ../
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
@@ -0,0 +1,11 @@
|
||||
/// Used to notify the WS connection status
|
||||
enum ConnectionStatus {
|
||||
/// WS is connected and everything is good
|
||||
connected,
|
||||
|
||||
/// WS is connecting (usually reconnecting)
|
||||
connecting,
|
||||
|
||||
/// WS is disconnected and it's not reconnecting
|
||||
disconnected,
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'requests.g.dart';
|
||||
|
||||
/// Sorting options
|
||||
@JsonSerializable(createFactory: false)
|
||||
class SortOption {
|
||||
/// Ascending order
|
||||
static const ASC = 1;
|
||||
|
||||
/// Descending order
|
||||
static const DESC = -1;
|
||||
|
||||
/// A sorting field name
|
||||
final String field;
|
||||
|
||||
/// A sorting direction
|
||||
final int direction;
|
||||
|
||||
/// Creates a new SortOption instance
|
||||
///
|
||||
/// For example:
|
||||
/// ```dart
|
||||
/// // Sort channels by the last message date:
|
||||
/// final sorting = SortOption("last_message_at")
|
||||
/// ```
|
||||
const SortOption(this.field, {this.direction = DESC});
|
||||
|
||||
/// Serialize model to json
|
||||
Map<String, dynamic> toJson() => _$SortOptionToJson(this);
|
||||
}
|
||||
|
||||
/// Pagination options.
|
||||
@JsonSerializable(createFactory: false, includeIfNull: false)
|
||||
class PaginationParams {
|
||||
/// The amount of items requested from the APIs.
|
||||
final int limit;
|
||||
|
||||
/// The offset of requesting items.
|
||||
final int offset;
|
||||
|
||||
/// Filter on ids greater than the given value.
|
||||
@JsonKey(name: 'id_gt')
|
||||
final String greaterThan;
|
||||
|
||||
/// Filter on ids greater than or equal to the given value.
|
||||
@JsonKey(name: 'id_gte')
|
||||
final String greaterThanOrEqual;
|
||||
|
||||
/// Filter on ids smaller than the given value.
|
||||
@JsonKey(name: 'id_lt')
|
||||
final String lessThan;
|
||||
|
||||
/// Filter on ids smaller than or equal to the given value.
|
||||
@JsonKey(name: 'id_lte')
|
||||
final String lessThanOrEqual;
|
||||
|
||||
/// Creates a new PaginationParams instance
|
||||
///
|
||||
/// For example:
|
||||
/// ```dart
|
||||
/// // limit to 50
|
||||
/// final paginationParams = PaginationParams(limit: 50);
|
||||
///
|
||||
/// // limit to 50 with offset
|
||||
/// final paginationParams = PaginationParams(limit: 50, offset: 50);
|
||||
/// ```
|
||||
const PaginationParams({
|
||||
this.limit = 10,
|
||||
this.offset,
|
||||
this.greaterThan,
|
||||
this.greaterThanOrEqual,
|
||||
this.lessThan,
|
||||
this.lessThanOrEqual,
|
||||
});
|
||||
|
||||
/// Serialize model to json
|
||||
Map<String, dynamic> toJson() => _$PaginationParamsToJson(this);
|
||||
|
||||
/// Creates a copy of [PaginationParams] with specified attributes overridden.
|
||||
PaginationParams copyWith({
|
||||
int limit,
|
||||
int offset,
|
||||
String greaterThan,
|
||||
String greaterThanOrEqual,
|
||||
String lessThan,
|
||||
String lessThanOrEqual,
|
||||
}) =>
|
||||
PaginationParams(
|
||||
limit: limit ?? this.limit,
|
||||
offset: offset ?? this.offset,
|
||||
greaterThan: greaterThan ?? this.greaterThan,
|
||||
greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual,
|
||||
lessThan: lessThan ?? this.lessThan,
|
||||
lessThanOrEqual: lessThanOrEqual ?? this.lessThanOrEqual,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'requests.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Map<String, dynamic> _$SortOptionToJson(SortOption instance) =>
|
||||
<String, dynamic>{
|
||||
'field': instance.field,
|
||||
'direction': instance.direction,
|
||||
};
|
||||
|
||||
Map<String, dynamic> _$PaginationParamsToJson(PaginationParams instance) {
|
||||
final val = <String, dynamic>{};
|
||||
|
||||
void writeNotNull(String key, dynamic value) {
|
||||
if (value != null) {
|
||||
val[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
writeNotNull('limit', instance.limit);
|
||||
writeNotNull('offset', instance.offset);
|
||||
writeNotNull('id_gt', instance.greaterThan);
|
||||
writeNotNull('id_gte', instance.greaterThanOrEqual);
|
||||
writeNotNull('id_lt', instance.lessThan);
|
||||
writeNotNull('id_lte', instance.lessThanOrEqual);
|
||||
return val;
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:stream_chat/src/client.dart';
|
||||
import 'package:stream_chat/src/models/device.dart';
|
||||
import 'package:stream_chat/src/models/event.dart';
|
||||
|
||||
import '../models/channel_model.dart';
|
||||
import '../models/channel_state.dart';
|
||||
import '../models/member.dart';
|
||||
import '../models/message.dart';
|
||||
import '../models/reaction.dart';
|
||||
import '../models/read.dart';
|
||||
import '../models/user.dart';
|
||||
|
||||
part 'responses.g.dart';
|
||||
|
||||
class _BaseResponse {
|
||||
String duration;
|
||||
}
|
||||
|
||||
/// Model response for [StreamChatClient.resync] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class SyncResponse extends _BaseResponse {
|
||||
/// The list of events
|
||||
List<Event> events;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static SyncResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$SyncResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [StreamChatClient.queryChannels] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class QueryChannelsResponse extends _BaseResponse {
|
||||
/// List of channels state returned by the query
|
||||
List<ChannelState> channels;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static QueryChannelsResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$QueryChannelsResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [StreamChatClient.queryChannels] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class TranslateMessageResponse extends _BaseResponse {
|
||||
/// List of channels state returned by the query
|
||||
TranslatedMessage message;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static TranslateMessageResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$TranslateMessageResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [StreamChatClient.queryChannels] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class QueryMembersResponse extends _BaseResponse {
|
||||
/// List of channels state returned by the query
|
||||
List<Member> members;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static QueryMembersResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$QueryMembersResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [StreamChatClient.queryUsers] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class QueryUsersResponse extends _BaseResponse {
|
||||
/// List of users returned by the query
|
||||
List<User> users;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static QueryUsersResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$QueryUsersResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [channel.getReactions] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class QueryReactionsResponse extends _BaseResponse {
|
||||
/// List of reactions returned by the query
|
||||
List<Reaction> reactions;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static QueryReactionsResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$QueryReactionsResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.getReplies] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class QueryRepliesResponse extends _BaseResponse {
|
||||
/// List of messages returned by the api call
|
||||
List<Message> messages;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static QueryRepliesResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$QueryRepliesResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [StreamChatClient.getDevices] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class ListDevicesResponse extends _BaseResponse {
|
||||
/// List of user devices
|
||||
List<Device> devices;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static ListDevicesResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$ListDevicesResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.sendFile] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class SendFileResponse extends _BaseResponse {
|
||||
/// The url of the uploaded file
|
||||
String file;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static SendFileResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$SendFileResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.sendImage] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class SendImageResponse extends _BaseResponse {
|
||||
/// The url of the uploaded file
|
||||
String file;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static SendImageResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$SendImageResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.sendReaction] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class SendReactionResponse extends _BaseResponse {
|
||||
/// Message returned by the api call
|
||||
Message message;
|
||||
|
||||
/// The reaction created by the api call
|
||||
Reaction reaction;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static SendReactionResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$SendReactionResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [StreamChatClient.setGuestUser] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class SetGuestUserResponse extends _BaseResponse {
|
||||
/// Guest user access token
|
||||
String accessToken;
|
||||
|
||||
/// Guest user
|
||||
User user;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static SetGuestUserResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$SetGuestUserResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [StreamChatClient.updateUser] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class UpdateUsersResponse extends _BaseResponse {
|
||||
/// Updated users
|
||||
Map<String, User> users;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static UpdateUsersResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$UpdateUsersResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [StreamChatClient.updateMessage] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class UpdateMessageResponse extends _BaseResponse {
|
||||
/// Message returned by the api call
|
||||
Message message;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static UpdateMessageResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$UpdateMessageResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.sendMessage] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class SendMessageResponse extends _BaseResponse {
|
||||
/// Message returned by the api call
|
||||
Message message;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static SendMessageResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$SendMessageResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [StreamChatClient.getMessage] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class GetMessageResponse extends _BaseResponse {
|
||||
/// Message returned by the api call
|
||||
Message message;
|
||||
|
||||
/// Channel of the message
|
||||
ChannelModel channel;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static GetMessageResponse fromJson(Map<String, dynamic> json) {
|
||||
final res = _$GetMessageResponseFromJson(json);
|
||||
final jsonChannel = res.message?.extraData?.remove('channel');
|
||||
if (jsonChannel != null) {
|
||||
res.channel = ChannelModel.fromJson(jsonChannel);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/// Model response for [StreamChatClient.search] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class SearchMessagesResponse extends _BaseResponse {
|
||||
/// List of messages returned by the api call
|
||||
List<GetMessageResponse> results;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static SearchMessagesResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$SearchMessagesResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.getMessagesById] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class GetMessagesByIdResponse extends _BaseResponse {
|
||||
/// Message returned by the api call
|
||||
List<Message> messages;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static GetMessagesByIdResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$GetMessagesByIdResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.update] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class UpdateChannelResponse extends _BaseResponse {
|
||||
/// Updated channel
|
||||
ChannelModel channel;
|
||||
|
||||
/// Channel members
|
||||
List<Member> members;
|
||||
|
||||
/// Message returned by the api call
|
||||
Message message;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static UpdateChannelResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$UpdateChannelResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.inviteMembers] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class InviteMembersResponse extends _BaseResponse {
|
||||
/// Updated channel
|
||||
ChannelModel channel;
|
||||
|
||||
/// Channel members
|
||||
List<Member> members;
|
||||
|
||||
/// Message returned by the api call
|
||||
Message message;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static InviteMembersResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$InviteMembersResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.removeMembers] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class RemoveMembersResponse extends _BaseResponse {
|
||||
/// Updated channel
|
||||
ChannelModel channel;
|
||||
|
||||
/// Channel members
|
||||
List<Member> members;
|
||||
|
||||
/// Message returned by the api call
|
||||
Message message;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static RemoveMembersResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$RemoveMembersResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.sendAction] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class SendActionResponse extends _BaseResponse {
|
||||
/// Message returned by the api call
|
||||
Message message;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static SendActionResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$SendActionResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.addMembers] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class AddMembersResponse extends _BaseResponse {
|
||||
/// Updated channel
|
||||
ChannelModel channel;
|
||||
|
||||
/// Channel members
|
||||
List<Member> members;
|
||||
|
||||
/// Message returned by the api call
|
||||
Message message;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static AddMembersResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$AddMembersResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.acceptInvite] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class AcceptInviteResponse extends _BaseResponse {
|
||||
/// Updated channel
|
||||
ChannelModel channel;
|
||||
|
||||
/// Channel members
|
||||
List<Member> members;
|
||||
|
||||
/// Message returned by the api call
|
||||
Message message;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static AcceptInviteResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$AcceptInviteResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.rejectInvite] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class RejectInviteResponse extends _BaseResponse {
|
||||
/// Updated channel
|
||||
ChannelModel channel;
|
||||
|
||||
/// Channel members
|
||||
List<Member> members;
|
||||
|
||||
/// Message returned by the api call
|
||||
Message message;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static RejectInviteResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$RejectInviteResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for empty responses
|
||||
@JsonSerializable(createToJson: false)
|
||||
class EmptyResponse extends _BaseResponse {
|
||||
/// Create a new instance from a json
|
||||
static EmptyResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$EmptyResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Model response for [Channel.query] api call
|
||||
@JsonSerializable(createToJson: false)
|
||||
class ChannelStateResponse extends _BaseResponse {
|
||||
/// Updated channel
|
||||
ChannelModel channel;
|
||||
|
||||
/// List of messages returned by the api call
|
||||
List<Message> messages;
|
||||
|
||||
/// Channel members
|
||||
List<Member> members;
|
||||
|
||||
/// Number of users watching the channel
|
||||
int watcherCount;
|
||||
|
||||
/// List of read states
|
||||
List<Read> read;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static ChannelStateResponse fromJson(Map<String, dynamic> json) =>
|
||||
_$ChannelStateResponseFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'responses.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
SyncResponse _$SyncResponseFromJson(Map json) {
|
||||
return SyncResponse()
|
||||
..duration = json['duration'] as String
|
||||
..events = (json['events'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Event.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList();
|
||||
}
|
||||
|
||||
QueryChannelsResponse _$QueryChannelsResponseFromJson(Map json) {
|
||||
return QueryChannelsResponse()
|
||||
..duration = json['duration'] as String
|
||||
..channels = (json['channels'] as List)
|
||||
?.map((e) => e == null ? null : ChannelState.fromJson(e as Map))
|
||||
?.toList();
|
||||
}
|
||||
|
||||
TranslateMessageResponse _$TranslateMessageResponseFromJson(Map json) {
|
||||
return TranslateMessageResponse()
|
||||
..duration = json['duration'] as String
|
||||
..message = json['message'] == null
|
||||
? null
|
||||
: TranslatedMessage.fromJson((json['message'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
));
|
||||
}
|
||||
|
||||
QueryMembersResponse _$QueryMembersResponseFromJson(Map json) {
|
||||
return QueryMembersResponse()
|
||||
..duration = json['duration'] as String
|
||||
..members = (json['members'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Member.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList();
|
||||
}
|
||||
|
||||
QueryUsersResponse _$QueryUsersResponseFromJson(Map json) {
|
||||
return QueryUsersResponse()
|
||||
..duration = json['duration'] as String
|
||||
..users = (json['users'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: User.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList();
|
||||
}
|
||||
|
||||
QueryReactionsResponse _$QueryReactionsResponseFromJson(Map json) {
|
||||
return QueryReactionsResponse()
|
||||
..duration = json['duration'] as String
|
||||
..reactions = (json['reactions'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Reaction.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList();
|
||||
}
|
||||
|
||||
QueryRepliesResponse _$QueryRepliesResponseFromJson(Map json) {
|
||||
return QueryRepliesResponse()
|
||||
..duration = json['duration'] as String
|
||||
..messages = (json['messages'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Message.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList();
|
||||
}
|
||||
|
||||
ListDevicesResponse _$ListDevicesResponseFromJson(Map json) {
|
||||
return ListDevicesResponse()
|
||||
..duration = json['duration'] as String
|
||||
..devices = (json['devices'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Device.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList();
|
||||
}
|
||||
|
||||
SendFileResponse _$SendFileResponseFromJson(Map json) {
|
||||
return SendFileResponse()
|
||||
..duration = json['duration'] as String
|
||||
..file = json['file'] as String;
|
||||
}
|
||||
|
||||
SendImageResponse _$SendImageResponseFromJson(Map json) {
|
||||
return SendImageResponse()
|
||||
..duration = json['duration'] as String
|
||||
..file = json['file'] as String;
|
||||
}
|
||||
|
||||
SendReactionResponse _$SendReactionResponseFromJson(Map json) {
|
||||
return SendReactionResponse()
|
||||
..duration = json['duration'] as String
|
||||
..message = json['message'] == null
|
||||
? null
|
||||
: Message.fromJson((json['message'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
))
|
||||
..reaction = json['reaction'] == null
|
||||
? null
|
||||
: Reaction.fromJson((json['reaction'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
));
|
||||
}
|
||||
|
||||
SetGuestUserResponse _$SetGuestUserResponseFromJson(Map json) {
|
||||
return SetGuestUserResponse()
|
||||
..duration = json['duration'] as String
|
||||
..accessToken = json['access_token'] as String
|
||||
..user = json['user'] == null
|
||||
? null
|
||||
: User.fromJson((json['user'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
));
|
||||
}
|
||||
|
||||
UpdateUsersResponse _$UpdateUsersResponseFromJson(Map json) {
|
||||
return UpdateUsersResponse()
|
||||
..duration = json['duration'] as String
|
||||
..users = (json['users'] as Map)?.map(
|
||||
(k, e) => MapEntry(
|
||||
k as String,
|
||||
e == null
|
||||
? null
|
||||
: User.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
UpdateMessageResponse _$UpdateMessageResponseFromJson(Map json) {
|
||||
return UpdateMessageResponse()
|
||||
..duration = json['duration'] as String
|
||||
..message = json['message'] == null
|
||||
? null
|
||||
: Message.fromJson((json['message'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
));
|
||||
}
|
||||
|
||||
SendMessageResponse _$SendMessageResponseFromJson(Map json) {
|
||||
return SendMessageResponse()
|
||||
..duration = json['duration'] as String
|
||||
..message = json['message'] == null
|
||||
? null
|
||||
: Message.fromJson((json['message'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
));
|
||||
}
|
||||
|
||||
GetMessageResponse _$GetMessageResponseFromJson(Map json) {
|
||||
return GetMessageResponse()
|
||||
..duration = json['duration'] as String
|
||||
..message = json['message'] == null
|
||||
? null
|
||||
: Message.fromJson((json['message'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
))
|
||||
..channel = json['channel'] == null
|
||||
? null
|
||||
: ChannelModel.fromJson((json['channel'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
));
|
||||
}
|
||||
|
||||
SearchMessagesResponse _$SearchMessagesResponseFromJson(Map json) {
|
||||
return SearchMessagesResponse()
|
||||
..duration = json['duration'] as String
|
||||
..results = (json['results'] as List)
|
||||
?.map((e) => e == null ? null : GetMessageResponse.fromJson(e as Map))
|
||||
?.toList();
|
||||
}
|
||||
|
||||
GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson(Map json) {
|
||||
return GetMessagesByIdResponse()
|
||||
..duration = json['duration'] as String
|
||||
..messages = (json['messages'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Message.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList();
|
||||
}
|
||||
|
||||
UpdateChannelResponse _$UpdateChannelResponseFromJson(Map json) {
|
||||
return UpdateChannelResponse()
|
||||
..duration = json['duration'] as String
|
||||
..channel = json['channel'] == null
|
||||
? null
|
||||
: ChannelModel.fromJson((json['channel'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
))
|
||||
..members = (json['members'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Member.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList()
|
||||
..message = json['message'] == null
|
||||
? null
|
||||
: Message.fromJson((json['message'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
));
|
||||
}
|
||||
|
||||
InviteMembersResponse _$InviteMembersResponseFromJson(Map json) {
|
||||
return InviteMembersResponse()
|
||||
..duration = json['duration'] as String
|
||||
..channel = json['channel'] == null
|
||||
? null
|
||||
: ChannelModel.fromJson((json['channel'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
))
|
||||
..members = (json['members'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Member.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList()
|
||||
..message = json['message'] == null
|
||||
? null
|
||||
: Message.fromJson((json['message'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
));
|
||||
}
|
||||
|
||||
RemoveMembersResponse _$RemoveMembersResponseFromJson(Map json) {
|
||||
return RemoveMembersResponse()
|
||||
..duration = json['duration'] as String
|
||||
..channel = json['channel'] == null
|
||||
? null
|
||||
: ChannelModel.fromJson((json['channel'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
))
|
||||
..members = (json['members'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Member.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList()
|
||||
..message = json['message'] == null
|
||||
? null
|
||||
: Message.fromJson((json['message'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
));
|
||||
}
|
||||
|
||||
SendActionResponse _$SendActionResponseFromJson(Map json) {
|
||||
return SendActionResponse()
|
||||
..duration = json['duration'] as String
|
||||
..message = json['message'] == null
|
||||
? null
|
||||
: Message.fromJson((json['message'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
));
|
||||
}
|
||||
|
||||
AddMembersResponse _$AddMembersResponseFromJson(Map json) {
|
||||
return AddMembersResponse()
|
||||
..duration = json['duration'] as String
|
||||
..channel = json['channel'] == null
|
||||
? null
|
||||
: ChannelModel.fromJson((json['channel'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
))
|
||||
..members = (json['members'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Member.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList()
|
||||
..message = json['message'] == null
|
||||
? null
|
||||
: Message.fromJson((json['message'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
));
|
||||
}
|
||||
|
||||
AcceptInviteResponse _$AcceptInviteResponseFromJson(Map json) {
|
||||
return AcceptInviteResponse()
|
||||
..duration = json['duration'] as String
|
||||
..channel = json['channel'] == null
|
||||
? null
|
||||
: ChannelModel.fromJson((json['channel'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
))
|
||||
..members = (json['members'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Member.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList()
|
||||
..message = json['message'] == null
|
||||
? null
|
||||
: Message.fromJson((json['message'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
));
|
||||
}
|
||||
|
||||
RejectInviteResponse _$RejectInviteResponseFromJson(Map json) {
|
||||
return RejectInviteResponse()
|
||||
..duration = json['duration'] as String
|
||||
..channel = json['channel'] == null
|
||||
? null
|
||||
: ChannelModel.fromJson((json['channel'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
))
|
||||
..members = (json['members'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Member.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList()
|
||||
..message = json['message'] == null
|
||||
? null
|
||||
: Message.fromJson((json['message'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
));
|
||||
}
|
||||
|
||||
EmptyResponse _$EmptyResponseFromJson(Map json) {
|
||||
return EmptyResponse()..duration = json['duration'] as String;
|
||||
}
|
||||
|
||||
ChannelStateResponse _$ChannelStateResponseFromJson(Map json) {
|
||||
return ChannelStateResponse()
|
||||
..duration = json['duration'] as String
|
||||
..channel = json['channel'] == null
|
||||
? null
|
||||
: ChannelModel.fromJson((json['channel'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
))
|
||||
..messages = (json['messages'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Message.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList()
|
||||
..members = (json['members'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Member.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList()
|
||||
..watcherCount = json['watcher_count'] as int
|
||||
..read = (json['read'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Read.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:stream_chat/src/client.dart';
|
||||
import 'package:stream_chat/src/exceptions.dart';
|
||||
|
||||
/// The retry options
|
||||
class RetryPolicy {
|
||||
/// Instantiate a new RetryPolicy
|
||||
RetryPolicy({
|
||||
@required this.shouldRetry,
|
||||
@required this.retryTimeout,
|
||||
this.attempt,
|
||||
});
|
||||
|
||||
/// The number of attempts tried so far
|
||||
int attempt = 0;
|
||||
|
||||
/// This function evaluates if we should retry the failure
|
||||
final bool Function(StreamChatClient client, int attempt, ApiError apiError)
|
||||
shouldRetry;
|
||||
|
||||
/// In the case that we want to retry a failed request the retryTimeout method is called to determine the timeout
|
||||
final Duration Function(
|
||||
StreamChatClient client, int attempt, ApiError apiError) retryTimeout;
|
||||
|
||||
/// Creates a copy of [RetryPolicy] with specified attributes overridden.
|
||||
RetryPolicy copyWith({
|
||||
bool Function(StreamChatClient client, int attempt, ApiError apiError)
|
||||
shouldRetry,
|
||||
Duration Function(StreamChatClient client, int attempt, ApiError apiError)
|
||||
retryTimeout,
|
||||
int attempt,
|
||||
}) =>
|
||||
RetryPolicy(
|
||||
retryTimeout: retryTimeout ?? this.retryTimeout,
|
||||
shouldRetry: shouldRetry ?? this.shouldRetry,
|
||||
attempt: attempt ?? this.attempt,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:stream_chat/src/api/channel.dart';
|
||||
import 'package:stream_chat/src/api/retry_policy.dart';
|
||||
import 'package:stream_chat/src/event_type.dart';
|
||||
import 'package:stream_chat/src/exceptions.dart';
|
||||
import 'package:stream_chat/src/models/message.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
/// The retry queue associated to a channel
|
||||
class RetryQueue {
|
||||
/// The channel of this queue
|
||||
final Channel channel;
|
||||
|
||||
/// The logger associated to this queue
|
||||
final Logger logger;
|
||||
|
||||
/// Instantiate a new RetryQueue object
|
||||
RetryQueue({
|
||||
@required this.channel,
|
||||
this.logger,
|
||||
}) {
|
||||
_retryPolicy = channel.client.retryPolicy;
|
||||
|
||||
_listenConnectionRecovered();
|
||||
|
||||
_listenFailedEvents();
|
||||
}
|
||||
|
||||
final _subscriptions = <StreamSubscription>[];
|
||||
|
||||
void _listenConnectionRecovered() {
|
||||
_subscriptions
|
||||
.add(channel.client.on(EventType.connectionRecovered).listen((event) {
|
||||
if (!_isRetrying && event.online) {
|
||||
_startRetrying();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
final HeapPriorityQueue<Message> _messageQueue = HeapPriorityQueue(_byDate);
|
||||
bool _isRetrying = false;
|
||||
RetryPolicy _retryPolicy;
|
||||
|
||||
/// Add a list of messages
|
||||
void add(List<Message> messages) {
|
||||
final messageList = _messageQueue.toList();
|
||||
_messageQueue.addAll(messages
|
||||
.where((element) => !messageList.any((m) => m.id == element.id)));
|
||||
|
||||
if (_messageQueue.isNotEmpty && !_isRetrying) {
|
||||
_startRetrying();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _startRetrying() async {
|
||||
logger?.info('start retrying');
|
||||
_isRetrying = true;
|
||||
final retryPolicy = _retryPolicy.copyWith(attempt: 0);
|
||||
|
||||
while (_messageQueue.isNotEmpty) {
|
||||
final message = _messageQueue.first;
|
||||
try {
|
||||
logger?.info('retry attempt ${retryPolicy.attempt}');
|
||||
await _sendMessage(message);
|
||||
logger?.info('message sent - removing it from the queue');
|
||||
_messageQueue.remove(message);
|
||||
logger?.info('now ${_messageQueue.length} messages in the queue');
|
||||
retryPolicy.attempt = 0;
|
||||
} catch (error) {
|
||||
ApiError apiError;
|
||||
if (error is DioError) {
|
||||
if (error.type == DioErrorType.RESPONSE) {
|
||||
_messageQueue.remove(message);
|
||||
return;
|
||||
}
|
||||
apiError = ApiError(
|
||||
error.response?.data,
|
||||
error.response?.statusCode,
|
||||
);
|
||||
} else if (error is ApiError) {
|
||||
apiError = error;
|
||||
if (apiError.status?.toString()?.startsWith('4') == true) {
|
||||
_messageQueue.remove(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!retryPolicy.shouldRetry(
|
||||
channel.client,
|
||||
retryPolicy.attempt,
|
||||
apiError,
|
||||
)) {
|
||||
_messageQueue.toList().forEach(_sendFailedEvent);
|
||||
_isRetrying = false;
|
||||
return;
|
||||
}
|
||||
|
||||
retryPolicy.attempt++;
|
||||
final timeout = retryPolicy.retryTimeout(
|
||||
channel.client,
|
||||
retryPolicy.attempt,
|
||||
apiError,
|
||||
);
|
||||
await Future.delayed(timeout);
|
||||
}
|
||||
}
|
||||
_isRetrying = false;
|
||||
}
|
||||
|
||||
void _sendFailedEvent(Message message) {
|
||||
final newStatus = message.status == MessageSendingStatus.sending
|
||||
? MessageSendingStatus.failed
|
||||
: (message.status == MessageSendingStatus.updating
|
||||
? MessageSendingStatus.failed_update
|
||||
: MessageSendingStatus.failed_delete);
|
||||
channel.state.addMessage(message.copyWith(
|
||||
status: newStatus,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _sendMessage(Message message) async {
|
||||
if (message.status == MessageSendingStatus.failed_update ||
|
||||
message.status == MessageSendingStatus.updating) {
|
||||
await channel.client.updateMessage(
|
||||
message,
|
||||
channel.cid,
|
||||
);
|
||||
} else if (message.status == MessageSendingStatus.failed ||
|
||||
message.status == MessageSendingStatus.sending) {
|
||||
await channel.sendMessage(
|
||||
message,
|
||||
);
|
||||
} else if (message.status == MessageSendingStatus.failed_delete ||
|
||||
message.status == MessageSendingStatus.deleting) {
|
||||
await channel.client.deleteMessage(
|
||||
message,
|
||||
channel.cid,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _listenFailedEvents() {
|
||||
_subscriptions.add(channel.on().listen((event) {
|
||||
final messageList = _messageQueue.toList();
|
||||
if (event.message != null) {
|
||||
final messageIndex =
|
||||
messageList.indexWhere((m) => m.id == event.message.id);
|
||||
if (messageIndex == -1 &&
|
||||
[
|
||||
MessageSendingStatus.failed_update,
|
||||
MessageSendingStatus.failed,
|
||||
MessageSendingStatus.failed_delete,
|
||||
].contains(event.message.status)) {
|
||||
logger?.info('add message from events');
|
||||
add([event.message]);
|
||||
} else if (messageIndex != -1 &&
|
||||
[
|
||||
MessageSendingStatus.sent,
|
||||
null,
|
||||
].contains(event.message.status)) {
|
||||
_messageQueue.remove(messageList[messageIndex]);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/// Call this method to dispose this object
|
||||
void dispose() {
|
||||
_messageQueue.clear();
|
||||
_subscriptions.forEach((s) => s.cancel());
|
||||
}
|
||||
|
||||
static int _byDate(Message m1, Message m2) {
|
||||
final date1 = _getMessageDate(m1);
|
||||
final date2 = _getMessageDate(m2);
|
||||
|
||||
return date1.compareTo(date2);
|
||||
}
|
||||
|
||||
static DateTime _getMessageDate(Message m1) {
|
||||
switch (m1.status) {
|
||||
case MessageSendingStatus.failed_delete:
|
||||
case MessageSendingStatus.deleting:
|
||||
return m1.deletedAt;
|
||||
|
||||
case MessageSendingStatus.failed:
|
||||
case MessageSendingStatus.sending:
|
||||
return m1.createdAt;
|
||||
|
||||
case MessageSendingStatus.failed_update:
|
||||
case MessageSendingStatus.updating:
|
||||
return m1.updatedAt;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'package:web_socket_channel/html.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
/// Html version of websocket implementation
|
||||
/// Used in Flutter web version
|
||||
WebSocketChannel connectWebSocket(String url, {Iterable<String> protocols}) =>
|
||||
HtmlWebSocketChannel.connect(url, protocols: protocols);
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'package:web_socket_channel/io.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
/// IO version of websocket implementation
|
||||
/// Used in Flutter mobile version
|
||||
WebSocketChannel connectWebSocket(String url, {Iterable<String> protocols}) =>
|
||||
IOWebSocketChannel.connect(url, protocols: protocols);
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
/// Stub version of websocket implementation
|
||||
/// Used just for conditional library import
|
||||
WebSocketChannel connectWebSocket(String url,
|
||||
{Iterable<String> protocols,
|
||||
Map<String, dynamic> headers,
|
||||
Duration pingInterval}) =>
|
||||
throw UnimplementedError();
|
||||
@@ -0,0 +1,316 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
import '../models/event.dart';
|
||||
import '../models/user.dart';
|
||||
import 'connection_status.dart';
|
||||
import 'web_socket_channel_stub.dart'
|
||||
if (dart.library.html) 'web_socket_channel_html.dart'
|
||||
if (dart.library.io) 'web_socket_channel_io.dart';
|
||||
|
||||
/// Typedef which exposes an [Event] as the only parameter.
|
||||
typedef EventHandler = void Function(Event);
|
||||
|
||||
/// Typedef used for connecting to a websocket. Method returns a [WebSocketChannel]
|
||||
/// and accepts a connection [url] and an optional [Iterable] of `protocols`.
|
||||
typedef ConnectWebSocket = WebSocketChannel Function(String url,
|
||||
{Iterable<String> protocols});
|
||||
|
||||
// TODO: parse error even
|
||||
// TODO: if parsing an error into an event fails we should not hide the original error
|
||||
/// A WebSocket connection that reconnects upon failure.
|
||||
class WebSocket {
|
||||
/// Creates a new websocket
|
||||
/// To connect the WS call [connect]
|
||||
WebSocket({
|
||||
@required this.baseUrl,
|
||||
this.user,
|
||||
this.connectParams,
|
||||
this.connectPayload,
|
||||
this.handler,
|
||||
this.logger,
|
||||
this.connectFunc = connectWebSocket,
|
||||
this.reconnectionMonitorInterval = 1,
|
||||
this.healthCheckInterval = 20,
|
||||
this.reconnectionMonitorTimeout = 40,
|
||||
}) {
|
||||
final qs = Map<String, String>.from(connectParams);
|
||||
|
||||
final data = Map<String, dynamic>.from(connectPayload);
|
||||
|
||||
data['user_details'] = user.toJson();
|
||||
qs['json'] = json.encode(data);
|
||||
|
||||
if (baseUrl.startsWith('https')) {
|
||||
_path = baseUrl.replaceFirst('https://', '');
|
||||
_path = Uri.https(_path, 'connect', qs)
|
||||
.toString()
|
||||
.replaceFirst('https', 'wss');
|
||||
} else if (baseUrl.startsWith('http')) {
|
||||
_path = baseUrl.replaceFirst('http://', '');
|
||||
_path =
|
||||
Uri.http(_path, 'connect', qs).toString().replaceFirst('http', 'ws');
|
||||
} else {
|
||||
_path = Uri.https(baseUrl, 'connect', qs)
|
||||
.toString()
|
||||
.replaceFirst('https', 'wss');
|
||||
}
|
||||
}
|
||||
|
||||
/// WS base url
|
||||
final String baseUrl;
|
||||
|
||||
/// User performing the WS connection
|
||||
final User user;
|
||||
|
||||
/// Querystring connection parameters
|
||||
final Map<String, String> connectParams;
|
||||
|
||||
/// WS connection payload
|
||||
final Map<String, dynamic> connectPayload;
|
||||
|
||||
/// Functions that will be called every time a new event is received from the connection
|
||||
final EventHandler handler;
|
||||
|
||||
/// A WS specific logger instance
|
||||
final Logger logger;
|
||||
|
||||
/// Connection function
|
||||
/// Used only for testing purpose
|
||||
@visibleForTesting
|
||||
final ConnectWebSocket connectFunc;
|
||||
|
||||
/// Interval of the reconnection monitor timer
|
||||
/// This checks that it received a new event in the last [reconnectionMonitorTimeout] seconds,
|
||||
/// otherwise it considers the connection unhealthy and reconnects the WS
|
||||
final int reconnectionMonitorInterval;
|
||||
|
||||
/// Interval of the health event sending timer
|
||||
/// This sends a health event every [healthCheckInterval] seconds in order to
|
||||
/// make the server aware that the client is still listening
|
||||
final int healthCheckInterval;
|
||||
|
||||
/// The timeout that uses the reconnection monitor timer to consider the connection unhealthy
|
||||
final int reconnectionMonitorTimeout;
|
||||
|
||||
final _connectionStatusController =
|
||||
BehaviorSubject.seeded(ConnectionStatus.disconnected);
|
||||
|
||||
set _connectionStatus(ConnectionStatus status) =>
|
||||
_connectionStatusController.add(status);
|
||||
|
||||
/// The current connection status value
|
||||
ConnectionStatus get connectionStatus => _connectionStatusController.value;
|
||||
|
||||
/// This notifies of connection status changes
|
||||
Stream<ConnectionStatus> get connectionStatusStream =>
|
||||
_connectionStatusController.stream;
|
||||
|
||||
String _path;
|
||||
int _retryAttempt = 1;
|
||||
WebSocketChannel _channel;
|
||||
Timer _healthCheck, _reconnectionMonitor;
|
||||
DateTime _lastEventAt;
|
||||
bool _manuallyDisconnected = false,
|
||||
_connecting = false,
|
||||
_reconnecting = false;
|
||||
|
||||
Event _decodeEvent(String source) {
|
||||
return Event.fromJson(json.decode(source));
|
||||
}
|
||||
|
||||
Completer<Event> _connectionCompleter = Completer<Event>();
|
||||
|
||||
/// Connect the WS using the parameters passed in the constructor
|
||||
Future<Event> connect() {
|
||||
_manuallyDisconnected = false;
|
||||
|
||||
if (_connecting) {
|
||||
logger.severe('already connecting');
|
||||
return null;
|
||||
}
|
||||
|
||||
_connecting = true;
|
||||
_connectionStatus = ConnectionStatus.connecting;
|
||||
|
||||
logger.info('connecting to $_path');
|
||||
|
||||
_channel = connectFunc(_path);
|
||||
_channel.stream.listen(
|
||||
(data) {
|
||||
final jsonData = json.decode(data);
|
||||
if (jsonData['error'] != null) {
|
||||
return _onConnectionError(jsonData['error']);
|
||||
}
|
||||
_onData(data);
|
||||
},
|
||||
onError: (error, stacktrace) {
|
||||
_onConnectionError(error, stacktrace);
|
||||
},
|
||||
onDone: () {
|
||||
_onDone();
|
||||
},
|
||||
);
|
||||
return _connectionCompleter.future;
|
||||
}
|
||||
|
||||
void _onDone() {
|
||||
_connecting = false;
|
||||
if (_manuallyDisconnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
'connection closed | closeCode: ${_channel.closeCode} | closedReason: ${_channel.closeReason}');
|
||||
|
||||
if (!_reconnecting) {
|
||||
_reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void _onData(data) {
|
||||
final event = _decodeEvent(data);
|
||||
logger.info('received new event: $data');
|
||||
|
||||
if (_lastEventAt == null) {
|
||||
logger.info('connection estabilished');
|
||||
_connecting = false;
|
||||
_reconnecting = false;
|
||||
_lastEventAt = DateTime.now();
|
||||
|
||||
_connectionStatus = ConnectionStatus.connected;
|
||||
_retryAttempt = 1;
|
||||
|
||||
if (!_connectionCompleter.isCompleted) {
|
||||
_connectionCompleter.complete(event);
|
||||
}
|
||||
|
||||
_startReconnectionMonitor();
|
||||
_startHealthCheck();
|
||||
}
|
||||
|
||||
handler(event);
|
||||
_lastEventAt = DateTime.now();
|
||||
}
|
||||
|
||||
Future<void> _onConnectionError(error, [stacktrace]) async {
|
||||
logger.severe('error connecting');
|
||||
logger.severe(error);
|
||||
if (stacktrace != null) {
|
||||
logger.severe(stacktrace);
|
||||
}
|
||||
_connecting = false;
|
||||
|
||||
if (!_reconnecting) {
|
||||
_connectionStatus = ConnectionStatus.disconnected;
|
||||
}
|
||||
|
||||
if (!_connectionCompleter.isCompleted) {
|
||||
_cancelTimers();
|
||||
_connectionCompleter.completeError(error, stacktrace);
|
||||
} else if (!_reconnecting) {
|
||||
return _reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void _startReconnectionMonitor() {
|
||||
final reconnectionTimer = (_) {
|
||||
final now = DateTime.now();
|
||||
if (_lastEventAt != null &&
|
||||
now.difference(_lastEventAt).inSeconds > reconnectionMonitorTimeout) {
|
||||
_channel.sink.close();
|
||||
}
|
||||
};
|
||||
|
||||
_reconnectionMonitor = Timer.periodic(
|
||||
Duration(seconds: reconnectionMonitorInterval),
|
||||
reconnectionTimer,
|
||||
);
|
||||
|
||||
reconnectionTimer(_reconnectionMonitor);
|
||||
}
|
||||
|
||||
void _reconnectTimer() async {
|
||||
if (!_reconnecting) {
|
||||
return;
|
||||
}
|
||||
if (_connecting) {
|
||||
logger.info('already connecting');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('reconnecting..');
|
||||
|
||||
_cancelTimers();
|
||||
|
||||
try {
|
||||
await connect();
|
||||
} catch (e) {
|
||||
logger.log(Level.SEVERE, e.toString());
|
||||
}
|
||||
await Future.delayed(
|
||||
Duration(seconds: min(_retryAttempt * 5, 25)),
|
||||
() {
|
||||
_reconnectTimer();
|
||||
_retryAttempt++;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _reconnect() async {
|
||||
logger.info('reconnect');
|
||||
if (!_reconnecting) {
|
||||
_reconnecting = true;
|
||||
_connectionStatus = ConnectionStatus.connecting;
|
||||
}
|
||||
|
||||
_reconnectTimer();
|
||||
}
|
||||
|
||||
void _cancelTimers() {
|
||||
_lastEventAt = null;
|
||||
if (_healthCheck != null) {
|
||||
_healthCheck.cancel();
|
||||
}
|
||||
if (_reconnectionMonitor != null) {
|
||||
_reconnectionMonitor.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void _startHealthCheck() {
|
||||
logger.info('start health check monitor');
|
||||
|
||||
final healthCheckTimer = (_) {
|
||||
logger.info('sending health.check');
|
||||
_channel.sink.add("{'type': 'health.check'}");
|
||||
};
|
||||
|
||||
_healthCheck = Timer.periodic(
|
||||
Duration(seconds: healthCheckInterval),
|
||||
healthCheckTimer,
|
||||
);
|
||||
|
||||
healthCheckTimer(_healthCheck);
|
||||
}
|
||||
|
||||
/// Disconnects the WS and releases eventual resources
|
||||
Future<void> disconnect() async {
|
||||
if (_manuallyDisconnected) {
|
||||
return;
|
||||
}
|
||||
logger.info('disconnecting');
|
||||
_connectionCompleter = Completer();
|
||||
_cancelTimers();
|
||||
_reconnecting = false;
|
||||
_manuallyDisconnected = true;
|
||||
_connectionStatus = ConnectionStatus.disconnected;
|
||||
await _connectionStatusController.close();
|
||||
return _channel.sink.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
import 'package:stream_chat/src/api/requests.dart';
|
||||
import 'package:stream_chat/src/models/channel_model.dart';
|
||||
import 'package:stream_chat/src/models/channel_state.dart';
|
||||
import 'package:stream_chat/src/models/event.dart';
|
||||
import 'package:stream_chat/src/models/member.dart';
|
||||
import 'package:stream_chat/src/models/message.dart';
|
||||
import 'package:stream_chat/src/models/reaction.dart';
|
||||
import 'package:stream_chat/src/models/read.dart';
|
||||
import 'package:stream_chat/src/models/user.dart';
|
||||
|
||||
/// A simple client used for persisting chat data locally.
|
||||
abstract class ChatPersistenceClient {
|
||||
/// Creates a new connection to the client
|
||||
Future<void> connect(String userId);
|
||||
|
||||
/// Closes the client connection
|
||||
/// If [flush] is true, the data will also be deleted
|
||||
Future<void> disconnect({bool flush = false});
|
||||
|
||||
/// Get stored replies by messageId
|
||||
Future<List<Message>> getReplies(
|
||||
String parentId, {
|
||||
PaginationParams options,
|
||||
});
|
||||
|
||||
/// Get stored connection event
|
||||
Future<Event> getConnectionInfo();
|
||||
|
||||
/// Get stored lastSyncAt
|
||||
Future<DateTime> getLastSyncAt();
|
||||
|
||||
/// Update stored connection event
|
||||
Future<void> updateConnectionInfo(Event event);
|
||||
|
||||
/// Update stored lastSyncAt
|
||||
Future<void> updateLastSyncAt(DateTime lastSyncAt);
|
||||
|
||||
/// Get the channel cids saved in the offline storage
|
||||
Future<List<String>> getChannelCids();
|
||||
|
||||
/// Get stored [ChannelModel]s by providing channel [cid]
|
||||
Future<ChannelModel> getChannelByCid(String cid);
|
||||
|
||||
/// Get stored channel [Member]s by providing channel [cid]
|
||||
Future<List<Member>> getMembersByCid(String cid);
|
||||
|
||||
/// Get stored channel [Read]s by providing channel [cid]
|
||||
Future<List<Read>> getReadsByCid(String cid);
|
||||
|
||||
/// Get stored [Message]s by providing channel [cid]
|
||||
///
|
||||
/// Optionally, you can [messagePagination]
|
||||
/// for filtering out messages
|
||||
Future<List<Message>> getMessagesByCid(
|
||||
String cid, {
|
||||
PaginationParams messagePagination,
|
||||
});
|
||||
|
||||
/// Get [ChannelState] data by providing channel [cid]
|
||||
Future<ChannelState> getChannelStateByCid(
|
||||
String cid, {
|
||||
PaginationParams messagePagination,
|
||||
}) async {
|
||||
final members = await getMembersByCid(cid);
|
||||
final reads = await getReadsByCid(cid);
|
||||
final channel = await getChannelByCid(cid);
|
||||
final messages = await getMessagesByCid(
|
||||
cid,
|
||||
messagePagination: messagePagination,
|
||||
);
|
||||
return ChannelState(
|
||||
members: members,
|
||||
read: reads,
|
||||
messages: messages,
|
||||
channel: channel,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get all the stored [ChannelState]s
|
||||
///
|
||||
/// Optionally, pass [filter], [sort], [paginationParams]
|
||||
/// for filtering out states.
|
||||
Future<List<ChannelState>> getChannelStates({
|
||||
Map<String, dynamic> filter,
|
||||
List<SortOption> sort = const [],
|
||||
PaginationParams paginationParams,
|
||||
});
|
||||
|
||||
/// Update list of channel queries.
|
||||
///
|
||||
/// If [clearQueryCache] is true before the insert
|
||||
/// the list of matching rows will be deleted
|
||||
Future<void> updateChannelQueries(
|
||||
Map<String, dynamic> filter,
|
||||
List<String> cids,
|
||||
bool clearQueryCache,
|
||||
);
|
||||
|
||||
/// Remove a message by [messageId]
|
||||
Future<void> deleteMessageById(String messageId) {
|
||||
return deleteMessageByIds([messageId]);
|
||||
}
|
||||
|
||||
/// Remove a message by [messageIds]
|
||||
Future<void> deleteMessageByIds(List<String> messageIds);
|
||||
|
||||
/// Remove a message by channel [cid]
|
||||
Future<void> deleteMessageByCid(String cid) {
|
||||
return deleteMessageByCids([cid]);
|
||||
}
|
||||
|
||||
/// Remove a message by message [cids]
|
||||
Future<void> deleteMessageByCids(List<String> cids);
|
||||
|
||||
/// Remove a channel by [cid]
|
||||
Future<void> deleteChannels(List<String> cids);
|
||||
|
||||
/// Updates the message data of a particular channel [cid] with
|
||||
/// the new [messages] data
|
||||
Future<void> updateMessages(String cid, List<Message> messages);
|
||||
|
||||
/// Returns all the threads by parent message of a particular channel by
|
||||
/// providing channel [cid]
|
||||
Future<Map<String, List<Message>>> getChannelThreads(String cid);
|
||||
|
||||
/// Updates all the channels using the new [channels] data.
|
||||
Future<void> updateChannels(List<ChannelModel> channels);
|
||||
|
||||
/// Updates all the members of a particular channle [cid]
|
||||
/// with the new [members] data
|
||||
Future<void> updateMembers(String cid, List<Member> members);
|
||||
|
||||
/// Updates the read data of a particular channel [cid] with
|
||||
/// the new [reads] data
|
||||
Future<void> updateReads(String cid, List<Read> reads);
|
||||
|
||||
/// Updates the users data with the new [users] data
|
||||
Future<void> updateUsers(List<User> users);
|
||||
|
||||
/// Updates the reactions data with the new [reactions] data
|
||||
Future<void> updateReactions(List<Reaction> reactions);
|
||||
|
||||
/// Deletes all the reactions by [messageIds]
|
||||
Future<void> deleteReactionsByMessageId(List<String> messageIds);
|
||||
|
||||
/// Deletes all the members by channel [cids]
|
||||
Future<void> deleteMembersByCids(List<String> cids);
|
||||
|
||||
/// Update the channel state data using [channelState]
|
||||
Future<void> updateChannelState(ChannelState channelState) {
|
||||
return updateChannelStates([channelState]);
|
||||
}
|
||||
|
||||
/// Update list of channel states
|
||||
Future<void> updateChannelStates(List<ChannelState> channelStates) async {
|
||||
final deleteReactions = deleteReactionsByMessageId(channelStates
|
||||
.expand((it) => it.messages)
|
||||
.map((m) => m.id)
|
||||
.toList(growable: false));
|
||||
|
||||
final deleteMembers = deleteMembersByCids(
|
||||
channelStates.map((it) => it.channel.cid).toList(growable: false),
|
||||
);
|
||||
|
||||
await Future.wait([
|
||||
deleteReactions,
|
||||
deleteMembers,
|
||||
]);
|
||||
|
||||
final channels = channelStates.map((it) {
|
||||
return it.channel;
|
||||
}).where((it) => it != null);
|
||||
|
||||
final reactions = channelStates.expand((it) => it.messages).expand((it) {
|
||||
return [
|
||||
...it.ownReactions.where((r) => r.userId != null),
|
||||
...it.latestReactions.where((r) => r.userId != null)
|
||||
];
|
||||
}).where((it) => it != null);
|
||||
|
||||
final users = channelStates
|
||||
.map((cs) => [
|
||||
cs.channel?.createdBy,
|
||||
...cs.messages?.map((m) {
|
||||
return [
|
||||
m.user,
|
||||
...m.latestReactions?.map((r) => r.user),
|
||||
...m.ownReactions?.map((r) => r.user),
|
||||
];
|
||||
})?.expand((v) => v),
|
||||
...cs.read?.map((r) => r.user),
|
||||
...cs.members?.map((m) => m.user),
|
||||
])
|
||||
.expand((it) => it)
|
||||
.where((it) => it != null);
|
||||
|
||||
final updateMessagesFuture = channelStates.map((it) {
|
||||
final cid = it.channel.cid;
|
||||
final messages = it.messages.where((it) => it != null);
|
||||
return updateMessages(cid, messages.toList(growable: false));
|
||||
}).toList(growable: false);
|
||||
|
||||
final updateReadsFuture = channelStates.map((it) {
|
||||
final cid = it.channel.cid;
|
||||
final reads = it.read.where((it) => it != null);
|
||||
return updateReads(cid, reads.toList(growable: false));
|
||||
}).toList(growable: false);
|
||||
|
||||
final updateMembersFuture = channelStates.map((it) {
|
||||
final cid = it.channel.cid;
|
||||
final members = it.members.where((it) => it != null);
|
||||
return updateMembers(cid, members.toList(growable: false));
|
||||
}).toList(growable: false);
|
||||
|
||||
await Future.wait([
|
||||
...updateMessagesFuture,
|
||||
...updateReadsFuture,
|
||||
...updateMembersFuture,
|
||||
updateUsers(users.toList(growable: false)),
|
||||
updateChannels(channels.toList(growable: false)),
|
||||
updateReactions(reactions.toList(growable: false)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/// This class defines some basic event types
|
||||
class EventType {
|
||||
/// Indicates any type of events
|
||||
static const String any = '*';
|
||||
|
||||
/// Event sent when a user starts typing a message
|
||||
static const String typingStart = 'typing.start';
|
||||
|
||||
/// Event sent when a user stops typing a message
|
||||
static const String typingStop = 'typing.stop';
|
||||
|
||||
/// Event sent when receiving a new message
|
||||
static const String messageNew = 'message.new';
|
||||
|
||||
/// Event sent when receiving a new message
|
||||
static const String notificationMessageNew = 'notification.message_new';
|
||||
|
||||
/// Event sent when the unread count changes
|
||||
static const String notificationMarkRead = 'notification.mark_read';
|
||||
|
||||
/// Event sent when deleting a new message
|
||||
static const String messageDeleted = 'message.deleted';
|
||||
|
||||
/// Event sent when receiving a new reaction
|
||||
static const String reactionNew = 'reaction.new';
|
||||
|
||||
/// Event sent when deleting a reaction
|
||||
static const String reactionDeleted = 'reaction.deleted';
|
||||
|
||||
/// Event sent when updating a reaction
|
||||
static const String reactionUpdated = 'reaction.updated';
|
||||
|
||||
/// Event sent when updating a message
|
||||
static const String messageUpdated = 'message.updated';
|
||||
|
||||
/// Event sent when reading a message
|
||||
static const String messageRead = 'message.read';
|
||||
|
||||
/// Event sent when a channel is deleted
|
||||
static const String channelDeleted = 'channel.deleted';
|
||||
|
||||
/// Event sent when a channel is deleted
|
||||
static const String notificationChannelDeleted =
|
||||
'notification.channel_deleted';
|
||||
|
||||
/// Event sent when a channel is truncated
|
||||
static const String channelTruncated = 'channel.truncated';
|
||||
|
||||
/// Event sent when a channel is truncated
|
||||
static const String notificationChannelTruncated =
|
||||
'notification.channel_truncated';
|
||||
|
||||
/// Event sent when the user is added to a channel
|
||||
static const String notificationAddedToChannel =
|
||||
'notification.added_to_channel';
|
||||
|
||||
/// Event sent when the user is removed to a channel
|
||||
static const String notificationRemovedFromChannel =
|
||||
'notification.removed_from_channel';
|
||||
|
||||
/// Event sent when a channel is updated
|
||||
static const String channelUpdated = 'channel.updated';
|
||||
|
||||
/// Event sent when a user is updated
|
||||
static const String userUpdated = 'user.updated';
|
||||
|
||||
/// Event sent when a member is added to a channel
|
||||
static const String memberAdded = 'member.added';
|
||||
|
||||
/// Event sent when a member is removed to a channel
|
||||
static const String memberRemoved = 'member.removed';
|
||||
|
||||
/// Event sent when a channel is hidden
|
||||
static const String channelHidden = 'channel.hidden';
|
||||
|
||||
/// Event sent when a channel is visible
|
||||
static const String channelVisible = 'channel.visible';
|
||||
|
||||
/// Event sent when the connection status changes
|
||||
static const String connectionChanged = 'connection.changed';
|
||||
|
||||
/// Event sent when the connection is recovered
|
||||
static const String connectionRecovered = 'connection.recovered';
|
||||
|
||||
/// Event sent when the user is accepts an invite
|
||||
static const String notificationInviteAccepted =
|
||||
'notification.invite_accepted';
|
||||
|
||||
/// Event sent when the user is invited
|
||||
static const String notificationInvited = 'notification.invited';
|
||||
|
||||
/// Event sent when the user's mutes list is updated
|
||||
static const String notificationMutesUpdated = 'notification.mutes_updated';
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'dart:convert';
|
||||
|
||||
/// Exception related to api calls
|
||||
class ApiError extends Error {
|
||||
/// Raw body of the response
|
||||
final String body;
|
||||
|
||||
/// Json parsed body
|
||||
final Map<String, dynamic> jsonData;
|
||||
|
||||
/// Http status code of the response
|
||||
final int status;
|
||||
|
||||
/// Stream specific error code
|
||||
int get code => _code;
|
||||
int _code;
|
||||
|
||||
static Map<String, dynamic> _decode(String body) {
|
||||
try {
|
||||
if (body == null) {
|
||||
return null;
|
||||
}
|
||||
return json.decode(body);
|
||||
} on FormatException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new ApiError instance using the response body and status code
|
||||
ApiError(this.body, this.status) : jsonData = _decode(body) {
|
||||
if (jsonData != null && jsonData.containsKey('code')) {
|
||||
_code = jsonData['code'];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ApiError &&
|
||||
runtimeType == other.runtimeType &&
|
||||
body == other.body &&
|
||||
jsonData == other.jsonData &&
|
||||
status == other.status &&
|
||||
_code == other._code;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
body.hashCode ^ jsonData.hashCode ^ status.hashCode ^ _code.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ApiError{body: $body, jsonData: $jsonData, status: $status, code: $_code}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'action.g.dart';
|
||||
|
||||
/// The class that contains the information about an action
|
||||
@JsonSerializable()
|
||||
class Action {
|
||||
/// The name of the action
|
||||
final String name;
|
||||
|
||||
/// The style of the action
|
||||
final String style;
|
||||
|
||||
/// The test of the action
|
||||
final String text;
|
||||
|
||||
/// The type of the action
|
||||
final String type;
|
||||
|
||||
/// The value of the action
|
||||
final String value;
|
||||
|
||||
/// Constructor used for json serialization
|
||||
Action({this.name, this.style, this.text, this.type, this.value});
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory Action.fromJson(Map<String, dynamic> json) => _$ActionFromJson(json);
|
||||
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() => _$ActionToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'action.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Action _$ActionFromJson(Map json) {
|
||||
return Action(
|
||||
name: json['name'] as String,
|
||||
style: json['style'] as String,
|
||||
text: json['text'] as String,
|
||||
type: json['type'] as String,
|
||||
value: json['value'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$ActionToJson(Action instance) => <String, dynamic>{
|
||||
'name': instance.name,
|
||||
'style': instance.style,
|
||||
'text': instance.text,
|
||||
'type': instance.type,
|
||||
'value': instance.value,
|
||||
};
|
||||
@@ -0,0 +1,207 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'action.dart';
|
||||
import 'serialization.dart';
|
||||
|
||||
part 'attachment.g.dart';
|
||||
|
||||
/// The class that contains the information about an attachment
|
||||
@JsonSerializable(includeIfNull: false)
|
||||
class Attachment {
|
||||
///The attachment type based on the URL resource. This can be: audio, image or video
|
||||
final String type;
|
||||
|
||||
///The link to which the attachment message points to.
|
||||
final String titleLink;
|
||||
|
||||
/// The attachment title
|
||||
final String title;
|
||||
|
||||
/// The URL to the attached file thumbnail. You can use this to represent the attached link.
|
||||
final String thumbUrl;
|
||||
|
||||
/// The attachment text. It will be displayed in the channel next to the original message.
|
||||
final String text;
|
||||
|
||||
/// Optional text that appears above the attachment block
|
||||
final String pretext;
|
||||
|
||||
/// The original URL that was used to scrape this attachment.
|
||||
final String ogScrapeUrl;
|
||||
|
||||
/// The URL to the attached image. This is present for URL pointing to an image article (eg. Unsplash)
|
||||
final String imageUrl;
|
||||
final String footerIcon;
|
||||
final String footer;
|
||||
final dynamic fields;
|
||||
final String fallback;
|
||||
final String color;
|
||||
|
||||
/// The name of the author.
|
||||
final String authorName;
|
||||
final String authorLink;
|
||||
final String authorIcon;
|
||||
|
||||
/// The URL to the audio, video or image related to the URL.
|
||||
final String assetUrl;
|
||||
|
||||
/// Actions from a command
|
||||
final List<Action> actions;
|
||||
|
||||
final Uri localUri;
|
||||
|
||||
/// Map of custom channel extraData
|
||||
@JsonKey(includeIfNull: false)
|
||||
final Map<String, dynamic> extraData;
|
||||
|
||||
/// Known top level fields.
|
||||
/// Useful for [Serialization] methods.
|
||||
static const topLevelFields = [
|
||||
'type',
|
||||
'title_link',
|
||||
'title',
|
||||
'thumb_url',
|
||||
'text',
|
||||
'pretext',
|
||||
'og_scrape_url',
|
||||
'image_url',
|
||||
'footer_icon',
|
||||
'footer',
|
||||
'fields',
|
||||
'fallback',
|
||||
'color',
|
||||
'author_name',
|
||||
'author_link',
|
||||
'author_icon',
|
||||
'asset_url',
|
||||
'actions',
|
||||
];
|
||||
|
||||
/// Constructor used for json serialization
|
||||
Attachment({
|
||||
this.type,
|
||||
this.titleLink,
|
||||
this.title,
|
||||
this.thumbUrl,
|
||||
this.text,
|
||||
this.pretext,
|
||||
this.ogScrapeUrl,
|
||||
this.imageUrl,
|
||||
this.footerIcon,
|
||||
this.footer,
|
||||
this.fields,
|
||||
this.fallback,
|
||||
this.color,
|
||||
this.authorName,
|
||||
this.authorLink,
|
||||
this.authorIcon,
|
||||
this.assetUrl,
|
||||
this.actions,
|
||||
this.extraData,
|
||||
this.localUri,
|
||||
});
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory Attachment.fromJson(Map<String, dynamic> json) {
|
||||
return _$AttachmentFromJson(
|
||||
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
|
||||
}
|
||||
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
|
||||
_$AttachmentToJson(this), topLevelFields);
|
||||
|
||||
Attachment copyWith({
|
||||
String type,
|
||||
String titleLink,
|
||||
String title,
|
||||
String thumbUrl,
|
||||
String text,
|
||||
String pretext,
|
||||
String ogScrapeUrl,
|
||||
String imageUrl,
|
||||
String footerIcon,
|
||||
String footer,
|
||||
dynamic fields,
|
||||
String fallback,
|
||||
String color,
|
||||
String authorName,
|
||||
String authorLink,
|
||||
String authorIcon,
|
||||
String assetUrl,
|
||||
List<Action> actions,
|
||||
Uri localUri,
|
||||
Map<String, dynamic> extraData,
|
||||
}) =>
|
||||
Attachment(
|
||||
type: type ?? this.type,
|
||||
titleLink: titleLink ?? this.titleLink,
|
||||
title: title ?? this.title,
|
||||
thumbUrl: thumbUrl ?? this.thumbUrl,
|
||||
text: text ?? this.text,
|
||||
pretext: pretext ?? this.pretext,
|
||||
ogScrapeUrl: ogScrapeUrl ?? this.ogScrapeUrl,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
footerIcon: footerIcon ?? this.footerIcon,
|
||||
footer: footer ?? this.footer,
|
||||
fields: fields ?? this.fields,
|
||||
fallback: fallback ?? this.fallback,
|
||||
color: color ?? this.color,
|
||||
authorName: authorName ?? this.authorName,
|
||||
authorLink: authorLink ?? this.authorLink,
|
||||
authorIcon: authorIcon ?? this.authorIcon,
|
||||
assetUrl: assetUrl ?? this.assetUrl,
|
||||
actions: actions ?? this.actions,
|
||||
localUri: localUri ?? this.localUri,
|
||||
extraData: extraData ?? this.extraData,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is Attachment &&
|
||||
runtimeType == other.runtimeType &&
|
||||
type == other.type &&
|
||||
titleLink == other.titleLink &&
|
||||
title == other.title &&
|
||||
thumbUrl == other.thumbUrl &&
|
||||
text == other.text &&
|
||||
pretext == other.pretext &&
|
||||
ogScrapeUrl == other.ogScrapeUrl &&
|
||||
imageUrl == other.imageUrl &&
|
||||
footerIcon == other.footerIcon &&
|
||||
footer == other.footer &&
|
||||
fields == other.fields &&
|
||||
fallback == other.fallback &&
|
||||
color == other.color &&
|
||||
authorName == other.authorName &&
|
||||
authorLink == other.authorLink &&
|
||||
authorIcon == other.authorIcon &&
|
||||
assetUrl == other.assetUrl &&
|
||||
actions == other.actions &&
|
||||
extraData == other.extraData;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
type.hashCode ^
|
||||
titleLink.hashCode ^
|
||||
title.hashCode ^
|
||||
thumbUrl.hashCode ^
|
||||
text.hashCode ^
|
||||
pretext.hashCode ^
|
||||
ogScrapeUrl.hashCode ^
|
||||
imageUrl.hashCode ^
|
||||
footerIcon.hashCode ^
|
||||
footer.hashCode ^
|
||||
fields.hashCode ^
|
||||
fallback.hashCode ^
|
||||
color.hashCode ^
|
||||
authorName.hashCode ^
|
||||
authorLink.hashCode ^
|
||||
authorIcon.hashCode ^
|
||||
assetUrl.hashCode ^
|
||||
actions.hashCode ^
|
||||
extraData.hashCode;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'attachment.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Attachment _$AttachmentFromJson(Map json) {
|
||||
return Attachment(
|
||||
type: json['type'] as String,
|
||||
titleLink: json['title_link'] as String,
|
||||
title: json['title'] as String,
|
||||
thumbUrl: json['thumb_url'] as String,
|
||||
text: json['text'] as String,
|
||||
pretext: json['pretext'] as String,
|
||||
ogScrapeUrl: json['og_scrape_url'] as String,
|
||||
imageUrl: json['image_url'] as String,
|
||||
footerIcon: json['footer_icon'] as String,
|
||||
footer: json['footer'] as String,
|
||||
fields: json['fields'],
|
||||
fallback: json['fallback'] as String,
|
||||
color: json['color'] as String,
|
||||
authorName: json['author_name'] as String,
|
||||
authorLink: json['author_link'] as String,
|
||||
authorIcon: json['author_icon'] as String,
|
||||
assetUrl: json['asset_url'] as String,
|
||||
actions: (json['actions'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Action.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList(),
|
||||
extraData: (json['extra_data'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
),
|
||||
localUri: json['local_uri'] == null
|
||||
? null
|
||||
: Uri.parse(json['local_uri'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$AttachmentToJson(Attachment instance) {
|
||||
final val = <String, dynamic>{};
|
||||
|
||||
void writeNotNull(String key, dynamic value) {
|
||||
if (value != null) {
|
||||
val[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
writeNotNull('type', instance.type);
|
||||
writeNotNull('title_link', instance.titleLink);
|
||||
writeNotNull('title', instance.title);
|
||||
writeNotNull('thumb_url', instance.thumbUrl);
|
||||
writeNotNull('text', instance.text);
|
||||
writeNotNull('pretext', instance.pretext);
|
||||
writeNotNull('og_scrape_url', instance.ogScrapeUrl);
|
||||
writeNotNull('image_url', instance.imageUrl);
|
||||
writeNotNull('footer_icon', instance.footerIcon);
|
||||
writeNotNull('footer', instance.footer);
|
||||
writeNotNull('fields', instance.fields);
|
||||
writeNotNull('fallback', instance.fallback);
|
||||
writeNotNull('color', instance.color);
|
||||
writeNotNull('author_name', instance.authorName);
|
||||
writeNotNull('author_link', instance.authorLink);
|
||||
writeNotNull('author_icon', instance.authorIcon);
|
||||
writeNotNull('asset_url', instance.assetUrl);
|
||||
writeNotNull('actions', instance.actions?.map((e) => e?.toJson())?.toList());
|
||||
writeNotNull('local_uri', instance.localUri?.toString());
|
||||
writeNotNull('extra_data', instance.extraData);
|
||||
return val;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'command.dart';
|
||||
|
||||
part 'channel_config.g.dart';
|
||||
|
||||
/// The class that contains the information about the configuration of a channel
|
||||
@JsonSerializable()
|
||||
class ChannelConfig {
|
||||
/// Moderation configuration
|
||||
final String automod;
|
||||
|
||||
/// List of available commands
|
||||
final List<Command> commands;
|
||||
|
||||
/// True if the channel should send connect events
|
||||
final bool connectEvents;
|
||||
|
||||
/// Date of channel creation
|
||||
final DateTime createdAt;
|
||||
|
||||
/// Date of last channel update
|
||||
final DateTime updatedAt;
|
||||
|
||||
/// Max channel message length
|
||||
final int maxMessageLength;
|
||||
|
||||
/// Duration of message retention
|
||||
final String messageRetention;
|
||||
|
||||
/// True if users can be muted
|
||||
final bool mutes;
|
||||
|
||||
/// Name of the channel
|
||||
final String name;
|
||||
|
||||
/// True if reaction are active for this channel
|
||||
final bool reactions;
|
||||
|
||||
/// True if readEvents are active for this channel
|
||||
final bool readEvents;
|
||||
|
||||
/// True if reply message are active for this channel
|
||||
final bool replies;
|
||||
|
||||
/// True if it's possible to perform a search in this channel
|
||||
final bool search;
|
||||
|
||||
/// True if typing events should be sent for this channel
|
||||
final bool typingEvents;
|
||||
|
||||
/// True if it's possible to upload files to this channel
|
||||
final bool uploads;
|
||||
|
||||
/// True if urls appears as attachments
|
||||
final bool urlEnrichment;
|
||||
|
||||
/// Constructor used for json serialization
|
||||
ChannelConfig({
|
||||
this.automod,
|
||||
this.commands,
|
||||
this.connectEvents,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.maxMessageLength,
|
||||
this.messageRetention,
|
||||
this.mutes,
|
||||
this.name,
|
||||
this.reactions,
|
||||
this.readEvents,
|
||||
this.replies,
|
||||
this.search,
|
||||
this.typingEvents,
|
||||
this.uploads,
|
||||
this.urlEnrichment,
|
||||
});
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory ChannelConfig.fromJson(Map<String, dynamic> json) =>
|
||||
_$ChannelConfigFromJson(json);
|
||||
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() => _$ChannelConfigToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'channel_config.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ChannelConfig _$ChannelConfigFromJson(Map json) {
|
||||
return ChannelConfig(
|
||||
automod: json['automod'] as String,
|
||||
commands: (json['commands'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Command.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList(),
|
||||
connectEvents: json['connect_events'] as bool,
|
||||
createdAt: json['created_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: json['updated_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['updated_at'] as String),
|
||||
maxMessageLength: json['max_message_length'] as int,
|
||||
messageRetention: json['message_retention'] as String,
|
||||
mutes: json['mutes'] as bool,
|
||||
name: json['name'] as String,
|
||||
reactions: json['reactions'] as bool,
|
||||
readEvents: json['read_events'] as bool,
|
||||
replies: json['replies'] as bool,
|
||||
search: json['search'] as bool,
|
||||
typingEvents: json['typing_events'] as bool,
|
||||
uploads: json['uploads'] as bool,
|
||||
urlEnrichment: json['url_enrichment'] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$ChannelConfigToJson(ChannelConfig instance) =>
|
||||
<String, dynamic>{
|
||||
'automod': instance.automod,
|
||||
'commands': instance.commands?.map((e) => e?.toJson())?.toList(),
|
||||
'connect_events': instance.connectEvents,
|
||||
'created_at': instance.createdAt?.toIso8601String(),
|
||||
'updated_at': instance.updatedAt?.toIso8601String(),
|
||||
'max_message_length': instance.maxMessageLength,
|
||||
'message_retention': instance.messageRetention,
|
||||
'mutes': instance.mutes,
|
||||
'name': instance.name,
|
||||
'reactions': instance.reactions,
|
||||
'read_events': instance.readEvents,
|
||||
'replies': instance.replies,
|
||||
'search': instance.search,
|
||||
'typing_events': instance.typingEvents,
|
||||
'uploads': instance.uploads,
|
||||
'url_enrichment': instance.urlEnrichment,
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'channel_config.dart';
|
||||
import 'serialization.dart';
|
||||
import 'user.dart';
|
||||
|
||||
part 'channel_model.g.dart';
|
||||
|
||||
/// The class that contains the information about a channel
|
||||
@JsonSerializable()
|
||||
class ChannelModel {
|
||||
/// The id of this channel
|
||||
final String id;
|
||||
|
||||
/// The type of this channel
|
||||
final String type;
|
||||
|
||||
/// The cid of this channel
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
final String cid;
|
||||
|
||||
/// The channel configuration data
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
final ChannelConfig config;
|
||||
|
||||
/// The user that created this channel
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
final User createdBy;
|
||||
|
||||
/// True if this channel is frozen
|
||||
@JsonKey(includeIfNull: false)
|
||||
final bool frozen;
|
||||
|
||||
/// The date of the last message
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
final DateTime lastMessageAt;
|
||||
|
||||
/// The date of channel creation
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
final DateTime createdAt;
|
||||
|
||||
/// The date of the last channel update
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
final DateTime updatedAt;
|
||||
|
||||
/// The date of channel deletion
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
final DateTime deletedAt;
|
||||
|
||||
/// The count of this channel members
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
final int memberCount;
|
||||
|
||||
/// Map of custom channel extraData
|
||||
@JsonKey(includeIfNull: false)
|
||||
final Map<String, dynamic> extraData;
|
||||
|
||||
/// The team the channel belongs to
|
||||
@JsonKey(includeIfNull: false, toJson: Serialization.readOnly)
|
||||
final String team;
|
||||
|
||||
/// Known top level fields.
|
||||
/// Useful for [Serialization] methods.
|
||||
static const topLevelFields = [
|
||||
'id',
|
||||
'type',
|
||||
'cid',
|
||||
'config',
|
||||
'created_by',
|
||||
'frozen',
|
||||
'last_message_at',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
'member_count',
|
||||
'team',
|
||||
];
|
||||
|
||||
/// Constructor used for json serialization
|
||||
ChannelModel({
|
||||
this.id,
|
||||
this.type,
|
||||
this.cid,
|
||||
this.config,
|
||||
this.createdBy,
|
||||
this.frozen,
|
||||
this.lastMessageAt,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.deletedAt,
|
||||
this.memberCount,
|
||||
this.extraData,
|
||||
this.team,
|
||||
});
|
||||
|
||||
/// Shortcut for channel name
|
||||
String get name =>
|
||||
extraData?.containsKey('name') == true ? extraData['name'] : cid;
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory ChannelModel.fromJson(Map<String, dynamic> json) {
|
||||
return _$ChannelModelFromJson(
|
||||
Serialization.moveToExtraDataFromRoot(json, topLevelFields));
|
||||
}
|
||||
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() {
|
||||
return Serialization.moveFromExtraDataToRoot(
|
||||
_$ChannelModelToJson(this),
|
||||
topLevelFields,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a copy of [ChannelModel] with specified attributes overridden.
|
||||
ChannelModel copyWith({
|
||||
String id,
|
||||
String type,
|
||||
String cid,
|
||||
ChannelConfig config,
|
||||
User createdBy,
|
||||
bool frozen,
|
||||
DateTime lastMessageAt,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime deletedAt,
|
||||
int memberCount,
|
||||
Map<String, dynamic> extraData,
|
||||
String team,
|
||||
}) =>
|
||||
ChannelModel(
|
||||
id: id ?? this.id,
|
||||
type: type ?? this.type,
|
||||
cid: cid ?? this.cid,
|
||||
config: config ?? this.config,
|
||||
createdBy: createdBy ?? this.createdBy,
|
||||
frozen: frozen ?? this.frozen,
|
||||
lastMessageAt: lastMessageAt ?? this.lastMessageAt,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
deletedAt: deletedAt ?? this.deletedAt,
|
||||
memberCount: memberCount ?? this.memberCount,
|
||||
extraData: extraData ?? this.extraData,
|
||||
team: team ?? this.team,
|
||||
);
|
||||
|
||||
/// Returns a new [ChannelModel] that is a combination of this channelModel and the given
|
||||
/// [other] channelModel.
|
||||
ChannelModel merge(ChannelModel other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
id: other.id,
|
||||
type: other.type,
|
||||
cid: other.cid,
|
||||
config: other.config,
|
||||
createdBy: other.createdBy,
|
||||
frozen: other.frozen,
|
||||
lastMessageAt: other.lastMessageAt,
|
||||
createdAt: other.createdAt,
|
||||
updatedAt: other.updatedAt,
|
||||
deletedAt: other.deletedAt,
|
||||
memberCount: other.memberCount,
|
||||
extraData: other.extraData,
|
||||
team: other.team,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'channel_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ChannelModel _$ChannelModelFromJson(Map json) {
|
||||
return ChannelModel(
|
||||
id: json['id'] as String,
|
||||
type: json['type'] as String,
|
||||
cid: json['cid'] as String,
|
||||
config: json['config'] == null
|
||||
? null
|
||||
: ChannelConfig.fromJson((json['config'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
createdBy: json['created_by'] == null
|
||||
? null
|
||||
: User.fromJson((json['created_by'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
frozen: json['frozen'] as bool,
|
||||
lastMessageAt: json['last_message_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['last_message_at'] as String),
|
||||
createdAt: json['created_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: json['updated_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['updated_at'] as String),
|
||||
deletedAt: json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
memberCount: json['member_count'] as int,
|
||||
extraData: (json['extra_data'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
),
|
||||
team: json['team'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$ChannelModelToJson(ChannelModel instance) {
|
||||
final val = <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'type': instance.type,
|
||||
};
|
||||
|
||||
void writeNotNull(String key, dynamic value) {
|
||||
if (value != null) {
|
||||
val[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
writeNotNull('cid', readonly(instance.cid));
|
||||
writeNotNull('config', readonly(instance.config));
|
||||
writeNotNull('created_by', readonly(instance.createdBy));
|
||||
writeNotNull('frozen', instance.frozen);
|
||||
writeNotNull('last_message_at', readonly(instance.lastMessageAt));
|
||||
writeNotNull('created_at', readonly(instance.createdAt));
|
||||
writeNotNull('updated_at', readonly(instance.updatedAt));
|
||||
writeNotNull('deleted_at', readonly(instance.deletedAt));
|
||||
writeNotNull('member_count', readonly(instance.memberCount));
|
||||
writeNotNull('extra_data', instance.extraData);
|
||||
writeNotNull('team', readonly(instance.team));
|
||||
return val;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import '../models/read.dart';
|
||||
import '../models/user.dart';
|
||||
import 'channel_model.dart';
|
||||
import 'member.dart';
|
||||
import 'message.dart';
|
||||
|
||||
part 'channel_state.g.dart';
|
||||
|
||||
/// The class that contains the information about a command
|
||||
@JsonSerializable()
|
||||
class ChannelState {
|
||||
/// The channel to which this state belongs
|
||||
final ChannelModel channel;
|
||||
|
||||
/// A paginated list of channel messages
|
||||
final List<Message> messages;
|
||||
|
||||
/// A paginated list of channel members
|
||||
final List<Member> members;
|
||||
|
||||
/// The count of users watching the channel
|
||||
final int watcherCount;
|
||||
|
||||
/// A paginated list of users watching the channel
|
||||
final List<User> watchers;
|
||||
|
||||
/// The list of channel reads
|
||||
final List<Read> read;
|
||||
|
||||
/// Constructor used for json serialization
|
||||
ChannelState({
|
||||
this.channel,
|
||||
this.messages = const [],
|
||||
this.members = const [],
|
||||
this.watcherCount,
|
||||
this.watchers = const [],
|
||||
this.read = const [],
|
||||
});
|
||||
|
||||
/// Create a new instance from a json
|
||||
static ChannelState fromJson(Map<String, dynamic> json) =>
|
||||
_$ChannelStateFromJson(json);
|
||||
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() => _$ChannelStateToJson(this);
|
||||
|
||||
/// Creates a copy of [ChannelState] with specified attributes overridden.
|
||||
ChannelState copyWith({
|
||||
ChannelModel channel,
|
||||
List<Message> messages,
|
||||
List<Member> members,
|
||||
int watcherCount,
|
||||
List<User> watchers,
|
||||
List<Read> read,
|
||||
}) =>
|
||||
ChannelState(
|
||||
channel: channel ?? this.channel,
|
||||
messages: messages ?? this.messages,
|
||||
members: members ?? this.members,
|
||||
watcherCount: watcherCount ?? this.watcherCount,
|
||||
watchers: watchers ?? this.watchers,
|
||||
read: read ?? this.read,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'channel_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ChannelState _$ChannelStateFromJson(Map json) {
|
||||
return ChannelState(
|
||||
channel: json['channel'] == null
|
||||
? null
|
||||
: ChannelModel.fromJson((json['channel'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
messages: (json['messages'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Message.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList(),
|
||||
members: (json['members'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Member.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList(),
|
||||
watcherCount: json['watcher_count'] as int,
|
||||
watchers: (json['watchers'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: User.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList(),
|
||||
read: (json['read'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Read.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$ChannelStateToJson(ChannelState instance) =>
|
||||
<String, dynamic>{
|
||||
'channel': instance.channel?.toJson(),
|
||||
'messages': instance.messages?.map((e) => e?.toJson())?.toList(),
|
||||
'members': instance.members?.map((e) => e?.toJson())?.toList(),
|
||||
'watcher_count': instance.watcherCount,
|
||||
'watchers': instance.watchers?.map((e) => e?.toJson())?.toList(),
|
||||
'read': instance.read?.map((e) => e?.toJson())?.toList(),
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'command.g.dart';
|
||||
|
||||
/// The class that contains the information about a command
|
||||
@JsonSerializable()
|
||||
class Command {
|
||||
/// The name of the command
|
||||
final String name;
|
||||
|
||||
/// The description explaining the command
|
||||
final String description;
|
||||
|
||||
/// The arguments of the command
|
||||
final String args;
|
||||
|
||||
/// Constructor used for json serialization
|
||||
Command({
|
||||
this.name,
|
||||
this.description,
|
||||
this.args,
|
||||
});
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory Command.fromJson(Map<String, dynamic> json) =>
|
||||
_$CommandFromJson(json);
|
||||
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() => _$CommandToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'command.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Command _$CommandFromJson(Map json) {
|
||||
return Command(
|
||||
name: json['name'] as String,
|
||||
description: json['description'] as String,
|
||||
args: json['args'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$CommandToJson(Command instance) => <String, dynamic>{
|
||||
'name': instance.name,
|
||||
'description': instance.description,
|
||||
'args': instance.args,
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'device.g.dart';
|
||||
|
||||
/// The class that contains the information about a device
|
||||
@JsonSerializable()
|
||||
class Device {
|
||||
/// The id of the device
|
||||
final String id;
|
||||
|
||||
/// The notification push provider
|
||||
final String pushProvider;
|
||||
|
||||
/// Constructor used for json serialization
|
||||
Device({
|
||||
this.id,
|
||||
this.pushProvider,
|
||||
});
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory Device.fromJson(Map<String, dynamic> json) => _$DeviceFromJson(json);
|
||||
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() => _$DeviceToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'device.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Device _$DeviceFromJson(Map json) {
|
||||
return Device(
|
||||
id: json['id'] as String,
|
||||
pushProvider: json['push_provider'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$DeviceToJson(Device instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'push_provider': instance.pushProvider,
|
||||
};
|
||||
@@ -0,0 +1,183 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:stream_chat/src/models/channel_model.dart';
|
||||
import 'package:stream_chat/src/models/message.dart';
|
||||
import 'package:stream_chat/src/models/serialization.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
import '../event_type.dart';
|
||||
import 'member.dart';
|
||||
import 'own_user.dart';
|
||||
import 'reaction.dart';
|
||||
import 'user.dart';
|
||||
|
||||
part 'event.g.dart';
|
||||
|
||||
/// The class that contains the information about an event
|
||||
@JsonSerializable()
|
||||
class Event {
|
||||
/// The type of the event
|
||||
/// [EventType] contains some predefined constant types
|
||||
final String type;
|
||||
|
||||
/// The channel cid to which the event belongs
|
||||
final String cid;
|
||||
|
||||
/// The channel id to which the event belongs
|
||||
final String channelId;
|
||||
|
||||
/// The channel type to which the event belongs
|
||||
final String channelType;
|
||||
|
||||
/// The connection id in which the event has been sent
|
||||
final String connectionId;
|
||||
|
||||
/// The date of creation of the event
|
||||
final DateTime createdAt;
|
||||
|
||||
/// User object of the health check user
|
||||
final OwnUser me;
|
||||
|
||||
/// User object of the current user
|
||||
final User user;
|
||||
|
||||
/// The message sent with the event
|
||||
final Message message;
|
||||
|
||||
/// The channel sent with the event
|
||||
final EventChannel channel;
|
||||
|
||||
/// The member sent with the event
|
||||
final Member member;
|
||||
|
||||
/// The reaction sent with the event
|
||||
final Reaction reaction;
|
||||
|
||||
/// The number of unread messages for current user
|
||||
final int totalUnreadCount;
|
||||
|
||||
/// User total unread channels
|
||||
final int unreadChannels;
|
||||
|
||||
/// Online status
|
||||
final bool online;
|
||||
|
||||
/// The id of the parent message of a thread
|
||||
final String parentId;
|
||||
|
||||
/// True if the event is generated by this client
|
||||
bool isLocal;
|
||||
|
||||
/// Map of custom channel extraData
|
||||
@JsonKey(includeIfNull: false)
|
||||
final Map<String, dynamic> extraData;
|
||||
|
||||
/// Constructor used for json serialization
|
||||
Event({
|
||||
this.type,
|
||||
this.cid,
|
||||
this.connectionId,
|
||||
this.createdAt,
|
||||
this.me,
|
||||
this.user,
|
||||
this.message,
|
||||
this.totalUnreadCount,
|
||||
this.unreadChannels,
|
||||
this.reaction,
|
||||
this.online,
|
||||
this.channel,
|
||||
this.member,
|
||||
this.channelId,
|
||||
this.channelType,
|
||||
this.parentId,
|
||||
this.extraData,
|
||||
}) : isLocal = true;
|
||||
|
||||
/// Known top level fields.
|
||||
/// Useful for [Serialization] methods.
|
||||
static final topLevelFields = [
|
||||
'type',
|
||||
'cid',
|
||||
'connection_id',
|
||||
'created_at',
|
||||
'me',
|
||||
'user',
|
||||
'message',
|
||||
'total_unread_count',
|
||||
'unread_channels',
|
||||
'reaction',
|
||||
'online',
|
||||
'channel',
|
||||
'member',
|
||||
'channel_id',
|
||||
'channel_type',
|
||||
'parent_id',
|
||||
'is_local',
|
||||
];
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory Event.fromJson(Map<String, dynamic> json) {
|
||||
return _$EventFromJson(Serialization.moveToExtraDataFromRoot(
|
||||
json,
|
||||
topLevelFields,
|
||||
))
|
||||
..isLocal = false;
|
||||
}
|
||||
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
|
||||
_$EventToJson(this),
|
||||
topLevelFields,
|
||||
);
|
||||
}
|
||||
|
||||
/// The channel embedded in the event object
|
||||
@JsonSerializable()
|
||||
class EventChannel extends ChannelModel {
|
||||
/// A paginated list of channel members
|
||||
final List<Member> members;
|
||||
|
||||
/// Known top level fields.
|
||||
/// Useful for [Serialization] methods.
|
||||
static final topLevelFields = [
|
||||
'members',
|
||||
...ChannelModel.topLevelFields,
|
||||
];
|
||||
|
||||
/// Constructor used for json serialization
|
||||
EventChannel({
|
||||
this.members,
|
||||
String id,
|
||||
String type,
|
||||
String cid,
|
||||
ChannelConfig config,
|
||||
User createdBy,
|
||||
bool frozen,
|
||||
DateTime lastMessageAt,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime deletedAt,
|
||||
int memberCount,
|
||||
Map<String, dynamic> extraData,
|
||||
}) : super(
|
||||
id: id,
|
||||
type: type,
|
||||
cid: cid,
|
||||
config: config,
|
||||
createdBy: createdBy,
|
||||
frozen: frozen,
|
||||
lastMessageAt: lastMessageAt,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
deletedAt: deletedAt,
|
||||
memberCount: memberCount,
|
||||
extraData: extraData,
|
||||
);
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory EventChannel.fromJson(Map<String, dynamic> json) {
|
||||
return _$EventChannelFromJson(Serialization.moveToExtraDataFromRoot(
|
||||
json,
|
||||
topLevelFields,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'event.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Event _$EventFromJson(Map json) {
|
||||
return Event(
|
||||
type: json['type'] as String,
|
||||
cid: json['cid'] as String,
|
||||
connectionId: json['connection_id'] as String,
|
||||
createdAt: json['created_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['created_at'] as String),
|
||||
me: json['me'] == null
|
||||
? null
|
||||
: OwnUser.fromJson((json['me'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
user: json['user'] == null
|
||||
? null
|
||||
: User.fromJson((json['user'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
message: json['message'] == null
|
||||
? null
|
||||
: Message.fromJson((json['message'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
totalUnreadCount: json['total_unread_count'] as int,
|
||||
unreadChannels: json['unread_channels'] as int,
|
||||
reaction: json['reaction'] == null
|
||||
? null
|
||||
: Reaction.fromJson((json['reaction'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
online: json['online'] as bool,
|
||||
channel: json['channel'] == null
|
||||
? null
|
||||
: EventChannel.fromJson((json['channel'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
member: json['member'] == null
|
||||
? null
|
||||
: Member.fromJson((json['member'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
channelId: json['channel_id'] as String,
|
||||
channelType: json['channel_type'] as String,
|
||||
parentId: json['parent_id'] as String,
|
||||
extraData: (json['extra_data'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
),
|
||||
)..isLocal = json['is_local'] as bool;
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$EventToJson(Event instance) {
|
||||
final val = <String, dynamic>{
|
||||
'type': instance.type,
|
||||
'cid': instance.cid,
|
||||
'channel_id': instance.channelId,
|
||||
'channel_type': instance.channelType,
|
||||
'connection_id': instance.connectionId,
|
||||
'created_at': instance.createdAt?.toIso8601String(),
|
||||
'me': instance.me?.toJson(),
|
||||
'user': instance.user?.toJson(),
|
||||
'message': instance.message?.toJson(),
|
||||
'channel': instance.channel?.toJson(),
|
||||
'member': instance.member?.toJson(),
|
||||
'reaction': instance.reaction?.toJson(),
|
||||
'total_unread_count': instance.totalUnreadCount,
|
||||
'unread_channels': instance.unreadChannels,
|
||||
'online': instance.online,
|
||||
'parent_id': instance.parentId,
|
||||
'is_local': instance.isLocal,
|
||||
};
|
||||
|
||||
void writeNotNull(String key, dynamic value) {
|
||||
if (value != null) {
|
||||
val[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
writeNotNull('extra_data', instance.extraData);
|
||||
return val;
|
||||
}
|
||||
|
||||
EventChannel _$EventChannelFromJson(Map json) {
|
||||
return EventChannel(
|
||||
members: (json['members'] as List)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: Member.fromJson((e as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)))
|
||||
?.toList(),
|
||||
id: json['id'] as String,
|
||||
type: json['type'] as String,
|
||||
cid: json['cid'] as String,
|
||||
config: json['config'] == null
|
||||
? null
|
||||
: ChannelConfig.fromJson((json['config'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
createdBy: json['created_by'] == null
|
||||
? null
|
||||
: User.fromJson((json['created_by'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
frozen: json['frozen'] as bool,
|
||||
lastMessageAt: json['last_message_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['last_message_at'] as String),
|
||||
createdAt: json['created_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: json['updated_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['updated_at'] as String),
|
||||
deletedAt: json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
memberCount: json['member_count'] as int,
|
||||
extraData: (json['extra_data'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$EventChannelToJson(EventChannel instance) {
|
||||
final val = <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'type': instance.type,
|
||||
};
|
||||
|
||||
void writeNotNull(String key, dynamic value) {
|
||||
if (value != null) {
|
||||
val[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
writeNotNull('cid', readonly(instance.cid));
|
||||
writeNotNull('config', readonly(instance.config));
|
||||
writeNotNull('created_by', readonly(instance.createdBy));
|
||||
writeNotNull('frozen', instance.frozen);
|
||||
writeNotNull('last_message_at', readonly(instance.lastMessageAt));
|
||||
writeNotNull('created_at', readonly(instance.createdAt));
|
||||
writeNotNull('updated_at', readonly(instance.updatedAt));
|
||||
writeNotNull('deleted_at', readonly(instance.deletedAt));
|
||||
writeNotNull('member_count', readonly(instance.memberCount));
|
||||
writeNotNull('extra_data', instance.extraData);
|
||||
val['members'] = instance.members?.map((e) => e?.toJson())?.toList();
|
||||
return val;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import '../models/user.dart';
|
||||
|
||||
part 'member.g.dart';
|
||||
|
||||
/// The class that contains the information about the user membership in a channel
|
||||
@JsonSerializable()
|
||||
class Member {
|
||||
/// The interested user
|
||||
final User user;
|
||||
|
||||
/// The date in which the user accepted the invite to the channel
|
||||
final DateTime inviteAcceptedAt;
|
||||
|
||||
/// The date in which the user rejected the invite to the channel
|
||||
final DateTime inviteRejectedAt;
|
||||
|
||||
/// True if the user has been invited to the channel
|
||||
final bool invited;
|
||||
|
||||
/// The role of the user in the channel
|
||||
final String role;
|
||||
|
||||
/// The id of the interested user
|
||||
final String userId;
|
||||
|
||||
/// True if the user is a moderator of the channel
|
||||
final bool isModerator;
|
||||
|
||||
/// True if the member is banned from the channel
|
||||
final bool banned;
|
||||
|
||||
/// True if the member is shadow banned from the channel
|
||||
final bool shadowBanned;
|
||||
|
||||
/// The date of creation
|
||||
final DateTime createdAt;
|
||||
|
||||
/// The last date of update
|
||||
final DateTime updatedAt;
|
||||
|
||||
/// Constructor used for json serialization
|
||||
Member({
|
||||
this.user,
|
||||
this.inviteAcceptedAt,
|
||||
this.inviteRejectedAt,
|
||||
this.invited,
|
||||
this.role,
|
||||
this.userId,
|
||||
this.isModerator,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.banned,
|
||||
this.shadowBanned,
|
||||
});
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory Member.fromJson(Map<String, dynamic> json) {
|
||||
final member = _$MemberFromJson(json);
|
||||
return member.copyWith(
|
||||
userId: member.user?.id,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a copy of [Member] with specified attributes overridden.
|
||||
Member copyWith({
|
||||
User user,
|
||||
DateTime inviteAcceptedAt,
|
||||
DateTime inviteRejectedAt,
|
||||
bool invited,
|
||||
String role,
|
||||
String userId,
|
||||
bool isModerator,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
bool banned,
|
||||
bool shadowBanned,
|
||||
}) =>
|
||||
Member(
|
||||
user: user ?? this.user,
|
||||
inviteAcceptedAt: inviteAcceptedAt ?? this.inviteAcceptedAt,
|
||||
inviteRejectedAt: inviteRejectedAt ?? this.inviteRejectedAt,
|
||||
invited: invited ?? this.invited,
|
||||
banned: banned ?? this.banned,
|
||||
shadowBanned: shadowBanned ?? this.shadowBanned,
|
||||
role: role ?? this.role,
|
||||
userId: userId ?? this.userId,
|
||||
isModerator: isModerator ?? this.isModerator,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() => _$MemberToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'member.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Member _$MemberFromJson(Map json) {
|
||||
return Member(
|
||||
user: json['user'] == null
|
||||
? null
|
||||
: User.fromJson((json['user'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
inviteAcceptedAt: json['invite_accepted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['invite_accepted_at'] as String),
|
||||
inviteRejectedAt: json['invite_rejected_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['invite_rejected_at'] as String),
|
||||
invited: json['invited'] as bool,
|
||||
role: json['role'] as String,
|
||||
userId: json['user_id'] as String,
|
||||
isModerator: json['is_moderator'] as bool,
|
||||
createdAt: json['created_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: json['updated_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['updated_at'] as String),
|
||||
banned: json['banned'] as bool,
|
||||
shadowBanned: json['shadow_banned'] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$MemberToJson(Member instance) => <String, dynamic>{
|
||||
'user': instance.user?.toJson(),
|
||||
'invite_accepted_at': instance.inviteAcceptedAt?.toIso8601String(),
|
||||
'invite_rejected_at': instance.inviteRejectedAt?.toIso8601String(),
|
||||
'invited': instance.invited,
|
||||
'role': instance.role,
|
||||
'user_id': instance.userId,
|
||||
'is_moderator': instance.isModerator,
|
||||
'banned': instance.banned,
|
||||
'shadow_banned': instance.shadowBanned,
|
||||
'created_at': instance.createdAt?.toIso8601String(),
|
||||
'updated_at': instance.updatedAt?.toIso8601String(),
|
||||
};
|
||||