FlutterDouBan icon indicating copy to clipboard operation
FlutterDouBan copied to clipboard

项目报错

Open pangliming opened this issue 6 years ago • 56 comments

image 项目down下来,就报错,这是怎么回事

pangliming avatar Jun 21 '19 06:06 pangliming

你的sdk环境

kaina404 avatar Jun 21 '19 06:06 kaina404

image 就是这样的,刚刚接触,不知道怎么回事

pangliming avatar Jun 21 '19 06:06 pangliming

我也是这个问题 ,我觉得我项目环境没问题

rongcheng2017 avatar Jun 24 '19 02:06 rongcheng2017

@rongchen2017 好的。我看看

kaina404 avatar Jun 25 '19 01:06 kaina404

@rongcheng2017

kaina404 avatar Jun 25 '19 01:06 kaina404

@pangliming @rongcheng2017 贴一个flutter doctor出来呀

CrazyCoderShi avatar Jun 25 '19 03:06 CrazyCoderShi

image 上面是flutter doctor的输出,目前用的是android studio ,下面是报错的地方 image

rongcheng2017 avatar Jun 26 '19 03:06 rongcheng2017

@rongcheng2017 你的dart版本是多少,我的是

kaina404 avatar Jun 26 '19 03:06 kaina404

image

kaina404 avatar Jun 26 '19 03:06 kaina404

@kaina404 image

rongcheng2017 avatar Jun 27 '19 08:06 rongcheng2017

@kaina404 麻烦您了

rongcheng2017 avatar Jun 27 '19 08:06 rongcheng2017

@rongcheng2017 安装我下面的代码复制过去试一下。(PS: 你可以吧GitHub跟你的email关联起来,这样我的回复你会立即看到的...)

library cached_network_image;

import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui show instantiateImageCodec, Codec;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';

/// CachedNetworkImage for Flutter
///
/// Copyright (c) 2017 Rene Floor
///
/// Released under MIT License.
///缓存网络图片
class CachedNetworkImage extends StatefulWidget {
  static List<Object> _registeredErrors = <Object>[];

  /// Creates a widget that displays a [placeholder] while an [imageUrl] is loading
  /// then cross-fades to display the [imageUrl].
  /// Optional [httpHeaders] can be used for example for authentication on the server.
  ///
  /// The [imageUrl], [fadeOutDuration], [fadeOutCurve],
  /// [fadeInDuration], [fadeInCurve], [alignment], [repeat], and
  /// [matchTextDirection] arguments must not be null. Arguments [width],
  /// [height], [fit], [alignment], [repeat] and [matchTextDirection]
  /// are only used for the image and not for the placeholder.
  const CachedNetworkImage({
    Key key,
    this.placeholder,
    @required this.imageUrl,
    this.errorWidget,
    this.fadeOutDuration: const Duration(milliseconds: 300),
    this.fadeOutCurve: Curves.easeOut,
    this.fadeInDuration: const Duration(milliseconds: 700),
    this.fadeInCurve: Curves.easeIn,
    this.width,
    this.height,
    this.fit,
    this.alignment: Alignment.center,
    this.repeat: ImageRepeat.noRepeat,
    this.matchTextDirection: false,
    this.httpHeaders,
  })  : assert(imageUrl != null),
        assert(fadeOutDuration != null),
        assert(fadeOutCurve != null),
        assert(fadeInDuration != null),
        assert(fadeInCurve != null),
        assert(alignment != null),
        assert(repeat != null),
        assert(matchTextDirection != null),
        super(key: key);

  /// Widget displayed while the target [imageUrl] is loading.
  final Widget placeholder;

  /// The target image that is displayed.
  final String imageUrl;

  /// Widget displayed while the target [imageUrl] failed loading.
  final Widget errorWidget;

  /// The duration of the fade-out animation for the [placeholder].
  final Duration fadeOutDuration;

  /// The curve of the fade-out animation for the [placeholder].
  final Curve fadeOutCurve;

  /// The duration of the fade-in animation for the [imageUrl].
  final Duration fadeInDuration;

  /// The curve of the fade-in animation for the [imageUrl].
  final Curve fadeInCurve;

  /// If non-null, require the image to have this width.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio. This may result in a sudden change if the size of the
  /// placeholder widget does not match that of the target image. The size is
  /// also affected by the scale factor.
  final double width;

  /// If non-null, require the image to have this height.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio. This may result in a sudden change if the size of the
  /// placeholder widget does not match that of the target image. The size is
  /// also affected by the scale factor.
  final double height;

  /// How to inscribe the image into the space allocated during layout.
  ///
  /// The default varies based on the other fields. See the discussion at
  /// [paintImage].
  final BoxFit fit;

  /// How to align the image within its bounds.
  ///
  /// The alignment aligns the given position in the image to the given position
  /// in the layout bounds. For example, a [Alignment] alignment of (-1.0,
  /// -1.0) aligns the image to the top-left corner of its layout bounds, while a
  /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
  /// image with the bottom right corner of its layout bounds. Similarly, an
  /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
  /// middle of the bottom edge of its layout bounds.
  ///
  /// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
  /// [AlignmentDirectional]), then an ambient [Directionality] widget
  /// must be in scope.
  ///
  /// Defaults to [Alignment.center].
  ///
  /// See also:
  ///
  ///  * [Alignment], a class with convenient constants typically used to
  ///    specify an [AlignmentGeometry].
  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
  ///    relative to text direction.
  final AlignmentGeometry alignment;

  /// How to paint any portions of the layout bounds not covered by the image.
  final ImageRepeat repeat;

  /// Whether to paint the image in the direction of the [TextDirection].
  ///
  /// If this is true, then in [TextDirection.ltr] contexts, the image will be
  /// drawn with its origin in the top left (the "normal" painting direction for
  /// images); and in [TextDirection.rtl] contexts, the image will be drawn with
  /// a scaling factor of -1 in the horizontal direction so that the origin is
  /// in the top right.
  ///
  /// This is occasionally used with images in right-to-left environments, for
  /// images that were designed for left-to-right locales. Be careful, when
  /// using this, to not flip images with integral shadows, text, or other
  /// effects that will look incorrect when flipped.
  ///
  /// If this is true, there must be an ambient [Directionality] widget in
  /// scope.
  final bool matchTextDirection;

  // Optional headers for the http request of the image url
  final Map<String, String> httpHeaders;

  @override
  State<StatefulWidget> createState() => new _CachedNetworkImageState();
}

/// The phases a [CachedNetworkImage] goes through.
@visibleForTesting
enum ImagePhase {
  /// The initial state.
  ///
  /// We do not yet know whether the target image is ready and therefore no
  /// animation is necessary, or whether we need to use the placeholder and
  /// wait for the image to load.
  start,

  /// Waiting for the target image to load.
  waiting,

  /// Fading out previous image.
  fadeOut,

  /// Fading in new image.
  fadeIn,

  /// Fade-in complete.
  completed,
}

typedef void _ImageProviderResolverListener();

class _ImageProviderResolver {
  _ImageProviderResolver({
    @required this.state,
    @required this.listener,
  });

  final _CachedNetworkImageState state;
  final _ImageProviderResolverListener listener;

  CachedNetworkImage get widget => state.widget;

  ImageStream _imageStream;
  ImageInfo _imageInfo;

  void resolve(CachedNetworkImageProvider provider) {
    final ImageStream oldImageStream = _imageStream;
    _imageStream = provider.resolve(createLocalImageConfiguration(state.context,
        size: widget.width != null && widget.height != null
            ? new Size(widget.width, widget.height)
            : null));

    if (_imageStream.key != oldImageStream?.key) {
      oldImageStream?.removeListener(_handleImageChanged);
      _imageStream.addListener(_handleImageChanged);
    }
  }

    get _handleImageChanged => _handleImageChanged2;

  void _handleImageChanged2(ImageInfo imageInfo, bool synchronousCall) {
    _imageInfo = imageInfo;
    listener();
  }

  void stopListening() {
    _imageStream?.removeListener(_handleImageChanged);
  }
}

class _CachedNetworkImageState extends State<CachedNetworkImage>
    with TickerProviderStateMixin {
  _ImageProviderResolver _imageResolver;
  CachedNetworkImageProvider _imageProvider;

  AnimationController _controller;
  Animation<double> _animation;

  ImagePhase _phase = ImagePhase.start;
  ImagePhase get phase => _phase;

  bool _hasError;

  @override
  void initState() {
    _hasError = false;
    _imageProvider = new CachedNetworkImageProvider(widget.imageUrl,
        headers: widget.httpHeaders, errorListener: _imageLoadingFailed);
    _imageResolver =
        new _ImageProviderResolver(state: this, listener: _updatePhase);

    _controller = new AnimationController(
      value: 1.0,
      vsync: this,
    );
    _controller.addListener(() {
      setState(() {
        // Trigger rebuild to update opacity value.
      });
    });
    _controller.addStatusListener((AnimationStatus status) {
      _updatePhase();
    });

    super.initState();
  }

  @override
  void didChangeDependencies() {
    _imageProvider
        .obtainKey(createLocalImageConfiguration(context))
        .then<void>((CachedNetworkImageProvider key) {
      if (CachedNetworkImage._registeredErrors.contains(key)) {
        setState(() => _hasError = true);
      }
    });

    _resolveImage();
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(CachedNetworkImage oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.imageUrl != oldWidget.imageUrl ||
        widget.placeholder != widget.placeholder) {
      _imageProvider = new CachedNetworkImageProvider(widget.imageUrl,
          headers: widget.httpHeaders,
          errorListener: _imageLoadingFailed);

      _resolveImage();
    }
  }

  @override
  void reassemble() {
    _resolveImage(); // in case the image cache was flushed
    super.reassemble();
  }

  void _resolveImage() {
    _imageResolver.resolve(_imageProvider);

    if (_phase == ImagePhase.start) _updatePhase();
  }

  void _updatePhase() {
    setState(() {
      switch (_phase) {
        case ImagePhase.start:
          if (_imageResolver._imageInfo != null || _hasError)
            _phase = ImagePhase.completed;
          else
            _phase = ImagePhase.waiting;
          break;
        case ImagePhase.waiting:
          if (_hasError && widget.errorWidget == null) {
            _phase = ImagePhase.completed;
            return;
          }

          if (_imageResolver._imageInfo != null || _hasError) {
            if (widget.placeholder == null) {
              _startFadeIn();
            } else {
              _startFadeOut();
            }
          }
          break;
        case ImagePhase.fadeOut:
          if (_controller.status == AnimationStatus.dismissed) {
            _startFadeIn();
          }
          break;
        case ImagePhase.fadeIn:
          if (_controller.status == AnimationStatus.completed) {
            // Done finding in new image.
            _phase = ImagePhase.completed;
          }
          break;
        case ImagePhase.completed:
          // Nothing to do.
          break;
      }
    });
  }

  // Received image data. Begin placeholder fade-out.
  void _startFadeOut() {
    _controller.duration = widget.fadeOutDuration;
    _animation = new CurvedAnimation(
      parent: _controller,
      curve: widget.fadeOutCurve,
    );
    _phase = ImagePhase.fadeOut;
    _controller.reverse(from: 1.0);
  }

  // Done fading out placeholder. Begin target image fade-in.
  void _startFadeIn() {
    _controller.duration = widget.fadeInDuration;
    _animation = new CurvedAnimation(
      parent: _controller,
      curve: widget.fadeInCurve,
    );
    _phase = ImagePhase.fadeIn;
    _controller.forward(from: 0.0);
  }

  @override
  void dispose() {
    _imageResolver.stopListening();
    _controller.dispose();
    super.dispose();
  }

  bool get _isShowingPlaceholder {
    assert(_phase != null);
    switch (_phase) {
      case ImagePhase.start:
      case ImagePhase.waiting:
      case ImagePhase.fadeOut:
        return true;
      case ImagePhase.fadeIn:
      case ImagePhase.completed:
        return _hasError && widget.errorWidget == null;
    }

    return null;
  }

  void _imageLoadingFailed() {
    _imageProvider
        .obtainKey(createLocalImageConfiguration(context))
        .then<void>((CachedNetworkImageProvider key) {
      if (!CachedNetworkImage._registeredErrors.contains(key)) {
        CachedNetworkImage._registeredErrors.add(key);
      }
    });
    _hasError = true;
    _updatePhase();
  }

  @override
  Widget build(BuildContext context) {
    assert(_phase != ImagePhase.start);
    if (_isShowingPlaceholder && widget.placeholder != null) {
      return _fadedWidget(widget.placeholder);
    }

    if (_hasError && widget.errorWidget != null) {
      return _fadedWidget(widget.errorWidget);
    }

    final ImageInfo imageInfo = _imageResolver._imageInfo;
    return new RawImage(
      image: imageInfo?.image,
      width: widget.width,
      height: widget.height,
      scale: imageInfo?.scale ?? 1.0,
      color: new Color.fromRGBO(255, 255, 255, _animation?.value ?? 1.0),
      colorBlendMode: BlendMode.modulate,
      fit: widget.fit,
      alignment: widget.alignment,
      repeat: widget.repeat,
      matchTextDirection: widget.matchTextDirection,
    );
  }

  Widget _fadedWidget(Widget w) {
    return new Opacity(
      opacity: _animation?.value ?? 1.0,
      child: w,
    );
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
    super.debugFillProperties(description);
    description.add(new EnumProperty<ImagePhase>('phase', _phase));
    description.add(new DiagnosticsProperty<ImageInfo>(
        'pixels', _imageResolver._imageInfo));
    description.add(new DiagnosticsProperty<ImageStream>(
        'image stream', _imageResolver._imageStream));
  }
}

typedef void ErrorListener();

class CachedNetworkImageProvider
    extends ImageProvider<CachedNetworkImageProvider> {
  /// Creates an ImageProvider which loads an image from the [url], using the [scale].
  /// When the image fails to load [errorListener] is called.
  const CachedNetworkImageProvider(this.url,
      {this.scale: 1.0, this.errorListener, this.headers})
      : assert(url != null),
        assert(scale != null);

  /// Web url of the image to load
  final String url;

  /// Scale of the image
  final double scale;

  /// Listener to be called when images fails to load.
  final ErrorListener errorListener;

  // Set headers for the image provider, for example for authentication
  final Map<String, String> headers;

  @override
  Future<CachedNetworkImageProvider> obtainKey(
      ImageConfiguration configuration) {
    return new SynchronousFuture<CachedNetworkImageProvider>(this);
  }

  @override
  ImageStreamCompleter load(CachedNetworkImageProvider key) {
    return new MultiFrameImageStreamCompleter(
        codec: _loadAsync(key),
        scale: key.scale,
        informationCollector: (StringBuffer information) {
          information.writeln('Image provider: $this');
          information.write('Image key: $key');
        });
  }

  Future<ui.Codec> _loadAsync(CachedNetworkImageProvider key) async {
    var cacheManager = await CacheManager.getInstance();
    var file = await cacheManager.getFile(url, headers: headers);
    if (file == null) {
      if (errorListener != null) errorListener();
      return Future<ui.Codec>.error("Couldn't download or retreive file.");
    }
    return await _loadAsyncFromFile(key, file);
  }

  Future<ui.Codec> _loadAsyncFromFile(
      CachedNetworkImageProvider key, File file) async {
    assert(key == this);

    final Uint8List bytes = await file.readAsBytes();

    if (bytes.lengthInBytes == 0) {
      if (errorListener != null) errorListener();
      throw new Exception("File was empty");
    }

    return await ui.instantiateImageCodec(bytes);
  }

  @override
  bool operator ==(dynamic other) {
    if (other.runtimeType != runtimeType) return false;
    final CachedNetworkImageProvider typedOther = other;
    return url == typedOther.url && scale == typedOther.scale;
  }

  @override
  int get hashCode => hashValues(url, scale);

  @override
  String toString() => '$runtimeType("$url", scale: $scale)';
}

kaina404 avatar Jun 27 '19 10:06 kaina404

替换了还是报错: error: The argument type '(StringBuffer) → Null' can't be assigned to the parameter type '() → Iterable<DiagnosticsNode>'. (argument_type_not_assignable at [doubanapp] lib/widgets/image/cached_network_image.dart:464)

image

/Users/cibn/flutter/bin/flutter doctor --verbose [✓] Flutter (Channel beta, v1.6.3, on Mac OS X 10.14.5 18F132, locale zh-Hans-CN) • Flutter version 1.6.3 at /Users/cibn/flutter • Framework revision bc7bc94083 (5 weeks ago), 2019-05-23 10:29:07 -0700 • Engine revision 8dc3a4cde2 • Dart version 2.3.2 (build 2.3.2-dev.0.0 e3edfd36b2)

[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) • Android SDK at /Users/cibn/Library/Android/sdk • Android NDK location not configured (optional; useful for native profiling support) • Platform android-28, build-tools 28.0.3 • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1248-b01) • All Android licenses accepted.

[✓] iOS toolchain - develop for iOS devices (Xcode 10.2.1) • Xcode at /Applications/Xcode.app/Contents/Developer • Xcode 10.2.1, Build version 10E1001 • ios-deploy 1.9.4 • CocoaPods version 1.6.1

[✓] Android Studio (version 3.3) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin version 34.0.1 • Dart plugin version 182.5215 • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1248-b01)

[✓] VS Code (version 1.34.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.0.2

[✓] Connected device (1 available) • iPhone Xʀ • 80609203-33C1-46F4-B5E3-F7984DE9A1B8 • ios • com.apple.CoreSimulator.SimRuntime.iOS-12-1 (simulator)

zhennayw avatar Jun 28 '19 02:06 zhennayw

@zhennayw 你这是什么问题?

kaina404 avatar Jun 28 '19 02:06 kaina404

@kaina404 _image The argument type '(StringBuffer) → Null' can't be assigned to the parameter type '() → Iterable<DiagnosticsNode>'.

yousaywhat avatar Jun 28 '19 07:06 yousaywhat

@kaina404 把这3行注释了也还是不行,还是运行不起来 会报错: Error: The argument type 'void Function(ImageIn fo, bool)' can't be assigned to the parameter type 'ImageStreamListener'

yousaywhat avatar Jun 28 '19 07:06 yousaywhat

@yousaywhat


@override
  ImageStreamCompleter load(CachedNetworkImageProvider key) {
    return new MultiFrameImageStreamCompleter(
        codec: _loadAsync(key),
        scale: key.scale);
  }

kaina404 avatar Jun 28 '19 07:06 kaina404

@kaina404 删除了报错的三行还是无法运行项目: Compiler message: file:///C:/Users/STARTIASOFT/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/palette_generator-0.1.1/lib/palette_generator.dart:188:29: Error: The argument type 'void Function(ImageIn fo, bool)' can't be assigned to the parameter type 'ImageStreamListener'.

  • 'ImageInfo' is from 'package:flutter/src/painting/image_stream.dart' ('file:///C:/Soft/flutter/packages/flutter/lib/src/painting/image_stream.dart').
  • 'ImageStreamListener' is from 'package:flutter/src/painting/image_stream.dart' ('file:///C:/Soft/flutter/packages/flutter/lib/src/painting/image_stream.dart'). Try changing the type of the parameter, or casting the argument to 'ImageStreamListener'. stream.removeListener(imageListener); ^ file:///C:/Users/STARTIASOFT/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/palette_generator-0.1.1/lib/palette_generator.dart:194:31: Error: The argument type 'void Function(ImageIn fo, bool)' can't be assigned to the parameter type 'ImageStreamListener'.
  • 'ImageInfo' is from 'package:flutter/src/painting/image_stream.dart' ('file:///C:/Soft/flutter/packages/flutter/lib/src/painting/image_stream.dart').
  • 'ImageStreamListener' is from 'package:flutter/src/painting/image_stream.dart' ('file:///C:/Soft/flutter/packages/flutter/lib/src/painting/image_stream.dart'). Try changing the type of the parameter, or casting the argument to 'ImageStreamListener'. stream.removeListener(imageListener); ^ file:///C:/Users/STARTIASOFT/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/palette_generator-0.1.1/lib/palette_generator.dart:201:24: Error: The argument type 'void Function(ImageIn fo, bool)' can't be assigned to the parameter type 'ImageStreamListener'.
  • 'ImageInfo' is from 'package:flutter/src/painting/image_stream.dart' ('file:///C:/Soft/flutter/packages/flutter/lib/src/painting/image_stream.dart').
  • 'ImageStreamListener' is from 'package:flutter/src/painting/image_stream.dart' ('file:///C:/Soft/flutter/packages/flutter/lib/src/painting/image_stream.dart'). Try changing the type of the parameter, or casting the argument to 'ImageStreamListener'. stream.addListener(imageListener); ^ Compiler failed on E:\flutterDemo\FlutterDouBan-master-new\lib/main.dart

FAILURE: Build failed with an exception.

  • Where: Script 'C:\Soft\flutter\packages\flutter_tools\gradle\flutter.gradle' line: 647

  • What went wrong: Execution failed for task ':app:compileflutterBuildDebugandroid-arm64'.

Process 'command 'C:\Soft\flutter\bin\flutter.bat'' finished with non-zero exit value 1

  • Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

  • Get more help at https://help.gradle.org

yousaywhat avatar Jun 28 '19 07:06 yousaywhat

@yousaywhat 你这样改了么? image

kaina404 avatar Jun 28 '19 07:06 kaina404

@kaina404
改过了 是直接Copy你上面的那份代码粘贴进去的

yousaywhat avatar Jun 28 '19 07:06 yousaywhat

@yousaywhat 你介意变更你的dart sdk 跟我相同的版本么?我感觉是dart sdk问题

kaina404 avatar Jun 28 '19 07:06 kaina404

@kaina404 我现在的Dart版本是2.3.2 不知道如何回退到2.3.0

yousaywhat avatar Jun 28 '19 07:06 yousaywhat

@yousaywhat 我升级下我的sdk 稍等

kaina404 avatar Jun 28 '19 08:06 kaina404

flutter channel stable && flutter upgrade

切稳定分支即可, beta的分支果然坑很多.

passioncsu avatar Jun 30 '19 01:06 passioncsu

这应该是官方修改了API。 现在stable分支下也出现这个问题了(版本flutter 1.7.8+hotfix.2)。

wycers avatar Jul 09 '19 17:07 wycers

同样的报错,用的是最新的Flutter SDK

guangxingmao avatar Jul 10 '19 15:07 guangxingmao

似乎这样改就可以了: _imageStream.addListener(_handleImageChanged); 替换为 _imageStream.addListener(ImageStreamListener(_handleImageChanged));

但是项目中使用的palette_generator还存在着无法通过升级解决的问题

wycers avatar Jul 10 '19 15:07 wycers

@Wycers 你把palette_generator的依赖中前面的"^"去掉试一试

kaina404 avatar Jul 11 '19 03:07 kaina404

应该不行,我是直接在pubspec.yaml里把它改到了^0.1.1(这个包在pub上的最新版本)然后pub get了

然后我直接进这个包的源码把那个api修了,现在已经能跑了。

但是这种解决方式非常强行,建议找同类包替换一下。

wycers avatar Jul 11 '19 03:07 wycers

@Wycers 是这样的 我是使用0.1.0开发的“^”这个玩意表示支持最新版本,所以你要把这个依赖改成palette_generator: 0.1.0 注意,没有"^"。你这样尝试下。

kaina404 avatar Jul 11 '19 03:07 kaina404