Toast fixed
parent
fe36263ac0
commit
2fb4a99a42
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,386 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mohem_flutter_app/api/offers_and_discounts_api_client.dart';
|
||||
import 'package:mohem_flutter_app/app_state/app_state.dart';
|
||||
import 'package:mohem_flutter_app/classes/colors.dart';
|
||||
import 'package:mohem_flutter_app/classes/utils.dart';
|
||||
import 'package:mohem_flutter_app/config/routes.dart';
|
||||
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
|
||||
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
|
||||
import 'package:mohem_flutter_app/models/generic_response_model.dart';
|
||||
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart';
|
||||
import 'package:mohem_flutter_app/ui/mazaya/widgets/mazaya_bottom_nav.dart';
|
||||
import 'package:mohem_flutter_app/ui/mazaya/widgets/wala_plus_retailer_grid.dart';
|
||||
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
|
||||
|
||||
// Mazaya Saved tab - full-screen version of what was FavoriteMerchantsBottomSheet (a modal
|
||||
// bottom sheet). Same fetch/search/pagination/toggle-favorite logic, now its own pushed route
|
||||
// with the shared MazayaBottomNav instead of a dismissible sheet - matches Discover's "own
|
||||
// route, fetches fresh on mount" pattern.
|
||||
class SavedRetailersScreen extends StatefulWidget {
|
||||
const SavedRetailersScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SavedRetailersScreen> createState() => _SavedRetailersScreenState();
|
||||
}
|
||||
|
||||
class _SavedRetailersScreenState extends State<SavedRetailersScreen> {
|
||||
List<WalaPlusRetailerModel> favoriteRetailers = <WalaPlusRetailerModel>[];
|
||||
bool isLoading = true;
|
||||
bool isLoadingMore = false;
|
||||
bool hasMore = true;
|
||||
bool _isFetchingPage = false;
|
||||
int currentPage = 1;
|
||||
int totalPages = 1;
|
||||
final int pageSize = 10;
|
||||
String searchQuery = "";
|
||||
Timer? _debounceTimer;
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController.addListener(_scrollListener);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) fetchFavorites();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debounceTimer?.cancel();
|
||||
_scrollController.removeListener(_scrollListener);
|
||||
_scrollController.dispose();
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _scrollListener() {
|
||||
if (!_scrollController.hasClients) return;
|
||||
double maxScroll = _scrollController.position.maxScrollExtent;
|
||||
double currentScroll = _scrollController.position.pixels;
|
||||
if (currentScroll >= maxScroll - 35) {
|
||||
if (!isLoading && !isLoadingMore && !_isFetchingPage && hasMore) {
|
||||
fetchNextPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String? _getUserReferenceForFavorites() {
|
||||
String ref = OffersAndDiscountsApiClient().resolveUserReference();
|
||||
if (ref.trim().isNotEmpty) return ref.trim();
|
||||
String? userName = AppState().getUserName;
|
||||
if (userName != null && userName.trim().isNotEmpty) return userName.trim();
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> fetchFavorites({String? query}) async {
|
||||
if (query != null) searchQuery = query;
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
currentPage = 1;
|
||||
hasMore = true;
|
||||
});
|
||||
try {
|
||||
String? queryParam =
|
||||
searchQuery.trim().isNotEmpty ? searchQuery.trim() : null;
|
||||
String? userRef = _getUserReferenceForFavorites();
|
||||
WalaPlusRetailersResponse? response = await OffersAndDiscountsApiClient()
|
||||
.getWalaPlusRetailers(
|
||||
categoryId: "",
|
||||
page: 1,
|
||||
perPage: pageSize,
|
||||
offerType: null,
|
||||
query: queryParam,
|
||||
sortBy: null,
|
||||
radius: null,
|
||||
minDiscount: null,
|
||||
context: null,
|
||||
userReference: userRef,
|
||||
);
|
||||
favoriteRetailers = response?.retailers ?? <WalaPlusRetailerModel>[];
|
||||
for (WalaPlusRetailerModel r in favoriteRetailers) {
|
||||
r.isFavorite = true;
|
||||
}
|
||||
currentPage = 1;
|
||||
totalPages = response?.pagesCount ?? 1;
|
||||
hasMore =
|
||||
currentPage < totalPages &&
|
||||
(response?.retailers?.isNotEmpty ?? false);
|
||||
} catch (e) {
|
||||
debugPrint("MazayaSavedScreen: error fetching favorites: $e");
|
||||
favoriteRetailers = <WalaPlusRetailerModel>[];
|
||||
} finally {
|
||||
if (mounted) setState(() => isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchNextPage() async {
|
||||
if (isLoading || isLoadingMore || _isFetchingPage || !hasMore) return;
|
||||
_isFetchingPage = true;
|
||||
setState(() => isLoadingMore = true);
|
||||
try {
|
||||
int nextPage = currentPage + 1;
|
||||
String? queryParam =
|
||||
searchQuery.trim().isNotEmpty ? searchQuery.trim() : null;
|
||||
String? userRef = _getUserReferenceForFavorites();
|
||||
WalaPlusRetailersResponse? response = await OffersAndDiscountsApiClient()
|
||||
.getWalaPlusRetailers(
|
||||
categoryId: "",
|
||||
page: nextPage,
|
||||
perPage: pageSize,
|
||||
offerType: null,
|
||||
query: queryParam,
|
||||
sortBy: null,
|
||||
radius: null,
|
||||
minDiscount: null,
|
||||
context: null,
|
||||
userReference: userRef,
|
||||
);
|
||||
List<WalaPlusRetailerModel> newRetailers =
|
||||
response?.retailers ?? <WalaPlusRetailerModel>[];
|
||||
if (newRetailers.isNotEmpty) {
|
||||
for (WalaPlusRetailerModel r in newRetailers) {
|
||||
r.isFavorite = true;
|
||||
}
|
||||
currentPage = nextPage;
|
||||
totalPages = response?.pagesCount ?? totalPages;
|
||||
hasMore = currentPage < totalPages;
|
||||
favoriteRetailers.addAll(newRetailers);
|
||||
} else {
|
||||
hasMore = false;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("MazayaSavedScreen: error fetching next page: $e");
|
||||
} finally {
|
||||
_isFetchingPage = false;
|
||||
if (mounted) setState(() => isLoadingMore = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _onSearchChanged(String query) {
|
||||
_debounceTimer?.cancel();
|
||||
searchQuery = query;
|
||||
if (query.trim().isEmpty) {
|
||||
fetchFavorites(query: "");
|
||||
} else {
|
||||
_debounceTimer = Timer(const Duration(milliseconds: 600), () {
|
||||
fetchFavorites(query: query.trim());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _toggleFavorite(WalaPlusRetailerModel item) async {
|
||||
String retailerId = item.id ?? "";
|
||||
if (retailerId.isEmpty) return;
|
||||
|
||||
bool currentFav = item.isFavorite ?? true;
|
||||
bool targetFav = !currentFav;
|
||||
|
||||
setState(() {
|
||||
item.isFavorite = targetFav;
|
||||
if (!targetFav) {
|
||||
favoriteRetailers.removeWhere(
|
||||
(WalaPlusRetailerModel r) => r.id == item.id,
|
||||
);
|
||||
}
|
||||
});
|
||||
Utils.showLoading(context);
|
||||
|
||||
try {
|
||||
GenericResponseModel? response;
|
||||
String? userRef = _getUserReferenceForFavorites();
|
||||
if (targetFav) {
|
||||
response = await OffersAndDiscountsApiClient().addWalaPlusFavorite(
|
||||
retailerId: retailerId,
|
||||
context: context,
|
||||
userReference: userRef,
|
||||
);
|
||||
} else {
|
||||
response = await OffersAndDiscountsApiClient().removeWalaPlusFavorite(
|
||||
retailerId: retailerId,
|
||||
context: context,
|
||||
userReference: userRef,
|
||||
);
|
||||
}
|
||||
|
||||
Utils.hideLoading(context);
|
||||
|
||||
if (response != null && response.errorMessage == null) {
|
||||
bool isAr = AppState().isArabic(context);
|
||||
String msg =
|
||||
isAr
|
||||
? (targetFav
|
||||
? LocaleKeys.favoriteAddedSuccess.tr()
|
||||
: LocaleKeys.favoriteRemovedSuccess.tr())
|
||||
: (response.wpFavoritesAddRemoveMessage ??
|
||||
(targetFav
|
||||
? LocaleKeys.favoriteAddedSuccess.tr()
|
||||
: LocaleKeys.favoriteRemovedSuccess.tr()));
|
||||
Utils.showToast(msg, isSuccess: true);
|
||||
} else {
|
||||
Utils.showToast(
|
||||
response?.errorEndUserMessage ??
|
||||
response?.errorMessage ??
|
||||
LocaleKeys.favoriteError.tr(),
|
||||
);
|
||||
await fetchFavorites(query: _searchController.text);
|
||||
}
|
||||
} catch (e) {
|
||||
Utils.hideLoading(context);
|
||||
debugPrint("MazayaSavedScreen: error toggling favorite: $e");
|
||||
Utils.showToast(LocaleKeys.favoriteError.tr());
|
||||
await fetchFavorites(query: _searchController.text);
|
||||
}
|
||||
}
|
||||
|
||||
void _openRetailer(WalaPlusRetailerModel item) async {
|
||||
await Navigator.pushNamed(
|
||||
context,
|
||||
AppRoutes.retailerDetailsScreen,
|
||||
arguments: item,
|
||||
);
|
||||
if (mounted) {
|
||||
await fetchFavorites(query: _searchController.text);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isAr = AppState().isArabic(context);
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBarWidget(
|
||||
context,
|
||||
title: LocaleKeys.favoriteOffers.tr(),
|
||||
showHomeButton: false,
|
||||
),
|
||||
bottomNavigationBar: const MazayaBottomNav(currentTab: MazayaTab.saved),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 70,
|
||||
padding: const EdgeInsets.only(
|
||||
top: 14,
|
||||
right: 16,
|
||||
bottom: 14,
|
||||
left: 16,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: const Color(0xFFE8E8E8), width: 1.0),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: const Color(0xFF000000).withOpacity(0.0314),
|
||||
offset: const Offset(0, 2),
|
||||
blurRadius: 5,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
LocaleKeys.search.tr().toText12(
|
||||
isBold: true,
|
||||
color: const Color(0xFF2A333C),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
TextField(
|
||||
controller: _searchController,
|
||||
onChanged: _onSearchChanged,
|
||||
onSubmitted: (String val) {
|
||||
_debounceTimer?.cancel();
|
||||
fetchFavorites(query: val.trim());
|
||||
},
|
||||
textInputAction: TextInputAction.search,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
hintText: LocaleKeys.searchItems.tr(),
|
||||
hintStyle: const TextStyle(
|
||||
color: Color(0xFFACAFB1),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.2,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
suffixIcon: ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: _searchController,
|
||||
builder: (
|
||||
BuildContext context,
|
||||
TextEditingValue value,
|
||||
Widget? child,
|
||||
) {
|
||||
if (value.text.isEmpty)
|
||||
return const SizedBox.shrink();
|
||||
return InkWell(
|
||||
child: const Icon(
|
||||
Icons.clear,
|
||||
size: 18,
|
||||
color: Color(0xFF848486),
|
||||
),
|
||||
onTap: () {
|
||||
_searchController.clear();
|
||||
_onSearchChanged("");
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
suffixIconConstraints: const BoxConstraints(
|
||||
maxWidth: 24,
|
||||
maxHeight: 28,
|
||||
),
|
||||
),
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF2A333C),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => fetchFavorites(query: _searchController.text),
|
||||
color: MyColors.textMixColor,
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(
|
||||
parent: BouncingScrollPhysics(),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
WalaPlusRetailerGrid(
|
||||
isLoading: isLoading,
|
||||
retailers: favoriteRetailers,
|
||||
isAr: isAr,
|
||||
isLoadingMore: isLoadingMore,
|
||||
onTapRetailer: _openRetailer,
|
||||
onFavoriteTap: _toggleFavorite,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
|
||||
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
|
||||
import 'package:mohem_flutter_app/ui/mazaya/mazaya_discover_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/mazaya/saved_retailers_screen.dart';
|
||||
import 'package:mohem_flutter_app/ui/profile/family_members.dart';
|
||||
|
||||
enum MazayaTab { discover, saved, myFamily }
|
||||
|
||||
// Shared bottom nav for the Mazaya module (Discover / Saved / My Family). Each tab is its own
|
||||
// screen, swapped in with zero-duration transition (see _onTabTapped) so switching tabs feels
|
||||
// instant rather than like a new screen opening - not an IndexedStack shell, since each tab still
|
||||
// needs to reload its own data fresh on every switch, not preserve state across tabs.
|
||||
class MazayaBottomNav extends StatelessWidget {
|
||||
final MazayaTab currentTab;
|
||||
|
||||
const MazayaBottomNav({Key? key, required this.currentTab}) : super(key: key);
|
||||
|
||||
static const Color _activeColor = Color(0xFF147D6B);
|
||||
static const Color _inactiveColor = Color(0xFF78817F);
|
||||
|
||||
void _onTabTapped(BuildContext context, MazayaTab tab) {
|
||||
if (tab == currentTab) return;
|
||||
Widget targetScreen;
|
||||
RouteSettings? settings;
|
||||
switch (tab) {
|
||||
case MazayaTab.discover:
|
||||
targetScreen = const MazayaDiscoverScreen();
|
||||
break;
|
||||
case MazayaTab.saved:
|
||||
targetScreen = const SavedRetailersScreen();
|
||||
break;
|
||||
case MazayaTab.myFamily:
|
||||
targetScreen = const FamilyMembers();
|
||||
settings = const RouteSettings(arguments: <String, dynamic>{'fromMazaya': true});
|
||||
break;
|
||||
}
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
PageRouteBuilder<void>(
|
||||
settings: settings,
|
||||
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => targetScreen,
|
||||
transitionDuration: Duration.zero,
|
||||
reverseTransitionDuration: Duration.zero,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItem(BuildContext context, {required MazayaTab tab, required String svgAsset, required String label}) {
|
||||
bool isActive = tab == currentTab;
|
||||
return Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => _onTabTapped(context, tab),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
SvgPicture.asset(svgAsset, width: 22, height: 22, colorFilter: ColorFilter.mode(isActive ? _activeColor : _inactiveColor, BlendMode.srcIn)),
|
||||
const SizedBox(height: 5),
|
||||
label.toText11(weight: isActive ? FontWeight.w600 : FontWeight.w400, color: isActive ? _activeColor : _inactiveColor),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: Color(0xFFE8EAE4)))),
|
||||
padding: const EdgeInsets.only(top: 13, bottom: 9),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
_buildItem(context, tab: MazayaTab.discover, svgAsset: "assets/icons/mazaya_compass.svg", label: LocaleKeys.discover.tr()),
|
||||
_buildItem(context, tab: MazayaTab.saved, svgAsset: "assets/icons/mazaya_heart_outline.svg", label: LocaleKeys.saved.tr()),
|
||||
_buildItem(context, tab: MazayaTab.myFamily, svgAsset: "assets/icons/mazaya_user_round.svg", label: LocaleKeys.myFamily.tr()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
|
||||
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
|
||||
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
|
||||
import 'package:mohem_flutter_app/main.dart';
|
||||
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart';
|
||||
import 'package:mohem_flutter_app/ui/mazaya/retailers_list_screen.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
class WalaPlusRetailerGrid extends StatelessWidget {
|
||||
final bool isLoading;
|
||||
final List<WalaPlusRetailerModel> retailers;
|
||||
final bool isAr;
|
||||
final void Function(WalaPlusRetailerModel item) onTapRetailer;
|
||||
final void Function(WalaPlusRetailerModel item) onFavoriteTap;
|
||||
final bool isLoadingMore;
|
||||
final int shimmerItemCount;
|
||||
|
||||
const WalaPlusRetailerGrid({
|
||||
Key? key,
|
||||
required this.isLoading,
|
||||
required this.retailers,
|
||||
required this.isAr,
|
||||
required this.onTapRetailer,
|
||||
required this.onFavoriteTap,
|
||||
this.isLoadingMore = false,
|
||||
this.shimmerItemCount = 4,
|
||||
}) : super(key: key);
|
||||
|
||||
SliverGridDelegateWithFixedCrossAxisCount get _gridDelegate =>
|
||||
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: isTablet ? 4 : 2, mainAxisExtent: 235, crossAxisSpacing: 12, mainAxisSpacing: 16);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isLoading) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: const Color(0xFFE2E8F0),
|
||||
highlightColor: const Color(0xFFF8FAFC),
|
||||
child: GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: _gridDelegate,
|
||||
itemCount: shimmerItemCount,
|
||||
itemBuilder: (BuildContext _, int __) => _buildShimmerCard(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (retailers.isEmpty) {
|
||||
return Padding(padding: const EdgeInsets.only(top: 40), child: Center(child: LocaleKeys.noOffersAvailable.tr().toText14(color: const Color(0xFF64748B))));
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: _gridDelegate,
|
||||
itemCount: retailers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return WalaPlusRetailerCard(item: retailers[index], isAr: isAr, onTap: () => onTapRetailer(retailers[index]), onFavoriteTap: () => onFavoriteTap(retailers[index]));
|
||||
},
|
||||
),
|
||||
if (isLoadingMore) ...<Widget>[
|
||||
16.height,
|
||||
Center(child: Padding(padding: const EdgeInsets.symmetric(vertical: 12), child: Image.asset("assets/images/logos/loading_mohemm_logo.gif", height: 48.0, width: 48.0))),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShimmerCard() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20)),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(height: 120, width: double.infinity, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(14))),
|
||||
const SizedBox(height: 8),
|
||||
Container(height: 14, width: 110, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))),
|
||||
const SizedBox(height: 6),
|
||||
Container(height: 14, width: 80, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Container(height: 28, width: 50, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(6))),
|
||||
const SizedBox(width: 5),
|
||||
Container(height: 28, width: 45, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(6))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue