Last active
September 20, 2023 10:42
-
-
Save PlugFox/60f41eb9f76d5513df0bfe80f9681271 to your computer and use it in GitHub Desktop.
Date time in chat bubbles example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* https://gist.github.com/PlugFox/60f41eb9f76d5513df0bfe80f9681271 | |
* https://dartpad.dev/?id=60f41eb9f76d5513df0bfe80f9681271 | |
*/ | |
import 'package:flutter/widgets.dart'; | |
void main() => runApp(const App()); | |
class App extends StatelessWidget { | |
const App({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) => Directionality( | |
textDirection: TextDirection.ltr, | |
child: DefaultTextStyle( | |
style: const TextStyle( | |
fontSize: 18, | |
color: Color(0xFF000000), | |
), | |
child: LayoutBuilder( | |
builder: (context, _) => MediaQuery( | |
data: MediaQueryData.fromWindow(WidgetsBinding.instance!.window), | |
child: Builder( | |
builder: (context) => Stack( | |
alignment: Alignment.topCenter, | |
children: <Widget>[ | |
Positioned.fill( | |
child: Center( | |
child: ConstrainedBox( | |
constraints: const BoxConstraints( | |
maxWidth: 300, | |
), | |
child: SingleChildScrollView( | |
child: Padding( | |
padding: EdgeInsets.symmetric(vertical: MediaQuery.of(context).padding.top + 80), | |
child: Column( | |
children: <Widget>[ | |
ChatBubble.left(), | |
ChatBubble.right(), | |
], | |
), | |
), | |
), | |
), | |
), | |
), | |
Positioned( | |
top: MediaQuery.of(context).padding.top + 12, | |
height: 65, | |
child: ConstrainedBox( | |
constraints: const BoxConstraints( | |
maxWidth: 350, | |
), | |
child: const ChatInputField(), | |
), | |
), | |
], | |
), | |
), | |
), | |
), | |
), | |
); | |
} | |
class ChatBubble extends StatelessWidget { | |
static const double _topPadding = 6; | |
static const double _sidePadding = 24; | |
final Alignment alignment; | |
final Color background; | |
final EdgeInsets padding; | |
const ChatBubble._({ | |
required this.alignment, | |
required this.background, | |
required this.padding, | |
Key? key, | |
}) : super(key: key); | |
factory ChatBubble.left({Key? key}) => ChatBubble._( | |
alignment: Alignment.topLeft, | |
background: const Color(0xFFef5350), | |
padding: const EdgeInsets.only( | |
top: _topPadding, | |
right: _sidePadding, | |
), | |
key: key, | |
); | |
factory ChatBubble.right({Key? key}) => ChatBubble._( | |
alignment: Alignment.topRight, | |
background: const Color(0xFF42a5f5), | |
padding: const EdgeInsets.only( | |
top: _topPadding, | |
left: _sidePadding, | |
), | |
key: key, | |
); | |
@override | |
Widget build(BuildContext context) => Padding( | |
padding: padding, | |
child: Align( | |
alignment: alignment, | |
child: DecoratedBox( | |
position: DecorationPosition.background, | |
decoration: BoxDecoration( | |
color: background, | |
borderRadius: BorderRadius.circular(5), | |
), | |
child: Padding( | |
padding: const EdgeInsets.symmetric( | |
horizontal: 8, | |
vertical: 5, | |
), | |
child: ValueListenableBuilder<TextEditingValue>( | |
valueListenable: ChatInputField.controller, | |
builder: (context, value, _) => Stack( | |
children: <Widget>[ | |
RichText( | |
text: TextSpan( | |
children: <InlineSpan>[ | |
TextSpan( | |
text: value.text, | |
style: DefaultTextStyle.of(context).style, | |
), | |
const WidgetSpan( | |
child: Opacity( | |
opacity: 0, | |
child: DateTimeWidget(), | |
), | |
), | |
], | |
), | |
), | |
const Positioned.fill( | |
child: Align( | |
alignment: Alignment.bottomRight, | |
child: DateTimeWidget(), | |
), | |
), | |
], | |
), | |
), | |
), | |
), | |
), | |
); | |
} | |
class DateTimeWidget extends StatelessWidget { | |
static String _evalDateTime() { | |
final now = DateTime.now(); | |
return '${now.hour}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}'; | |
} | |
static final Stream<String> _timeStream = | |
Stream<void>.periodic(const Duration(seconds: 1)).map<String>((_) => _evalDateTime()).asBroadcastStream(); | |
const DateTimeWidget({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) => Padding( | |
padding: const EdgeInsets.only(left: 8), | |
child: StreamBuilder<String>( | |
initialData: _evalDateTime(), | |
stream: _timeStream, | |
builder: (context, snapshot) => Text( | |
snapshot.data ?? '', | |
style: DefaultTextStyle.of(context).style.copyWith( | |
color: const Color(0xFF263238), | |
fontSize: 12, | |
), | |
), | |
), | |
); | |
} | |
class ChatInputField extends StatelessWidget { | |
static final TextEditingController controller = TextEditingController(text: 'Hello world!'); | |
static final FocusNode _focusNode = FocusNode(); | |
const ChatInputField({ | |
Key? key, | |
}) : super(key: key); | |
@override | |
Widget build(BuildContext context) => DecoratedBox( | |
position: DecorationPosition.background, | |
decoration: BoxDecoration( | |
color: const Color(0xFFcfd8dc), | |
borderRadius: BorderRadius.circular(5), | |
), | |
child: Align( | |
alignment: Alignment.centerLeft, | |
child: Padding( | |
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), | |
child: EditableText( | |
controller: controller, | |
focusNode: _focusNode, | |
backgroundCursorColor: const Color(0xFF000000), | |
cursorColor: const Color(0xFF000000), | |
style: DefaultTextStyle.of(context).style, | |
textAlign: TextAlign.left, | |
minLines: 1, | |
maxLines: 2, | |
keyboardType: TextInputType.multiline, | |
onSubmitted: (_) => FocusScope.of(context).unfocus(), | |
), | |
), | |
), | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment