Implement blurhash for images and avatars, start basic full screen image page
This commit is contained in:
parent
5a8f6bd8e7
commit
7ed1afbfdd
|
@ -1,4 +1,5 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:interstellar/src/models/image.dart';
|
||||
import 'package:interstellar/src/models/magazine.dart';
|
||||
import 'package:interstellar/src/models/post.dart';
|
||||
import 'package:interstellar/src/models/user.dart';
|
||||
|
@ -46,7 +47,7 @@ class CommentModel with _$CommentModel {
|
|||
required int postId,
|
||||
required int? rootId,
|
||||
required int? parentId,
|
||||
required String? image,
|
||||
required ImageModel? image,
|
||||
required String? body,
|
||||
required String? lang,
|
||||
required int? upvotes,
|
||||
|
@ -71,7 +72,7 @@ class CommentModel with _$CommentModel {
|
|||
postId: (json['entryId'] ?? json['postId']) as int,
|
||||
rootId: json['rootId'] as int?,
|
||||
parentId: json['parentId'] as int?,
|
||||
image: kbinGetImageUrl(json['image'] as Map<String, Object?>?),
|
||||
image: kbinGetImage(json['image'] as Map<String, Object?>?),
|
||||
body: json['body'] as String?,
|
||||
lang: json['lang'] as String,
|
||||
upvotes: json['favourites'] as int?,
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'image.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class ImageModel with _$ImageModel {
|
||||
const factory ImageModel({
|
||||
required String src,
|
||||
required String? altText,
|
||||
required String? blurHash,
|
||||
required int? blurHashWidth,
|
||||
required int? blurHashHeight,
|
||||
}) = _ImageModel;
|
||||
|
||||
factory ImageModel.fromKbin(Map<String, Object?> json) => ImageModel(
|
||||
src: (json['storageUrl'] ?? json['sourceUrl']) as String,
|
||||
altText: json['altText'] as String?,
|
||||
blurHash: json['blurHash'] as String?,
|
||||
blurHashWidth: json['width'] as int?,
|
||||
blurHashHeight: json['height'] as int?,
|
||||
);
|
||||
|
||||
factory ImageModel.fromLemmy(String json) => ImageModel(
|
||||
src: json,
|
||||
altText: null,
|
||||
blurHash: null,
|
||||
blurHashWidth: null,
|
||||
blurHashHeight: null,
|
||||
);
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:interstellar/src/models/image.dart';
|
||||
import 'package:interstellar/src/models/user.dart';
|
||||
import 'package:interstellar/src/utils/models.dart';
|
||||
import 'package:interstellar/src/widgets/markdown/markdown_mention.dart';
|
||||
|
@ -38,7 +39,7 @@ class DetailedMagazineModel with _$DetailedMagazineModel {
|
|||
required int id,
|
||||
required String name,
|
||||
required String title,
|
||||
required String? icon,
|
||||
required ImageModel? icon,
|
||||
required String? description,
|
||||
required String? rules,
|
||||
required List<UserModel> moderators,
|
||||
|
@ -57,7 +58,7 @@ class DetailedMagazineModel with _$DetailedMagazineModel {
|
|||
id: json['magazineId'] as int,
|
||||
name: json['name'] as String,
|
||||
title: json['title'] as String,
|
||||
icon: kbinGetImageUrl(json['icon'] as Map<String, Object?>?),
|
||||
icon: kbinGetImage(json['icon'] as Map<String, Object?>?),
|
||||
description: json['description'] as String?,
|
||||
rules: json['rules'] as String?,
|
||||
moderators: ((json['moderators'] ?? []) as List<dynamic>)
|
||||
|
@ -86,7 +87,7 @@ class DetailedMagazineModel with _$DetailedMagazineModel {
|
|||
id: lemmyCommunity['id'] as int,
|
||||
name: lemmyGetActorName(lemmyCommunity),
|
||||
title: lemmyCommunity['title'] as String,
|
||||
icon: lemmyCommunity['icon'] as String?,
|
||||
icon: lemmyGetImage(lemmyCommunity['icon'] as String?),
|
||||
description: lemmyCommunity['description'] as String?,
|
||||
rules: null,
|
||||
moderators: [],
|
||||
|
@ -111,18 +112,18 @@ class MagazineModel with _$MagazineModel {
|
|||
const factory MagazineModel({
|
||||
required int id,
|
||||
required String name,
|
||||
required String? icon,
|
||||
required ImageModel? icon,
|
||||
}) = _MagazineModel;
|
||||
|
||||
factory MagazineModel.fromKbin(Map<String, Object?> json) => MagazineModel(
|
||||
id: json['magazineId'] as int,
|
||||
name: json['name'] as String,
|
||||
icon: kbinGetImageUrl(json['icon'] as Map<String, Object?>?),
|
||||
icon: kbinGetImage(json['icon'] as Map<String, Object?>?),
|
||||
);
|
||||
|
||||
factory MagazineModel.fromLemmy(Map<String, Object?> json) => MagazineModel(
|
||||
id: json['id'] as int,
|
||||
name: lemmyGetActorName(json),
|
||||
icon: json['icon'] as String?,
|
||||
icon: lemmyGetImage(json['icon'] as String?),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:interstellar/src/models/domain.dart';
|
||||
import 'package:interstellar/src/models/image.dart';
|
||||
import 'package:interstellar/src/models/magazine.dart';
|
||||
import 'package:interstellar/src/models/user.dart';
|
||||
import 'package:interstellar/src/utils/models.dart';
|
||||
|
@ -52,7 +53,7 @@ class PostModel with _$PostModel {
|
|||
required DomainModel? domain,
|
||||
required String? title,
|
||||
required String? url,
|
||||
required String? image,
|
||||
required ImageModel? image,
|
||||
required String? body,
|
||||
required String? lang,
|
||||
required int numComments,
|
||||
|
@ -79,7 +80,7 @@ class PostModel with _$PostModel {
|
|||
domain: DomainModel.fromKbin(json['domain'] as Map<String, Object?>),
|
||||
title: json['title'] as String,
|
||||
url: json['url'] as String?,
|
||||
image: kbinGetImageUrl(json['image'] as Map<String, Object?>?),
|
||||
image: kbinGetImage(json['image'] as Map<String, Object?>?),
|
||||
body: json['body'] as String?,
|
||||
lang: json['lang'] as String,
|
||||
numComments: json['numComments'] as int,
|
||||
|
@ -108,7 +109,7 @@ class PostModel with _$PostModel {
|
|||
domain: null,
|
||||
title: null,
|
||||
url: null,
|
||||
image: kbinGetImageUrl(json['image'] as Map<String, Object?>?),
|
||||
image: kbinGetImage(json['image'] as Map<String, Object?>?),
|
||||
body: json['body'] as String,
|
||||
lang: json['lang'] as String,
|
||||
numComments: json['comments'] as int,
|
||||
|
@ -141,7 +142,7 @@ class PostModel with _$PostModel {
|
|||
domain: null,
|
||||
title: lemmyPost['name'] as String,
|
||||
url: lemmyPost['url'] as String?,
|
||||
image: lemmyPost['thumbnail_url'] as String?,
|
||||
image: lemmyGetImage(lemmyPost['thumbnail_url'] as String?),
|
||||
body: lemmyPost['body'] as String?,
|
||||
lang: null,
|
||||
numComments: lemmyCounts['comments'] as int,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:interstellar/src/models/image.dart';
|
||||
import 'package:interstellar/src/utils/models.dart';
|
||||
import 'package:interstellar/src/widgets/markdown/markdown_mention.dart';
|
||||
|
||||
|
@ -28,8 +29,8 @@ class DetailedUserModel with _$DetailedUserModel {
|
|||
required int id,
|
||||
required String name,
|
||||
required String? displayName,
|
||||
required String? avatar,
|
||||
required String? cover,
|
||||
required ImageModel? avatar,
|
||||
required ImageModel? cover,
|
||||
required DateTime createdAt,
|
||||
required bool isBot,
|
||||
required String? about,
|
||||
|
@ -44,8 +45,8 @@ class DetailedUserModel with _$DetailedUserModel {
|
|||
id: json['userId'] as int,
|
||||
name: kbinNormalizeUsername(json['username'] as String),
|
||||
displayName: null,
|
||||
avatar: kbinGetImageUrl(json['avatar'] as Map<String, Object?>?),
|
||||
cover: kbinGetImageUrl(json['cover'] as Map<String, Object?>?),
|
||||
avatar: kbinGetImage(json['avatar'] as Map<String, Object?>?),
|
||||
cover: kbinGetImage(json['cover'] as Map<String, Object?>?),
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
isBot: json['isBot'] as bool,
|
||||
about: json['about'] as String?,
|
||||
|
@ -68,8 +69,8 @@ class DetailedUserModel with _$DetailedUserModel {
|
|||
id: lemmyPerson['id'] as int,
|
||||
name: lemmyGetActorName(lemmyPerson),
|
||||
displayName: lemmyPerson['display_name'] as String?,
|
||||
avatar: lemmyPerson['avatar'] as String?,
|
||||
cover: lemmyPerson['banner'] as String?,
|
||||
avatar: lemmyGetImage(lemmyPerson['avatar'] as String?),
|
||||
cover: lemmyGetImage(lemmyPerson['banner'] as String?),
|
||||
createdAt: DateTime.parse(lemmyPerson['published'] as String),
|
||||
isBot: lemmyPerson['bot_account'] as bool,
|
||||
about: lemmyPerson['bio'] as String?,
|
||||
|
@ -86,19 +87,19 @@ class UserModel with _$UserModel {
|
|||
const factory UserModel({
|
||||
required int id,
|
||||
required String name,
|
||||
required String? avatar,
|
||||
required ImageModel? avatar,
|
||||
}) = _UserModel;
|
||||
|
||||
factory UserModel.fromKbin(Map<String, Object?> json) => UserModel(
|
||||
id: json['userId'] as int,
|
||||
name: kbinNormalizeUsername(json['username'] as String),
|
||||
avatar: kbinGetImageUrl(json['avatar'] as Map<String, Object?>?),
|
||||
avatar: kbinGetImage(json['avatar'] as Map<String, Object?>?),
|
||||
);
|
||||
|
||||
factory UserModel.fromLemmy(Map<String, Object?> json) => UserModel(
|
||||
id: json['id'] as int,
|
||||
name: lemmyGetActorName(json),
|
||||
avatar: json['avatar'] as String?,
|
||||
avatar: lemmyGetImage(json['avatar'] as String?),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'package:interstellar/src/screens/profile/profile_edit_screen.dart';
|
|||
import 'package:interstellar/src/screens/settings/settings_controller.dart';
|
||||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:interstellar/src/widgets/avatar.dart';
|
||||
import 'package:interstellar/src/widgets/image.dart';
|
||||
import 'package:interstellar/src/widgets/loading_template.dart';
|
||||
import 'package:interstellar/src/widgets/markdown/markdown.dart';
|
||||
import 'package:interstellar/src/widgets/markdown/markdown_editor.dart';
|
||||
|
@ -137,10 +138,9 @@ class _UserScreenState extends State<UserScreen> {
|
|||
),
|
||||
height: user.cover == null ? 100 : null,
|
||||
child: user.cover != null
|
||||
? Image.network(
|
||||
? AdvancedImage(
|
||||
user.cover!,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
fit: BoxFit.fitWidth,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:interstellar/src/models/user.dart';
|
||||
import 'package:interstellar/src/widgets/image.dart';
|
||||
import 'package:interstellar/src/widgets/markdown/markdown_editor.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -125,10 +126,9 @@ class _ProfileEditScreen extends State<ProfileEditScreen> {
|
|||
: widget.user.cover != null
|
||||
? _deleteCover
|
||||
? null
|
||||
: Image.network(
|
||||
: AdvancedImage(
|
||||
widget.user.cover!,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
fit: BoxFit.fitWidth,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:interstellar/src/models/image.dart';
|
||||
|
||||
DateTime? optionalDateTime(String? value) =>
|
||||
value == null ? null : DateTime.parse(value);
|
||||
|
||||
|
@ -7,8 +9,12 @@ String? kbinCalcNextPaginationPage(Map<String, Object?> pagination) {
|
|||
: null;
|
||||
}
|
||||
|
||||
String? kbinGetImageUrl(Map<String, Object?>? image) {
|
||||
return image == null ? null : image['storageUrl'] as String;
|
||||
ImageModel? kbinGetImage(Map<String, Object?>? json) {
|
||||
return json == null ? null : ImageModel.fromKbin(json);
|
||||
}
|
||||
|
||||
ImageModel? lemmyGetImage(String? json) {
|
||||
return json == null ? null : ImageModel.fromLemmy(json);
|
||||
}
|
||||
|
||||
String kbinNormalizeUsername(String username) {
|
||||
|
|
|
@ -1,23 +1,32 @@
|
|||
import 'package:blurhash_ffi/blurhash_ffi.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:interstellar/src/models/image.dart';
|
||||
|
||||
class Avatar extends StatelessWidget {
|
||||
final String? url;
|
||||
final ImageModel? image;
|
||||
final double? radius;
|
||||
final double? borderRadius;
|
||||
|
||||
const Avatar(this.url, {super.key, this.radius, this.borderRadius});
|
||||
const Avatar(this.image, {super.key, this.radius, this.borderRadius});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CircleAvatar(
|
||||
radius: radius != null && borderRadius != null ? radius! + borderRadius! : radius,
|
||||
backgroundColor: radius == null || borderRadius == null ? Colors.transparent : null,
|
||||
child: CircleAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundImage: url != null ? NetworkImage(url!) : null,
|
||||
backgroundImage: url == null ? const AssetImage('assets/icons/logo.png') : null,
|
||||
radius: radius,
|
||||
)
|
||||
radius: radius != null && borderRadius != null
|
||||
? radius! + borderRadius!
|
||||
: radius,
|
||||
backgroundColor:
|
||||
radius == null || borderRadius == null ? Colors.transparent : null,
|
||||
child: CircleAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundImage: image == null ? null : NetworkImage(image!.src),
|
||||
backgroundImage: image == null
|
||||
? const AssetImage('assets/icons/logo.png')
|
||||
: (image!.blurHash != null
|
||||
? BlurhashFfiImage(image!.blurHash!) as ImageProvider<Object>
|
||||
: null),
|
||||
radius: radius,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:interstellar/src/models/image.dart';
|
||||
import 'package:interstellar/src/screens/explore/domain_screen.dart';
|
||||
import 'package:interstellar/src/screens/explore/magazine_screen.dart';
|
||||
import 'package:interstellar/src/screens/explore/user_screen.dart';
|
||||
|
@ -6,6 +7,7 @@ import 'package:interstellar/src/screens/settings/settings_controller.dart';
|
|||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:interstellar/src/widgets/blur.dart';
|
||||
import 'package:interstellar/src/widgets/display_name.dart';
|
||||
import 'package:interstellar/src/widgets/image.dart';
|
||||
import 'package:interstellar/src/widgets/markdown/markdown.dart';
|
||||
import 'package:interstellar/src/widgets/markdown/markdown_editor.dart';
|
||||
import 'package:interstellar/src/widgets/open_webpage.dart';
|
||||
|
@ -18,7 +20,7 @@ class ContentItem extends StatefulWidget {
|
|||
final String originInstance;
|
||||
|
||||
final String? title;
|
||||
final String? image;
|
||||
final ImageModel? image;
|
||||
final Uri? link;
|
||||
final Uri? video;
|
||||
final String? body;
|
||||
|
@ -32,12 +34,12 @@ class ContentItem extends StatefulWidget {
|
|||
final bool isOC;
|
||||
|
||||
final String? user;
|
||||
final String? userIcon;
|
||||
final ImageModel? userIcon;
|
||||
final int? userIdOnClick;
|
||||
final int? opUserId;
|
||||
|
||||
final String? magazine;
|
||||
final String? magazineIcon;
|
||||
final ImageModel? magazineIcon;
|
||||
final int? magazineIdOnClick;
|
||||
|
||||
final String? domain;
|
||||
|
@ -118,30 +120,6 @@ class _ContentItemState extends State<ContentItem> {
|
|||
TextEditingController? _editTextController;
|
||||
final MenuController _menuController = MenuController();
|
||||
|
||||
_onImageClick(BuildContext context) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
title: widget.title != null ? Text(widget.title!) : null,
|
||||
backgroundColor: const Color(0x66000000),
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: InteractiveViewer(
|
||||
child: Image.network(widget.image!),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Widget? userWidget = widget.user != null
|
||||
|
@ -212,33 +190,37 @@ class _ContentItemState extends State<ContentItem> {
|
|||
|
||||
final double rightImageSize = hasWideSize ? 128 : 64;
|
||||
|
||||
final imageOpenTitle = widget.title ?? widget.body ?? '';
|
||||
|
||||
final imageWidget = widget.image == null
|
||||
? null
|
||||
: Wrapper(
|
||||
shouldWrap: widget.video == null,
|
||||
parentBuilder: (child) => InkWell(
|
||||
onTap: () => _onImageClick(context),
|
||||
child: child,
|
||||
),
|
||||
child: Wrapper(
|
||||
shouldWrap: widget.isNSFW,
|
||||
parentBuilder: (child) => Blur(child),
|
||||
child: isRightImage
|
||||
? Image.network(
|
||||
shouldWrap: widget.isNSFW,
|
||||
parentBuilder: (child) => Blur(child),
|
||||
child: isRightImage
|
||||
? SizedBox(
|
||||
height: rightImageSize,
|
||||
width: rightImageSize,
|
||||
child: AdvancedImage(
|
||||
widget.image!,
|
||||
height: rightImageSize,
|
||||
width: rightImageSize,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: (widget.isPreview
|
||||
? Image.network(
|
||||
openTitle: imageOpenTitle,
|
||||
),
|
||||
)
|
||||
: (widget.isPreview
|
||||
? SizedBox(
|
||||
height: 160,
|
||||
width: double.infinity,
|
||||
child: AdvancedImage(
|
||||
widget.image!,
|
||||
height: 160,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Image.network(widget.image!)),
|
||||
),
|
||||
openTitle: imageOpenTitle,
|
||||
),
|
||||
)
|
||||
: AdvancedImage(
|
||||
widget.image!,
|
||||
openTitle: imageOpenTitle,
|
||||
)),
|
||||
);
|
||||
|
||||
final titleStyle = hasWideSize
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:interstellar/src/models/image.dart';
|
||||
import 'package:interstellar/src/screens/settings/settings_controller.dart';
|
||||
import 'package:interstellar/src/widgets/avatar.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -7,7 +8,7 @@ class DisplayName extends StatelessWidget {
|
|||
const DisplayName(this.name, {super.key, this.icon, this.onTap});
|
||||
|
||||
final String name;
|
||||
final String? icon;
|
||||
final ImageModel? icon;
|
||||
final void Function()? onTap;
|
||||
|
||||
@override
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:blurhash_ffi/blurhash_ffi.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:interstellar/src/models/image.dart';
|
||||
import 'package:interstellar/src/widgets/wrapper.dart';
|
||||
|
||||
class AdvancedImage extends StatelessWidget {
|
||||
final ImageModel image;
|
||||
final BoxFit fit;
|
||||
final String? openTitle;
|
||||
|
||||
const AdvancedImage(
|
||||
this.image, {
|
||||
super.key,
|
||||
this.fit = BoxFit.contain,
|
||||
this.openTitle,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final blurHashSizeFactor = image.blurHash == null
|
||||
? null
|
||||
: sqrt(1080 / (image.blurHashWidth! * image.blurHashHeight!));
|
||||
|
||||
return Wrapper(
|
||||
shouldWrap: openTitle != null,
|
||||
parentBuilder: (child) => GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AdvancedImagePage(
|
||||
image,
|
||||
title: openTitle!,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
),
|
||||
child: image.blurHash == null
|
||||
? Image.network(
|
||||
image.src,
|
||||
fit: fit,
|
||||
)
|
||||
: BlurhashFfi(
|
||||
hash: image.blurHash!,
|
||||
decodingWidth:
|
||||
(blurHashSizeFactor! * image.blurHashWidth!).ceil(),
|
||||
decodingHeight:
|
||||
(blurHashSizeFactor * image.blurHashHeight!).ceil(),
|
||||
image: image.src,
|
||||
imageFit: fit,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AdvancedImagePage extends StatelessWidget {
|
||||
final ImageModel image;
|
||||
final String title;
|
||||
|
||||
const AdvancedImagePage(this.image, {super.key, required this.title});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const shadows = <Shadow>[
|
||||
Shadow(color: Colors.black, blurRadius: 1.0, offset: Offset(0, 1))
|
||||
];
|
||||
|
||||
final titleStyle =
|
||||
Theme.of(context).textTheme.titleLarge!.copyWith(shadows: shadows);
|
||||
|
||||
return Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
title: Text(title, style: titleStyle),
|
||||
iconTheme: const IconThemeData(
|
||||
color: Colors.white,
|
||||
shadows: shadows,
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: InteractiveViewer(
|
||||
child: AdvancedImage(image),
|
||||
),
|
||||
),
|
||||
if (image.altText != null)
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
image.altText!,
|
||||
textAlign: TextAlign.center,
|
||||
style: titleStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart' as mdf;
|
||||
import 'package:interstellar/src/models/image.dart';
|
||||
import 'package:interstellar/src/models/magazine.dart';
|
||||
import 'package:interstellar/src/models/user.dart';
|
||||
import 'package:interstellar/src/screens/explore/magazine_screen.dart';
|
||||
|
@ -135,7 +136,7 @@ Map<String, DetailedMagazineModel> magazineMentionCache = {};
|
|||
|
||||
class MentionWidgetState extends State<MentionWidget> {
|
||||
late String _displayName;
|
||||
String? _icon;
|
||||
ImageModel? _icon;
|
||||
void Function()? _onClick;
|
||||
|
||||
@override
|
||||
|
|
|
@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
blurhash_ffi
|
||||
media_kit_native_event_loop
|
||||
)
|
||||
|
||||
|
|
|
@ -49,6 +49,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
blurhash_ffi:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: blurhash_ffi
|
||||
sha256: "941868602bb3bc34b0a7d630e4bf0e88e4c93af36090fe1cc0d63021c9a46cb3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.6"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -35,6 +35,7 @@ dependencies:
|
|||
dynamic_color: ^1.7.0
|
||||
markdown: ^7.2.2
|
||||
expandable: ^5.0.1
|
||||
blurhash_ffi: ^1.2.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^3.0.2
|
||||
|
|
|
@ -12,6 +12,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
blurhash_ffi
|
||||
media_kit_native_event_loop
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue