Release/1.4.0 beta (#318)

* fix: fixed long system messages overflowing

* fix: fixed long system messages overflowing

* [UI-Kit] Refactor, add support for downloading attachments.

Signed-off-by: Sahil Kumar <[email protected]>

* fix: header casing

* [UI-Kit] Disable attachment download dialog pop using back button

Signed-off-by: Sahil Kumar <[email protected]>

* stream_chat_flutter fix svg on web

* [UI-Kit] Pop attachment download dialog in case of any error.

Signed-off-by: Sahil Kumar <[email protected]>

* restore svg

* feat: add pr title linter

* fix test

* fix client headers

* update system message design

* update gh token env var

* add scopes

* expose systemMessageBuilder in messagelistview

* fix(ui): fix button size

* add dispatch workflow

* add dispatch workflow

* update dispatch acton

* add runson

* fix ref

* fix ref

* update secret

* specify repo

* fix: list scroll keyboard behaviour  (#304)

* fix: Keyboard now closes by clicking on space or scrolling.

* fix: Exposed system and normal message tap builders

* fmt: dartfmt

* feat: Add support for messages filter in `MessageListView` and `MessageListCore` (#303)

* [MessageListView, MessageListCore] Add support for message filter

Signed-off-by: Sahil Kumar <[email protected]>

* [UI-Kit -> Pubspec] Remove `stream_chat_flutter_core` relative import

Signed-off-by: Sahil Kumar <[email protected]>

* add relative import

* add relative import

* remove svg for web

* remove badge logic on web

* style: Update for team lint (#297)

* Update for team lint

* fix tests

* remove pedantic and sort deps

* remove jiffy from system message

Co-authored-by: Salvatore Giordano <[email protected]>

* add section highlighting sample app repo (#307)

Co-authored-by: Salvatore Giordano <[email protected]>

* add null check to debounce

* fix messagelistview loading errors

* remove core relative import

* fix: fixes message newline issue (#308)

* fix: minor

* fix: generate image thumbnails and use transparent image as placeholder for cached network images (#310)

* update cdn handling

* fix image attachment with new cdn

* fix(ui): Fix message input permission request crash (#311)

Signed-off-by: Sahil Kumar <[email protected]>

* fix(llc): update member presence and add skip_push to message (#314)

* update member presence

* add skip_push to message

* fix tests

* fix: Fixed shimmer overflow (#316)

* fix dialogs

* fix(llc): Fix attachment upload state uneven progress. (#315)

* feat(llc): Add rate limiter functions.
fix(llc): Fix attachment upload state uneven progress.

Signed-off-by: Sahil Kumar <[email protected]>

* feat(llc): Export `async.dart` and `rate_limit.dart`

Signed-off-by: Sahil Kumar <[email protected]>

* refactor(llc): Replace our `rate_limit` implementation with the dart port of js `lodash` to better handlw `cancel` and `flush` functions.

Signed-off-by: Sahil Kumar <[email protected]>

* fix(UI-Kit): Make android example run on real devices

Signed-off-by: Sahil Kumar <[email protected]>

* fix offline behaviour

Co-authored-by: Salvatore Giordano <[email protected]>

* feat: Introduce onAttachmentTap on messages attachment (#309)

* introdce onAttachmentTap on message

* correct indent

* sdfsd

Co-authored-by: Salvatore Giordano <[email protected]>

* feat: added customization options in main widgets (#312)

* polish channelpreview customization options

* polish channelheader customization options

* polish messageinput customization options

* polish threadheader customization options

* polish channellistheader customization options

* show the parent message if no messages in thread

* fix edit message

* fix channelinfo textstyle

* fix review

* fix useravatar

* add ontitle tap to thread header

* add custom message actions

* add borderradius and button builder

* add ontap to messageinput custom send button

* fix bottom sheet

* add doc

* use placeholder image

* add doc

* fix listtile density

* remove subtitle from ChannelListHeaderTheme.copyWith

* add InputDecoration.merge extension

* fix: use shimmer in images

* fix: remove notification badge logic from the sdk and move it to sample app

* fix import

* fix: Method being called during build (#317)

* method call bug fix

* dartfmt

* fix: show error messages as system and keep them in the message input (#319)

* fix: pre release (#324)

* use share_plus plugin

* bump dependency versions

* fix ui for web

* unfocus messageinput only on commands

* fix default error for messagesearchlistview

* fix thumbnail animation

* fix lint

* ignore example in linter

* update changelogs

* fix lint

* fix lint

* update stream_chat pana min

* add doc

Co-authored-by: Sahil Kumar <[email protected]>
Co-authored-by: Deven Joshi <[email protected]>
Co-authored-by: Neevash Ramdial (Nash) <[email protected]>
Co-authored-by: Nelson Nunes <[email protected]>
This commit is contained in:
Salvatore Giordano
2021-03-09 18:58:23 +01:00
committed by GitHub
co-authored by Sahil Kumar Deven Joshi Neevash Ramdial Nelson Nunes
parent 3332a48a0e
commit 4488fa6c3a
121 changed files with 3768 additions and 2376 deletions
+14
View File
@@ -1,3 +1,17 @@
## 1.4.0-beta
- Unfocus `MessageInput` only when sending commands
- Updated default error for `MessageSearchListView`
- Show error messages as system and keep them in the message input
- Remove notification badge logic
- Use shimmer while loading images
- Polished `StreamChatTheme` adding more options and a new `MessageInputTheme` dedicated to `MessageInput`
- Add possibility to specify custom message actions using `MessageWidget.customActions`
- Added `MessageListView.onAttachmentTap` callback
- Fixed message newline issue
- Fixed `MessageListView` scroll keyboard behaviour
- Minor fixes and improveqments
## 1.3.2-beta
- Updated `stream_chat_core` dependency
@@ -0,0 +1 @@
/Users/salvatoregiordano/fvm/versions/beta
@@ -0,0 +1,3 @@
{
"flutterSdkVersion": "beta"
}
@@ -34,6 +34,7 @@ android {
lintOptions {
disable 'InvalidPackage'
checkReleaseBuilds false
}
defaultConfig {
@@ -7,7 +7,7 @@
FlutterApplication and put your custom class here. -->
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name="io.flutter.app.FlutterApplication"
android:name=".Application"
android:label="example"
android:icon="@mipmap/ic_launcher">
<activity
@@ -0,0 +1,17 @@
package com.example.example
import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.pathprovider.PathProviderPlugin
class Application : FlutterApplication(), PluginRegistrantCallback {
override fun onCreate() {
super.onCreate()
}
override fun registerWith(registry: PluginRegistry?) {
PathProviderPlugin.registerWith(registry?.registrarFor(
"io.flutter.plugins.pathprovider.PathProviderPlugin"))
}
}
@@ -1,6 +1,12 @@
package com.example.example
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}
@@ -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:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<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:colorBackground</item>
</style>
</resources>
@@ -6,7 +6,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.android.tools.build:gradle:3.6.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
location = "self:">
</FileRef>
</Workspace>
@@ -44,7 +44,12 @@ class MyApp extends StatelessWidget {
final defaultTheme = StreamChatThemeData.fromTheme(themeData);
final colorTheme = defaultTheme.colorTheme;
final customTheme = defaultTheme.merge(StreamChatThemeData(
ownMessageTheme: MessageTheme(
channelPreviewTheme: ChannelPreviewTheme(
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(8),
),
),
otherMessageTheme: MessageTheme(
messageBackgroundColor: colorTheme.black,
messageText: TextStyle(
color: colorTheme.white,
@@ -25,7 +25,11 @@ dependencies:
sdk: flutter
stream_chat_flutter:
path: ../
stream_chat_persistence: ^1.3.0-beta
stream_chat_persistence:
git:
url: https://github.com/GetStream/stream-chat-flutter.git
ref: develop
path: packages/stream_chat_persistence
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
Fore more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
-->
<base href="/">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<script defer src="sql-wasm.js"></script>
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="example">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>example</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('flutter-first-frame', function () {
navigator.serviceWorker.register('flutter_service_worker.js');
});
}
</script>
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
@@ -0,0 +1,23 @@
{
"name": "example",
"short_name": "example",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
@@ -0,0 +1,203 @@
// We are modularizing this manually because the current modularize setting in Emscripten has some issues:
// https://github.com/kripken/emscripten/issues/5820
// In addition, When you use emcc's modularization, it still expects to export a global object called `Module`,
// which is able to be used/called before the WASM is loaded.
// The modularization below exports a promise that loads and resolves to the actual sql.js module.
// That way, this module can't be used before the WASM is finished loading.
// We are going to define a function that a user will call to start loading initializing our Sql.js library
// However, that function might be called multiple times, and on subsequent calls, we don't actually want it to instantiate a new instance of the Module
// Instead, we want to return the previously loaded module
// TODO: Make this not declare a global if used in the browser
var initSqlJsPromise = undefined;
var initSqlJs = function (moduleConfig) {
if (initSqlJsPromise){
return initSqlJsPromise;
}
// If we're here, we've never called this function before
initSqlJsPromise = new Promise(function (resolveModule, reject) {
// We are modularizing this manually because the current modularize setting in Emscripten has some issues:
// https://github.com/kripken/emscripten/issues/5820
// The way to affect the loading of emcc compiled modules is to create a variable called `Module` and add
// properties to it, like `preRun`, `postRun`, etc
// We are using that to get notified when the WASM has finished loading.
// Only then will we return our promise
// If they passed in a moduleConfig object, use that
// Otherwise, initialize Module to the empty object
var Module = typeof moduleConfig !== 'undefined' ? moduleConfig : {};
// EMCC only allows for a single onAbort function (not an array of functions)
// So if the user defined their own onAbort function, we remember it and call it
var originalOnAbortFunction = Module['onAbort'];
Module['onAbort'] = function (errorThatCausedAbort) {
reject(new Error(errorThatCausedAbort));
if (originalOnAbortFunction){
originalOnAbortFunction(errorThatCausedAbort);
}
};
Module['postRun'] = Module['postRun'] || [];
Module['postRun'].push(function () {
// When Emscripted calls postRun, this promise resolves with the built Module
resolveModule(Module);
});
// There is a section of code in the emcc-generated code below that looks like this:
// (Note that this is lowercase `module`)
// if (typeof module !== 'undefined') {
// module['exports'] = Module;
// }
// When that runs, it's going to overwrite our own modularization export efforts in shell-post.js!
// The only way to tell emcc not to emit it is to pass the MODULARIZE=1 or MODULARIZE_INSTANCE=1 flags,
// but that carries with it additional unnecessary baggage/bugs we don't want either.
// So, we have three options:
// 1) We undefine `module`
// 2) We remember what `module['exports']` was at the beginning of this function and we restore it later
// 3) We write a script to remove those lines of code as part of the Make process.
//
// Since those are the only lines of code that care about module, we will undefine it. It's the most straightforward
// of the options, and has the side effect of reducing emcc's efforts to modify the module if its output were to change in the future.
// That's a nice side effect since we're handling the modularization efforts ourselves
module = undefined;
// The emcc-generated code and shell-post.js code goes below,
// meaning that all of it runs inside of this promise. If anything throws an exception, our promise will abort
var e;e||(e=typeof Module !== 'undefined' ? Module : {});null;
e.onRuntimeInitialized=function(){function a(h,l){this.Ra=h;this.db=l;this.Qa=1;this.lb=[]}function b(h,l){this.db=l;l=aa(h)+1;this.eb=ba(l);if(null===this.eb)throw Error("Unable to allocate memory for the SQL string");k(h,m,this.eb,l);this.jb=this.eb;this.$a=this.pb=null}function c(h){this.filename="dbfile_"+(4294967295*Math.random()>>>0);if(null!=h){var l=this.filename,p=l?r("//"+l):"/";l=ca(!0,!0);p=da(p,(void 0!==l?l:438)&4095|32768,0);if(h){if("string"===typeof h){for(var q=Array(h.length),B=
0,ha=h.length;B<ha;++B)q[B]=h.charCodeAt(B);h=q}ea(p,l|146);q=u(p,"w");fa(q,h,0,h.length,0,void 0);ka(q);ea(p,l)}}this.handleError(g(this.filename,d));this.db=x(d,"i32");nc(this.db);this.fb={};this.Xa={}}var d=y(4),f=e.cwrap,g=f("sqlite3_open","number",["string","number"]),n=f("sqlite3_close_v2","number",["number"]),t=f("sqlite3_exec","number",["number","string","number","number","number"]),w=f("sqlite3_changes","number",["number"]),v=f("sqlite3_prepare_v2","number",["number","string","number","number",
"number"]),C=f("sqlite3_sql","string",["number"]),H=f("sqlite3_normalized_sql","string",["number"]),Y=f("sqlite3_prepare_v2","number",["number","number","number","number","number"]),oc=f("sqlite3_bind_text","number",["number","number","number","number","number"]),pb=f("sqlite3_bind_blob","number",["number","number","number","number","number"]),pc=f("sqlite3_bind_double","number",["number","number","number"]),qc=f("sqlite3_bind_int","number",["number","number","number"]),rc=f("sqlite3_bind_parameter_index",
"number",["number","string"]),sc=f("sqlite3_step","number",["number"]),tc=f("sqlite3_errmsg","string",["number"]),uc=f("sqlite3_column_count","number",["number"]),vc=f("sqlite3_data_count","number",["number"]),wc=f("sqlite3_column_double","number",["number","number"]),xc=f("sqlite3_column_text","string",["number","number"]),yc=f("sqlite3_column_blob","number",["number","number"]),zc=f("sqlite3_column_bytes","number",["number","number"]),Ac=f("sqlite3_column_type","number",["number","number"]),Bc=
f("sqlite3_column_name","string",["number","number"]),Cc=f("sqlite3_reset","number",["number"]),Dc=f("sqlite3_clear_bindings","number",["number"]),Ec=f("sqlite3_finalize","number",["number"]),Fc=f("sqlite3_create_function_v2","number","number string number number number number number number number".split(" ")),Gc=f("sqlite3_value_type","number",["number"]),Hc=f("sqlite3_value_bytes","number",["number"]),Ic=f("sqlite3_value_text","string",["number"]),Jc=f("sqlite3_value_blob","number",["number"]),
Kc=f("sqlite3_value_double","number",["number"]),Lc=f("sqlite3_result_double","",["number","number"]),qb=f("sqlite3_result_null","",["number"]),Mc=f("sqlite3_result_text","",["number","string","number","number"]),Nc=f("sqlite3_result_blob","",["number","number","number","number"]),Oc=f("sqlite3_result_int","",["number","number"]),rb=f("sqlite3_result_error","",["number","string","number"]),nc=f("RegisterExtensionFunctions","number",["number"]);a.prototype.bind=function(h){if(!this.Ra)throw"Statement closed";
this.reset();return Array.isArray(h)?this.Bb(h):null!=h&&"object"===typeof h?this.Cb(h):!0};a.prototype.step=function(){if(!this.Ra)throw"Statement closed";this.Qa=1;var h=sc(this.Ra);switch(h){case 100:return!0;case 101:return!1;default:throw this.db.handleError(h);}};a.prototype.Ib=function(h){null==h&&(h=this.Qa,this.Qa+=1);return wc(this.Ra,h)};a.prototype.Jb=function(h){null==h&&(h=this.Qa,this.Qa+=1);return xc(this.Ra,h)};a.prototype.getBlob=function(h){null==h&&(h=this.Qa,this.Qa+=1);var l=
zc(this.Ra,h);h=yc(this.Ra,h);for(var p=new Uint8Array(l),q=0;q<l;q+=1)p[q]=z[h+q];return p};a.prototype.get=function(h){null!=h&&this.bind(h)&&this.step();h=[];for(var l=vc(this.Ra),p=0;p<l;p+=1)switch(Ac(this.Ra,p)){case 1:case 2:h.push(this.Ib(p));break;case 3:h.push(this.Jb(p));break;case 4:h.push(this.getBlob(p));break;default:h.push(null)}return h};a.prototype.getColumnNames=function(){for(var h=[],l=uc(this.Ra),p=0;p<l;p+=1)h.push(Bc(this.Ra,p));return h};a.prototype.getAsObject=function(h){h=
this.get(h);for(var l=this.getColumnNames(),p={},q=0;q<l.length;q+=1)p[l[q]]=h[q];return p};a.prototype.getSQL=function(){return C(this.Ra)};a.prototype.getNormalizedSQL=function(){return H(this.Ra)};a.prototype.run=function(h){null!=h&&this.bind(h);this.step();return this.reset()};a.prototype.Fb=function(h,l){null==l&&(l=this.Qa,this.Qa+=1);h=la(h);var p=ma(h);this.lb.push(p);this.db.handleError(oc(this.Ra,l,p,h.length-1,0))};a.prototype.Ab=function(h,l){null==l&&(l=this.Qa,this.Qa+=1);var p=ma(h);
this.lb.push(p);this.db.handleError(pb(this.Ra,l,p,h.length,0))};a.prototype.Eb=function(h,l){null==l&&(l=this.Qa,this.Qa+=1);this.db.handleError((h===(h|0)?qc:pc)(this.Ra,l,h))};a.prototype.Db=function(h){null==h&&(h=this.Qa,this.Qa+=1);pb(this.Ra,h,0,0,0)};a.prototype.tb=function(h,l){null==l&&(l=this.Qa,this.Qa+=1);switch(typeof h){case "string":this.Fb(h,l);return;case "number":case "boolean":this.Eb(h+0,l);return;case "object":if(null===h){this.Db(l);return}if(null!=h.length){this.Ab(h,l);return}}throw"Wrong API use : tried to bind a value of an unknown type ("+
h+").";};a.prototype.Cb=function(h){var l=this;Object.keys(h).forEach(function(p){var q=rc(l.Ra,p);0!==q&&l.tb(h[p],q)});return!0};a.prototype.Bb=function(h){for(var l=0;l<h.length;l+=1)this.tb(h[l],l+1);return!0};a.prototype.reset=function(){return 0===Dc(this.Ra)&&0===Cc(this.Ra)};a.prototype.freemem=function(){for(var h;void 0!==(h=this.lb.pop());)na(h)};a.prototype.free=function(){var h=0===Ec(this.Ra);delete this.db.fb[this.Ra];this.Ra=0;return h};b.prototype.next=function(){if(null===this.eb)return{done:!0};
null!==this.$a&&(this.$a.free(),this.$a=null);if(!this.db.db)throw this.nb(),Error("Database closed");var h=oa(),l=y(4);pa(d);pa(l);try{this.db.handleError(Y(this.db.db,this.jb,-1,d,l));this.jb=x(l,"i32");var p=x(d,"i32");if(0===p)return this.nb(),{done:!0};this.$a=new a(p,this.db);this.db.fb[p]=this.$a;return{value:this.$a,done:!1}}catch(q){throw this.pb=A(this.jb),this.nb(),q;}finally{qa(h)}};b.prototype.nb=function(){na(this.eb);this.eb=null};b.prototype.getRemainingSQL=function(){return null!==
this.pb?this.pb:A(this.jb)};"function"===typeof Symbol&&"symbol"===typeof Symbol.iterator&&(b.prototype[Symbol.iterator]=function(){return this});c.prototype.run=function(h,l){if(!this.db)throw"Database closed";if(l){h=this.prepare(h,l);try{h.step()}finally{h.free()}}else this.handleError(t(this.db,h,0,0,d));return this};c.prototype.exec=function(h,l){if(!this.db)throw"Database closed";var p=oa(),q=null;try{var B=aa(h)+1,ha=y(B);k(h,z,ha,B);var D=ha;var ia=y(4);for(h=[];0!==x(D,"i8");){pa(d);pa(ia);
this.handleError(Y(this.db,D,-1,d,ia));var ja=x(d,"i32");D=x(ia,"i32");if(0!==ja){B=null;q=new a(ja,this);for(null!=l&&q.bind(l);q.step();)null===B&&(B={columns:q.getColumnNames(),values:[]},h.push(B)),B.values.push(q.get());q.free()}}return h}catch(E){throw q&&q.free(),E;}finally{qa(p)}};c.prototype.each=function(h,l,p,q){"function"===typeof l&&(q=p,p=l,l=void 0);h=this.prepare(h,l);try{for(;h.step();)p(h.getAsObject())}finally{h.free()}if("function"===typeof q)return q()};c.prototype.prepare=function(h,
l){pa(d);this.handleError(v(this.db,h,-1,d,0));h=x(d,"i32");if(0===h)throw"Nothing to prepare";var p=new a(h,this);null!=l&&p.bind(l);return this.fb[h]=p};c.prototype.iterateStatements=function(h){return new b(h,this)};c.prototype["export"]=function(){Object.values(this.fb).forEach(function(l){l.free()});Object.values(this.Xa).forEach(ra);this.Xa={};this.handleError(n(this.db));var h=sa(this.filename);this.handleError(g(this.filename,d));this.db=x(d,"i32");return h};c.prototype.close=function(){null!==
this.db&&(Object.values(this.fb).forEach(function(h){h.free()}),Object.values(this.Xa).forEach(ra),this.Xa={},this.handleError(n(this.db)),ta("/"+this.filename),this.db=null)};c.prototype.handleError=function(h){if(0===h)return null;h=tc(this.db);throw Error(h);};c.prototype.getRowsModified=function(){return w(this.db)};c.prototype.create_function=function(h,l){Object.prototype.hasOwnProperty.call(this.Xa,h)&&(ua(this.Xa[h]),delete this.Xa[h]);var p=va(function(q,B,ha){for(var D,ia=[],ja=0;ja<B;ja+=
1){var E=x(ha+4*ja,"i32"),R=Gc(E);if(1===R||2===R)E=Kc(E);else if(3===R)E=Ic(E);else if(4===R){R=E;E=Hc(R);R=Jc(R);for(var wb=new Uint8Array(E),Ea=0;Ea<E;Ea+=1)wb[Ea]=z[R+Ea];E=wb}else E=null;ia.push(E)}try{D=l.apply(null,ia)}catch(Rc){rb(q,Rc,-1);return}switch(typeof D){case "boolean":Oc(q,D?1:0);break;case "number":Lc(q,D);break;case "string":Mc(q,D,-1,-1);break;case "object":null===D?qb(q):null!=D.length?(B=ma(D),Nc(q,B,D.length,-1),na(B)):rb(q,"Wrong API use : tried to return a value of an unknown type ("+
D+").",-1);break;default:qb(q)}});this.Xa[h]=p;this.handleError(Fc(this.db,h,l.length,1,0,p,0,0,0));return this};e.Database=c};var wa={},F;for(F in e)e.hasOwnProperty(F)&&(wa[F]=e[F]);var xa="./this.program",ya=!1,G=!1,za=!1,Aa=!1;ya="object"===typeof window;G="function"===typeof importScripts;za="object"===typeof process&&"object"===typeof process.versions&&"string"===typeof process.versions.node;Aa=!ya&&!za&&!G;var I="",Ba,Ca,Da,Fa;
if(za)I=G?require("path").dirname(I)+"/":__dirname+"/",Ba=function(a,b){Da||(Da=require("fs"));Fa||(Fa=require("path"));a=Fa.normalize(a);return Da.readFileSync(a,b?null:"utf8")},Ca=function(a){a=Ba(a,!0);a.buffer||(a=new Uint8Array(a));assert(a.buffer);return a},1<process.argv.length&&(xa=process.argv[1].replace(/\\/g,"/")),process.argv.slice(2),"undefined"!==typeof module&&(module.exports=e),e.inspect=function(){return"[Emscripten Module object]"};else if(Aa)"undefined"!=typeof read&&(Ba=function(a){return read(a)}),
Ca=function(a){if("function"===typeof readbuffer)return new Uint8Array(readbuffer(a));a=read(a,"binary");assert("object"===typeof a);return a},"undefined"!==typeof print&&("undefined"===typeof console&&(console={}),console.log=print,console.warn=console.error="undefined"!==typeof printErr?printErr:print);else if(ya||G)G?I=self.location.href:document.currentScript&&(I=document.currentScript.src),I=0!==I.indexOf("blob:")?I.substr(0,I.lastIndexOf("/")+1):"",Ba=function(a){var b=new XMLHttpRequest;b.open("GET",
a,!1);b.send(null);return b.responseText},G&&(Ca=function(a){var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)});var Ga=e.print||console.log.bind(console),J=e.printErr||console.warn.bind(console);for(F in wa)wa.hasOwnProperty(F)&&(e[F]=wa[F]);wa=null;e.thisProgram&&(xa=e.thisProgram);var Ha=[],Ia;function ua(a){Ia.delete(Ja.get(a));Ha.push(a)}
function va(a){var b=Ja;if(!Ia){Ia=new WeakMap;for(var c=0;c<b.length;c++){var d=b.get(c);d&&Ia.set(d,c)}}if(Ia.has(a))a=Ia.get(a);else{if(Ha.length)c=Ha.pop();else{c=b.length;try{b.grow(1)}catch(n){if(!(n instanceof RangeError))throw n;throw"Unable to grow wasm table. Set ALLOW_TABLE_GROWTH.";}}try{b.set(c,a)}catch(n){if(!(n instanceof TypeError))throw n;if("function"===typeof WebAssembly.Function){var f={i:"i32",j:"i64",f:"f32",d:"f64"},g={parameters:[],results:[]};for(d=1;4>d;++d)g.parameters.push(f["viii"[d]]);
d=new WebAssembly.Function(g,a)}else{f=[1,0,1,96];g={i:127,j:126,f:125,d:124};f.push(3);for(d=0;3>d;++d)f.push(g["iii"[d]]);f.push(0);f[1]=f.length-2;d=new Uint8Array([0,97,115,109,1,0,0,0].concat(f,[2,7,1,1,101,1,102,0,0,7,5,1,1,102,0,0]));d=new WebAssembly.Module(d);d=(new WebAssembly.Instance(d,{e:{f:a}})).exports.f}b.set(c,d)}Ia.set(a,c);a=c}return a}function ra(a){ua(a)}var Ka;e.wasmBinary&&(Ka=e.wasmBinary);var noExitRuntime;e.noExitRuntime&&(noExitRuntime=e.noExitRuntime);
"object"!==typeof WebAssembly&&K("no native wasm support detected");
function pa(a){var b="i32";"*"===b.charAt(b.length-1)&&(b="i32");switch(b){case "i1":z[a>>0]=0;break;case "i8":z[a>>0]=0;break;case "i16":La[a>>1]=0;break;case "i32":L[a>>2]=0;break;case "i64":M=[0,(N=0,1<=+Math.abs(N)?0<N?(Math.min(+Math.floor(N/4294967296),4294967295)|0)>>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];L[a>>2]=M[0];L[a+4>>2]=M[1];break;case "float":Ma[a>>2]=0;break;case "double":Na[a>>3]=0;break;default:K("invalid type for setValue: "+b)}}
function x(a,b){b=b||"i8";"*"===b.charAt(b.length-1)&&(b="i32");switch(b){case "i1":return z[a>>0];case "i8":return z[a>>0];case "i16":return La[a>>1];case "i32":return L[a>>2];case "i64":return L[a>>2];case "float":return Ma[a>>2];case "double":return Na[a>>3];default:K("invalid type for getValue: "+b)}return null}var Oa,Ja,Pa=!1;function assert(a,b){a||K("Assertion failed: "+b)}function Qa(a){var b=e["_"+a];assert(b,"Cannot call unknown function "+a+", make sure it is exported");return b}
function Ra(a,b,c,d){var f={string:function(v){var C=0;if(null!==v&&void 0!==v&&0!==v){var H=(v.length<<2)+1;C=y(H);k(v,m,C,H)}return C},array:function(v){var C=y(v.length);z.set(v,C);return C}},g=Qa(a),n=[];a=0;if(d)for(var t=0;t<d.length;t++){var w=f[c[t]];w?(0===a&&(a=oa()),n[t]=w(d[t])):n[t]=d[t]}c=g.apply(null,n);c=function(v){return"string"===b?A(v):"boolean"===b?!!v:v}(c);0!==a&&qa(a);return c}var Sa=0,Ta=1;
function ma(a){var b=Sa==Ta?y(a.length):ba(a.length);a.subarray||a.slice?m.set(a,b):m.set(new Uint8Array(a),b);return b}var Ua="undefined"!==typeof TextDecoder?new TextDecoder("utf8"):void 0;
function Va(a,b,c){var d=b+c;for(c=b;a[c]&&!(c>=d);)++c;if(16<c-b&&a.subarray&&Ua)return Ua.decode(a.subarray(b,c));for(d="";b<c;){var f=a[b++];if(f&128){var g=a[b++]&63;if(192==(f&224))d+=String.fromCharCode((f&31)<<6|g);else{var n=a[b++]&63;f=224==(f&240)?(f&15)<<12|g<<6|n:(f&7)<<18|g<<12|n<<6|a[b++]&63;65536>f?d+=String.fromCharCode(f):(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else d+=String.fromCharCode(f)}return d}function A(a,b){return a?Va(m,a,b):""}
function k(a,b,c,d){if(!(0<d))return 0;var f=c;d=c+d-1;for(var g=0;g<a.length;++g){var n=a.charCodeAt(g);if(55296<=n&&57343>=n){var t=a.charCodeAt(++g);n=65536+((n&1023)<<10)|t&1023}if(127>=n){if(c>=d)break;b[c++]=n}else{if(2047>=n){if(c+1>=d)break;b[c++]=192|n>>6}else{if(65535>=n){if(c+2>=d)break;b[c++]=224|n>>12}else{if(c+3>=d)break;b[c++]=240|n>>18;b[c++]=128|n>>12&63}b[c++]=128|n>>6&63}b[c++]=128|n&63}}b[c]=0;return c-f}
function aa(a){for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=d&&(d=65536+((d&1023)<<10)|a.charCodeAt(++c)&1023);127>=d?++b:b=2047>=d?b+2:65535>=d?b+3:b+4}return b}function Wa(a){var b=aa(a)+1,c=ba(b);c&&k(a,z,c,b);return c}var Xa,z,m,La,L,Ma,Na;
function Ya(a){Xa=a;e.HEAP8=z=new Int8Array(a);e.HEAP16=La=new Int16Array(a);e.HEAP32=L=new Int32Array(a);e.HEAPU8=m=new Uint8Array(a);e.HEAPU16=new Uint16Array(a);e.HEAPU32=new Uint32Array(a);e.HEAPF32=Ma=new Float32Array(a);e.HEAPF64=Na=new Float64Array(a)}var Za=e.INITIAL_MEMORY||16777216;e.wasmMemory?Oa=e.wasmMemory:Oa=new WebAssembly.Memory({initial:Za/65536,maximum:32768});Oa&&(Xa=Oa.buffer);Za=Xa.byteLength;Ya(Xa);var $a=[],ab=[],bb=[],cb=[];
function db(){var a=e.preRun.shift();$a.unshift(a)}var eb=0,fb=null,gb=null;e.preloadedImages={};e.preloadedAudios={};function K(a){if(e.onAbort)e.onAbort(a);J(a);Pa=!0;throw new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");}function hb(a){var b=ib;return String.prototype.startsWith?b.startsWith(a):0===b.indexOf(a)}function jb(){return hb("data:application/octet-stream;base64,")}var ib="sql-wasm.wasm";
if(!jb()){var kb=ib;ib=e.locateFile?e.locateFile(kb,I):I+kb}function lb(){try{if(Ka)return new Uint8Array(Ka);if(Ca)return Ca(ib);throw"both async and sync fetching of the wasm failed";}catch(a){K(a)}}function mb(){return Ka||!ya&&!G||"function"!==typeof fetch||hb("file://")?Promise.resolve().then(lb):fetch(ib,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+ib+"'";return a.arrayBuffer()}).catch(function(){return lb()})}var N,M;
function nb(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b(e);else{var c=b.Hb;"number"===typeof c?void 0===b.mb?Ja.get(c)():Ja.get(c)(b.mb):c(void 0===b.mb?null:b.mb)}}}function ob(a){return a.replace(/\b_Z[\w\d_]+/g,function(b){return b===b?b:b+" ["+b+"]"})}
function sb(){function a(g){return(g=g.toTimeString().match(/\(([A-Za-z ]+)\)$/))?g[1]:"GMT"}if(!tb){tb=!0;L[ub()>>2]=60*(new Date).getTimezoneOffset();var b=(new Date).getFullYear(),c=new Date(b,0,1);b=new Date(b,6,1);L[vb()>>2]=Number(c.getTimezoneOffset()!=b.getTimezoneOffset());var d=a(c),f=a(b);d=Wa(d);f=Wa(f);b.getTimezoneOffset()<c.getTimezoneOffset()?(L[xb()>>2]=d,L[xb()+4>>2]=f):(L[xb()>>2]=f,L[xb()+4>>2]=d)}}var tb;
function yb(a,b){for(var c=0,d=a.length-1;0<=d;d--){var f=a[d];"."===f?a.splice(d,1):".."===f?(a.splice(d,1),c++):c&&(a.splice(d,1),c--)}if(b)for(;c;c--)a.unshift("..");return a}function r(a){var b="/"===a.charAt(0),c="/"===a.substr(-1);(a=yb(a.split("/").filter(function(d){return!!d}),!b).join("/"))||b||(a=".");a&&c&&(a+="/");return(b?"/":"")+a}
function zb(a){var b=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(a).slice(1);a=b[0];b=b[1];if(!a&&!b)return".";b&&(b=b.substr(0,b.length-1));return a+b}function Ab(a){if("/"===a)return"/";a=r(a);a=a.replace(/\/$/,"");var b=a.lastIndexOf("/");return-1===b?a:a.substr(b+1)}function Bb(a){L[Cb()>>2]=a}
function Db(){if("object"===typeof crypto&&"function"===typeof crypto.getRandomValues){var a=new Uint8Array(1);return function(){crypto.getRandomValues(a);return a[0]}}if(za)try{var b=require("crypto");return function(){return b.randomBytes(1)[0]}}catch(c){}return function(){K("randomDevice")}}
function Eb(){for(var a="",b=!1,c=arguments.length-1;-1<=c&&!b;c--){b=0<=c?arguments[c]:"/";if("string"!==typeof b)throw new TypeError("Arguments to path.resolve must be strings");if(!b)return"";a=b+"/"+a;b="/"===b.charAt(0)}a=yb(a.split("/").filter(function(d){return!!d}),!b).join("/");return(b?"/":"")+a||"."}var Fb=[];function Gb(a,b){Fb[a]={input:[],output:[],cb:b};Hb(a,Ib)}
var Ib={open:function(a){var b=Fb[a.node.rdev];if(!b)throw new O(43);a.tty=b;a.seekable=!1},close:function(a){a.tty.cb.flush(a.tty)},flush:function(a){a.tty.cb.flush(a.tty)},read:function(a,b,c,d){if(!a.tty||!a.tty.cb.xb)throw new O(60);for(var f=0,g=0;g<d;g++){try{var n=a.tty.cb.xb(a.tty)}catch(t){throw new O(29);}if(void 0===n&&0===f)throw new O(6);if(null===n||void 0===n)break;f++;b[c+g]=n}f&&(a.node.timestamp=Date.now());return f},write:function(a,b,c,d){if(!a.tty||!a.tty.cb.qb)throw new O(60);
try{for(var f=0;f<d;f++)a.tty.cb.qb(a.tty,b[c+f])}catch(g){throw new O(29);}d&&(a.node.timestamp=Date.now());return f}},Jb={xb:function(a){if(!a.input.length){var b=null;if(za){var c=Buffer.zb?Buffer.zb(256):new Buffer(256),d=0;try{d=Da.readSync(process.stdin.fd,c,0,256,null)}catch(f){if(-1!=f.toString().indexOf("EOF"))d=0;else throw f;}0<d?b=c.slice(0,d).toString("utf-8"):b=null}else"undefined"!=typeof window&&"function"==typeof window.prompt?(b=window.prompt("Input: "),null!==b&&(b+="\n")):"function"==
typeof readline&&(b=readline(),null!==b&&(b+="\n"));if(!b)return null;a.input=la(b,!0)}return a.input.shift()},qb:function(a,b){null===b||10===b?(Ga(Va(a.output,0)),a.output=[]):0!=b&&a.output.push(b)},flush:function(a){a.output&&0<a.output.length&&(Ga(Va(a.output,0)),a.output=[])}},Kb={qb:function(a,b){null===b||10===b?(J(Va(a.output,0)),a.output=[]):0!=b&&a.output.push(b)},flush:function(a){a.output&&0<a.output.length&&(J(Va(a.output,0)),a.output=[])}},P={Va:null,Wa:function(){return P.createNode(null,
"/",16895,0)},createNode:function(a,b,c,d){if(24576===(c&61440)||4096===(c&61440))throw new O(63);P.Va||(P.Va={dir:{node:{Ua:P.Na.Ua,Ta:P.Na.Ta,lookup:P.Na.lookup,gb:P.Na.gb,rename:P.Na.rename,unlink:P.Na.unlink,rmdir:P.Na.rmdir,readdir:P.Na.readdir,symlink:P.Na.symlink},stream:{Za:P.Oa.Za}},file:{node:{Ua:P.Na.Ua,Ta:P.Na.Ta},stream:{Za:P.Oa.Za,read:P.Oa.read,write:P.Oa.write,sb:P.Oa.sb,hb:P.Oa.hb,ib:P.Oa.ib}},link:{node:{Ua:P.Na.Ua,Ta:P.Na.Ta,readlink:P.Na.readlink},stream:{}},ub:{node:{Ua:P.Na.Ua,
Ta:P.Na.Ta},stream:Lb}});c=Mb(a,b,c,d);Q(c.mode)?(c.Na=P.Va.dir.node,c.Oa=P.Va.dir.stream,c.Ma={}):32768===(c.mode&61440)?(c.Na=P.Va.file.node,c.Oa=P.Va.file.stream,c.Sa=0,c.Ma=null):40960===(c.mode&61440)?(c.Na=P.Va.link.node,c.Oa=P.Va.link.stream):8192===(c.mode&61440)&&(c.Na=P.Va.ub.node,c.Oa=P.Va.ub.stream);c.timestamp=Date.now();a&&(a.Ma[b]=c);return c},Sb:function(a){if(a.Ma&&a.Ma.subarray){for(var b=[],c=0;c<a.Sa;++c)b.push(a.Ma[c]);return b}return a.Ma},Tb:function(a){return a.Ma?a.Ma.subarray?
a.Ma.subarray(0,a.Sa):new Uint8Array(a.Ma):new Uint8Array(0)},vb:function(a,b){var c=a.Ma?a.Ma.length:0;c>=b||(b=Math.max(b,c*(1048576>c?2:1.125)>>>0),0!=c&&(b=Math.max(b,256)),c=a.Ma,a.Ma=new Uint8Array(b),0<a.Sa&&a.Ma.set(c.subarray(0,a.Sa),0))},Pb:function(a,b){if(a.Sa!=b)if(0==b)a.Ma=null,a.Sa=0;else{if(!a.Ma||a.Ma.subarray){var c=a.Ma;a.Ma=new Uint8Array(b);c&&a.Ma.set(c.subarray(0,Math.min(b,a.Sa)))}else if(a.Ma||(a.Ma=[]),a.Ma.length>b)a.Ma.length=b;else for(;a.Ma.length<b;)a.Ma.push(0);a.Sa=
b}},Na:{Ua:function(a){var b={};b.dev=8192===(a.mode&61440)?a.id:1;b.ino=a.id;b.mode=a.mode;b.nlink=1;b.uid=0;b.gid=0;b.rdev=a.rdev;Q(a.mode)?b.size=4096:32768===(a.mode&61440)?b.size=a.Sa:40960===(a.mode&61440)?b.size=a.link.length:b.size=0;b.atime=new Date(a.timestamp);b.mtime=new Date(a.timestamp);b.ctime=new Date(a.timestamp);b.Gb=4096;b.blocks=Math.ceil(b.size/b.Gb);return b},Ta:function(a,b){void 0!==b.mode&&(a.mode=b.mode);void 0!==b.timestamp&&(a.timestamp=b.timestamp);void 0!==b.size&&P.Pb(a,
b.size)},lookup:function(){throw Nb[44];},gb:function(a,b,c,d){return P.createNode(a,b,c,d)},rename:function(a,b,c){if(Q(a.mode)){try{var d=Ob(b,c)}catch(g){}if(d)for(var f in d.Ma)throw new O(55);}delete a.parent.Ma[a.name];a.name=c;b.Ma[c]=a;a.parent=b},unlink:function(a,b){delete a.Ma[b]},rmdir:function(a,b){var c=Ob(a,b),d;for(d in c.Ma)throw new O(55);delete a.Ma[b]},readdir:function(a){var b=[".",".."],c;for(c in a.Ma)a.Ma.hasOwnProperty(c)&&b.push(c);return b},symlink:function(a,b,c){a=P.createNode(a,
b,41471,0);a.link=c;return a},readlink:function(a){if(40960!==(a.mode&61440))throw new O(28);return a.link}},Oa:{read:function(a,b,c,d,f){var g=a.node.Ma;if(f>=a.node.Sa)return 0;a=Math.min(a.node.Sa-f,d);if(8<a&&g.subarray)b.set(g.subarray(f,f+a),c);else for(d=0;d<a;d++)b[c+d]=g[f+d];return a},write:function(a,b,c,d,f,g){b.buffer===z.buffer&&(g=!1);if(!d)return 0;a=a.node;a.timestamp=Date.now();if(b.subarray&&(!a.Ma||a.Ma.subarray)){if(g)return a.Ma=b.subarray(c,c+d),a.Sa=d;if(0===a.Sa&&0===f)return a.Ma=
b.slice(c,c+d),a.Sa=d;if(f+d<=a.Sa)return a.Ma.set(b.subarray(c,c+d),f),d}P.vb(a,f+d);if(a.Ma.subarray&&b.subarray)a.Ma.set(b.subarray(c,c+d),f);else for(g=0;g<d;g++)a.Ma[f+g]=b[c+g];a.Sa=Math.max(a.Sa,f+d);return d},Za:function(a,b,c){1===c?b+=a.position:2===c&&32768===(a.node.mode&61440)&&(b+=a.node.Sa);if(0>b)throw new O(28);return b},sb:function(a,b,c){P.vb(a.node,b+c);a.node.Sa=Math.max(a.node.Sa,b+c)},hb:function(a,b,c,d,f,g){assert(0===b);if(32768!==(a.node.mode&61440))throw new O(43);a=a.node.Ma;
if(g&2||a.buffer!==Xa){if(0<d||d+c<a.length)a.subarray?a=a.subarray(d,d+c):a=Array.prototype.slice.call(a,d,d+c);d=!0;g=16384*Math.ceil(c/16384);for(b=ba(g);c<g;)z[b+c++]=0;c=b;if(!c)throw new O(48);z.set(a,c)}else d=!1,c=a.byteOffset;return{Ob:c,kb:d}},ib:function(a,b,c,d,f){if(32768!==(a.node.mode&61440))throw new O(43);if(f&2)return 0;P.Oa.write(a,b,0,d,c,!1);return 0}}},Pb=null,Qb={},S=[],Rb=1,T=null,Sb=!0,U={},O=null,Nb={};
function V(a,b){a=Eb("/",a);b=b||{};if(!a)return{path:"",node:null};var c={wb:!0,rb:0},d;for(d in c)void 0===b[d]&&(b[d]=c[d]);if(8<b.rb)throw new O(32);a=yb(a.split("/").filter(function(n){return!!n}),!1);var f=Pb;c="/";for(d=0;d<a.length;d++){var g=d===a.length-1;if(g&&b.parent)break;f=Ob(f,a[d]);c=r(c+"/"+a[d]);f.ab&&(!g||g&&b.wb)&&(f=f.ab.root);if(!g||b.Ya)for(g=0;40960===(f.mode&61440);)if(f=Tb(c),c=Eb(zb(c),f),f=V(c,{rb:b.rb}).node,40<g++)throw new O(32);}return{path:c,node:f}}
function Ub(a){for(var b;;){if(a===a.parent)return a=a.Wa.yb,b?"/"!==a[a.length-1]?a+"/"+b:a+b:a;b=b?a.name+"/"+b:a.name;a=a.parent}}function Vb(a,b){for(var c=0,d=0;d<b.length;d++)c=(c<<5)-c+b.charCodeAt(d)|0;return(a+c>>>0)%T.length}function Wb(a){var b=Vb(a.parent.id,a.name);if(T[b]===a)T[b]=a.bb;else for(b=T[b];b;){if(b.bb===a){b.bb=a.bb;break}b=b.bb}}
function Ob(a,b){var c;if(c=(c=Xb(a,"x"))?c:a.Na.lookup?0:2)throw new O(c,a);for(c=T[Vb(a.id,b)];c;c=c.bb){var d=c.name;if(c.parent.id===a.id&&d===b)return c}return a.Na.lookup(a,b)}function Mb(a,b,c,d){a=new Yb(a,b,c,d);b=Vb(a.parent.id,a.name);a.bb=T[b];return T[b]=a}function Q(a){return 16384===(a&61440)}var Zb={r:0,rs:1052672,"r+":2,w:577,wx:705,xw:705,"w+":578,"wx+":706,"xw+":706,a:1089,ax:1217,xa:1217,"a+":1090,"ax+":1218,"xa+":1218};
function $b(a){var b=["r","w","rw"][a&3];a&512&&(b+="w");return b}function Xb(a,b){if(Sb)return 0;if(-1===b.indexOf("r")||a.mode&292){if(-1!==b.indexOf("w")&&!(a.mode&146)||-1!==b.indexOf("x")&&!(a.mode&73))return 2}else return 2;return 0}function ac(a,b){try{return Ob(a,b),20}catch(c){}return Xb(a,"wx")}function bc(a,b,c){try{var d=Ob(a,b)}catch(f){return f.Pa}if(a=Xb(a,"wx"))return a;if(c){if(!Q(d.mode))return 54;if(d===d.parent||"/"===Ub(d))return 10}else if(Q(d.mode))return 31;return 0}
function cc(a){var b=4096;for(a=a||0;a<=b;a++)if(!S[a])return a;throw new O(33);}function dc(a,b){ec||(ec=function(){},ec.prototype={});var c=new ec,d;for(d in a)c[d]=a[d];a=c;b=cc(b);a.fd=b;return S[b]=a}var Lb={open:function(a){a.Oa=Qb[a.node.rdev].Oa;a.Oa.open&&a.Oa.open(a)},Za:function(){throw new O(70);}};function Hb(a,b){Qb[a]={Oa:b}}
function fc(a,b){var c="/"===b,d=!b;if(c&&Pb)throw new O(10);if(!c&&!d){var f=V(b,{wb:!1});b=f.path;f=f.node;if(f.ab)throw new O(10);if(!Q(f.mode))throw new O(54);}b={type:a,Ub:{},yb:b,Mb:[]};a=a.Wa(b);a.Wa=b;b.root=a;c?Pb=a:f&&(f.ab=b,f.Wa&&f.Wa.Mb.push(b))}function da(a,b,c){var d=V(a,{parent:!0}).node;a=Ab(a);if(!a||"."===a||".."===a)throw new O(28);var f=ac(d,a);if(f)throw new O(f);if(!d.Na.gb)throw new O(63);return d.Na.gb(d,a,b,c)}function W(a,b){da(a,(void 0!==b?b:511)&1023|16384,0)}
function hc(a,b,c){"undefined"===typeof c&&(c=b,b=438);da(a,b|8192,c)}function ic(a,b){if(!Eb(a))throw new O(44);var c=V(b,{parent:!0}).node;if(!c)throw new O(44);b=Ab(b);var d=ac(c,b);if(d)throw new O(d);if(!c.Na.symlink)throw new O(63);c.Na.symlink(c,b,a)}
function ta(a){var b=V(a,{parent:!0}).node,c=Ab(a),d=Ob(b,c),f=bc(b,c,!1);if(f)throw new O(f);if(!b.Na.unlink)throw new O(63);if(d.ab)throw new O(10);try{U.willDeletePath&&U.willDeletePath(a)}catch(g){J("FS.trackingDelegate['willDeletePath']('"+a+"') threw an exception: "+g.message)}b.Na.unlink(b,c);Wb(d);try{if(U.onDeletePath)U.onDeletePath(a)}catch(g){J("FS.trackingDelegate['onDeletePath']('"+a+"') threw an exception: "+g.message)}}
function Tb(a){a=V(a).node;if(!a)throw new O(44);if(!a.Na.readlink)throw new O(28);return Eb(Ub(a.parent),a.Na.readlink(a))}function jc(a,b){a=V(a,{Ya:!b}).node;if(!a)throw new O(44);if(!a.Na.Ua)throw new O(63);return a.Na.Ua(a)}function kc(a){return jc(a,!0)}function ea(a,b){var c;"string"===typeof a?c=V(a,{Ya:!0}).node:c=a;if(!c.Na.Ta)throw new O(63);c.Na.Ta(c,{mode:b&4095|c.mode&-4096,timestamp:Date.now()})}
function lc(a){var b;"string"===typeof a?b=V(a,{Ya:!0}).node:b=a;if(!b.Na.Ta)throw new O(63);b.Na.Ta(b,{timestamp:Date.now()})}function mc(a,b){if(0>b)throw new O(28);var c;"string"===typeof a?c=V(a,{Ya:!0}).node:c=a;if(!c.Na.Ta)throw new O(63);if(Q(c.mode))throw new O(31);if(32768!==(c.mode&61440))throw new O(28);if(a=Xb(c,"w"))throw new O(a);c.Na.Ta(c,{size:b,timestamp:Date.now()})}
function u(a,b,c,d){if(""===a)throw new O(44);if("string"===typeof b){var f=Zb[b];if("undefined"===typeof f)throw Error("Unknown file open mode: "+b);b=f}c=b&64?("undefined"===typeof c?438:c)&4095|32768:0;if("object"===typeof a)var g=a;else{a=r(a);try{g=V(a,{Ya:!(b&131072)}).node}catch(n){}}f=!1;if(b&64)if(g){if(b&128)throw new O(20);}else g=da(a,c,0),f=!0;if(!g)throw new O(44);8192===(g.mode&61440)&&(b&=-513);if(b&65536&&!Q(g.mode))throw new O(54);if(!f&&(c=g?40960===(g.mode&61440)?32:Q(g.mode)&&
("r"!==$b(b)||b&512)?31:Xb(g,$b(b)):44))throw new O(c);b&512&&mc(g,0);b&=-131713;d=dc({node:g,path:Ub(g),flags:b,seekable:!0,position:0,Oa:g.Oa,Rb:[],error:!1},d);d.Oa.open&&d.Oa.open(d);!e.logReadFiles||b&1||(Pc||(Pc={}),a in Pc||(Pc[a]=1,J("FS.trackingDelegate error on read file: "+a)));try{U.onOpenFile&&(g=0,1!==(b&2097155)&&(g|=1),0!==(b&2097155)&&(g|=2),U.onOpenFile(a,g))}catch(n){J("FS.trackingDelegate['onOpenFile']('"+a+"', flags) threw an exception: "+n.message)}return d}
function ka(a){if(null===a.fd)throw new O(8);a.ob&&(a.ob=null);try{a.Oa.close&&a.Oa.close(a)}catch(b){throw b;}finally{S[a.fd]=null}a.fd=null}function Qc(a,b,c){if(null===a.fd)throw new O(8);if(!a.seekable||!a.Oa.Za)throw new O(70);if(0!=c&&1!=c&&2!=c)throw new O(28);a.position=a.Oa.Za(a,b,c);a.Rb=[]}
function Sc(a,b,c,d,f){if(0>d||0>f)throw new O(28);if(null===a.fd)throw new O(8);if(1===(a.flags&2097155))throw new O(8);if(Q(a.node.mode))throw new O(31);if(!a.Oa.read)throw new O(28);var g="undefined"!==typeof f;if(!g)f=a.position;else if(!a.seekable)throw new O(70);b=a.Oa.read(a,b,c,d,f);g||(a.position+=b);return b}
function fa(a,b,c,d,f,g){if(0>d||0>f)throw new O(28);if(null===a.fd)throw new O(8);if(0===(a.flags&2097155))throw new O(8);if(Q(a.node.mode))throw new O(31);if(!a.Oa.write)throw new O(28);a.seekable&&a.flags&1024&&Qc(a,0,2);var n="undefined"!==typeof f;if(!n)f=a.position;else if(!a.seekable)throw new O(70);b=a.Oa.write(a,b,c,d,f,g);n||(a.position+=b);try{if(a.path&&U.onWriteToFile)U.onWriteToFile(a.path)}catch(t){J("FS.trackingDelegate['onWriteToFile']('"+a.path+"') threw an exception: "+t.message)}return b}
function sa(a){var b={encoding:"binary"};b=b||{};b.flags=b.flags||"r";b.encoding=b.encoding||"binary";if("utf8"!==b.encoding&&"binary"!==b.encoding)throw Error('Invalid encoding type "'+b.encoding+'"');var c,d=u(a,b.flags);a=jc(a).size;var f=new Uint8Array(a);Sc(d,f,0,a,0);"utf8"===b.encoding?c=Va(f,0):"binary"===b.encoding&&(c=f);ka(d);return c}
function Tc(){O||(O=function(a,b){this.node=b;this.Qb=function(c){this.Pa=c};this.Qb(a);this.message="FS error"},O.prototype=Error(),O.prototype.constructor=O,[44].forEach(function(a){Nb[a]=new O(a);Nb[a].stack="<generic error, no stack>"}))}var Uc;function ca(a,b){var c=0;a&&(c|=365);b&&(c|=146);return c}
function Vc(a,b,c){a=r("/dev/"+a);var d=ca(!!b,!!c);Wc||(Wc=64);var f=Wc++<<8|0;Hb(f,{open:function(g){g.seekable=!1},close:function(){c&&c.buffer&&c.buffer.length&&c(10)},read:function(g,n,t,w){for(var v=0,C=0;C<w;C++){try{var H=b()}catch(Y){throw new O(29);}if(void 0===H&&0===v)throw new O(6);if(null===H||void 0===H)break;v++;n[t+C]=H}v&&(g.node.timestamp=Date.now());return v},write:function(g,n,t,w){for(var v=0;v<w;v++)try{c(n[t+v])}catch(C){throw new O(29);}w&&(g.node.timestamp=Date.now());return v}});
hc(a,d,f)}var Wc,X={},ec,Pc,Xc={};
function Yc(a,b,c){try{var d=a(b)}catch(f){if(f&&f.node&&r(b)!==r(Ub(f.node)))return-54;throw f;}L[c>>2]=d.dev;L[c+4>>2]=0;L[c+8>>2]=d.ino;L[c+12>>2]=d.mode;L[c+16>>2]=d.nlink;L[c+20>>2]=d.uid;L[c+24>>2]=d.gid;L[c+28>>2]=d.rdev;L[c+32>>2]=0;M=[d.size>>>0,(N=d.size,1<=+Math.abs(N)?0<N?(Math.min(+Math.floor(N/4294967296),4294967295)|0)>>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];L[c+40>>2]=M[0];L[c+44>>2]=M[1];L[c+48>>2]=4096;L[c+52>>2]=d.blocks;L[c+56>>2]=d.atime.getTime()/1E3|0;L[c+60>>2]=
0;L[c+64>>2]=d.mtime.getTime()/1E3|0;L[c+68>>2]=0;L[c+72>>2]=d.ctime.getTime()/1E3|0;L[c+76>>2]=0;M=[d.ino>>>0,(N=d.ino,1<=+Math.abs(N)?0<N?(Math.min(+Math.floor(N/4294967296),4294967295)|0)>>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];L[c+80>>2]=M[0];L[c+84>>2]=M[1];return 0}var Zc=void 0;function $c(){Zc+=4;return L[Zc-4>>2]}function Z(a){a=S[a];if(!a)throw new O(8);return a}var ad={};
function bd(){if(!cd){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"===typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:xa||"./this.program"},b;for(b in ad)a[b]=ad[b];var c=[];for(b in a)c.push(b+"="+a[b]);cd=c}return cd}var cd,dd;za?dd=function(){var a=process.hrtime();return 1E3*a[0]+a[1]/1E6}:"undefined"!==typeof dateNow?dd=dateNow:dd=function(){return performance.now()};
function ed(a){for(var b=dd();dd()-b<a/1E3;);}e._usleep=ed;function Yb(a,b,c,d){a||(a=this);this.parent=a;this.Wa=a.Wa;this.ab=null;this.id=Rb++;this.name=b;this.mode=c;this.Na={};this.Oa={};this.rdev=d}Object.defineProperties(Yb.prototype,{read:{get:function(){return 365===(this.mode&365)},set:function(a){a?this.mode|=365:this.mode&=-366}},write:{get:function(){return 146===(this.mode&146)},set:function(a){a?this.mode|=146:this.mode&=-147}}});Tc();T=Array(4096);fc(P,"/");W("/tmp");W("/home");W("/home/web_user");
(function(){W("/dev");Hb(259,{read:function(){return 0},write:function(b,c,d,f){return f}});hc("/dev/null",259);Gb(1280,Jb);Gb(1536,Kb);hc("/dev/tty",1280);hc("/dev/tty1",1536);var a=Db();Vc("random",a);Vc("urandom",a);W("/dev/shm");W("/dev/shm/tmp")})();W("/proc");W("/proc/self");W("/proc/self/fd");
fc({Wa:function(){var a=Mb("/proc/self","fd",16895,73);a.Na={lookup:function(b,c){var d=S[+c];if(!d)throw new O(8);b={parent:null,Wa:{yb:"fake"},Na:{readlink:function(){return d.path}}};return b.parent=b}};return a}},"/proc/self/fd");function la(a,b){var c=Array(aa(a)+1);a=k(a,c,0,c.length);b&&(c.length=a);return c}ab.push({Hb:function(){fd()}});
var id={b:function(a,b,c,d){K("Assertion failed: "+A(a)+", at: "+[b?A(b):"unknown filename",c,d?A(d):"unknown function"])},r:function(a,b){sb();a=new Date(1E3*L[a>>2]);L[b>>2]=a.getSeconds();L[b+4>>2]=a.getMinutes();L[b+8>>2]=a.getHours();L[b+12>>2]=a.getDate();L[b+16>>2]=a.getMonth();L[b+20>>2]=a.getFullYear()-1900;L[b+24>>2]=a.getDay();var c=new Date(a.getFullYear(),0,1);L[b+28>>2]=(a.getTime()-c.getTime())/864E5|0;L[b+36>>2]=-(60*a.getTimezoneOffset());var d=(new Date(a.getFullYear(),6,1)).getTimezoneOffset();
c=c.getTimezoneOffset();a=(d!=c&&a.getTimezoneOffset()==Math.min(c,d))|0;L[b+32>>2]=a;a=L[xb()+(a?4:0)>>2];L[b+40>>2]=a;return b},j:function(a,b){try{a=A(a);if(b&-8)var c=-28;else{var d;(d=V(a,{Ya:!0}).node)?(a="",b&4&&(a+="r"),b&2&&(a+="w"),b&1&&(a+="x"),c=a&&Xb(d,a)?-2:0):c=-44}return c}catch(f){return"undefined"!==typeof X&&f instanceof O||K(f),-f.Pa}},v:function(a,b){try{return a=A(a),ea(a,b),0}catch(c){return"undefined"!==typeof X&&c instanceof O||K(c),-c.Pa}},D:function(a){try{return a=A(a),
lc(a),0}catch(b){return"undefined"!==typeof X&&b instanceof O||K(b),-b.Pa}},w:function(a,b){try{var c=S[a];if(!c)throw new O(8);ea(c.node,b);return 0}catch(d){return"undefined"!==typeof X&&d instanceof O||K(d),-d.Pa}},E:function(a){try{var b=S[a];if(!b)throw new O(8);lc(b.node);return 0}catch(c){return"undefined"!==typeof X&&c instanceof O||K(c),-c.Pa}},c:function(a,b,c){Zc=c;try{var d=Z(a);switch(b){case 0:var f=$c();return 0>f?-28:u(d.path,d.flags,0,f).fd;case 1:case 2:return 0;case 3:return d.flags;
case 4:return f=$c(),d.flags|=f,0;case 12:return f=$c(),La[f+0>>1]=2,0;case 13:case 14:return 0;case 16:case 8:return-28;case 9:return Bb(28),-1;default:return-28}}catch(g){return"undefined"!==typeof X&&g instanceof O||K(g),-g.Pa}},x:function(a,b){try{var c=Z(a);return Yc(jc,c.path,b)}catch(d){return"undefined"!==typeof X&&d instanceof O||K(d),-d.Pa}},i:function(a,b,c){try{var d=S[a];if(!d)throw new O(8);if(0===(d.flags&2097155))throw new O(28);mc(d.node,c);return 0}catch(f){return"undefined"!==typeof X&&
f instanceof O||K(f),-f.Pa}},J:function(a,b){try{if(0===b)return-28;if(b<aa("/")+1)return-68;k("/",m,a,b);return a}catch(c){return"undefined"!==typeof X&&c instanceof O||K(c),-c.Pa}},F:function(){return 0},e:function(){return 42},u:function(a,b){try{return a=A(a),Yc(kc,a,b)}catch(c){return"undefined"!==typeof X&&c instanceof O||K(c),-c.Pa}},t:function(a,b){try{return a=A(a),a=r(a),"/"===a[a.length-1]&&(a=a.substr(0,a.length-1)),W(a,b),0}catch(c){return"undefined"!==typeof X&&c instanceof O||K(c),
-c.Pa}},A:function(a,b,c,d,f,g){try{a:{g<<=12;var n=!1;if(0!==(d&16)&&0!==a%16384)var t=-28;else{if(0!==(d&32)){var w=gd(16384,b);if(!w){t=-48;break a}hd(w,0,b);n=!0}else{var v=S[f];if(!v){t=-8;break a}var C=g;if(0!==(c&2)&&0===(d&2)&&2!==(v.flags&2097155))throw new O(2);if(1===(v.flags&2097155))throw new O(2);if(!v.Oa.hb)throw new O(43);var H=v.Oa.hb(v,a,b,C,c,d);w=H.Ob;n=H.kb}Xc[w]={Lb:w,Kb:b,kb:n,fd:f,Nb:c,flags:d,offset:g};t=w}}return t}catch(Y){return"undefined"!==typeof X&&Y instanceof O||K(Y),
-Y.Pa}},z:function(a,b){try{if(-1===(a|0)||0===b)var c=-28;else{var d=Xc[a];if(d&&b===d.Kb){var f=S[d.fd];if(d.Nb&2){var g=d.flags,n=d.offset,t=m.slice(a,a+b);f&&f.Oa.ib&&f.Oa.ib(f,t,n,b,g)}Xc[a]=null;d.kb&&na(d.Lb)}c=0}return c}catch(w){return"undefined"!==typeof X&&w instanceof O||K(w),-w.Pa}},y:function(a,b,c){Zc=c;try{var d=A(a),f=$c();return u(d,b,f).fd}catch(g){return"undefined"!==typeof X&&g instanceof O||K(g),-g.Pa}},G:function(a,b,c){try{var d=Z(a);return Sc(d,z,b,c)}catch(f){return"undefined"!==
typeof X&&f instanceof O||K(f),-f.Pa}},l:function(a,b,c){try{a=A(a);if(0>=c)var d=-28;else{var f=Tb(a),g=Math.min(c,aa(f)),n=z[b+g];k(f,m,b,c+1);z[b+g]=n;d=g}return d}catch(t){return"undefined"!==typeof X&&t instanceof O||K(t),-t.Pa}},C:function(a){try{a=A(a);var b=V(a,{parent:!0}).node,c=Ab(a),d=Ob(b,c),f=bc(b,c,!0);if(f)throw new O(f);if(!b.Na.rmdir)throw new O(63);if(d.ab)throw new O(10);try{U.willDeletePath&&U.willDeletePath(a)}catch(g){J("FS.trackingDelegate['willDeletePath']('"+a+"') threw an exception: "+
g.message)}b.Na.rmdir(b,c);Wb(d);try{if(U.onDeletePath)U.onDeletePath(a)}catch(g){J("FS.trackingDelegate['onDeletePath']('"+a+"') threw an exception: "+g.message)}return 0}catch(g){return"undefined"!==typeof X&&g instanceof O||K(g),-g.Pa}},f:function(a,b){try{return a=A(a),Yc(jc,a,b)}catch(c){return"undefined"!==typeof X&&c instanceof O||K(c),-c.Pa}},H:function(a){try{return a=A(a),ta(a),0}catch(b){return"undefined"!==typeof X&&b instanceof O||K(b),-b.Pa}},n:function(a,b,c){m.copyWithin(a,b,b+c)},
d:function(a){a>>>=0;var b=m.length;if(2147483648<a)return!1;for(var c=1;4>=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,a+100663296);d=Math.max(16777216,a,d);0<d%65536&&(d+=65536-d%65536);a:{try{Oa.grow(Math.min(2147483648,d)-Xa.byteLength+65535>>>16);Ya(Oa.buffer);var f=1;break a}catch(g){}f=void 0}if(f)return!0}return!1},p:function(a,b){var c=0;bd().forEach(function(d,f){var g=b+c;f=L[a+4*f>>2]=g;for(g=0;g<d.length;++g)z[f++>>0]=d.charCodeAt(g);z[f>>0]=0;c+=d.length+1});return 0},q:function(a,b){var c=
bd();L[a>>2]=c.length;var d=0;c.forEach(function(f){d+=f.length+1});L[b>>2]=d;return 0},g:function(a){try{var b=Z(a);ka(b);return 0}catch(c){return"undefined"!==typeof X&&c instanceof O||K(c),c.Pa}},o:function(a,b){try{var c=Z(a);z[b>>0]=c.tty?2:Q(c.mode)?3:40960===(c.mode&61440)?7:4;return 0}catch(d){return"undefined"!==typeof X&&d instanceof O||K(d),d.Pa}},m:function(a,b,c,d,f){try{var g=Z(a);a=4294967296*c+(b>>>0);if(-9007199254740992>=a||9007199254740992<=a)return-61;Qc(g,a,d);M=[g.position>>>
0,(N=g.position,1<=+Math.abs(N)?0<N?(Math.min(+Math.floor(N/4294967296),4294967295)|0)>>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];L[f>>2]=M[0];L[f+4>>2]=M[1];g.ob&&0===a&&0===d&&(g.ob=null);return 0}catch(n){return"undefined"!==typeof X&&n instanceof O||K(n),n.Pa}},K:function(a){try{var b=Z(a);return b.Oa&&b.Oa.fsync?-b.Oa.fsync(b):0}catch(c){return"undefined"!==typeof X&&c instanceof O||K(c),c.Pa}},I:function(a,b,c,d){try{a:{for(var f=Z(a),g=a=0;g<c;g++){var n=fa(f,z,L[b+8*g>>2],L[b+(8*
g+4)>>2],void 0);if(0>n){var t=-1;break a}a+=n}t=a}L[d>>2]=t;return 0}catch(w){return"undefined"!==typeof X&&w instanceof O||K(w),w.Pa}},h:function(a){var b=Date.now();L[a>>2]=b/1E3|0;L[a+4>>2]=b%1E3*1E3|0;return 0},a:Oa,k:function(a,b){if(0===a)return Bb(28),-1;var c=L[a>>2];a=L[a+4>>2];if(0>a||999999999<a||0>c)return Bb(28),-1;0!==b&&(L[b>>2]=0,L[b+4>>2]=0);return ed(1E6*c+a/1E3)},B:function(a){switch(a){case 30:return 16384;case 85:return 131072;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:case 79:return 200809;
case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32;
case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1E3;case 89:return 700;case 71:return 256;case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:return"object"===typeof navigator?navigator.hardwareConcurrency||1:1}Bb(28);return-1},
L:function(a){var b=Date.now()/1E3|0;a&&(L[a>>2]=b);return b},s:function(a,b){if(b){var c=1E3*L[b+8>>2];c+=L[b+12>>2]/1E3}else c=Date.now();a=A(a);try{b=c;var d=V(a,{Ya:!0}).node;d.Na.Ta(d,{timestamp:Math.max(b,c)});return 0}catch(f){a=f;if(!(a instanceof O)){a+=" : ";a:{d=Error();if(!d.stack){try{throw Error();}catch(g){d=g}if(!d.stack){d="(no stack trace available)";break a}}d=d.stack.toString()}e.extraStackTrace&&(d+="\n"+e.extraStackTrace());d=ob(d);throw a+d;}Bb(a.Pa);return-1}}};
(function(){function a(f){e.asm=f.exports;Ja=e.asm.M;eb--;e.monitorRunDependencies&&e.monitorRunDependencies(eb);0==eb&&(null!==fb&&(clearInterval(fb),fb=null),gb&&(f=gb,gb=null,f()))}function b(f){a(f.instance)}function c(f){return mb().then(function(g){return WebAssembly.instantiate(g,d)}).then(f,function(g){J("failed to asynchronously prepare wasm: "+g);K(g)})}var d={a:id};eb++;e.monitorRunDependencies&&e.monitorRunDependencies(eb);if(e.instantiateWasm)try{return e.instantiateWasm(d,a)}catch(f){return J("Module.instantiateWasm callback failed with error: "+
f),!1}(function(){if(Ka||"function"!==typeof WebAssembly.instantiateStreaming||jb()||hb("file://")||"function"!==typeof fetch)return c(b);fetch(ib,{credentials:"same-origin"}).then(function(f){return WebAssembly.instantiateStreaming(f,d).then(b,function(g){J("wasm streaming compile failed: "+g);J("falling back to ArrayBuffer instantiation");return c(b)})})})();return{}})();
var fd=e.___wasm_call_ctors=function(){return(fd=e.___wasm_call_ctors=e.asm.N).apply(null,arguments)},hd=e._memset=function(){return(hd=e._memset=e.asm.O).apply(null,arguments)};e._sqlite3_free=function(){return(e._sqlite3_free=e.asm.P).apply(null,arguments)};var Cb=e.___errno_location=function(){return(Cb=e.___errno_location=e.asm.Q).apply(null,arguments)};e._sqlite3_finalize=function(){return(e._sqlite3_finalize=e.asm.R).apply(null,arguments)};
e._sqlite3_reset=function(){return(e._sqlite3_reset=e.asm.S).apply(null,arguments)};e._sqlite3_clear_bindings=function(){return(e._sqlite3_clear_bindings=e.asm.T).apply(null,arguments)};e._sqlite3_value_blob=function(){return(e._sqlite3_value_blob=e.asm.U).apply(null,arguments)};e._sqlite3_value_text=function(){return(e._sqlite3_value_text=e.asm.V).apply(null,arguments)};e._sqlite3_value_bytes=function(){return(e._sqlite3_value_bytes=e.asm.W).apply(null,arguments)};
e._sqlite3_value_double=function(){return(e._sqlite3_value_double=e.asm.X).apply(null,arguments)};e._sqlite3_value_int=function(){return(e._sqlite3_value_int=e.asm.Y).apply(null,arguments)};e._sqlite3_value_type=function(){return(e._sqlite3_value_type=e.asm.Z).apply(null,arguments)};e._sqlite3_result_blob=function(){return(e._sqlite3_result_blob=e.asm._).apply(null,arguments)};e._sqlite3_result_double=function(){return(e._sqlite3_result_double=e.asm.$).apply(null,arguments)};
e._sqlite3_result_error=function(){return(e._sqlite3_result_error=e.asm.aa).apply(null,arguments)};e._sqlite3_result_int=function(){return(e._sqlite3_result_int=e.asm.ba).apply(null,arguments)};e._sqlite3_result_int64=function(){return(e._sqlite3_result_int64=e.asm.ca).apply(null,arguments)};e._sqlite3_result_null=function(){return(e._sqlite3_result_null=e.asm.da).apply(null,arguments)};e._sqlite3_result_text=function(){return(e._sqlite3_result_text=e.asm.ea).apply(null,arguments)};
e._sqlite3_step=function(){return(e._sqlite3_step=e.asm.fa).apply(null,arguments)};e._sqlite3_column_count=function(){return(e._sqlite3_column_count=e.asm.ga).apply(null,arguments)};e._sqlite3_data_count=function(){return(e._sqlite3_data_count=e.asm.ha).apply(null,arguments)};e._sqlite3_column_blob=function(){return(e._sqlite3_column_blob=e.asm.ia).apply(null,arguments)};e._sqlite3_column_bytes=function(){return(e._sqlite3_column_bytes=e.asm.ja).apply(null,arguments)};
e._sqlite3_column_double=function(){return(e._sqlite3_column_double=e.asm.ka).apply(null,arguments)};e._sqlite3_column_text=function(){return(e._sqlite3_column_text=e.asm.la).apply(null,arguments)};e._sqlite3_column_type=function(){return(e._sqlite3_column_type=e.asm.ma).apply(null,arguments)};e._sqlite3_column_name=function(){return(e._sqlite3_column_name=e.asm.na).apply(null,arguments)};e._sqlite3_bind_blob=function(){return(e._sqlite3_bind_blob=e.asm.oa).apply(null,arguments)};
e._sqlite3_bind_double=function(){return(e._sqlite3_bind_double=e.asm.pa).apply(null,arguments)};e._sqlite3_bind_int=function(){return(e._sqlite3_bind_int=e.asm.qa).apply(null,arguments)};e._sqlite3_bind_text=function(){return(e._sqlite3_bind_text=e.asm.ra).apply(null,arguments)};e._sqlite3_bind_parameter_index=function(){return(e._sqlite3_bind_parameter_index=e.asm.sa).apply(null,arguments)};e._sqlite3_sql=function(){return(e._sqlite3_sql=e.asm.ta).apply(null,arguments)};
e._sqlite3_normalized_sql=function(){return(e._sqlite3_normalized_sql=e.asm.ua).apply(null,arguments)};e._sqlite3_errmsg=function(){return(e._sqlite3_errmsg=e.asm.va).apply(null,arguments)};e._sqlite3_exec=function(){return(e._sqlite3_exec=e.asm.wa).apply(null,arguments)};e._sqlite3_prepare_v2=function(){return(e._sqlite3_prepare_v2=e.asm.xa).apply(null,arguments)};e._sqlite3_changes=function(){return(e._sqlite3_changes=e.asm.ya).apply(null,arguments)};
e._sqlite3_close_v2=function(){return(e._sqlite3_close_v2=e.asm.za).apply(null,arguments)};e._sqlite3_create_function_v2=function(){return(e._sqlite3_create_function_v2=e.asm.Aa).apply(null,arguments)};e._sqlite3_open=function(){return(e._sqlite3_open=e.asm.Ba).apply(null,arguments)};var ba=e._malloc=function(){return(ba=e._malloc=e.asm.Ca).apply(null,arguments)},na=e._free=function(){return(na=e._free=e.asm.Da).apply(null,arguments)};
e._RegisterExtensionFunctions=function(){return(e._RegisterExtensionFunctions=e.asm.Ea).apply(null,arguments)};
var xb=e.__get_tzname=function(){return(xb=e.__get_tzname=e.asm.Fa).apply(null,arguments)},vb=e.__get_daylight=function(){return(vb=e.__get_daylight=e.asm.Ga).apply(null,arguments)},ub=e.__get_timezone=function(){return(ub=e.__get_timezone=e.asm.Ha).apply(null,arguments)},oa=e.stackSave=function(){return(oa=e.stackSave=e.asm.Ia).apply(null,arguments)},qa=e.stackRestore=function(){return(qa=e.stackRestore=e.asm.Ja).apply(null,arguments)},y=e.stackAlloc=function(){return(y=e.stackAlloc=e.asm.Ka).apply(null,
arguments)},gd=e._memalign=function(){return(gd=e._memalign=e.asm.La).apply(null,arguments)};e.cwrap=function(a,b,c,d){c=c||[];var f=c.every(function(g){return"number"===g});return"string"!==b&&f&&!d?Qa(a):function(){return Ra(a,b,c,arguments)}};e.UTF8ToString=A;e.stackSave=oa;e.stackRestore=qa;e.stackAlloc=y;var jd;gb=function kd(){jd||ld();jd||(gb=kd)};
function ld(){function a(){if(!jd&&(jd=!0,e.calledRun=!0,!Pa)){e.noFSInit||Uc||(Uc=!0,Tc(),e.stdin=e.stdin,e.stdout=e.stdout,e.stderr=e.stderr,e.stdin?Vc("stdin",e.stdin):ic("/dev/tty","/dev/stdin"),e.stdout?Vc("stdout",null,e.stdout):ic("/dev/tty","/dev/stdout"),e.stderr?Vc("stderr",null,e.stderr):ic("/dev/tty1","/dev/stderr"),u("/dev/stdin","r"),u("/dev/stdout","w"),u("/dev/stderr","w"));nb(ab);Sb=!1;nb(bb);if(e.onRuntimeInitialized)e.onRuntimeInitialized();if(e.postRun)for("function"==typeof e.postRun&&
(e.postRun=[e.postRun]);e.postRun.length;){var b=e.postRun.shift();cb.unshift(b)}nb(cb)}}if(!(0<eb)){if(e.preRun)for("function"==typeof e.preRun&&(e.preRun=[e.preRun]);e.preRun.length;)db();nb($a);0<eb||(e.setStatus?(e.setStatus("Running..."),setTimeout(function(){setTimeout(function(){e.setStatus("")},1);a()},1)):a())}}e.run=ld;if(e.preInit)for("function"==typeof e.preInit&&(e.preInit=[e.preInit]);0<e.preInit.length;)e.preInit.pop()();noExitRuntime=!0;ld();
// The shell-pre.js and emcc-generated code goes above
return Module;
}); // The end of the promise being returned
return initSqlJsPromise;
} // The end of our initSqlJs function
// This bit below is copied almost exactly from what you get when you use the MODULARIZE=1 flag with emcc
// However, we don't want to use the emcc modularization. See shell-pre.js
if (typeof exports === 'object' && typeof module === 'object'){
module.exports = initSqlJs;
// This will allow the module to be used in ES6 or CommonJS
module.exports.default = initSqlJs;
}
else if (typeof define === 'function' && define['amd']) {
define([], function() { return initSqlJs; });
}
else if (typeof exports === 'object'){
exports["Module"] = initSqlJs;
}
Binary file not shown.
@@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/utils.dart';
@@ -43,10 +44,10 @@ class FileAttachment extends AttachmentWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
child: _getFileTypeImage(context),
height: 40.0,
width: 33.33,
margin: EdgeInsets.all(8.0),
child: _getFileTypeImage(context),
),
SizedBox(width: 8.0),
Expanded(
@@ -102,12 +103,15 @@ class FileAttachment extends AttachmentWidget {
errorWidget: (_, obj, trace) {
return getFileTypeImage(attachment.extraData['other']);
},
progressIndicatorBuilder: (context, _, progress) {
return Center(
child: Container(
width: 20.0,
height: 20.0,
child: const CircularProgressIndicator(),
placeholder: (_, __) {
return Shimmer.fromColors(
baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro,
highlightColor:
StreamChatTheme.of(context).colorTheme.whiteSmoke,
child: Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
package: 'stream_chat_flutter',
),
);
},
@@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import '../full_screen_media.dart';
@@ -139,6 +140,7 @@ class GiphyAttachment extends AttachmentWidget {
Card(
color: Colors.white,
elevation: 2,
shape: CircleBorder(),
child: IconButton(
padding: const EdgeInsets.all(0),
constraints: BoxConstraints.tight(Size(32, 32)),
@@ -152,7 +154,6 @@ class GiphyAttachment extends AttachmentWidget {
});
},
),
shape: CircleBorder(),
),
Expanded(
child: Center(
@@ -167,6 +168,7 @@ class GiphyAttachment extends AttachmentWidget {
Card(
color: Colors.white,
elevation: 2,
shape: CircleBorder(),
child: IconButton(
padding: const EdgeInsets.all(0),
constraints: BoxConstraints.tight(Size(32, 32)),
@@ -180,7 +182,6 @@ class GiphyAttachment extends AttachmentWidget {
});
},
),
shape: CircleBorder(),
),
],
),
@@ -345,11 +346,15 @@ class GiphyAttachment extends AttachmentWidget {
height: size?.height,
width: size?.width,
placeholder: (_, __) {
return Container(
width: size?.width,
height: size?.height,
child: Center(
child: CircularProgressIndicator(),
return Shimmer.fromColors(
baseColor:
StreamChatTheme.of(context).colorTheme.greyGainsboro,
highlightColor:
StreamChatTheme.of(context).colorTheme.whiteSmoke,
child: Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
package: 'stream_chat_flutter',
),
);
},
@@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/src/attachment/attachment_upload_state_builder.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -51,22 +52,46 @@ class ImageAttachment extends AttachmentWidget {
);
},
network: () {
final imageUrl =
var imageUrl =
attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl;
if (imageUrl == null) {
return AttachmentError(size: size);
}
var imageUri = Uri.parse(imageUrl);
if (imageUri.host == 'stream-io-cdn.com') {
imageUri = imageUri.replace(queryParameters: {
...imageUri.queryParameters,
'h': '500',
'w': '500',
'crop': 'center',
'resize': 'crop',
});
} else if (imageUri.host == 'stream-cloud-uploads.imgix.net') {
imageUri = imageUri.replace(queryParameters: {
...imageUri.queryParameters,
'height': '500',
'width': '500',
'fit': 'crop',
});
}
imageUrl = imageUri.toString();
return _buildImageAttachment(
context,
CachedNetworkImage(
height: size?.height,
width: size?.width,
placeholder: (_, __) {
return Container(
width: size?.width,
height: size?.height,
child: Center(
child: CircularProgressIndicator(),
return Shimmer.fromColors(
baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro,
highlightColor:
StreamChatTheme.of(context).colorTheme.whiteSmoke,
child: Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
package: 'stream_chat_flutter',
),
);
},
@@ -11,6 +11,7 @@ class VideoAttachment extends AttachmentWidget {
final MessageTheme messageTheme;
final ShowMessageCallback onShowMessage;
final ValueChanged<ReturnActionType> onReturnAction;
final VoidCallback onAttachmentTap;
const VideoAttachment({
Key key,
@@ -20,6 +21,7 @@ class VideoAttachment extends AttachmentWidget {
this.messageTheme,
this.onShowMessage,
this.onReturnAction,
this.onAttachmentTap,
}) : super(key: key, message: message, attachment: attachment, size: size);
@override
@@ -65,25 +67,26 @@ class VideoAttachment extends AttachmentWidget {
children: <Widget>[
Expanded(
child: GestureDetector(
onTap: () async {
final channel = StreamChannel.of(context).channel;
final res = await Navigator.push(
context,
MaterialPageRoute(
builder: (_) => StreamChannel(
channel: channel,
child: FullScreenMedia(
mediaAttachments: [attachment],
userName: message.user.name,
sentAt: message.createdAt,
message: message,
onShowMessage: onShowMessage,
onTap: onAttachmentTap ??
() async {
final channel = StreamChannel.of(context).channel;
final res = await Navigator.push(
context,
MaterialPageRoute(
builder: (_) => StreamChannel(
channel: channel,
child: FullScreenMedia(
mediaAttachments: [attachment],
userName: message.user.name,
sentAt: message.createdAt,
message: message,
onShowMessage: onShowMessage,
),
),
),
),
),
);
if (res != null) onReturnAction(res);
},
);
if (res != null) onReturnAction(res);
},
child: Stack(
children: [
videoWidget,
@@ -0,0 +1,324 @@
import 'dart:ui';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:path_provider/path_provider.dart';
import '../stream_chat_flutter.dart';
import 'extension.dart';
/// Callback to download an attachment asset
typedef AttachmentDownloader = Future<String> Function(
Attachment attachment, {
ProgressCallback progressCallback,
});
/// Widget that shows the options in the gallery view
class AttachmentActionsModal extends StatelessWidget {
/// The message containing the attachments
final Message message;
/// Current page index
final currentIndex;
/// Callback to show the message
final VoidCallback onShowMessage;
/// Callback to download images
final AttachmentDownloader imageDownloader;
/// Callback to provide download files
final AttachmentDownloader fileDownloader;
/// Returns a new [AttachmentActionsModal]
const AttachmentActionsModal({
this.message,
this.currentIndex,
this.onShowMessage,
this.imageDownloader,
this.fileDownloader,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => Navigator.maybePop(context),
child: _buildPage(context),
);
}
Widget _buildPage(context) {
final theme = StreamChatTheme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
SizedBox(height: kToolbarHeight),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Container(
width: MediaQuery.of(context).size.width * 0.5,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.0),
),
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
_buildButton(
context,
'Reply',
StreamSvgIcon.iconCurveLineLeftUp(
size: 24.0,
color: theme.colorTheme.grey,
),
() {
Navigator.pop(context, ReturnActionType.reply);
},
),
_buildButton(
context,
'Show in Chat',
StreamSvgIcon.eye(
size: 24.0,
color: theme.colorTheme.black,
),
onShowMessage,
),
_buildButton(
context,
'Save ${message.attachments[currentIndex].type == 'video' ? 'Video' : 'Image'}',
StreamSvgIcon.iconSave(
size: 24.0,
color: theme.colorTheme.grey,
),
() {
final attachment = message.attachments[currentIndex];
final isImage = attachment.type == 'image';
final saveFile = fileDownloader ?? _downloadAttachment;
final saveImage = imageDownloader ?? _downloadAttachment;
final downloader = isImage ? saveImage : saveFile;
final progressNotifier = ValueNotifier<_DownloadProgress>(
_DownloadProgress.initial(),
);
downloader(
attachment,
progressCallback: (received, total) {
progressNotifier.value = _DownloadProgress(
total,
received,
);
},
).catchError((_) {
progressNotifier.value = null;
});
// Closing attachment actions modal before opening
// attachment download dialog
Navigator.pop(context);
showDialog(
barrierDismissible: false,
context: context,
barrierColor: theme.colorTheme.overlay,
builder: (context) => _buildDownloadProgressDialog(
context,
progressNotifier,
),
);
},
),
if (StreamChat.of(context).user.id == message.user.id)
_buildButton(
context,
'Delete',
StreamSvgIcon.delete(
size: 24.0,
color: theme.colorTheme.accentRed,
),
() {
final channel = StreamChannel.of(context).channel;
if (message.attachments.length > 1 ||
message.text.isNotEmpty) {
final remainingAttachments = [...message.attachments]
..removeAt(currentIndex);
channel.updateMessage(message.copyWith(
attachments: remainingAttachments,
));
Navigator.pop(context);
Navigator.pop(context);
} else {
channel.deleteMessage(message).then((value) {
Navigator.pop(context);
Navigator.pop(context);
});
}
},
color: theme.colorTheme.accentRed,
),
]
.map<Widget>((e) =>
Align(alignment: Alignment.centerRight, child: e))
.insertBetween(
Container(
height: 1,
color: theme.colorTheme.greyWhisper,
),
),
),
),
),
)
],
);
}
Widget _buildButton(
context,
String title,
StreamSvgIcon icon,
VoidCallback onTap, {
Color color,
}) {
return Material(
color: StreamChatTheme.of(context).colorTheme.white,
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Row(
children: [
icon,
SizedBox(width: 16),
Text(
title,
style: StreamChatTheme.of(context)
.textTheme
.body
.copyWith(color: color),
),
],
),
),
),
);
}
Widget _buildDownloadProgressDialog(
BuildContext context,
ValueNotifier<_DownloadProgress> progressNotifier,
) {
final theme = StreamChatTheme.of(context);
return WillPopScope(
onWillPop: () => Future.value(false),
child: ValueListenableBuilder(
valueListenable: progressNotifier,
builder: (_, _DownloadProgress progress, __) {
// Pop the dialog in case the progress is null or it's completed.
if (progress == null || progress?.toProgressIndicatorValue == 1.0) {
Future.delayed(
const Duration(milliseconds: 500),
Navigator.of(context).pop,
);
}
return Material(
type: MaterialType.transparency,
child: Center(
child: Container(
height: 182,
width: 182,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: theme.colorTheme.white,
),
child: Center(
child: progress == null
? Container(
height: 100,
width: 100,
child: StreamSvgIcon.error(
color: theme.colorTheme.greyGainsboro,
),
)
: progress.toProgressIndicatorValue == 1.0
? Container(
height: 160,
width: 160,
child: StreamSvgIcon.check(
color: theme.colorTheme.greyGainsboro,
),
)
: Container(
height: 100,
width: 100,
child: Stack(
fit: StackFit.expand,
children: [
CircularProgressIndicator(
value: progress.toProgressIndicatorValue,
strokeWidth: 8.0,
valueColor: AlwaysStoppedAnimation(
theme.colorTheme.accentBlue,
),
),
Center(
child: Text(
'${progress.toPercentage}%',
style: theme.textTheme.headline.copyWith(
color: theme.colorTheme.grey,
),
),
),
],
),
),
),
),
),
);
},
),
);
}
Future<String> _downloadAttachment(
Attachment attachment, {
ProgressCallback progressCallback,
}) async {
String filePath;
final appDocDir = await getTemporaryDirectory();
await Dio().download(
attachment.assetUrl ?? attachment.imageUrl ?? attachment.thumbUrl,
(Headers responseHeaders) {
final contentType = responseHeaders[Headers.contentTypeHeader];
final mimeType = contentType.first?.split('/')?.last;
filePath ??= '${appDocDir.path}/${attachment.id}.$mimeType';
return filePath;
},
onReceiveProgress: progressCallback,
);
final result = await ImageGallerySaver.saveFile(filePath);
return (result as Map)['filePath'];
}
}
class _DownloadProgress {
final int total;
final int received;
const _DownloadProgress(this.total, this.received);
factory _DownloadProgress.initial() {
return _DownloadProgress(double.maxFinite.toInt(), 0);
}
double get toProgressIndicatorValue => received / total;
int get toPercentage => (received * 100) ~/ total;
}
@@ -21,28 +21,28 @@ class StreamBackButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Padding(
RawMaterialButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
elevation: 0,
highlightElevation: 0,
focusElevation: 0,
disabledElevation: 0,
hoverElevation: 0,
onPressed: () {
if (onPressed != null) {
onPressed();
} else {
Navigator.maybePop(context);
}
},
padding: const EdgeInsets.all(14.0),
child: RawMaterialButton(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
elevation: 0,
highlightElevation: 0,
focusElevation: 0,
disabledElevation: 0,
hoverElevation: 0,
onPressed: () {
if (onPressed != null) {
onPressed();
} else {
Navigator.maybePop(context);
}
},
child: StreamSvgIcon.left(
size: 24,
color: StreamChatTheme.of(context).colorTheme.black,
),
child: StreamSvgIcon.left(
size: 24,
color: StreamChatTheme.of(context).colorTheme.black,
),
),
if (showUnreads)
@@ -114,9 +114,9 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
children: [
UserAvatar(
user: members[index].user,
constraints: BoxConstraints(
maxHeight: 64.0,
maxWidth: 64.0,
constraints: BoxConstraints.tightFor(
height: 64.0,
width: 64.0,
),
borderRadius: BorderRadius.circular(32.0),
onlineIndicatorConstraints:
@@ -238,8 +238,8 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
color: StreamChatTheme.of(context).colorTheme.accentRed,
),
);
var channel = StreamChannel.of(context).channel;
if (res == true) {
final channel = StreamChannel.of(context).channel;
await channel.removeMembers([StreamChat.of(context).user.id]);
Navigator.pop(context);
}
@@ -71,12 +71,12 @@ class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
Navigator.of(context).pop();
},
child: Container(
width: 24.0,
height: 24.0,
child: StreamSvgIcon.left(
color: StreamChatTheme.of(context).colorTheme.black,
size: 24.0,
),
width: 24.0,
height: 24.0,
),
),
),
@@ -71,6 +71,19 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
final bool showConnectionStateTile;
/// Title widget
final Widget title;
/// Subtitle widget
final Widget subtitle;
/// Leading widget
final Widget leading;
/// AppBar actions
/// By default it shows the [ChannelImage]
final List<Widget> actions;
/// Creates a channel header
ChannelHeader({
Key key,
@@ -80,6 +93,10 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
this.showTypingIndicator = true,
this.onImageTap,
this.showConnectionStateTile = false,
this.title,
this.subtitle,
this.leading,
this.actions,
}) : preferredSize = Size.fromHeight(kToolbarHeight),
super(key: key);
@@ -87,6 +104,14 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
Widget build(BuildContext context) {
final channel = StreamChannel.of(context).channel;
final leadingWidget = leading ??
(showBackButton
? StreamBackButton(
onPressed: onBackPressed,
showUnreads: true,
)
: SizedBox());
return ConnectionStatusBuilder(
statusBuilder: (context, status) {
var statusString = '';
@@ -111,26 +136,32 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
child: AppBar(
brightness: Theme.of(context).brightness,
elevation: 1,
leading: showBackButton
? StreamBackButton(
onPressed: onBackPressed,
showUnreads: true,
)
: SizedBox(),
leading: leadingWidget,
backgroundColor: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.color,
actions: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: Center(
child: ChannelImage(
onTap: onImageTap,
actions: actions ??
<Widget>[
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: Center(
child: ChannelImage(
borderRadius: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.avatarTheme
.borderRadius,
constraints: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.avatarTheme
.constraints,
onTap: onImageTap,
),
),
),
),
),
],
],
centerTitle: true,
title: InkWell(
onTap: onTitleTap,
@@ -141,20 +172,23 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ChannelName(
textStyle: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.title,
),
title ??
ChannelName(
textStyle: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.title,
),
SizedBox(height: 2),
ChannelInfo(
showTypingIndicator: showTypingIndicator,
channel: channel,
textStyle: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle,
),
subtitle ??
ChannelInfo(
showTypingIndicator: showTypingIndicator,
channel: channel,
textStyle: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.subtitle,
),
],
),
),
@@ -96,7 +96,11 @@ class ChannelImage extends StatelessWidget {
initialData: otherMember.user,
builder: (context, snapshot) {
return UserAvatar(
borderRadius: borderRadius,
borderRadius: borderRadius ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.borderRadius,
user: snapshot.data ?? otherMember.user,
constraints: constraints ??
StreamChatTheme.of(context)
@@ -120,7 +124,11 @@ class ChannelImage extends StatelessWidget {
.toList();
return GroupImage(
images: images,
borderRadius: borderRadius,
borderRadius: borderRadius ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.borderRadius,
constraints: constraints ??
StreamChatTheme.of(context)
.channelPreviewTheme
@@ -57,7 +57,7 @@ class ChannelInfo extends StatelessWidget {
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.lastMessageAt,
.subtitle,
);
} else {
final otherMember = members.firstWhere(
@@ -69,10 +69,7 @@ class ChannelInfo extends StatelessWidget {
if (otherMember.user.online) {
alternativeWidget = Text(
'Online',
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.lastMessageAt,
style: textStyle,
);
} else {
alternativeWidget = Text(
@@ -45,7 +45,7 @@ typedef _TitleBuilder = Widget Function(
/// The widget by default uses the inherited [StreamChatClient] to fetch information about the status.
/// However you can also pass your own [StreamChatClient] if you don't have it in the widget tree.
///
/// The widget components render the ui based on the first ancestor of type [StreamChatTheme] and on its [ChannelTheme.channelHeaderTheme] property.
/// The widget components render the ui based on the first ancestor of type [StreamChatTheme] and on its [ChannelListHeaderTheme] property.
/// Modify it to change the widget appearance.
class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
/// Instantiates a ChannelListHeader
@@ -57,6 +57,9 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
this.onNewChatButtonTap,
this.showConnectionStateTile = false,
this.preNavigationCallback,
this.subtitle,
this.leading,
this.actions,
}) : super(key: key);
/// Pass this if you don't have a [StreamChatClient] in your widget tree.
@@ -76,6 +79,17 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
final VoidCallback preNavigationCallback;
/// Subtitle widget
final Widget subtitle;
/// Leading widget
/// By default it shows the logged in user avatar
final Widget leading;
/// AppBar actions
/// By default it shows the new chat button
final List<Widget> actions;
@override
Widget build(BuildContext context) {
final _client = client ?? StreamChat.of(context).client;
@@ -104,76 +118,85 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
child: AppBar(
brightness: Theme.of(context).brightness,
elevation: 1,
backgroundColor: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.color,
backgroundColor:
StreamChatTheme.of(context).channelListHeaderTheme.color,
centerTitle: true,
leading: Center(
child: UserAvatar(
user: user,
showOnlineStatus: false,
onTap: onUserAvatarTap ??
(_) {
if (preNavigationCallback != null) {
preNavigationCallback();
}
Scaffold.of(context).openDrawer();
},
borderRadius: BorderRadius.circular(20),
constraints: BoxConstraints.tightFor(
height: 40,
width: 40,
),
),
),
actions: [
StreamNeumorphicButton(
child: IconButton(
icon: ConnectionStatusBuilder(
statusBuilder: (context, status) {
var color;
switch (status) {
case ConnectionStatus.connected:
color =
StreamChatTheme.of(context).colorTheme.accentBlue;
break;
case ConnectionStatus.connecting:
color = Colors.grey;
break;
case ConnectionStatus.disconnected:
color = Colors.grey;
break;
}
return SvgPicture.asset(
'svgs/icon_pen_write.svg',
package: 'stream_chat_flutter',
width: 24.0,
height: 24.0,
color: color,
);
},
leading: leading ??
Center(
child: UserAvatar(
user: user,
showOnlineStatus: false,
onTap: onUserAvatarTap ??
(_) {
if (preNavigationCallback != null) {
preNavigationCallback();
}
Scaffold.of(context).openDrawer();
},
borderRadius: StreamChatTheme.of(context)
.channelListHeaderTheme
.avatarTheme
.borderRadius,
constraints: StreamChatTheme.of(context)
.channelListHeaderTheme
.avatarTheme
.constraints,
),
onPressed: onNewChatButtonTap,
),
)
],
title: Builder(
builder: (context) {
if (titleBuilder != null) {
return titleBuilder(context, status, _client);
}
switch (status) {
case ConnectionStatus.connected:
return _buildConnectedTitleState(context);
case ConnectionStatus.connecting:
return _buildConnectingTitleState(context);
case ConnectionStatus.disconnected:
return _buildDisconnectedTitleState(context, _client);
default:
return Offstage();
}
},
actions: actions ??
[
StreamNeumorphicButton(
child: IconButton(
icon: ConnectionStatusBuilder(
statusBuilder: (context, status) {
var color;
switch (status) {
case ConnectionStatus.connected:
color = StreamChatTheme.of(context)
.colorTheme
.accentBlue;
break;
case ConnectionStatus.connecting:
color = Colors.grey;
break;
case ConnectionStatus.disconnected:
color = Colors.grey;
break;
}
return SvgPicture.asset(
'svgs/icon_pen_write.svg',
package: 'stream_chat_flutter',
width: 24.0,
height: 24.0,
color: color,
);
},
),
onPressed: onNewChatButtonTap,
),
)
],
title: Column(
children: [
Builder(
builder: (context) {
if (titleBuilder != null) {
return titleBuilder(context, status, _client);
}
switch (status) {
case ConnectionStatus.connected:
return _buildConnectedTitleState(context);
case ConnectionStatus.connecting:
return _buildConnectingTitleState(context);
case ConnectionStatus.disconnected:
return _buildDisconnectedTitleState(context, _client);
default:
return Offstage();
}
},
),
subtitle ?? Offstage(),
],
),
),
);
@@ -202,14 +225,11 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
SizedBox(width: 10),
Text(
'Searching for Network',
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.title
.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
),
style:
StreamChatTheme.of(context).channelListHeaderTheme.title.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
);
@@ -222,14 +242,11 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
children: [
Text(
'Offline...',
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.title
.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
),
style:
StreamChatTheme.of(context).channelListHeaderTheme.title.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
TextButton(
onPressed: () async {
@@ -239,8 +256,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
child: Text(
'Try Again',
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.channelListHeaderTheme
.title
.copyWith(
fontSize: 16,
@@ -223,8 +223,8 @@ class _ChannelListViewState extends State<ChannelListView> {
}
return AnimatedSwitcher(
child: child,
duration: const Duration(milliseconds: 500),
child: child,
);
}
@@ -393,16 +393,17 @@ class _ChannelListViewState extends State<ChannelListView> {
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Align(
alignment: Alignment.centerLeft,
child: Container(
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white,
borderRadius: BorderRadius.circular(11),
),
constraints: BoxConstraints.tightFor(
height: 16,
width: 238,
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Container(
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white,
borderRadius: BorderRadius.circular(11),
),
constraints: BoxConstraints.expand(
height: 16,
),
),
),
),
@@ -468,8 +469,8 @@ class _ChannelListViewState extends State<ChannelListView> {
MaterialPageRoute(
builder: (context) {
return StreamChannel(
child: widget.channelWidget,
channel: client,
child: widget.channelWidget,
);
},
),
@@ -505,12 +506,12 @@ class _ChannelListViewState extends State<ChannelListView> {
context: context,
builder: (context) {
return StreamChannel(
channel: channel,
child: ChannelBottomSheet(
onViewInfoTap: () {
widget.onViewInfoTap(channel);
},
),
channel: channel,
);
},
);
@@ -553,7 +554,7 @@ class _ChannelListViewState extends State<ChannelListView> {
ChannelPreview(
onLongPress: widget.onChannelLongPress,
channel: channel,
onImageTap: widget.onImageTap?.call(channel),
onImageTap: () => widget.onImageTap?.call(channel),
onTap: (channel) => onTap(channel, widget.channelWidget),
),
),
@@ -591,13 +592,13 @@ class _ChannelListViewState extends State<ChannelListView> {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: StreamChannel(
channel: channel,
child: ChannelName(
textStyle: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
channel: channel,
),
),
],
@@ -78,12 +78,12 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
Navigator.of(context).pop();
},
child: Container(
width: 24.0,
height: 24.0,
child: StreamSvgIcon.left(
color: StreamChatTheme.of(context).colorTheme.black,
size: 24.0,
),
width: 24.0,
height: 24.0,
),
),
),
@@ -32,16 +32,37 @@ class ChannelPreview extends StatelessWidget {
/// The function called when the image is tapped
final VoidCallback onImageTap;
/// Widget rendering the title
final Widget title;
/// Widget rendering the subtitle
final Widget subtitle;
/// Widget rendering the leading element, by default it shows the [ChannelImage]
final Widget leading;
/// Widget rendering the trailing element, by default it shows the last message date
final Widget trailing;
/// Widget rendering the sending indicator, by default it uses the [SendingIndicator] widget
final Widget sendingIndicator;
ChannelPreview({
@required this.channel,
Key key,
this.onTap,
this.onLongPress,
this.onImageTap,
this.title,
this.subtitle,
this.leading,
this.sendingIndicator,
this.trailing,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final channelPreviewTheme = StreamChatTheme.of(context).channelPreviewTheme;
return StreamBuilder<bool>(
stream: channel.isMutedStream,
initialData: channel.isMuted,
@@ -49,6 +70,7 @@ class ChannelPreview extends StatelessWidget {
return Opacity(
opacity: snapshot.data ? 0.5 : 1,
child: ListTile(
visualDensity: VisualDensity.compact,
contentPadding: const EdgeInsets.symmetric(
horizontal: 8,
),
@@ -62,17 +84,18 @@ class ChannelPreview extends StatelessWidget {
onLongPress(channel);
}
},
leading: ChannelImage(
onTap: onImageTap,
),
leading: leading ??
ChannelImage(
onTap: onImageTap,
),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: ChannelName(
textStyle:
StreamChatTheme.of(context).channelPreviewTheme.title,
),
child: title ??
ChannelName(
textStyle: channelPreviewTheme.title,
),
),
StreamBuilder<List<Member>>(
stream: channel.state.membersStream,
@@ -93,37 +116,36 @@ class ChannelPreview extends StatelessWidget {
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(child: _buildSubtitle(context)),
Builder(
builder: (context) {
final lastMessage = channel.state.messages.lastWhere(
(m) => !m.isDeleted && m.shadowed != true,
orElse: () => null,
);
if (lastMessage?.user?.id ==
StreamChat.of(context).user.id) {
return Padding(
padding: const EdgeInsets.only(right: 4.0),
child: SendingIndicator(
message: lastMessage,
size: StreamChatTheme.of(context)
.channelPreviewTheme
.indicatorIconSize,
isMessageRead: channel.state.read
?.where((element) =>
element.user.id !=
channel.client.state.user.id)
?.where((element) => element.lastRead
.isAfter(lastMessage.createdAt))
?.isNotEmpty ==
true,
),
);
}
return SizedBox();
},
),
_buildDate(context),
Flexible(child: subtitle ?? _buildSubtitle(context)),
sendingIndicator ??
Builder(
builder: (context) {
final lastMessage = channel.state.messages.lastWhere(
(m) => !m.isDeleted && m.shadowed != true,
orElse: () => null,
);
if (lastMessage?.user?.id ==
StreamChat.of(context).user.id) {
return Padding(
padding: const EdgeInsets.only(right: 4.0),
child: SendingIndicator(
message: lastMessage,
size: channelPreviewTheme.indicatorIconSize,
isMessageRead: channel.state.read
?.where((element) =>
element.user.id !=
channel.client.state.user.id)
?.where((element) => element.lastRead
.isAfter(lastMessage.createdAt))
?.isNotEmpty ==
true,
),
);
}
return SizedBox();
},
),
trailing ?? _buildDate(context),
],
),
),
@@ -176,15 +198,7 @@ class ChannelPreview extends StatelessWidget {
),
Text(
' Channel is muted',
style: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle
.copyWith(
color: StreamChatTheme.of(context)
.channelPreviewTheme
.subtitle
.color,
),
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle,
),
],
);
@@ -192,10 +206,7 @@ class ChannelPreview extends StatelessWidget {
return TypingIndicator(
channel: channel,
alternativeWidget: _buildLastMessage(context),
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
color:
StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
),
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle,
);
}
@@ -1,6 +1,7 @@
import 'package:characters/characters.dart';
import 'package:emojis/emoji.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
final _emojis = Emoji.all();
@@ -44,3 +45,55 @@ extension PlatformFileX on PlatformFile {
size: size,
);
}
extension InputDecorationX on InputDecoration {
InputDecoration merge(InputDecoration other) {
if (other == null) return this;
return copyWith(
icon: other?.icon,
labelText: other?.labelText,
labelStyle: labelStyle?.merge(other.labelStyle) ?? other.labelStyle,
helperText: other?.helperText,
helperStyle: helperStyle?.merge(other.helperStyle) ?? other.helperStyle,
helperMaxLines: other?.helperMaxLines,
hintText: other?.hintText,
hintStyle: hintStyle?.merge(other.hintStyle) ?? other.hintStyle,
hintTextDirection: other?.hintTextDirection,
hintMaxLines: other?.hintMaxLines,
errorText: other?.errorText,
errorStyle: errorStyle?.merge(other.errorStyle) ?? other.errorStyle,
errorMaxLines: other?.errorMaxLines,
floatingLabelBehavior: other?.floatingLabelBehavior,
isCollapsed: other?.isCollapsed,
isDense: other?.isDense,
contentPadding: other?.contentPadding,
prefixIcon: other?.prefixIcon,
prefix: other?.prefix,
prefixText: other?.prefixText,
prefixIconConstraints: other?.prefixIconConstraints,
prefixStyle: prefixStyle?.merge(other.prefixStyle) ?? other.prefixStyle,
suffixIcon: other?.suffixIcon,
suffix: other?.suffix,
suffixText: other?.suffixText,
suffixStyle: suffixStyle?.merge(other.suffixStyle) ?? other.suffixStyle,
suffixIconConstraints: other?.suffixIconConstraints,
counter: other?.counter,
counterText: other?.counterText,
counterStyle:
counterStyle?.merge(other.counterStyle) ?? other.counterStyle,
filled: other?.filled,
fillColor: other?.fillColor,
focusColor: other?.focusColor,
hoverColor: other?.hoverColor,
errorBorder: other?.errorBorder,
focusedBorder: other?.focusedBorder,
focusedErrorBorder: other?.focusedErrorBorder,
disabledBorder: other?.disabledBorder,
enabledBorder: other?.enabledBorder,
border: other?.border,
enabled: other?.enabled,
semanticCounterText: other?.semanticCounterText,
alignLabelWithHint: other?.alignLabelWithHint,
);
}
}
@@ -243,7 +243,7 @@ class VideoPackage {
ChewieController get chewieController => _chewieController;
bool get initialized => _videoPlayerController.value.initialized;
bool get initialized => _videoPlayerController.value.isInitialized;
VideoPackage(
Attachment attachment, {
@@ -1,192 +0,0 @@
import 'dart:typed_data';
import 'dart:ui';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:path_provider/path_provider.dart';
import '../stream_chat_flutter.dart';
import 'extension.dart';
class ImageActionsModal extends StatelessWidget {
final Message message;
final String userName;
final String sentAt;
final List<Attachment> urls;
final currentIndex;
final VoidCallback onShowMessage;
ImageActionsModal(
{this.message,
this.userName,
this.sentAt,
this.urls,
this.currentIndex,
this.onShowMessage});
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => Navigator.maybePop(context),
child: _buildPage(context),
);
}
Widget _buildPage(context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
SizedBox(height: kToolbarHeight),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Container(
width: MediaQuery.of(context).size.width * 0.5,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.0),
),
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
_buildButton(
context,
'Reply',
StreamSvgIcon.iconCurveLineLeftUp(
size: 24.0,
color: StreamChatTheme.of(context).colorTheme.grey,
),
() {
Navigator.pop(context, ReturnActionType.reply);
},
),
_buildButton(
context,
'Show in Chat',
StreamSvgIcon.eye(
size: 24.0,
color: StreamChatTheme.of(context).colorTheme.black,
),
onShowMessage,
),
_buildButton(
context,
'Save ${urls[currentIndex].type == 'video' ? 'Video' : 'Image'}',
StreamSvgIcon.iconSave(
size: 24.0,
color: StreamChatTheme.of(context).colorTheme.grey,
),
() async {
var url = urls[currentIndex].imageUrl ??
urls[currentIndex].assetUrl ??
urls[currentIndex].thumbUrl;
Navigator.pop(context);
if (urls[currentIndex].type == 'video') {
await _saveVideo(url);
} else {
await _saveImage(url);
}
},
),
if (StreamChat.of(context).user.id == message.user.id)
_buildButton(
context,
'Delete',
StreamSvgIcon.delete(
size: 24.0,
color: StreamChatTheme.of(context).colorTheme.accentRed,
),
() {
final channel = StreamChannel.of(context).channel;
if (message.attachments.length > 1 ||
message.text.isNotEmpty) {
final remainingAttachments = [...message.attachments]
..removeAt(currentIndex);
channel.updateMessage(message.copyWith(
attachments: remainingAttachments,
));
Navigator.pop(context);
Navigator.pop(context);
} else {
channel.deleteMessage(message).then((value) {
Navigator.pop(context);
Navigator.pop(context);
});
}
},
color: StreamChatTheme.of(context).colorTheme.accentRed,
),
]
.map<Widget>((e) =>
Align(alignment: Alignment.centerRight, child: e))
.insertBetween(
Container(
height: 1,
color:
StreamChatTheme.of(context).colorTheme.greyWhisper,
),
),
),
),
),
)
],
);
}
Widget _buildButton(
context,
String title,
StreamSvgIcon icon,
VoidCallback onTap, {
Color color,
}) {
return Material(
color: StreamChatTheme.of(context).colorTheme.white,
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Row(
children: [
icon,
SizedBox(width: 16),
Text(
title,
style: StreamChatTheme.of(context)
.textTheme
.body
.copyWith(color: color),
),
],
),
),
),
);
}
Future<void> _saveImage(String url) async {
var response = await Dio()
.get(url, options: Options(responseType: ResponseType.bytes));
final result = await ImageGallerySaver.saveImage(
Uint8List.fromList(response.data),
quality: 60,
name: '${DateTime.now().millisecondsSinceEpoch}');
return result;
}
Future<void> _saveVideo(String url) async {
var appDocDir = await getTemporaryDirectory();
var savePath =
appDocDir.path + '/${DateTime.now().millisecondsSinceEpoch}.mp4';
await Dio().download(url, savePath);
final result = await ImageGallerySaver.saveFile(savePath);
print(result);
}
}
@@ -3,9 +3,10 @@ import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dio/dio.dart';
import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:share_plus/share_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -90,27 +91,40 @@ class _ImageFooterState extends State<ImageFooter> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: StreamSvgIcon.iconShare(
size: 24.0,
color: StreamChatTheme.of(context).colorTheme.black,
),
onPressed: () async {
final attachment =
widget.mediaAttachments[widget.currentPage];
var url = attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl;
var type = attachment.type == 'image'
? 'jpg'
: url?.split('?')?.first?.split('.')?.last ?? 'jpg';
var request = await HttpClient().getUrl(Uri.parse(url));
var response = await request.close();
var bytes =
await consolidateHttpClientResponseBytes(response);
await Share.file('File', 'image.$type', bytes, 'image/$type');
},
),
kIsWeb
? SizedBox()
: IconButton(
icon: StreamSvgIcon.iconShare(
size: 24.0,
color: StreamChatTheme.of(context).colorTheme.black,
),
onPressed: () async {
final attachment =
widget.mediaAttachments[widget.currentPage];
final url = attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl;
final type = attachment.type == 'image'
? 'jpg'
: url?.split('?')?.first?.split('.')?.last ?? 'jpg';
final request =
await HttpClient().getUrl(Uri.parse(url));
final response = await request.close();
final bytes =
await consolidateHttpClientResponseBytes(response);
final tmpPath = await getTemporaryDirectory();
final filePath =
'${tmpPath.path}/${attachment.id}.$type';
final file = File(filePath);
await file.writeAsBytes(bytes);
await Share.shareFiles(
[filePath],
mimeTypes: [
'image/$type',
],
);
},
),
InkWell(
onTap: widget.onTitleTap,
child: Container(
@@ -223,13 +237,13 @@ class _ImageFooterState extends State<ImageFooter> {
media = InkWell(
onTap: () => widget.mediaSelectedCallBack(index),
child: AspectRatio(
aspectRatio: 1.0,
child: CachedNetworkImage(
imageUrl: attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl,
fit: BoxFit.cover,
),
aspectRatio: 1.0,
),
);
}
@@ -3,7 +3,7 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'image_actions_modal.dart';
import 'attachment_actions_modal.dart';
class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
/// True if this header shows the leading back button
@@ -110,11 +110,8 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
builder: (context) {
return StreamChannel(
channel: channel,
child: ImageActionsModal(
userName: userName,
sentAt: sentAt,
child: AttachmentActionsModal(
message: message,
urls: urls,
currentIndex: currentIndex,
onShowMessage: onShowMessage,
),
@@ -56,6 +56,11 @@ class _MediaListViewState extends State<MediaListView> {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 1.0, vertical: 1.0),
child: InkWell(
onTap: () {
if (widget.onSelect != null) {
widget.onSelect(media);
}
},
child: Stack(
children: [
AspectRatio(
@@ -125,11 +130,6 @@ class _MediaListViewState extends State<MediaListView> {
]
],
),
onTap: () {
if (widget.onSelect != null) {
widget.onSelect(media);
}
},
),
);
},
@@ -0,0 +1,21 @@
import 'package:flutter/widgets.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Class describing a message action
class MessageAction {
/// leading widget
final Widget leading;
/// title widget
final Widget title;
/// callback called on tap
final OnMessageTap onTap;
/// returns a new instance of a [MessageAction]
MessageAction({
this.leading,
this.title,
this.onTap,
});
}
@@ -4,6 +4,7 @@ import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:stream_chat_flutter/src/message_action.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/reaction_picker.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
@@ -34,6 +35,9 @@ class MessageActionsModal extends StatefulWidget {
final ShapeBorder attachmentShape;
final DisplayWidget showUserAvatar;
/// List of custom actions
final List<MessageAction> customActions;
const MessageActionsModal({
Key key,
@required this.message,
@@ -53,6 +57,7 @@ class MessageActionsModal extends StatefulWidget {
this.messageShape,
this.attachmentShape,
this.reverse = false,
this.customActions = const [],
}) : super(key: key);
@override
@@ -223,6 +228,12 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
_buildFlagButton(context),
if (widget.showDeleteMessage)
_buildDeleteButton(context),
...widget.customActions.map((action) {
return _buildCustomAction(
context,
action,
);
})
].insertBetween(
Container(
height: 1,
@@ -248,27 +259,69 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
);
}
InkWell _buildCustomAction(
BuildContext context,
MessageAction messageAction,
) {
return InkWell(
onTap: () {
messageAction.onTap?.call(widget.message);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 11.0, horizontal: 16.0),
child: Row(
children: [
messageAction.leading ?? Offstage(),
const SizedBox(width: 16),
messageAction.title ?? Offstage(),
],
),
),
);
}
void _showFlagDialog() async {
final client = StreamChat.of(context).client;
var answer = await showConfirmationDialog(context,
title: 'Flag Message',
icon: StreamSvgIcon.flag(
color: StreamChatTheme.of(context).colorTheme.accentRed,
size: 24.0,
),
question:
'Do you want to send a copy of this message to a\nmoderator for further investigation?',
okText: 'FLAG',
cancelText: 'CANCEL');
final answer = await showConfirmationDialog(
context,
title: 'Flag Message',
icon: StreamSvgIcon.flag(
color: StreamChatTheme.of(context).colorTheme.accentRed,
size: 24.0,
),
question:
'Do you want to send a copy of this message to a\nmoderator for further investigation?',
okText: 'FLAG',
cancelText: 'CANCEL',
);
if (answer) {
final theme = StreamChatTheme.of(context);
if (answer == true) {
try {
await client.flagMessage(widget.message.id);
_showDismissAlert();
await showInfoDialog(
context,
icon: StreamSvgIcon.flag(
color: theme.colorTheme.accentRed,
size: 24.0,
),
details: 'The message has been reported to a moderator.',
title: 'Message flagged',
okText: 'OK',
);
} catch (err) {
if (json.decode(err?.body ?? {})['code'] == 4) {
_showDismissAlert();
await showInfoDialog(
context,
icon: StreamSvgIcon.flag(
color: theme.colorTheme.accentRed,
size: 24.0,
),
details: 'The message has been reported to a moderator.',
title: 'Message flagged',
okText: 'OK',
);
} else {
_showErrorAlert();
}
@@ -306,133 +359,16 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
}
}
void _showDismissAlert() {
showModalBottomSheet(
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16.0),
topRight: Radius.circular(16.0),
)),
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 26.0,
),
StreamSvgIcon.flag(
color: StreamChatTheme.of(context).colorTheme.accentRed,
size: 24.0,
),
SizedBox(
height: 26.0,
),
Text(
'Message flagged',
style: StreamChatTheme.of(context).textTheme.headlineBold,
),
SizedBox(
height: 7.0,
),
Text('The message has been reported to a moderator.'),
SizedBox(
height: 36.0,
),
Container(
color:
StreamChatTheme.of(context).colorTheme.black.withOpacity(.08),
height: 1.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
child: Text(
'OK',
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue),
),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
],
);
},
);
}
void _showErrorAlert() {
showModalBottomSheet(
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16.0),
topRight: Radius.circular(16.0),
)),
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 26.0,
),
StreamSvgIcon.error(
color: StreamChatTheme.of(context).colorTheme.accentRed,
size: 24.0,
),
SizedBox(
height: 26.0,
),
Text(
'Something went wrong',
style: StreamChatTheme.of(context).textTheme.headlineBold,
),
SizedBox(
height: 7.0,
),
Text('The operation couldn\'t be completed.'),
SizedBox(
height: 36.0,
),
Container(
color:
StreamChatTheme.of(context).colorTheme.black.withOpacity(.08),
height: 1.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
child: Text(
'OK',
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue),
),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
],
);
},
showInfoDialog(
context,
icon: StreamSvgIcon.error(
color: StreamChatTheme.of(context).colorTheme.accentRed,
size: 24.0,
),
details: 'The operation couldn\'t be completed.',
title: 'Something went wrong',
okText: 'OK',
);
}
@@ -637,21 +573,16 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
],
),
),
Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: widget.editMessageInputBuilder != null
? widget.editMessageInputBuilder(context, widget.message)
: MessageInput(
editMessage: widget.message,
preMessageSending: (m) {
FocusScope.of(context).unfocus();
Navigator.pop(context);
return m;
},
),
),
widget.editMessageInputBuilder != null
? widget.editMessageInputBuilder(context, widget.message)
: MessageInput(
editMessage: widget.message,
preMessageSending: (m) {
FocusScope.of(context).unfocus();
Navigator.pop(context);
return m;
},
),
],
),
);
@@ -11,6 +11,7 @@ import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:image_picker/image_picker.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/src/video_service.dart';
import 'package:stream_chat_flutter/src/media_list_view.dart';
import 'package:stream_chat_flutter/src/message_list_view.dart';
@@ -42,6 +43,15 @@ enum DefaultAttachmentTypes {
file,
}
/// Available locations for the sendMessage button relative to the textField
enum SendButtonLocation {
/// inside the textField
inside,
/// outside the textField
outside,
}
const _kMinMediaPickerSize = 360.0;
const _kMaxAttachmentSize = 20971520; // 20MB in Bytes
@@ -107,6 +117,11 @@ class MessageInput extends StatefulWidget {
this.focusNode,
this.quotedMessage,
this.onQuotedMessageCleared,
this.sendButtonLocation = SendButtonLocation.outside,
this.autofocus = false,
this.hideSendAsDm = false,
this.idleSendButton,
this.activeSendButton,
}) : super(key: key);
/// Message to edit
@@ -134,6 +149,9 @@ class MessageInput extends StatefulWidget {
/// If true the attachments button will not be displayed
final bool disableAttachments;
/// Hide send as dm checkbox
final bool hideSendAsDm;
/// The text controller of the TextField
final TextEditingController textEditingController;
@@ -155,6 +173,18 @@ class MessageInput extends StatefulWidget {
///
final VoidCallback onQuotedMessageCleared;
/// The location of the send button
final SendButtonLocation sendButtonLocation;
/// Autofocus property passed to the TextField
final bool autofocus;
/// Send button widget in an idle state
final Widget idleSendButton;
/// Send button widget in an active state
final Widget activeSendButton;
@override
MessageInputState createState() => MessageInputState();
@@ -281,7 +311,7 @@ class MessageInputState extends State<MessageInput> {
padding: const EdgeInsets.all(8.0),
child: _buildTextField(context),
),
if (widget.parentMessage != null)
if (widget.parentMessage != null && !widget.hideSendAsDm)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: _buildDmCheckbox(),
@@ -304,15 +334,16 @@ class MessageInputState extends State<MessageInput> {
Flex _buildTextField(BuildContext context) {
return Flex(
direction: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
if (!_commandEnabled) _buildExpandActionsButton(),
if (widget.actionsLocation == ActionsLocation.left)
...widget.actions ?? [],
_buildTextInput(context),
_animateSendButton(context),
if (widget.actionsLocation == ActionsLocation.right)
...widget.actions ?? [],
if (widget.sendButtonLocation == SendButtonLocation.outside)
_animateSendButton(context),
],
);
}
@@ -384,14 +415,20 @@ class MessageInputState extends State<MessageInput> {
}
Widget _animateSendButton(BuildContext context) {
final sendButton = widget.activeSendButton != null
? InkWell(
onTap: sendMessage,
child: widget.activeSendButton,
)
: _buildSendButton(context);
return Padding(
padding: const EdgeInsets.all(8.0),
child: AnimatedCrossFade(
crossFadeState: (_messageIsPresent || _attachments.isNotEmpty)
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: _buildSendButton(context),
secondChild: _buildIdleSendButton(context),
firstChild: sendButton,
secondChild: widget.idleSendButton ?? _buildIdleSendButton(context),
duration:
StreamChatTheme.of(context).messageInputTheme.sendAnimationDuration,
alignment: Alignment.center,
@@ -447,101 +484,139 @@ class MessageInputState extends State<MessageInput> {
child: Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
border: Border.all(color: theme.colorTheme.greyGainsboro),
borderRadius: theme.messageInputTheme.borderRadius,
gradient: _focusNode.hasFocus
? theme.messageInputTheme.activeBorderGradient
: theme.messageInputTheme.idleBorderGradient,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildReplyToMessage(),
_buildAttachments(),
LimitedBox(
maxHeight: widget.maxHeight,
child: TextField(
key: Key('messageInputText'),
enabled: _inputEnabled,
minLines: null,
maxLines: null,
onSubmitted: (_) => sendMessage(),
keyboardType: widget.keyboardType,
controller: textEditingController,
focusNode: _focusNode,
style: theme.textTheme.body,
autofocus: false,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
isDense: true,
hintText: _getHint(),
hintStyle: theme.textTheme.body.copyWith(
color: theme.colorTheme.grey,
child: Padding(
padding: const EdgeInsets.all(1.5),
child: Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
borderRadius: theme.messageInputTheme.borderRadius,
color: theme.messageInputTheme.inputBackground,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildReplyToMessage(),
_buildAttachments(),
LimitedBox(
maxHeight: widget.maxHeight,
child: TextField(
key: Key('messageInputText'),
enabled: _inputEnabled,
minLines: null,
maxLines: null,
onSubmitted: (_) => sendMessage(),
keyboardType: widget.keyboardType,
controller: textEditingController,
focusNode: _focusNode,
style: theme.messageInputTheme.inputTextStyle,
autofocus: widget.autofocus,
textAlignVertical: TextAlignVertical.center,
decoration: _getInputDecoration(),
textCapitalization: TextCapitalization.sentences,
),
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.transparent)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.transparent)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.transparent)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.transparent)),
disabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.transparent)),
contentPadding: const EdgeInsets.fromLTRB(16, 12, 13, 11),
prefixIconConstraints: BoxConstraints.tight(Size(78, 24)),
suffixIconConstraints: BoxConstraints.tight(Size(40, 40)),
prefixIcon: _commandEnabled
? Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: theme.colorTheme.accentBlue,
),
margin: const EdgeInsets.only(right: 4, left: 8),
alignment: Alignment.center,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
StreamSvgIcon.lightning(
color: Colors.white,
size: 16.0,
),
Text(
_chosenCommand?.name?.toUpperCase() ?? '',
style: StreamChatTheme.of(context)
.textTheme
.footnoteBold
.copyWith(
color: Colors.white,
),
),
],
),
)
: null,
suffixIcon: _commandEnabled
? IconButton(
icon: StreamSvgIcon.closeSmall(),
splashRadius: 24,
padding: const EdgeInsets.all(0),
constraints: BoxConstraints.tightFor(
height: 24,
width: 24,
),
onPressed: () {
setState(() => _commandEnabled = false);
},
)
: null,
),
textCapitalization: TextCapitalization.sentences,
),
)
],
)
],
),
),
),
),
),
);
}
InputDecoration _getInputDecoration() {
final theme = StreamChatTheme.of(context);
final passedDecoration = theme.messageInputTheme.inputDecoration;
return InputDecoration(
isDense: true,
hintText: _getHint(),
hintStyle: theme.messageInputTheme.inputTextStyle.copyWith(
color: theme.colorTheme.grey,
),
border: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.transparent,
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.transparent,
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.transparent,
),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.transparent,
),
),
disabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.transparent,
),
),
contentPadding: const EdgeInsets.fromLTRB(16, 12, 13, 11),
prefixIconConstraints: BoxConstraints.tight(Size(78, 24)),
suffixIconConstraints: BoxConstraints.tight(Size(40, 40)),
prefixIcon: _commandEnabled
? Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: theme.colorTheme.accentBlue,
),
margin: const EdgeInsets.only(right: 4, left: 8),
alignment: Alignment.center,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
StreamSvgIcon.lightning(
color: Colors.white,
size: 16.0,
),
Text(
_chosenCommand?.name?.toUpperCase() ?? '',
style: StreamChatTheme.of(context)
.textTheme
.footnoteBold
.copyWith(
color: Colors.white,
),
),
],
),
)
: null,
suffixIcon: Row(
children: [
if (_commandEnabled)
IconButton(
icon: StreamSvgIcon.closeSmall(),
splashRadius: 24,
padding: const EdgeInsets.all(0),
constraints: BoxConstraints.tightFor(
height: 24,
width: 24,
),
onPressed: () {
setState(() => _commandEnabled = false);
},
),
if (widget.sendButtonLocation == SendButtonLocation.inside)
_animateSendButton(context),
],
),
).merge(passedDecoration);
}
Timer _debounce;
void _onChanged(BuildContext context, String s) {
@@ -621,7 +696,9 @@ class MessageInputState extends State<MessageInput> {
.last
.contains('@')) {
_mentionsOverlay = _buildMentionsOverlayEntry();
Overlay.of(context).insert(_mentionsOverlay);
if (_mentionsOverlay != null) {
Overlay.of(context).insert(_mentionsOverlay);
}
}
}
@@ -646,7 +723,9 @@ class MessageInputState extends State<MessageInput> {
_commandsOverlay = null;
} else {
_commandsOverlay = _buildCommandsOverlayEntry();
Overlay.of(context).insert(_commandsOverlay);
if (_commandsOverlay != null) {
Overlay.of(context).insert(_commandsOverlay);
}
}
}
}
@@ -661,6 +740,10 @@ class MessageInputState extends State<MessageInput> {
?.toList() ??
[];
if (commands.isEmpty) {
return null;
}
RenderBox renderBox = context.findRenderObject();
final size = renderBox.size;
@@ -833,6 +916,7 @@ class MessageInputState extends State<MessageInput> {
child: Material(
color: StreamChatTheme.of(context).colorTheme.whiteSmoke,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
@@ -930,7 +1014,19 @@ class MessageInputState extends State<MessageInput> {
color: StreamChatTheme.of(context).colorTheme.white,
borderRadius: BorderRadius.circular(8.0),
),
child: _buildPickerSection(),
child: _PickerWidget(
filePickerIndex: _filePickerIndex,
containsFile: _attachmentContainsFile,
selectedMedias: _attachments.keys.toList(),
onAddMoreFilesClick: pickFile,
onMediaSelected: (media) {
if (_attachments.containsKey(media.id)) {
setState(() => _attachments.remove(media.id));
} else {
_addAttachment(media);
}
},
),
),
),
],
@@ -939,109 +1035,6 @@ class MessageInputState extends State<MessageInput> {
);
}
Widget _buildPickerSection() {
final _attachmentContainsFile = _attachments.values.any((it) {
return it.type == 'file';
});
switch (_filePickerIndex) {
case 0:
return FutureBuilder<bool>(
future: PhotoManager.requestPermission(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.data) {
if (_attachmentContainsFile) {
return GestureDetector(
onTap: () {
pickFile(DefaultAttachmentTypes.file);
},
child: Container(
constraints: BoxConstraints.expand(),
color: StreamChatTheme.of(context).colorTheme.whiteSmoke,
child: Text(
'Add more files',
style: TextStyle(
color:
StreamChatTheme.of(context).colorTheme.accentBlue,
fontWeight: FontWeight.bold,
),
),
alignment: Alignment.center,
),
);
}
return MediaListView(
selectedIds: _attachments.keys.toList(),
onSelect: (media) async {
if (!_attachments.containsKey(media.id)) {
_addAttachment(media);
} else {
setState(() => _attachments.remove(media.id));
}
},
);
}
return InkWell(
onTap: () async {
PhotoManager.openSetting();
},
child: Container(
color: StreamChatTheme.of(context).colorTheme.whiteSmoke,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SvgPicture.asset(
'svgs/icon_picture_empty_state.svg',
package: 'stream_chat_flutter',
height: 140,
color: StreamChatTheme.of(context)
.colorTheme
.greyGainsboro,
),
Text(
'Please enable access to your photos \nand videos so you can share them with friends.',
style: StreamChatTheme.of(context)
.textTheme
.body
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.grey),
textAlign: TextAlign.center,
),
SizedBox(height: 6.0),
Center(
child: Text(
'Allow access to your gallery',
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
),
),
),
],
),
),
);
});
break;
default:
return SizedBox();
}
}
void _addAttachment(AssetEntity medium) async {
final mediaFile = await medium.originFile.timeout(
Duration(seconds: 5),
@@ -1090,81 +1083,81 @@ class MessageInputState extends State<MessageInput> {
switch (iconType) {
case 'giphy':
return CircleAvatar(
radius: 12,
child: StreamSvgIcon.giphyIcon(
size: 24.0,
),
radius: 12,
);
break;
case 'ban':
return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue,
radius: 12,
child: StreamSvgIcon.iconUserDelete(
size: 16.0,
color: Colors.white,
),
radius: 12,
);
break;
case 'flag':
return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue,
radius: 12,
child: StreamSvgIcon.flag(
size: 14.0,
color: Colors.white,
),
radius: 12,
);
break;
case 'imgur':
return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue,
radius: 12,
child: ClipOval(
child: StreamSvgIcon.imgur(
size: 24.0,
),
),
radius: 12,
);
break;
case 'mute':
return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue,
radius: 12,
child: StreamSvgIcon.mute(
size: 16.0,
color: Colors.white,
),
radius: 12,
);
break;
case 'unban':
return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue,
radius: 12,
child: StreamSvgIcon.userAdd(
size: 16.0,
color: Colors.white,
),
radius: 12,
);
break;
case 'unmute':
return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue,
radius: 12,
child: StreamSvgIcon.volumeUp(
size: 16.0,
color: Colors.white,
),
radius: 12,
);
break;
default:
return CircleAvatar(
backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue,
radius: 12,
child: StreamSvgIcon.lightning(
size: 16.0,
color: Colors.white,
),
radius: 12,
);
break;
}
@@ -1191,6 +1184,10 @@ class MessageInputState extends State<MessageInput> {
})?.toList() ??
[];
if (members.isEmpty) {
return null;
}
RenderBox renderBox = context.findRenderObject();
final size = renderBox.size;
@@ -1646,11 +1643,15 @@ class MessageInputState extends State<MessageInput> {
return getFileTypeImage(attachment.extraData['other']);
},
progressIndicatorBuilder: (context, _, progress) {
return Center(
child: Container(
width: 20.0,
height: 20.0,
child: const CircularProgressIndicator(),
return Shimmer.fromColors(
baseColor:
StreamChatTheme.of(context).colorTheme.greyGainsboro,
highlightColor:
StreamChatTheme.of(context).colorTheme.whiteSmoke,
child: Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
package: 'stream_chat_flutter',
),
);
},
@@ -1676,8 +1677,8 @@ class MessageInputState extends State<MessageInput> {
);
default:
return Container(
child: Icon(Icons.insert_drive_file),
color: Colors.black26,
child: Icon(Icons.insert_drive_file),
);
}
}
@@ -1903,14 +1904,12 @@ class MessageInputState extends State<MessageInput> {
if (file == null) return;
final mimeType = file.path.split('/').last.mimeType;
final mimeType = file.name?.mimeType;
final extraDataMap = <String, dynamic>{};
if (camera) {
if (mimeType.type == 'video' || mimeType.type == 'image') {
attachmentType = mimeType.type;
}
if (mimeType.type == 'video' || mimeType.type == 'image') {
attachmentType = mimeType.type;
} else {
attachmentType = 'file';
}
@@ -2013,9 +2012,10 @@ class MessageInputState extends State<MessageInput> {
return;
}
final shouldUnfocus = _commandEnabled;
if (_commandEnabled) {
text = '/${_chosenCommand.name} ' + text;
FocusScope.of(context).unfocus();
}
final attachments = [..._attachments.values];
@@ -2082,7 +2082,14 @@ class MessageInputState extends State<MessageInput> {
sendingFuture = channel.updateMessage(message);
}
if (!shouldUnfocus) {
FocusScope.of(context).requestFocus(_focusNode);
}
return sendingFuture.then((resp) {
if (resp.message?.type == 'error') {
_parseExistingMessage(message);
}
if (widget.onMessageSent != null) {
widget.onMessageSent(resp.message);
}
@@ -2140,6 +2147,9 @@ class MessageInputState extends State<MessageInput> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'OK',
style: StreamChatTheme.of(context)
@@ -2150,9 +2160,6 @@ class MessageInputState extends State<MessageInput> {
.colorTheme
.accentBlue),
),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
@@ -2240,3 +2247,114 @@ class Tuple2<T1, T2> {
@override
int get hashCode => item1.hashCode ^ item2.hashCode;
}
class _PickerWidget extends StatefulWidget {
final int filePickerIndex;
final bool containsFile;
final List<String> selectedMedias;
final void Function(DefaultAttachmentTypes) onAddMoreFilesClick;
final void Function(AssetEntity) onMediaSelected;
const _PickerWidget({
Key key,
@required this.filePickerIndex,
@required this.containsFile,
@required this.selectedMedias,
@required this.onAddMoreFilesClick,
@required this.onMediaSelected,
}) : super(key: key);
@override
__PickerWidgetState createState() => __PickerWidgetState();
}
class __PickerWidgetState extends State<_PickerWidget> {
Future<bool> requestPermission;
@override
void initState() {
super.initState();
requestPermission = PhotoManager.requestPermission();
}
@override
Widget build(BuildContext context) {
if (widget.filePickerIndex != 0) {
return Offstage();
}
return FutureBuilder<bool>(
future: requestPermission,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.data) {
if (widget.containsFile) {
return GestureDetector(
onTap: () {
widget.onAddMoreFilesClick(DefaultAttachmentTypes.file);
},
child: Container(
constraints: BoxConstraints.expand(),
color: StreamChatTheme.of(context).colorTheme.whiteSmoke,
alignment: Alignment.center,
child: Text(
'Add more files',
style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
fontWeight: FontWeight.bold,
),
),
),
);
}
return MediaListView(
selectedIds: widget.selectedMedias,
onSelect: widget.onMediaSelected,
);
}
return InkWell(
onTap: () async {
PhotoManager.openSetting();
},
child: Container(
color: StreamChatTheme.of(context).colorTheme.whiteSmoke,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SvgPicture.asset(
'svgs/icon_picture_empty_state.svg',
package: 'stream_chat_flutter',
height: 140,
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
),
Text(
'Please enable access to your photos \nand videos so you can share them with friends.',
style: StreamChatTheme.of(context).textTheme.body.copyWith(
color: StreamChatTheme.of(context).colorTheme.grey),
textAlign: TextAlign.center,
),
SizedBox(height: 6.0),
Center(
child: Text(
'Allow access to your gallery',
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
),
),
),
],
),
),
);
});
}
}
@@ -28,10 +28,15 @@ typedef ParentMessageBuilder = Widget Function(
BuildContext,
Message,
);
typedef SystemMessageBuilder = Widget Function(
BuildContext,
Message,
);
typedef ThreadBuilder = Widget Function(BuildContext context, Message parent);
typedef ThreadTapCallback = void Function(Message, Widget);
typedef OnMessageSwiped = void Function(Message);
typedef OnMessageTap = void Function(Message);
typedef ReplyTapCallback = void Function(Message);
class MessageDetails {
@@ -128,14 +133,22 @@ class MessageListView extends StatefulWidget {
this.showConnectionStateTile = false,
this.loadingBuilder,
this.emptyBuilder,
this.systemMessageBuilder,
this.messageListBuilder,
this.errorWidgetBuilder,
this.messageFilter,
this.customAttachmentBuilders,
this.onMessageTap,
this.onSystemMessageTap,
this.onAttachmentTap,
}) : super(key: key);
/// Function used to build a custom message widget
final MessageBuilder messageBuilder;
/// Function used to build a custom system message widget
final SystemMessageBuilder systemMessageBuilder;
/// Function used to build a custom parent message widget
final ParentMessageBuilder parentMessageBuilder;
@@ -204,10 +217,22 @@ class MessageListView extends StatefulWidget {
/// of a connection failure.
final ErrorBuilder errorWidgetBuilder;
/// Predicate used to filter messages
final bool Function(Message) messageFilter;
/// Attachment builders for the default message widget
/// Please change this in the [MessageWidget] if you are using a custom implementation
final Map<String, AttachmentBuilder> customAttachmentBuilders;
/// Called when any message is tapped except a system message (use [onSystemMessageTap] instead)
final OnMessageTap onMessageTap;
/// Called when system message is tapped
final OnMessageTap onSystemMessageTap;
// Customize onTap on attachment
final void Function(Message message, Attachment attachment) onAttachmentTap;
@override
_MessageListViewState createState() => _MessageListViewState();
}
@@ -265,6 +290,7 @@ class _MessageListViewState extends State<MessageListView> {
@override
Widget build(BuildContext context) {
return MessageListCore(
messageFilter: widget.messageFilter,
loadingBuilder: widget.loadingBuilder ??
(context) {
return Center(
@@ -356,6 +382,9 @@ class _MessageListViewState extends State<MessageListView> {
childAnchor: Alignment.topCenter,
message: statusString,
child: LazyLoadScrollView(
onPageScrollStart: () {
FocusScope.of(context).unfocus();
},
onStartOfPage: () async {
_inBetweenList = false;
if (!_upToDate) {
@@ -408,7 +437,7 @@ class _MessageListViewState extends State<MessageListView> {
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.lastMessageAt,
.subtitle,
),
),
);
@@ -595,9 +624,6 @@ class _MessageListViewState extends State<MessageListView> {
children: [
FloatingActionButton(
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
child: StreamSvgIcon.down(
color: StreamChatTheme.of(context).colorTheme.black,
),
onPressed: () {
if (unreadCount > 0) {
streamChannel.channel.markRead();
@@ -615,6 +641,9 @@ class _MessageListViewState extends State<MessageListView> {
);
}
},
child: StreamSvgIcon.down(
color: StreamChatTheme.of(context).colorTheme.black,
),
),
if (showUnreadCount)
Positioned(
@@ -804,6 +833,12 @@ class _MessageListViewState extends State<MessageListView> {
}
},
customAttachmentBuilders: widget.customAttachmentBuilders,
onMessageTap: (message) {
if (widget.onMessageTap != null) {
widget.onMessageTap(message);
}
FocusScope.of(context).unfocus();
},
);
}
@@ -812,11 +847,19 @@ class _MessageListViewState extends State<MessageListView> {
List<Message> messages,
int index,
) {
if (message.type == 'system' && message.text?.isNotEmpty == true) {
return SystemMessage(
key: ValueKey<String>('MESSAGE-${message.id}'),
message: message,
);
if ((message.type == 'system' || message.type == 'error') &&
message.text?.isNotEmpty == true) {
return widget.systemMessageBuilder?.call(context, message) ??
SystemMessage(
key: ValueKey<String>('MESSAGE-${message.id}'),
message: message,
onMessageTap: (message) {
if (widget.onSystemMessageTap != null) {
widget.onSystemMessageTap(message);
}
FocusScope.of(context).unfocus();
},
);
}
final userId = StreamChat.of(context).user.id;
@@ -965,6 +1008,13 @@ class _MessageListViewState extends State<MessageListView> {
}
},
customAttachmentBuilders: widget.customAttachmentBuilders,
onMessageTap: (message) {
if (widget.onMessageTap != null) {
widget.onMessageTap(message);
}
FocusScope.of(context).unfocus();
},
onAttachmentTap: widget.onAttachmentTap,
);
if (!message.isDeleted &&
@@ -995,10 +1045,6 @@ class _MessageListViewState extends State<MessageListView> {
end: colorTheme.white.withOpacity(0),
),
duration: const Duration(seconds: 3),
child: Padding(
padding: const EdgeInsets.only(top: 4.0),
child: child,
),
onEnd: () => initialMessageHighlightComplete = true,
builder: (_, color, child) {
return Container(
@@ -1006,6 +1052,10 @@ class _MessageListViewState extends State<MessageListView> {
child: child,
);
},
child: Padding(
padding: const EdgeInsets.only(top: 4.0),
child: child,
),
);
}
return child;
@@ -237,6 +237,9 @@ class MessageReactionsModal extends StatelessWidget {
borderRadius: BorderRadius.circular(32),
),
Positioned(
bottom: 6,
left: isCurrentUser ? -3 : null,
right: isCurrentUser ? -3 : null,
child: Align(
alignment:
reverse ? Alignment.centerRight : Alignment.centerLeft,
@@ -250,9 +253,6 @@ class MessageReactionsModal extends StatelessWidget {
highlightOwnReactions: false,
),
),
bottom: 6,
left: isCurrentUser ? -3 : null,
right: isCurrentUser ? -3 : null,
),
],
),
@@ -157,53 +157,14 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
errorBuilder: widget.errorBuilder ??
(BuildContext context, dynamic error) {
if (error is Error) {
print((error).stackTrace);
}
var message = error.toString();
if (error is DioError) {
if (error.type == DioErrorType.RESPONSE) {
message = error.message;
} else {
message = 'Check your connection and retry';
}
print(error.stackTrace);
}
return InfoTile(
showMessage: widget.showErrorTile,
tileAnchor: Alignment.topCenter,
childAnchor: Alignment.topCenter,
message: 'An error occurred.',
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text.rich(
TextSpan(
children: [
WidgetSpan(
child: Padding(
padding: const EdgeInsets.only(right: 2.0),
child: Icon(Icons.error_outline),
),
),
TextSpan(text: 'Error loading messages'),
],
),
style: Theme.of(context).textTheme.headline6,
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Text(message),
),
ElevatedButton(
onPressed: () {
_messageSearchListController.loadData();
},
child: Text('Retry'),
),
],
),
),
child: Container(),
);
},
loadingBuilder: widget.loadingBuilder ??
@@ -21,7 +21,8 @@ class MessageText extends StatelessWidget {
@override
Widget build(BuildContext context) {
final text = _replaceMentions(message.text);
final text = _replaceMentions(message.text).replaceAll('\n', '\\\n');
return MarkdownBody(
data: text,
onTapLink: (
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/message_action.dart';
import 'package:stream_chat_flutter/src/message_actions_modal.dart';
import 'package:stream_chat_flutter/src/message_reactions_modal.dart';
import 'package:stream_chat_flutter/src/quoted_message_widget.dart';
@@ -143,6 +144,15 @@ class MessageWidget extends StatefulWidget {
/// Function called when quotedMessage is tapped
final OnQuotedMessageTap onQuotedMessageTap;
/// Function called when message is tapped
final void Function(Message) onMessageTap;
/// List of custom actions shown on message long tap
final List<MessageAction> customActions;
// Customize onTap on attachment
final void Function(Message message, Attachment attachment) onAttachmentTap;
///
MessageWidget({
Key key,
@@ -157,6 +167,7 @@ class MessageWidget extends StatefulWidget {
this.borderRadiusGeometry,
this.attachmentBorderRadiusGeometry,
this.onMentionTap,
this.onMessageTap,
this.showReactionPickerIndicator = false,
this.showUserAvatar = DisplayWidget.show,
this.showSendingIndicator = true,
@@ -191,6 +202,8 @@ class MessageWidget extends StatefulWidget {
this.attachmentPadding = EdgeInsets.zero,
this.allRead = false,
this.onQuotedMessageTap,
this.customActions = const [],
this.onAttachmentTap,
}) : attachmentBuilders = {
'image': (context, message, attachment) {
return ImageAttachment(
@@ -203,6 +216,11 @@ class MessageWidget extends StatefulWidget {
),
onShowMessage: onShowMessage,
onReturnAction: onReturnAction,
onAttachmentTap: onAttachmentTap != null
? () {
onAttachmentTap?.call(message, attachment);
}
: null,
);
},
'video': (context, message, attachment) {
@@ -216,6 +234,11 @@ class MessageWidget extends StatefulWidget {
message: message,
onShowMessage: onShowMessage,
onReturnAction: onReturnAction,
onAttachmentTap: onAttachmentTap != null
? () {
onAttachmentTap?.call(message, attachment);
}
: null,
);
},
'giphy': (context, message, attachment) {
@@ -311,6 +334,9 @@ class _MessageWidgetState extends State<MessageWidget>
type: MaterialType.transparency,
child: Portal(
child: InkWell(
onTap: () {
widget.onMessageTap(widget.message);
},
onLongPress: widget.message.isDeleted && !isFailedState
? null
: () => onLongPress(context),
@@ -351,9 +377,9 @@ class _MessageWidgetState extends State<MessageWidget>
portal: Container(
transform:
Matrix4.translationValues(-12, 0, 0),
child: _buildReactionIndicator(context),
constraints:
BoxConstraints(maxWidth: 22 * 6.0),
child: _buildReactionIndicator(context),
),
portalAnchor: Alignment(-1.0, -1.0),
childAnchor: Alignment(1, -1.0),
@@ -745,6 +771,7 @@ class _MessageWidgetState extends State<MessageWidget>
!isFailedState &&
widget.onThreadTap != null,
showFlagButton: widget.showFlagButton,
customActions: widget.customActions,
),
);
});
@@ -951,6 +978,7 @@ class _MessageWidgetState extends State<MessageWidget>
user: widget.message.user,
onTap: widget.onUserAvatarTap,
constraints: widget.messageTheme.avatarTheme.constraints,
borderRadius: widget.messageTheme.avatarTheme.borderRadius,
showOnlineStatus: false,
),
),
@@ -55,7 +55,7 @@ class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> {
return Container(
height: widget.size.height,
width: widget.size.width,
child: _controller.value.initialized
child: _controller.value.isInitialized
? VideoPlayer(_controller)
: CircularProgressIndicator());
}
@@ -94,6 +94,19 @@ class _ReactionPickerState extends State<ReactionPicker>
height: 24,
width: 24,
),
onPressed: () {
if (ownReactionIndex != -1) {
removeReaction(
context,
widget.message.ownReactions[ownReactionIndex],
);
} else {
sendReaction(
context,
reactionIcon.type,
);
}
},
child: AnimatedBuilder(
animation: animations[index],
builder: (context, val) {
@@ -121,19 +134,6 @@ class _ReactionPickerState extends State<ReactionPicker>
),
);
}),
onPressed: () {
if (ownReactionIndex != -1) {
removeReaction(
context,
widget.message.ownReactions[ownReactionIndex],
);
} else {
sendReaction(
context,
reactionIcon.type,
);
}
},
),
);
})
@@ -2,7 +2,6 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_badger/flutter_app_badger.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -93,9 +92,9 @@ class StreamChatState extends State<StreamChat> {
),
child: StreamChatCore(
client: client,
child: widget.child,
onBackgroundEventReceived: widget.onBackgroundEventReceived,
backgroundKeepAlive: widget.backgroundKeepAlive,
child: widget.child,
),
);
},
@@ -121,12 +120,5 @@ class StreamChatState extends State<StreamChat> {
@override
void initState() {
super.initState();
client.state?.totalUnreadCountStream?.listen((count) {
if (count > 0) {
FlutterAppBadger.updateBadgeCount(count);
} else {
FlutterAppBadger.removeBadge();
}
});
}
}
@@ -5,6 +5,7 @@ import 'package:stream_chat_flutter/src/channel_preview.dart';
import 'package:stream_chat_flutter/src/message_input.dart';
import 'package:stream_chat_flutter/src/reaction_icon.dart';
import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Inherited widget providing the [StreamChatThemeData] to the widget tree
@@ -45,12 +46,15 @@ class StreamChatThemeData {
/// The text themes used in the widgets
final TextTheme textTheme;
/// The text themes used in the widgets
/// The color themes used in the widgets
final ColorTheme colorTheme;
/// Theme of the [ChannelPreview]
final ChannelPreviewTheme channelPreviewTheme;
/// Theme of the [ChannelListHeader]
final ChannelListHeaderTheme channelListHeaderTheme;
/// Theme of the chat widgets dedicated to a channel
final ChannelTheme channelTheme;
@@ -60,7 +64,7 @@ class StreamChatThemeData {
/// Theme of other users messages
final MessageTheme otherMessageTheme;
/// Theme of other users messages
/// Theme dedicated to the [MessageInput] widget
final MessageInputTheme messageInputTheme;
/// The widget that will be built when the channel image is unavailable
@@ -79,6 +83,7 @@ class StreamChatThemeData {
const StreamChatThemeData({
this.textTheme,
this.colorTheme,
this.channelListHeaderTheme,
this.channelPreviewTheme,
this.channelTheme,
this.otherMessageTheme,
@@ -98,9 +103,7 @@ class StreamChatThemeData {
accentBlue: theme.accentColor,
),
defaultTheme.textTheme,
).copyWith(
// primaryIconTheme: theme.primaryIconTheme,
);
);
return defaultTheme.merge(customizedTheme) ?? customizedTheme;
}
@@ -116,9 +119,12 @@ class StreamChatThemeData {
Widget Function(BuildContext, Channel) defaultChannelImage,
Widget Function(BuildContext, User) defaultUserImage,
IconThemeData primaryIconTheme,
ChannelListHeaderTheme channelListHeaderTheme,
List<ReactionIcon> reactionIcons,
}) =>
StreamChatThemeData(
channelListHeaderTheme:
channelListHeaderTheme ?? this.channelListHeaderTheme,
textTheme: textTheme ?? this.textTheme,
colorTheme: colorTheme ?? this.colorTheme,
primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme,
@@ -135,6 +141,9 @@ class StreamChatThemeData {
StreamChatThemeData merge(StreamChatThemeData other) {
if (other == null) return this;
return copyWith(
channelListHeaderTheme:
channelListHeaderTheme?.merge(other.channelListHeaderTheme) ??
other.channelListHeaderTheme,
textTheme: textTheme?.merge(other.textTheme) ?? other.textTheme,
colorTheme: colorTheme?.merge(other.colorTheme) ?? other.colorTheme,
primaryIconTheme: other.primaryIconTheme,
@@ -189,6 +198,17 @@ class StreamChatThemeData {
color: colorTheme.black.withOpacity(.5),
),
indicatorIconSize: 16.0),
channelListHeaderTheme: ChannelListHeaderTheme(
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20),
constraints: BoxConstraints.tightFor(
height: 40,
width: 40,
),
),
color: colorTheme.white,
title: textTheme.headlineBold,
),
channelTheme: ChannelTheme(
channelHeaderTheme: ChannelHeaderTheme(
avatarTheme: AvatarTheme(
@@ -200,9 +220,8 @@ class StreamChatThemeData {
),
color: colorTheme.white,
title: textTheme.headlineBold,
lastMessageAt: TextStyle(
fontSize: 11,
color: colorTheme.black.withOpacity(.5),
subtitle: textTheme.footnote.copyWith(
color: Color(0xff7A7A7A),
),
),
),
@@ -249,12 +268,26 @@ class StreamChatThemeData {
),
),
messageInputTheme: MessageInputTheme(
borderRadius: BorderRadius.circular(20),
sendAnimationDuration: Duration(milliseconds: 300),
actionButtonColor: colorTheme.accentBlue,
actionButtonIdleColor: colorTheme.grey,
sendButtonColor: colorTheme.accentBlue,
sendButtonIdleColor: colorTheme.greyGainsboro,
inputBackground: colorTheme.white,
inputTextStyle: textTheme.body,
idleBorderGradient: LinearGradient(
colors: [
colorTheme.greyGainsboro,
colorTheme.greyGainsboro,
],
),
activeBorderGradient: LinearGradient(
colors: [
colorTheme.greyGainsboro,
colorTheme.greyGainsboro,
],
),
),
reactionIcons: [
ReactionIcon(
@@ -809,26 +842,26 @@ class ChannelPreviewTheme {
class ChannelHeaderTheme {
final TextStyle title;
final TextStyle lastMessageAt;
final TextStyle subtitle;
final AvatarTheme avatarTheme;
final Color color;
const ChannelHeaderTheme({
this.title,
this.lastMessageAt,
this.subtitle,
this.avatarTheme,
this.color,
});
ChannelHeaderTheme copyWith({
TextStyle title,
TextStyle lastMessageAt,
TextStyle subtitle,
AvatarTheme avatarTheme,
Color color,
}) =>
ChannelHeaderTheme(
title: title ?? this.title,
lastMessageAt: lastMessageAt ?? this.lastMessageAt,
subtitle: subtitle ?? this.subtitle,
avatarTheme: avatarTheme ?? this.avatarTheme,
color: color ?? this.color,
);
@@ -837,8 +870,48 @@ class ChannelHeaderTheme {
if (other == null) return this;
return copyWith(
title: title?.merge(other.title) ?? other.title,
lastMessageAt:
lastMessageAt?.merge(other.lastMessageAt) ?? other.lastMessageAt,
subtitle: subtitle?.merge(other.subtitle) ?? other.subtitle,
avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme,
color: other.color,
);
}
}
/// Theme dedicated to the [ChannelListHeader]
class ChannelListHeaderTheme {
/// Style of the title text
final TextStyle title;
/// Theme dedicated to the userAvatar
final AvatarTheme avatarTheme;
/// Background color of the appbar
final Color color;
/// Returns a new [ChannelListHeaderTheme]
const ChannelListHeaderTheme({
this.title,
this.avatarTheme,
this.color,
});
/// Returns a new [ChannelListHeaderTheme] replacing some of its properties
ChannelListHeaderTheme copyWith({
TextStyle title,
AvatarTheme avatarTheme,
Color color,
}) =>
ChannelListHeaderTheme(
title: title ?? this.title,
avatarTheme: avatarTheme ?? this.avatarTheme,
color: color ?? this.color,
);
/// Merges [this] [ChannelListHeaderTheme] with the [other]
ChannelListHeaderTheme merge(ChannelListHeaderTheme other) {
if (other == null) return this;
return copyWith(
title: title?.merge(other.title) ?? other.title,
avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme,
color: other.color,
);
@@ -865,6 +938,21 @@ class MessageInputTheme {
/// Background color of [MessageInput]
final Color inputBackground;
/// TextStyle of [MessageInput]
final TextStyle inputTextStyle;
/// InputDecoration of [MessageInput]
final InputDecoration inputDecoration;
/// Border gradient when the [MessageInput] is not focused
final Gradient idleBorderGradient;
/// Border gradient when the [MessageInput] is focused
final Gradient activeBorderGradient;
/// Border radius of [MessageInput]
final BorderRadius borderRadius;
/// Returns a new [MessageInputTheme]
const MessageInputTheme({
this.sendAnimationDuration,
@@ -873,6 +961,11 @@ class MessageInputTheme {
this.actionButtonIdleColor,
this.sendButtonIdleColor,
this.inputBackground,
this.inputTextStyle,
this.inputDecoration,
this.activeBorderGradient,
this.idleBorderGradient,
this.borderRadius,
});
/// Returns a new [MessageInputTheme] replacing some of its properties
@@ -883,6 +976,11 @@ class MessageInputTheme {
Color sendButtonColor,
Color actionButtonIdleColor,
Color sendButtonIdleColor,
TextStyle inputTextStyle,
InputDecoration inputDecoration,
Gradient activeBorderGradient,
Gradient idleBorderGradient,
BorderRadius borderRadius,
}) =>
MessageInputTheme(
sendAnimationDuration:
@@ -892,7 +990,12 @@ class MessageInputTheme {
sendButtonColor: sendButtonColor ?? this.sendButtonColor,
actionButtonIdleColor:
actionButtonIdleColor ?? this.actionButtonIdleColor,
inputTextStyle: inputTextStyle ?? this.inputTextStyle,
sendButtonIdleColor: sendButtonIdleColor ?? this.sendButtonIdleColor,
inputDecoration: inputDecoration ?? this.inputDecoration,
activeBorderGradient: activeBorderGradient ?? this.activeBorderGradient,
idleBorderGradient: idleBorderGradient ?? this.idleBorderGradient,
borderRadius: borderRadius ?? this.borderRadius,
);
/// Merges [this] [MessageInputTheme] with the [other]
@@ -905,6 +1008,12 @@ class MessageInputTheme {
actionButtonIdleColor: other.actionButtonIdleColor,
sendButtonColor: other.sendButtonColor,
sendButtonIdleColor: other.sendButtonIdleColor,
inputTextStyle: other.inputTextStyle,
inputDecoration: inputDecoration?.merge(other.inputDecoration) ??
other.inputDecoration,
activeBorderGradient: other.activeBorderGradient,
idleBorderGradient: other.idleBorderGradient,
borderRadius: other.borderRadius,
);
}
}
@@ -13,7 +13,6 @@ class StreamNeumorphicButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: child,
margin: EdgeInsets.all(8.0),
height: 40,
width: 40,
@@ -35,6 +34,7 @@ class StreamNeumorphicButton extends StatelessWidget {
),
],
),
child: child,
);
}
}
@@ -18,24 +18,15 @@ class StreamSvgIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
final key = Key('StreamSvgIcon-$assetName');
return kIsWeb
? Image.network(
'packages/stream_chat_flutter/svgs/$assetName',
width: width,
height: height,
key: key,
color: color,
alignment: Alignment.center,
)
: SvgPicture.asset(
'lib/svgs/$assetName',
package: 'stream_chat_flutter',
key: key,
width: width,
height: height,
color: color,
alignment: Alignment.center,
);
return SvgPicture.asset(
'lib/svgs/$assetName',
package: 'stream_chat_flutter',
key: key,
width: width,
height: height,
color: color,
alignment: Alignment.center,
);
}
factory StreamSvgIcon.settings({
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// It shows a date divider depending on the date difference
@@ -18,37 +17,7 @@ class SystemMessage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final divider = Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Divider(),
),
);
final createdAt = Jiffy(message.createdAt.toLocal());
final now = DateTime.now();
final hourInfo = createdAt.format('h:mm a');
String dayInfo;
if (Jiffy(createdAt).isSame(now, Units.DAY)) {
dayInfo = 'TODAY';
} else if (Jiffy(createdAt)
.isSame(now.subtract(Duration(days: 1)), Units.DAY)) {
dayInfo = 'YESTERDAY';
} else if (Jiffy(createdAt).isAfter(
now.subtract(Duration(days: 7)),
Units.DAY,
)) {
dayInfo = createdAt.format('EEEE').toUpperCase();
} else if (Jiffy(createdAt).isAfter(
Jiffy(now).subtract(years: 1),
Units.DAY,
)) {
dayInfo = createdAt.format('dd/MM').toUpperCase();
} else {
dayInfo = createdAt.format('dd/MM/yyyy').toUpperCase();
}
final theme = StreamChatTheme.of(context);
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
@@ -56,59 +25,12 @@ class SystemMessage extends StatelessWidget {
onMessageTap(message);
}
},
child: Container(
width: double.infinity,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
divider,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
message.text,
style: TextStyle(
fontSize: 10,
color: Theme.of(context)
.textTheme
.headline6
.color
.withOpacity(.5),
fontWeight: FontWeight.bold,
),
),
Text.rich(
TextSpan(
children: [
TextSpan(
text: dayInfo,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
TextSpan(text: ' AT'),
TextSpan(text: ' $hourInfo'),
],
style: TextStyle(
fontWeight: FontWeight.normal,
),
),
style: TextStyle(
fontSize: 10,
color: Theme.of(context)
.textTheme
.headline6
.color
.withOpacity(.5),
),
),
],
),
),
divider,
],
child: Text(
message.text,
textAlign: TextAlign.center,
softWrap: true,
style: theme.textTheme.captionBold.copyWith(
color: theme.colorTheme.grey,
),
),
);
@@ -63,15 +63,35 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
/// By default it calls [Navigator.pop]
final VoidCallback onBackPressed;
/// Callback to call when the title is tapped.
final VoidCallback onTitleTap;
/// The message parent of this thread
final Message parent;
/// Title widget
final Widget title;
/// Subtitle widget
final Widget subtitle;
/// Leading widget
final Widget leading;
/// AppBar actions
final List<Widget> actions;
/// Instantiate a new ThreadHeader
ThreadHeader({
Key key,
@required this.parent,
this.showBackButton = true,
this.onBackPressed,
this.title,
this.subtitle,
this.leading,
this.actions,
this.onTitleTap,
}) : preferredSize = Size.fromHeight(kToolbarHeight),
super(key: key);
@@ -81,51 +101,61 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
automaticallyImplyLeading: false,
brightness: Theme.of(context).brightness,
elevation: 1,
leading: showBackButton
? StreamBackButton(
cid: StreamChannel.of(context).channel.cid,
onPressed: onBackPressed,
showUnreads: true,
)
: SizedBox(),
leading: leading ??
(showBackButton
? StreamBackButton(
cid: StreamChannel.of(context).channel.cid,
onPressed: onBackPressed,
showUnreads: true,
)
: SizedBox()),
backgroundColor:
StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color,
centerTitle: true,
title: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Thread Reply',
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.title,
),
SizedBox(height: 2),
Row(
mainAxisSize: MainAxisSize.min,
actions: actions,
title: InkWell(
onTap: onTitleTap,
child: Container(
height: preferredSize.height,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'with ',
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.lastMessageAt,
),
Flexible(
child: ChannelName(
textStyle: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.lastMessageAt,
),
),
title ??
Text(
'Thread Reply',
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.title,
),
SizedBox(height: 2),
subtitle ??
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'with ',
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.subtitle,
),
Flexible(
child: ChannelName(
textStyle: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.subtitle,
),
),
],
),
],
),
],
),
),
);
}
@@ -14,41 +14,43 @@ class UnreadIndicator extends StatelessWidget {
@override
Widget build(BuildContext context) {
final client = StreamChat.of(context).client;
return StreamBuilder<int>(
stream: cid != null
? client.state.channels[cid].state.unreadCountStream
: client.state.totalUnreadCountStream,
initialData: cid != null
? client.state.channels[cid].state.unreadCount
: client.state.totalUnreadCount,
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == 0) {
return SizedBox();
}
return Material(
borderRadius: BorderRadius.circular(8),
color: StreamChatTheme.of(context)
.channelPreviewTheme
.unreadCounterColor,
child: Padding(
padding: const EdgeInsets.only(
left: 5.0,
right: 5.0,
top: 2,
bottom: 1,
),
child: Center(
child: Text(
'${snapshot.data}',
style: TextStyle(
fontSize: 11,
color: Colors.white,
return IgnorePointer(
child: StreamBuilder<int>(
stream: cid != null
? client.state.channels[cid].state.unreadCountStream
: client.state.totalUnreadCountStream,
initialData: cid != null
? client.state.channels[cid].state.unreadCount
: client.state.totalUnreadCount,
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == 0) {
return SizedBox();
}
return Material(
borderRadius: BorderRadius.circular(8),
color: StreamChatTheme.of(context)
.channelPreviewTheme
.unreadCounterColor,
child: Padding(
padding: const EdgeInsets.only(
left: 5.0,
right: 5.0,
top: 2,
bottom: 1,
),
child: Center(
child: Text(
'${snapshot.data}',
style: TextStyle(
fontSize: 11,
color: Colors.white,
),
),
),
),
),
);
},
);
},
),
);
}
}
@@ -28,6 +28,9 @@ class UrlAttachment extends StatelessWidget {
Container(
clipBehavior: Clip.antiAliasWithSaveLayer,
margin: EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
),
child: Stack(
children: [
CachedNetworkImage(
@@ -39,6 +42,12 @@ class UrlAttachment extends StatelessWidget {
left: 0.0,
bottom: -1,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(16.0),
),
color: StreamChatTheme.of(context).colorTheme.blueAlice,
),
child: Padding(
padding: const EdgeInsets.only(
top: 8.0,
@@ -57,19 +66,10 @@ class UrlAttachment extends StatelessWidget {
),
),
),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(16.0),
),
color: StreamChatTheme.of(context).colorTheme.blueAlice,
),
),
),
],
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
),
),
Padding(
padding: textPadding,
@@ -39,26 +39,29 @@ class UserAvatar extends StatelessWidget {
user.extraData['image'] != '';
final streamChatTheme = StreamChatTheme.of(context);
Widget avatar = ClipRRect(
clipBehavior: Clip.antiAlias,
borderRadius: borderRadius ??
streamChatTheme.ownMessageTheme.avatarTheme.borderRadius,
child: Container(
constraints: constraints ??
streamChatTheme.ownMessageTheme.avatarTheme.constraints,
decoration: BoxDecoration(
color: streamChatTheme.colorTheme.accentBlue,
Widget avatar = FittedBox(
fit: BoxFit.cover,
child: ClipRRect(
clipBehavior: Clip.antiAlias,
borderRadius: borderRadius ??
streamChatTheme.ownMessageTheme.avatarTheme.borderRadius,
child: Container(
constraints: constraints ??
streamChatTheme.ownMessageTheme.avatarTheme.constraints,
decoration: BoxDecoration(
color: streamChatTheme.colorTheme.accentBlue,
),
child: hasImage
? CachedNetworkImage(
filterQuality: FilterQuality.high,
imageUrl: user.extraData['image'],
errorWidget: (_, __, ___) {
return streamChatTheme.defaultUserImage(context, user);
},
fit: BoxFit.cover,
)
: streamChatTheme.defaultUserImage(context, user),
),
child: hasImage
? CachedNetworkImage(
filterQuality: FilterQuality.high,
imageUrl: user.extraData['image'],
errorWidget: (_, __, ___) {
return streamChatTheme.defaultUserImage(context, user);
},
fit: BoxFit.cover,
)
: streamChatTheme.defaultUserImage(context, user),
),
);
@@ -91,6 +94,7 @@ class UserAvatar extends StatelessWidget {
alignment: onlineIndicatorAlignment,
child: Material(
type: MaterialType.circle,
color: streamChatTheme.colorTheme.white,
child: Container(
margin: const EdgeInsets.all(2.0),
constraints: onlineIndicatorConstraints ??
@@ -103,7 +107,6 @@ class UserAvatar extends StatelessWidget {
color: streamChatTheme.colorTheme.accentGreen,
),
),
color: streamChatTheme.colorTheme.white,
),
),
),
+110 -102
View File
@@ -36,63 +36,74 @@ Future<bool> showConfirmationDialog(
)),
builder: (context) {
final effect = StreamChatTheme.of(context).colorTheme.borderTop;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 26.0),
if (icon != null) icon,
SizedBox(height: 26.0),
Text(
title,
style: StreamChatTheme.of(context).textTheme.headlineBold,
),
SizedBox(height: 7.0),
Text(
question,
textAlign: TextAlign.center,
),
SizedBox(height: 36.0),
Container(
color: effect.color.withOpacity(effect.alpha ?? 1),
height: 1,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: Text(
cancelText,
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.5)),
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 26.0),
if (icon != null) icon,
SizedBox(height: 26.0),
Text(
title,
style: StreamChatTheme.of(context).textTheme.headlineBold,
),
SizedBox(height: 7.0),
Text(
question,
textAlign: TextAlign.center,
),
SizedBox(height: 36.0),
Container(
color: effect.color.withOpacity(effect.alpha ?? 1),
height: 1,
),
Row(
children: [
Flexible(
child: Container(
alignment: Alignment.center,
child: TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text(
cancelText,
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.5)),
),
),
),
),
onPressed: () {
Navigator.of(context).pop(false);
},
),
TextButton(
child: Text(
okText,
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentRed),
Flexible(
child: Container(
alignment: Alignment.center,
child: TextButton(
onPressed: () {
Navigator.pop(context, true);
},
child: Text(
okText,
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentRed),
),
),
),
),
onPressed: () {
Navigator.pop(context, true);
},
),
],
),
],
],
),
],
),
);
});
}
@@ -101,13 +112,13 @@ Future<bool> showInfoDialog(
BuildContext context, {
String title,
Widget icon,
String question,
String details,
String okText,
StreamChatThemeData theme,
}) {
return showModalBottomSheet(
backgroundColor:
theme.colorTheme.white ?? StreamChatTheme.of(context).colorTheme.white,
backgroundColor: theme?.colorTheme?.white ??
StreamChatTheme.of(context).colorTheme.white,
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
@@ -115,54 +126,51 @@ Future<bool> showInfoDialog(
topRight: Radius.circular(16.0),
)),
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 26.0,
),
if (icon != null) icon,
SizedBox(
height: 26.0,
),
Text(
title,
style: theme.textTheme.headlineBold ??
StreamChatTheme.of(context).textTheme.headlineBold,
),
SizedBox(
height: 7.0,
),
Text(question),
SizedBox(
height: 36.0,
),
Container(
color: theme.colorTheme.black.withOpacity(.08) ??
StreamChatTheme.of(context).colorTheme.black.withOpacity(.08),
height: 1.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: Text(
okText,
style: TextStyle(
color: theme.colorTheme.black.withOpacity(0.5) ??
StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.5),
fontWeight: FontWeight.w400),
),
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 26.0,
),
if (icon != null) icon,
SizedBox(
height: 26.0,
),
Text(
title,
style: theme?.textTheme?.headlineBold ??
StreamChatTheme.of(context).textTheme.headlineBold,
),
SizedBox(
height: 7.0,
),
Text(details),
SizedBox(
height: 36.0,
),
Container(
color: theme?.colorTheme?.black?.withOpacity(.08) ??
StreamChatTheme.of(context).colorTheme.black.withOpacity(.08),
height: 1.0,
),
Center(
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
okText,
style: TextStyle(
color: theme?.colorTheme?.black?.withOpacity(0.5) ??
StreamChatTheme.of(context).colorTheme.accentBlue,
fontWeight: FontWeight.w400,
),
),
),
],
),
],
),
],
),
);
},
);
@@ -1,6 +1,8 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_thumbnail/video_thumbnail.dart';
import 'stream_svg_icon.dart';
@@ -65,17 +67,27 @@ class _VideoThumbnailImageState extends State<VideoThumbnailImage> {
builder: (_) {
if (snapshot.hasError) {
return widget.errorBuilder?.call(context, snapshot.error) ??
Center(child: StreamSvgIcon.error());
Center(
child: StreamSvgIcon.error(),
);
}
if (!snapshot.hasData) {
return widget.placeholderBuilder?.call(context) ??
Image.asset(
'images/placeholder.png',
package: 'stream_chat_flutter',
fit: widget.fit,
height: widget.height,
width: widget.width,
);
return Container(
constraints: BoxConstraints.expand(),
child: widget.placeholderBuilder?.call(context) ??
Shimmer.fromColors(
baseColor: StreamChatTheme.of(context)
.colorTheme
.greyGainsboro,
highlightColor:
StreamChatTheme.of(context).colorTheme.whiteSmoke,
child: Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
package: 'stream_chat_flutter',
),
),
);
}
return Image.memory(
snapshot.data,
@@ -7,6 +7,7 @@ export 'src/channel_name.dart';
export 'src/channel_preview.dart';
export 'src/date_divider.dart';
export 'src/deleted_message.dart';
export 'src/message_action.dart';
export 'src/attachment/attachment.dart';
export 'src/full_screen_media.dart';
export 'src/image_header.dart';
+9 -10
View File
@@ -1,7 +1,7 @@
name: stream_chat_flutter
homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
version: 1.3.2-beta
version: 1.4.0-beta
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -11,21 +11,20 @@ environment:
dependencies:
flutter:
sdk: flutter
stream_chat_flutter_core: ^1.3.2-beta
flutter_app_badger: ^1.1.2
photo_view: ^0.10.3
stream_chat_flutter_core: ^1.4.0-beta
photo_view: ^0.11.0
rxdart: ^0.25.0
scrollable_positioned_list: ^0.1.8
jiffy: ^3.0.1
flutter_svg: ^0.19.1
flutter_svg: ^0.19.3
flutter_portal: ^0.3.0
cached_network_image: ^2.5.0
shimmer: ^1.1.2
flutter_markdown: ^0.5.2
url_launcher: ^5.7.10
emojis: ^0.9.3
video_player: ^1.0.1
chewie: ^0.12.1+1
video_player: ^2.0.0
chewie: ^1.0.0
file_picker: ^2.1.5
image_picker: ^0.6.7+17
flutter_keyboard_visibility: ^4.0.2
@@ -38,11 +37,11 @@ dependencies:
flutter_slidable: ^0.5.7
clipboard: ^0.1.2+8
image_gallery_saver: ^1.6.7
esys_flutter_share: ^1.0.2
photo_manager: ^0.6.0
share_plus: ^1.2.0
photo_manager: ^1.0.0
transparent_image: ^1.0.0
ezanimation: ^0.4.1
synchronized: ^2.2.0+2
synchronized: ^2.1.0
characters: ^1.0.0
dio: ^3.0.10
path_provider: ^1.6.27