Skip to content

Instantly share code, notes, and snippets.

@ChangJoo-Park
Last active July 23, 2021 04:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ChangJoo-Park/95ef23ed23a169cd34ef87de9a8d5a52 to your computer and use it in GitHub Desktop.
Save ChangJoo-Park/95ef23ed23a169cd34ef87de9a8d5a52 to your computer and use it in GitHub Desktop.
왓섭 whatssub 온보딩 UI
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final Color primaryColor = Color(0xFF0068FF);
final Color lightColor = Color(0xFFF0F0F5);
final double fontSize = 16;
final BorderRadius chatMessageBorderRadius = BorderRadius.circular(12);
final Border chatDefaultBorder =
Border.all(color: Colors.grey, width: 0.8, style: BorderStyle.solid);
final int baseDelayMilliseconds = 2000;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: CustomScrollView(
physics: BouncingScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: AnimatedAppear(
delay: Duration(milliseconds: baseDelayMilliseconds ~/ 2),
duration: Duration(milliseconds: 400),
child: AppHeader(),
),
),
SliverList(
delegate: SliverChildListDelegate.fixed([
AnimatedAppear(
delay: Duration(milliseconds: baseDelayMilliseconds),
duration: Duration(milliseconds: 400),
child: ChatMessage(
text: "[WEB 발신] 구독서비스가 갱신되었습니다",
border: chatDefaultBorder,
borderRadius: chatMessageBorderRadius,
),
),
AnimatedAppear(
delay: Duration(milliseconds: baseDelayMilliseconds + 1000),
duration: Duration(milliseconds: 250),
child: ChatMessage(
text: "??? 뭔데",
mainAxisAlignment: MainAxisAlignment.end,
backgroundColor: lightColor,
borderRadius: chatMessageBorderRadius,
),
),
AnimatedAppear(
delay: Duration(milliseconds: baseDelayMilliseconds + 1800),
duration: Duration(milliseconds: 250),
child: ChatMessage(
text: "하... 맞다...",
mainAxisAlignment: MainAxisAlignment.end,
backgroundColor: lightColor,
borderRadius: chatMessageBorderRadius,
),
),
AnimatedAppear(
delay: Duration(milliseconds: baseDelayMilliseconds + 2500),
duration: Duration(milliseconds: 250),
child: ChatMessage(
text: "이런 경험 다들 있으시죠?",
border: chatDefaultBorder,
borderRadius: chatMessageBorderRadius,
),
),
AnimatedAppear(
delay: Duration(milliseconds: baseDelayMilliseconds + 3500),
duration: Duration(milliseconds: 250),
child: ChatMessage(
text: "🤗 우리, 왓섭에서 편하게 알림받고 해지해요",
textColor: primaryColor,
backgroundColor: primaryColor.withOpacity(0.1),
borderRadius: chatMessageBorderRadius,
),
),
]),
),
SliverFillRemaining(
fillOverscroll: false,
hasScrollBody: false,
child: AnimatedAppear(
delay: Duration(milliseconds: baseDelayMilliseconds + 5000),
duration: Duration(milliseconds: 250),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
RichText(
textAlign: TextAlign.center,
text: TextSpan(
children: [
TextSpan(
text: '\'동의 및 시작\'을 누르는 것으로 ',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
TextSpan(
text: '왓섭 필수 이용약관',
style: TextStyle(
color: Colors.grey,
decoration: TextDecoration.underline,
fontSize: 12),
),
TextSpan(
text: ' 및\n',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
TextSpan(
text: '개인정보 이용방침',
style: TextStyle(
color: Colors.grey,
decoration: TextDecoration.underline,
fontSize: 12),
),
TextSpan(
text: '에 동의하게 됩니다.',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
],
),
),
SizedBox(height: 16),
Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 16),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(0, 48),
elevation: 0,
primary: primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {},
child: Text('동의하고 시작하기'),
),
),
],
),
),
),
)
],
),
);
}
}
class AppHeader extends StatelessWidget {
AppHeader({Key? key}) : super(key: key);
final Color primaryColor = Color(0xFF0068FF);
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 40),
padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 16),
child: RichText(
text: TextSpan(children: [
TextSpan(
text: '내가 ',
style: TextStyle(
color: Colors.black,
fontSize: 24,
fontWeight: FontWeight.bold,
)),
TextSpan(
text: '어떤 서비스',
style: TextStyle(
color: primaryColor,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
TextSpan(
text: '를\n',
style: TextStyle(
color: Colors.black,
fontSize: 24,
fontWeight: FontWeight.bold,
)),
TextSpan(
text: '언제 구독',
style: TextStyle(
color: primaryColor,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
TextSpan(
text: '했더라?',
style: TextStyle(
color: Colors.black,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
]),
),
);
}
}
class AnimatedAppear extends StatefulWidget {
const AnimatedAppear(
{Key? key,
required this.child,
this.delay = const Duration(milliseconds: 0),
this.duration = const Duration(milliseconds: 0)})
: super(key: key);
final Widget child;
final Duration delay;
final Duration duration;
@override
_AnimatedAppearState createState() => _AnimatedAppearState();
}
class _AnimatedAppearState extends State<AnimatedAppear>
with TickerProviderStateMixin {
late AnimationController _opacityController;
late Animation<Offset> _slideAnimationOffset;
Timer? _timer;
@override
void initState() {
super.initState();
_opacityController =
AnimationController(vsync: this, duration: widget.duration);
_slideAnimationOffset = Tween<Offset>(
begin: const Offset(0.0, 0.35),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _opacityController,
curve: Curves.fastOutSlowIn,
),
);
_animate();
}
@override
void dispose() {
_opacityController.dispose();
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _opacityController,
child: SlideTransition(
position: _slideAnimationOffset,
child: widget.child,
),
);
}
_animate() {
_timer = Timer(widget.delay, () {
_opacityController.forward();
});
}
}
class ChatMessage extends StatelessWidget {
const ChatMessage({
Key? key,
required this.text,
this.textColor = Colors.black,
this.backgroundColor,
this.alignment,
this.mainAxisAlignment = MainAxisAlignment.start,
this.border,
this.borderRadius,
}) : super(key: key);
final String text;
final Color? textColor;
final Color? backgroundColor;
final Alignment? alignment;
final MainAxisAlignment mainAxisAlignment;
final Border? border;
final BorderRadius? borderRadius;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: mainAxisAlignment,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
border: border,
borderRadius: borderRadius,
color: backgroundColor,
),
child: Text(
text,
style: TextStyle(color: textColor),
),
)
],
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment