| 1 | import 'package:cake_wallet/src/widgets/standard_list_card.dart'; |
| 2 | import 'package:cake_wallet/src/widgets/standard_list_status_row.dart'; |
| 3 | import 'package:flutter/material.dart'; |
| 4 | |
| 5 | class StandardListRow extends StatelessWidget { |
| 6 | StandardListRow({ |
| 7 | required this.title, |
| 8 | required this.isSelected, |
| 9 | this.subtitle, |
| 10 | this.onTap, |
| 11 | this.decoration, |
| 12 | super.key, |
| 13 | }); |
| 14 | |
| 15 | final String title; |
| 16 | final String? subtitle; |
| 17 | final bool isSelected; |
| 18 | final void Function(BuildContext context)? onTap; |
| 19 | final Decoration? decoration; |
| 20 | |
| 21 | @override |
| 22 | Widget build(BuildContext context) { |
| 23 | final leading = buildLeading(context); |
| 24 | final trailing = buildTrailing(context); |
| 25 | return Container( |
| 26 | height: 56, |
| 27 | padding: EdgeInsets.only(left: 12, right: 12), |
| 28 | child: TextButton( |
| 29 | onPressed: () => onTap?.call(context), |
| 30 | style: ButtonStyle( |
| 31 | //backgroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.surfaceContainer), |
| 32 | shape: WidgetStateProperty.all( |
| 33 | RoundedRectangleBorder( |
| 34 | borderRadius: BorderRadius.all(Radius.circular(10)), |
| 35 | ), |
| 36 | ), |
| 37 | ), |
| 38 | child: Row( |
| 39 | mainAxisAlignment: MainAxisAlignment.spaceBetween, |
| 40 | children: <Widget>[ |
| 41 | if (leading != null) leading, |
| 42 | buildCenter(context, hasLeftOffset: leading != null), |
| 43 | if (trailing != null) trailing, |
| 44 | ], |
| 45 | ), |
| 46 | ), |
| 47 | ); |
| 48 | } |
| 49 | |
| 50 | Widget? buildLeading(BuildContext context) => null; |
| 51 | |
| 52 | Widget buildCenter(BuildContext context, {required bool hasLeftOffset}) { |
| 53 | return Expanded( |
| 54 | child: Row( |
| 55 | mainAxisAlignment: MainAxisAlignment.start, |
| 56 | children: [ |
| 57 | if (hasLeftOffset) SizedBox(width: 10), |
| 58 | Expanded( |
| 59 | child: Column( |
| 60 | mainAxisSize: MainAxisSize.min, |
| 61 | children: [ |
| 62 | if (subtitle != null && subtitle!.isNotEmpty) |
| 63 | Align( |
| 64 | alignment: Alignment.centerLeft, |
| 65 | child: Text( |
| 66 | textAlign: TextAlign.left, |
| 67 | subtitle!, |
| 68 | style: Theme.of(context).textTheme.labelMedium?.copyWith( |
| 69 | color: titleColor(context), |
| 70 | fontWeight: isSelected ? FontWeight.w800 : FontWeight.w400, |
| 71 | ), |
| 72 | ), |
| 73 | ), |
| 74 | if (subtitle != null && subtitle!.isNotEmpty) |
| 75 | Align( |
| 76 | alignment: Alignment.centerLeft, |
| 77 | child: Text( |
| 78 | textAlign: TextAlign.left, |
| 79 | title, |
| 80 | style: Theme.of(context).textTheme.labelSmall?.copyWith( |
| 81 | color: Theme.of(context).colorScheme.onSurfaceVariant, |
| 82 | fontWeight: isSelected ? FontWeight.w800 : FontWeight.w400, |
| 83 | ), |
| 84 | ), |
| 85 | ) |
| 86 | else |
| 87 | Align( |
| 88 | alignment: Alignment.centerLeft, |
| 89 | child: Text( |
| 90 | title, |
| 91 | style: Theme.of(context).textTheme.bodyMedium?.copyWith( |
| 92 | color: titleColor(context), |
| 93 | fontWeight: isSelected ? FontWeight.w800 : FontWeight.w400, |
| 94 | ), |
| 95 | ), |
| 96 | ), |
| 97 | ], |
| 98 | ), |
| 99 | ) |
| 100 | ], |
| 101 | ), |
| 102 | ); |
| 103 | } |
| 104 | |
| 105 | Widget? buildTrailing(BuildContext context) => null; |
| 106 | |
| 107 | Color titleColor(BuildContext context) => |
| 108 | isSelected ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onSurface; |
| 109 | } |
| 110 | |
| 111 | class SectionHeaderListRow extends StatelessWidget { |
| 112 | @override |
| 113 | Widget build(BuildContext context) => Column(children: [ |
| 114 | StandardListSeparator(padding: EdgeInsets.only(left: 24)), |
| 115 | Container( |
| 116 | width: double.infinity, |
| 117 | height: 40, |
| 118 | color: Theme.of(context).colorScheme.surface, |
| 119 | ), |
| 120 | //StandardListSeparator(padding: EdgeInsets.only(left: 24)) |
| 121 | ]); |
| 122 | } |
| 123 | |
| 124 | class StandardListSeparator extends StatelessWidget { |
| 125 | const StandardListSeparator({this.padding, this.height = 1}); |
| 126 | |
| 127 | final EdgeInsets? padding; |
| 128 | final double height; |
| 129 | |
| 130 | @override |
| 131 | Widget build(BuildContext context) { |
| 132 | return Container( |
| 133 | height: height, |
| 134 | padding: padding, |
| 135 | color: Colors.transparent, |
| 136 | child: Container( |
| 137 | height: height, |
| 138 | color: Colors.transparent, |
| 139 | ), |
| 140 | ); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | class StandardList extends StatelessWidget { |
| 145 | StandardList({required this.itemCount, required this.itemBuilder}); |
| 146 | |
| 147 | final int itemCount; |
| 148 | final IndexedWidgetBuilder itemBuilder; |
| 149 | |
| 150 | @override |
| 151 | Widget build(BuildContext context) { |
| 152 | return ListView.separated( |
| 153 | separatorBuilder: (_, __) => StandardListSeparator(padding: EdgeInsets.only(left: 24)), |
| 154 | itemCount: itemCount, |
| 155 | itemBuilder: itemBuilder); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | class SectionStandardListItem { |
| 160 | SectionStandardListItem({this.hasFullSeparator = false, required this.child}); |
| 161 | |
| 162 | final bool hasFullSeparator; |
| 163 | final Widget child; |
| 164 | } |
| 165 | |
| 166 | class SectionStandardList extends StatelessWidget { |
| 167 | SectionStandardList({ |
| 168 | required this.itemCounter, |
| 169 | required this.itemBuilder, |
| 170 | required this.sectionCount, |
| 171 | this.dividerPadding = const EdgeInsets.only(left: 24), |
| 172 | this.sectionTitleBuilder, |
| 173 | this.hasTopSeparator = false, |
| 174 | this.scrollController, |
| 175 | this.physics, |
| 176 | this.shrinkWrap = false, |
| 177 | }) : totalRows = []; |
| 178 | |
| 179 | final int sectionCount; |
| 180 | final bool hasTopSeparator; |
| 181 | |
| 182 | final int Function(int sectionIndex) itemCounter; |
| 183 | final Widget Function(int sectionIndex, int itemIndex) itemBuilder; |
| 184 | final Widget Function(int sectionIndex)? sectionTitleBuilder; |
| 185 | final List<Widget> totalRows; |
| 186 | final EdgeInsets dividerPadding; |
| 187 | final ScrollController? scrollController; |
| 188 | final ScrollPhysics? physics; |
| 189 | final bool shrinkWrap; |
| 190 | |
| 191 | List<Widget> transform( |
| 192 | bool hasTopSeparator, |
| 193 | int sectionCount, |
| 194 | int Function(int sectionIndex) itemCounter, |
| 195 | Widget Function(int sectionIndex, int itemIndex) itemBuilder, |
| 196 | Widget Function(int sectionIndex)? sectionTitleBuilder) { |
| 197 | final items = <Widget>[]; |
| 198 | |
| 199 | for (var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) { |
| 200 | if ((sectionIndex == 0) && (hasTopSeparator)) { |
| 201 | items.add(StandardListSeparator(padding: EdgeInsets.only(left: 24))); |
| 202 | } |
| 203 | |
| 204 | if (sectionTitleBuilder != null) { |
| 205 | items.add(buildTitle(items, sectionIndex)); |
| 206 | } |
| 207 | |
| 208 | final itemCount = itemCounter(sectionIndex); |
| 209 | |
| 210 | items.addAll(buildSection(itemCount, items, sectionIndex)); |
| 211 | |
| 212 | items.add(sectionIndex + 1 != sectionCount |
| 213 | ? SectionHeaderListRow() |
| 214 | : StandardListSeparator(padding: dividerPadding)); |
| 215 | } |
| 216 | |
| 217 | return items; |
| 218 | } |
| 219 | |
| 220 | Widget buildTitle(List<Widget> items, int sectionIndex) { |
| 221 | if (sectionTitleBuilder == null) { |
| 222 | throw Exception('Cannot to build title. sectionTitleBuilder is null'); |
| 223 | } |
| 224 | |
| 225 | return sectionTitleBuilder!.call(sectionIndex); |
| 226 | } |
| 227 | |
| 228 | List<Widget> buildSection(int itemCount, List<Widget> items, int sectionIndex) { |
| 229 | final List<Widget> section = []; |
| 230 | |
| 231 | for (var itemIndex = 0; itemIndex < itemCount; itemIndex++) { |
| 232 | final item = itemBuilder(sectionIndex, itemIndex); |
| 233 | |
| 234 | section.add(item); |
| 235 | } |
| 236 | return section; |
| 237 | } |
| 238 | |
| 239 | @override |
| 240 | Widget build(BuildContext context) { |
| 241 | totalRows.clear(); |
| 242 | totalRows.addAll( |
| 243 | transform(hasTopSeparator, sectionCount, itemCounter, itemBuilder, sectionTitleBuilder)); |
| 244 | |
| 245 | return ListView.separated( |
| 246 | controller: scrollController, |
| 247 | physics: physics, |
| 248 | shrinkWrap: shrinkWrap, |
| 249 | separatorBuilder: (_, index) { |
| 250 | final row = totalRows[index]; |
| 251 | |
| 252 | if (row is StandardListSeparator || row is SectionHeaderListRow) { |
| 253 | return Container(); |
| 254 | } |
| 255 | |
| 256 | if (row is StandardListStatusRow || row is TradeDetailsStandardListCard) { |
| 257 | return Container(); |
| 258 | } |
| 259 | |
| 260 | final nextRow = totalRows[index + 1]; |
| 261 | |
| 262 | // If current row is pre last and last row is separator. |
| 263 | if (nextRow is StandardListSeparator || nextRow is SectionHeaderListRow) { |
| 264 | return Container(); |
| 265 | } |
| 266 | |
| 267 | return StandardListSeparator(padding: dividerPadding); |
| 268 | }, |
| 269 | itemCount: totalRows.length, |
| 270 | itemBuilder: (_, index) => totalRows[index], |
| 271 | ); |
| 272 | } |
| 273 | } |