mirror of
https://github.com/ErfanRht/MovieLab.git
synced 2025-05-17 14:05:55 +08:00
(add) history timeline ui
This commit is contained in:
@ -10,5 +10,6 @@ const List<String> months = [
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
'December',
|
||||
''
|
||||
];
|
||||
|
@ -23,4 +23,11 @@ enum ListName {
|
||||
|
||||
enum ItemType { MOVIE, TV, ARTIST, OTHER, UNKNOWN }
|
||||
|
||||
enum ShowType { SEARCH_PAGE, OFFICIAL_LIST, USER_LIST, EPISODE, BOX_OFFICE }
|
||||
enum ShowType {
|
||||
SEARCH_PAGE,
|
||||
OFFICIAL_LIST,
|
||||
USER_LIST,
|
||||
USER_HISTORY,
|
||||
EPISODE,
|
||||
BOX_OFFICE
|
||||
}
|
||||
|
@ -37,7 +37,11 @@ class PreferencesShareholder {
|
||||
late HiveShowPreview hiveShow;
|
||||
Box<HiveShowPreview> list = Hive.box<HiveShowPreview>(listName);
|
||||
if (fullShow != null) {
|
||||
hiveShow = await convertFullShowToHive(fullShow: fullShow);
|
||||
hiveShow = await convertFullShowToHive(
|
||||
fullShow: fullShow,
|
||||
date: date,
|
||||
time: time,
|
||||
);
|
||||
} else {
|
||||
hiveShow = convertShowPreviewToHive(
|
||||
showPreview: showPreview,
|
||||
|
@ -5,6 +5,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:movielab/constants/colors.dart';
|
||||
import 'package:movielab/constants/types.dart';
|
||||
import 'package:movielab/models/hive/convertor.dart';
|
||||
import 'package:movielab/models/hive/models/show_preview.dart';
|
||||
import 'package:movielab/models/show_models/show_preview_model.dart';
|
||||
@ -17,6 +18,8 @@ import 'package:movielab/widgets/inefficacious_refresh_indicator.dart';
|
||||
import 'package:movielab/widgets/toast.dart';
|
||||
import 'package:ms_undraw/ms_undraw.dart';
|
||||
|
||||
import 'sections/history_timeline.dart';
|
||||
|
||||
class ListPage extends StatefulWidget {
|
||||
final String listName;
|
||||
const ListPage({Key? key, required this.listName}) : super(key: key);
|
||||
@ -145,18 +148,49 @@ class _ListPageState extends State<ListPage> {
|
||||
builder: (context, box, _) {
|
||||
final list = box.values.toList().cast<HiveShowPreview>();
|
||||
return list.isNotEmpty
|
||||
? InefficaciousRefreshIndicator(
|
||||
child: ListView.builder(
|
||||
itemCount: list.length,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return ListShowBox(
|
||||
listName: widget.listName,
|
||||
showPreview: convertHiveToShowPreview(
|
||||
list[list.length - index - 1]));
|
||||
},
|
||||
),
|
||||
)
|
||||
? widget.listName == "history"
|
||||
? Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
TimelineSteps(
|
||||
steps: [
|
||||
for (HiveShowPreview show in list)
|
||||
ListShowBox(
|
||||
listName: widget.listName,
|
||||
width: MediaQuery.of(context)
|
||||
.size
|
||||
.width -
|
||||
76,
|
||||
showPreview:
|
||||
convertHiveToShowPreview(
|
||||
show),
|
||||
showType: ShowType.USER_HISTORY)
|
||||
],
|
||||
watchDates: [
|
||||
for (HiveShowPreview show in list)
|
||||
convertHiveToShowPreview(show)
|
||||
.watchDate
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: InefficaciousRefreshIndicator(
|
||||
child: ListView.builder(
|
||||
itemCount: list.length,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return ListShowBox(
|
||||
listName: widget.listName,
|
||||
showPreview: convertHiveToShowPreview(
|
||||
list[list.length - index - 1]));
|
||||
},
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
@ -0,0 +1,131 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:movielab/constants/colors.dart';
|
||||
import 'package:movielab/constants/general.dart';
|
||||
import 'package:movielab/pages/show/show_box/lists_show_box.dart';
|
||||
import 'package:timeline_tile/timeline_tile.dart';
|
||||
|
||||
class TimelineSteps extends StatelessWidget {
|
||||
const TimelineSteps({Key? key, required this.steps, required this.watchDates})
|
||||
: super(key: key);
|
||||
|
||||
final List<ListShowBox> steps;
|
||||
final List<DateTime?> watchDates;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
if (index.isOdd) {
|
||||
return const TimelineDivider(
|
||||
color: kPrimaryColor,
|
||||
thickness: 7.5,
|
||||
begin: 0.1,
|
||||
end: 0.9,
|
||||
);
|
||||
}
|
||||
|
||||
final int itemIndex = index ~/ 2;
|
||||
final ListShowBox step = steps[itemIndex];
|
||||
|
||||
final bool isLeftAlign = itemIndex.isEven;
|
||||
|
||||
final child = _TimelineStepsChild(
|
||||
step: step,
|
||||
isLeftAlign: isLeftAlign,
|
||||
);
|
||||
|
||||
final isFirst = itemIndex == 0;
|
||||
final isLast = itemIndex == steps.length - 1;
|
||||
double indicatorY;
|
||||
if (isFirst) {
|
||||
indicatorY = 0.2;
|
||||
} else if (isLast) {
|
||||
indicatorY = 0.8;
|
||||
} else {
|
||||
indicatorY = 0.5;
|
||||
}
|
||||
|
||||
return TimelineTile(
|
||||
alignment: TimelineAlign.manual,
|
||||
endChild: isLeftAlign ? child : null,
|
||||
startChild: isLeftAlign ? null : child,
|
||||
lineXY: isLeftAlign ? 0.1 : 0.9,
|
||||
isFirst: isFirst,
|
||||
isLast: isLast,
|
||||
indicatorStyle: IndicatorStyle(
|
||||
width: 67.5,
|
||||
height: 25,
|
||||
indicatorXY: indicatorY,
|
||||
indicator: _TimelineStepIndicator(
|
||||
step:
|
||||
'${months[(watchDates[itemIndex]?.month ?? 14) - 1].substring(0, 3)} ${watchDates[itemIndex]?.year}'),
|
||||
),
|
||||
beforeLineStyle: const LineStyle(
|
||||
color: kPrimaryColor,
|
||||
thickness: 7.5,
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: max(0, steps.length * 2 - 1),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TimelineStepIndicator extends StatelessWidget {
|
||||
const _TimelineStepIndicator({Key? key, required this.step})
|
||||
: super(key: key);
|
||||
|
||||
final String step;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: kPrimaryColor, borderRadius: BorderRadius.circular(8.5)),
|
||||
child: Center(
|
||||
child: Text(
|
||||
step,
|
||||
style: const TextStyle(
|
||||
fontSize: 13.5,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TimelineStepsChild extends StatelessWidget {
|
||||
const _TimelineStepsChild({
|
||||
Key? key,
|
||||
required this.isLeftAlign,
|
||||
required this.step,
|
||||
}) : super(key: key);
|
||||
|
||||
final ListShowBox step;
|
||||
final bool isLeftAlign;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: isLeftAlign
|
||||
? const EdgeInsets.only(
|
||||
right: 20,
|
||||
top: 16,
|
||||
bottom: 16,
|
||||
)
|
||||
: const EdgeInsets.only(left: 20, top: 16, bottom: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
isLeftAlign ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [step],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -15,11 +15,13 @@ class ExpandedItemBox extends StatefulWidget {
|
||||
final String? iRank;
|
||||
final String preTag;
|
||||
final ShowType showType;
|
||||
final double? width;
|
||||
const ExpandedItemBox(
|
||||
{Key? key,
|
||||
required this.show,
|
||||
this.iRank,
|
||||
this.preTag = "",
|
||||
this.width,
|
||||
required this.showType})
|
||||
: super(key: key);
|
||||
|
||||
@ -68,7 +70,7 @@ class _ExpandedItemBoxState extends State<ExpandedItemBox>
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 8.5, top: 10),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
width: widget.width ?? MediaQuery.of(context).size.width,
|
||||
height: widget.show.image != 'null' ? 160 : 205,
|
||||
child: Row(
|
||||
children: [
|
||||
@ -170,8 +172,10 @@ class _ExpandedItemBoxState extends State<ExpandedItemBox>
|
||||
Container(
|
||||
alignment: Alignment.bottomLeft,
|
||||
width: widget.show.image != 'null'
|
||||
? MediaQuery.of(context).size.width - 160
|
||||
: MediaQuery.of(context).size.width - 40,
|
||||
? (widget.width ?? MediaQuery.of(context).size.width) -
|
||||
160
|
||||
: (widget.width ?? MediaQuery.of(context).size.width) -
|
||||
40,
|
||||
padding: const EdgeInsets.only(left: 10, top: 5),
|
||||
child: Column(
|
||||
children: [
|
||||
@ -180,7 +184,8 @@ class _ExpandedItemBoxState extends State<ExpandedItemBox>
|
||||
children: [
|
||||
Flexible(
|
||||
child: showBoxText(
|
||||
text: widget.showType == ShowType.USER_LIST
|
||||
text: widget.showType == ShowType.USER_LIST ||
|
||||
widget.showType == ShowType.USER_HISTORY
|
||||
? widget.show.title
|
||||
: widget.show.rank != ""
|
||||
? "${widget.show.rank}. ${widget.show.title}"
|
||||
@ -235,8 +240,16 @@ class _ExpandedItemBoxState extends State<ExpandedItemBox>
|
||||
children: [
|
||||
SizedBox(
|
||||
width: widget.show.image != 'null'
|
||||
? MediaQuery.of(context).size.width - 180
|
||||
: MediaQuery.of(context).size.width - 40,
|
||||
? (widget.width ??
|
||||
MediaQuery.of(context)
|
||||
.size
|
||||
.width) -
|
||||
180
|
||||
: (widget.width ??
|
||||
MediaQuery.of(context)
|
||||
.size
|
||||
.width) -
|
||||
40,
|
||||
child: showBoxText(
|
||||
text: widget.show.released != ""
|
||||
? widget.show.released ??
|
||||
|
@ -11,13 +11,18 @@ import 'package:movielab/modules/tools/capitalizer.dart';
|
||||
import 'package:movielab/pages/show/show_box/expanded_item_box/expanded_item_box.dart';
|
||||
import 'package:movielab/widgets/toast.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class ListShowBox extends StatelessWidget {
|
||||
final ShowPreview showPreview;
|
||||
final String listName;
|
||||
final double? width;
|
||||
final ShowType? showType;
|
||||
ListShowBox({
|
||||
Key? key,
|
||||
required this.showPreview,
|
||||
required this.listName,
|
||||
this.width,
|
||||
this.showType,
|
||||
}) : super(key: key);
|
||||
late FToast fToast;
|
||||
|
||||
@ -26,48 +31,46 @@ class ListShowBox extends StatelessWidget {
|
||||
fToast = FToast();
|
||||
fToast.init(context);
|
||||
String id = showPreview.id;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Slidable(
|
||||
key: UniqueKey(),
|
||||
startActionPane: ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
dismissible: DismissiblePane(
|
||||
onDismissed: () => delete(),
|
||||
),
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (context) => delete(),
|
||||
autoClose: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
icon: Icons.delete,
|
||||
label: 'Delete',
|
||||
),
|
||||
],
|
||||
return Slidable(
|
||||
key: UniqueKey(),
|
||||
startActionPane: ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
dismissible: DismissiblePane(
|
||||
onDismissed: () => delete(),
|
||||
),
|
||||
endActionPane: ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
dismissible: DismissiblePane(
|
||||
onDismissed: () => delete(),
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (context) => delete(),
|
||||
autoClose: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
icon: Icons.delete,
|
||||
label: 'Delete',
|
||||
),
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (context) => delete(),
|
||||
autoClose: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
icon: Icons.delete,
|
||||
label: 'Delete',
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
endActionPane: ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
dismissible: DismissiblePane(
|
||||
onDismissed: () => delete(),
|
||||
),
|
||||
child: ExpandedItemBox(
|
||||
show: showPreview,
|
||||
preTag: "${listName}_",
|
||||
showType: ShowType.USER_LIST,
|
||||
)),
|
||||
);
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (context) => delete(),
|
||||
autoClose: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
icon: Icons.delete,
|
||||
label: 'Delete',
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ExpandedItemBox(
|
||||
show: showPreview,
|
||||
preTag: "${listName}_",
|
||||
width: width,
|
||||
showType: showType ?? ShowType.USER_LIST,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> delete() async {
|
||||
|
322
lib/widgets/timeline.dart
Normal file
322
lib/widgets/timeline.dart
Normal file
@ -0,0 +1,322 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:movielab/pages/show/show_box/compressed_item_box/compressed_item_box.dart';
|
||||
|
||||
class Config {
|
||||
static String rightDirection = 'R';
|
||||
static String leftDirection = 'L';
|
||||
}
|
||||
|
||||
/// A Bubble Timeline Widget.
|
||||
class BubbleTimeline extends StatefulWidget {
|
||||
final double bubbleDiameter;
|
||||
final List<CompressedItemBox> items;
|
||||
final Color stripColor;
|
||||
|
||||
/// This is color of your scaffold.
|
||||
/// Use same color as used for Scaffold background.
|
||||
final Color scaffoldColor;
|
||||
|
||||
const BubbleTimeline({
|
||||
required this.bubbleDiameter,
|
||||
required this.items,
|
||||
required this.stripColor,
|
||||
required this.scaffoldColor,
|
||||
});
|
||||
|
||||
@override
|
||||
_BubbleTimelineState createState() => _BubbleTimelineState();
|
||||
}
|
||||
|
||||
class _BubbleTimelineState extends State<BubbleTimeline> {
|
||||
bool checkEven(int n) {
|
||||
return n % 2 == 0;
|
||||
}
|
||||
|
||||
// List<TimelineBubble> createTimeline() {
|
||||
// final List<TimelineBubble> items = [];
|
||||
// for (var i = 0; i < widget.items.length; i++) {
|
||||
// items.add(
|
||||
// TimelineBubble(
|
||||
// direction:
|
||||
// checkEven(i) ? Config.leftDirection : Config.rightDirection,
|
||||
// size: widget.bubbleDiameter,
|
||||
// title: widget.items[i].title,
|
||||
// subtitle: widget.items[i].subtitle,
|
||||
// description: widget.items[i].description,
|
||||
// icon: widget.items[i].child,
|
||||
// stripColor: widget.stripColor,
|
||||
// bubbleColor: widget.items[i].bubbleColor,
|
||||
// bgColor: widget.scaffoldColor,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// return items;
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
TopHandle(widget.stripColor),
|
||||
...widget.items,
|
||||
BottomHandle(widget.stripColor),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TopHandle extends StatelessWidget {
|
||||
final Color handleColor;
|
||||
const TopHandle(this.handleColor);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: handleColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
height: 20,
|
||||
),
|
||||
Container(
|
||||
height: 20,
|
||||
width: 5,
|
||||
color: handleColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BottomHandle extends StatelessWidget {
|
||||
final Color handleColor;
|
||||
const BottomHandle(this.handleColor);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: 20,
|
||||
width: 5,
|
||||
color: handleColor,
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: handleColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TimelineBubble extends StatelessWidget {
|
||||
final String direction;
|
||||
final double size;
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final String description;
|
||||
final Widget icon;
|
||||
final Color stripColor;
|
||||
final Color bgColor;
|
||||
final Color bubbleColor;
|
||||
|
||||
const TimelineBubble({
|
||||
required this.direction,
|
||||
required this.size,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.description,
|
||||
required this.icon,
|
||||
required this.stripColor,
|
||||
required this.bgColor,
|
||||
required this.bubbleColor,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: direction == Config.leftDirection
|
||||
? <Widget>[
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
],
|
||||
if (description != null) ...[
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Text(
|
||||
description,
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
],
|
||||
]
|
||||
: [],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: 10,
|
||||
width: 5,
|
||||
color: stripColor,
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: size,
|
||||
child: ClipPath(
|
||||
clipper: direction == Config.leftDirection
|
||||
? LeftClipper()
|
||||
: RightClipper(),
|
||||
child: Container(
|
||||
width: size,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: stripColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: size - 10,
|
||||
width: size - 10,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: bgColor,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: size - 20,
|
||||
width: size - 20,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: bubbleColor,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: icon,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(height: 10, width: 5, color: stripColor),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: direction == Config.rightDirection
|
||||
? <Widget>[
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
],
|
||||
if (description != null) ...[
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Text(
|
||||
description,
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
],
|
||||
]
|
||||
: [],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RightClipper extends CustomClipper<Path> {
|
||||
@override
|
||||
Path getClip(Size size) {
|
||||
final path = Path();
|
||||
path.lineTo(0, size.height);
|
||||
path.lineTo(size.width / 2 + 3, size.height);
|
||||
path.lineTo(size.width / 2 + 3, 0);
|
||||
path.close();
|
||||
return path;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
|
||||
}
|
||||
|
||||
class LeftClipper extends CustomClipper<Path> {
|
||||
@override
|
||||
Path getClip(Size size) {
|
||||
final path = Path();
|
||||
path.moveTo(size.width, 0);
|
||||
path.lineTo(size.width, size.height);
|
||||
path.lineTo(size.width / 2 - 3, size.height);
|
||||
path.lineTo(size.width / 2 - 3, 0);
|
||||
path.close();
|
||||
return path;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
|
||||
}
|
||||
|
||||
class TimelineItem {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final String description;
|
||||
final Widget child;
|
||||
final Color bubbleColor;
|
||||
|
||||
const TimelineItem({
|
||||
required this.title,
|
||||
this.subtitle = '',
|
||||
this.description = '',
|
||||
required this.child,
|
||||
required this.bubbleColor,
|
||||
});
|
||||
}
|
@ -1015,6 +1015,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.13"
|
||||
timeline_tile:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: timeline_tile
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -47,6 +47,7 @@ dependencies:
|
||||
file_picker: ^5.0.1
|
||||
flashy_tab_bar2: ^0.0.4
|
||||
animated_theme_switcher: ^2.0.6
|
||||
timeline_tile: ^2.0.0
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user