Skip to content

Instantly share code, notes, and snippets.

@slightfoot
Created January 31, 2024 19:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slightfoot/915b9e069d920c464100f8980c547e4c to your computer and use it in GitHub Desktop.
Save slightfoot/915b9e069d920c464100f8980c547e4c to your computer and use it in GitHub Desktop.
Chat Message photo stack example - by Simon Lightfoot - Humpday Q&A :: 31st January 2024 #Flutter #Dart https://www.youtube.com/watch?v=YS_g2TJN-cQ
// MIT License
//
// Copyright (c) 2023 Simon Lightfoot
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(
debugShowCheckedModeBanner: false,
home: Home(),
));
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
typedef MessageModel = ({bool self, String text, List<String> images});
class _HomeState extends State<Home> {
final _messages = <MessageModel>[
(self: true, text: 'hello world', images: []),
(
self: true,
text: 'Good morning Orion!',
images: [
'https://picsum.photos/500/500?1',
'https://picsum.photos/500/500?2',
'https://picsum.photos/500/500?3',
'https://picsum.photos/500/500?4',
]
),
(self: false, text: 'How are you', images: []),
(self: true, text: 'I am well', images: []),
(self: false, text: 'This is great', images: []),
(self: true, text: 'Blah blah blah', images: []),
];
@override
Widget build(BuildContext context) {
return Material(
child: ListView.builder(
itemCount: _messages.length,
itemBuilder: (BuildContext context, int index) {
final message = _messages[index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: MessageWidget(message),
);
},
),
);
}
}
class MessageWidget extends StatelessWidget {
const MessageWidget(this.message, {super.key});
final MessageModel message;
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: Colors.grey.shade100,
border: Border.all(color: Colors.grey),
borderRadius: const BorderRadius.all(
Radius.circular(12.0),
),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: message.self //
? CrossAxisAlignment.start
: CrossAxisAlignment.end,
children: [
Text(message.text),
if (message.images.isNotEmpty) ...[
SizedBox(
height: 10 * message.images.length + 100.0,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
Navigator.of(context).push(
ImageScreen.route(message.images),
);
},
child: Stack(
children: [
for (final (index, url) in message.images.indexed) ...[
Positioned(
left: index * 10,
top: index * 10,
width: 100.0,
height: 100.0,
child: Image.network(url),
),
],
],
),
),
),
),
],
],
),
),
);
}
}
class ImageScreen extends StatefulWidget {
const ImageScreen._({
required this.images,
});
final List<String> images;
static Route<void> route(List<String> images) {
return PageRouteBuilder(
settings: const RouteSettings(name: '/images'),
opaque: false,
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return FadeTransition(
opacity: animation,
child: ImageScreen._(images: images),
);
},
);
}
@override
State<ImageScreen> createState() => _ImageScreenState();
}
class _ImageScreenState extends State<ImageScreen> {
late final PageController controller;
@override
void initState() {
super.initState();
controller = PageController(
viewportFraction: 0.9,
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Material(
color: Colors.black38,
child: Center(
child: PageView.builder(
controller: controller,
itemCount: widget.images.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Image.network(widget.images[index]),
);
},
),
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment