Added liked tenders desigm and data from api

This commit is contained in:
llsajjad
2025-09-02 16:56:42 +03:30
parent e1b4c1dae7
commit 79874c40b1
32 changed files with 2028 additions and 23 deletions
@@ -0,0 +1,169 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/view_models/liked_tenders_view_model.dart';
import 'package:tm_app/views/liked_tenders/widgets/liked_tenders_list_item.dart';
import '../../../core/routes/app_routes.dart';
import '../../shared/desktop_navigation_widget.dart';
class LikedTendersDesktopPage extends StatefulWidget {
const LikedTendersDesktopPage({super.key});
@override
State<LikedTendersDesktopPage> createState() =>
_LikedTendersDesktopPageState();
}
class _LikedTendersDesktopPageState extends State<LikedTendersDesktopPage> {
late ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = ScrollController()..addListener(_onScroll);
}
void _onScroll() {
if (!_scrollController.hasClients) {
return;
}
final max = _scrollController.position.maxScrollExtent;
final pos = _scrollController.position.pixels;
const threshold = 200.0;
if (max - pos <= threshold) {
final vm = Provider.of<LikedTendersViewModel>(context, listen: false);
vm.loadMoreIfNeeded();
}
}
@override
void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.backgroundColor,
body: Column(
children: [
DesktopNavigationWidget(
currentIndex: 1,
onTabChanged: (index) {
switch (index) {
case 0:
Router.neglect(context, () => HomeRouteData().go(context));
break;
case 1:
break;
case 2:
Router.neglect(context, () => ProfileRouteData().go(context));
break;
}
},
),
Expanded(
child: SizedBox(
width: 740,
child: Column(
children: [
Expanded(
child: Consumer<LikedTendersViewModel>(
builder: (context, viewModel, child) {
if (viewModel.isLoading &&
(viewModel.data?.data?.feedback ?? []).isEmpty) {
return const Center(
child: CircularProgressIndicator(
color: AppColors.secondary50,
),
);
}
if (viewModel.errorMessage != null) {
return Center(child: Text(viewModel.errorMessage!));
}
final feedback = viewModel.data?.data?.feedback ?? [];
if (feedback.isEmpty) {
return const Center(
child: Text('چیزی برای نمایش نیست'),
);
}
final itemCount =
feedback.length + (viewModel.isLoadingMore ? 1 : 0);
return RefreshIndicator(
onRefresh: () async {
await viewModel.getLikedTenders(reset: true);
},
child: ListView.builder(
controller: _scrollController,
padding: EdgeInsets.symmetric(
horizontal: 24.0.w(),
vertical: 15.0.h(),
),
itemCount: itemCount,
itemBuilder: (context, index) {
if (index < feedback.length) {
final item = feedback[index];
return Padding(
padding: EdgeInsets.only(bottom: 20.0.h()),
child: Dismissible(
key: ValueKey(item.id ?? index),
direction: DismissDirection.endToStart,
background: Container(
alignment: Alignment.centerRight,
padding: EdgeInsets.symmetric(
horizontal: 20.0.w(),
),
child: SvgPicture.asset(
AssetsManager.trash,
),
),
onDismissed: (_) {
if (item.id != null) {
viewModel.removeTenderById(item.id!);
}
},
child: LikedListItem(tender: item.tender!),
),
);
} else {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Center(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
color: AppColors.secondary50,
strokeWidth: 2,
),
),
),
);
}
},
),
);
},
),
),
],
),
),
),
],
),
);
}
}
@@ -0,0 +1,136 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/view_models/liked_tenders_view_model.dart';
import 'package:tm_app/views/liked_tenders/strings/liked_tenders_strings.dart';
import 'package:tm_app/views/liked_tenders/widgets/liked_tenders_list_item.dart';
import 'package:tm_app/views/shared/tender_app_bar.dart';
class LikedTendersMobilePage extends StatefulWidget {
const LikedTendersMobilePage({super.key});
@override
State<LikedTendersMobilePage> createState() => _LikedTendersMobilePageState();
}
class _LikedTendersMobilePageState extends State<LikedTendersMobilePage> {
late final ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = ScrollController()..addListener(_onScroll);
}
void _onScroll() {
if (!_scrollController.hasClients) {
return;
}
final max = _scrollController.position.maxScrollExtent;
final pos = _scrollController.position.pixels;
const threshold = 200.0;
if (max - pos <= threshold) {
final vm = Provider.of<LikedTendersViewModel>(context, listen: false);
vm.loadMoreIfNeeded();
}
}
@override
void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.backgroundColor,
appBar: appBar(context: context, title: LikedTendersStrings.likedTenders),
body: Column(
children: [
Expanded(
child: Consumer<LikedTendersViewModel>(
builder: (context, viewModel, child) {
if (viewModel.isLoading &&
(viewModel.data?.data?.feedback ?? []).isEmpty) {
return const Center(
child: CircularProgressIndicator(
color: AppColors.secondary50,
),
);
}
if (viewModel.errorMessage != null) {
return Center(child: Text(viewModel.errorMessage!));
}
final feedback = viewModel.data?.data?.feedback ?? [];
if (feedback.isEmpty) {
return const Center(child: Text('چیزی برای نمایش نیست'));
}
final itemCount = feedback.length + (viewModel.isLoadingMore ? 1 : 0);
return RefreshIndicator(
onRefresh: () async {
await viewModel.getLikedTenders(reset: true);
},
child: ListView.builder(
controller: _scrollController,
padding: EdgeInsets.symmetric(
horizontal: 24.0.w(),
vertical: 15.0.h(),
),
itemCount: itemCount,
itemBuilder: (context, index) {
if (index < feedback.length) {
final item = feedback[index];
return Padding(
padding: EdgeInsets.only(bottom: 20.0.h()),
child: Dismissible(
key: ValueKey(item.id ?? index),
direction: DismissDirection.endToStart,
background: Container(
alignment: Alignment.centerRight,
padding: EdgeInsets.symmetric(horizontal: 20.0.w()),
child: SvgPicture.asset(AssetsManager.trash),
),
onDismissed: (_) {
if (item.id != null) {
viewModel.removeTenderById(item.id!);
}
},
child: LikedListItem(tender: item.tender!),
),
);
} else {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Center(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
color: AppColors.secondary50,
strokeWidth: 2,
),
),
),
);
}
},
),
);
},
),
),
],
),
);
}
}
@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/shared/responsive_builder.dart';
import 'package:tm_app/views/liked_tenders/pages/liked_tenders_desktop_page.dart';
import 'package:tm_app/views/liked_tenders/pages/liked_tenders_mobile_page.dart';
import 'package:tm_app/views/liked_tenders/pages/liked_tenders_tablet_page.dart';
class LikedTendersScreen extends StatelessWidget {
const LikedTendersScreen({super.key});
@override
Widget build(BuildContext context) {
SizeConfig.init(context);
return ResponsiveBuilder(
mobile: LikedTendersMobilePage(),
tablet: LikedTendersTabletPage(),
desktop: LikedTendersDesktopPage(),
);
}
}
@@ -0,0 +1,166 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/routes/app_routes.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/view_models/liked_tenders_view_model.dart';
import 'package:tm_app/views/liked_tenders/strings/liked_tenders_strings.dart';
import 'package:tm_app/views/liked_tenders/widgets/liked_tenders_list_item.dart';
import '../../shared/tablet_navigation_widget.dart';
import '../../shared/tender_app_bar.dart';
class LikedTendersTabletPage extends StatefulWidget {
const LikedTendersTabletPage({super.key});
@override
State<LikedTendersTabletPage> createState() => _LikedTendersTabletPageState();
}
class _LikedTendersTabletPageState extends State<LikedTendersTabletPage> {
late final ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = ScrollController()..addListener(_onScroll);
}
void _onScroll() {
if (!_scrollController.hasClients) {
return;
}
final max = _scrollController.position.maxScrollExtent;
final pos = _scrollController.position.pixels;
const threshold = 200.0;
if (max - pos <= threshold) {
final vm = Provider.of<LikedTendersViewModel>(context, listen: false);
vm.loadMoreIfNeeded();
}
}
@override
void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final GlobalKey<ScaffoldState> key = GlobalKey();
return Scaffold(
key: key,
backgroundColor: AppColors.backgroundColor,
appBar: tabletAppBar(title: LikedTendersStrings.likedTenders, key: key),
drawer: TabletNavigationWidget(
currentIndex: 1,
onTabChanged: (index) {
switch (index) {
case 0:
Router.neglect(context, () => HomeRouteData().go(context));
break;
case 1:
break;
case 2:
context.pop();
Router.neglect(context, () => ProfileRouteData().go(context));
break;
}
},
),
body: Center(
child: SizedBox(
width: 720,
child: Column(
children: [
Expanded(
child: Consumer<LikedTendersViewModel>(
builder: (context, viewModel, child) {
if (viewModel.isLoading &&
(viewModel.data?.data?.feedback ?? []).isEmpty) {
return const Center(
child: CircularProgressIndicator(
color: AppColors.secondary50,
),
);
}
if (viewModel.errorMessage != null) {
return Center(child: Text(viewModel.errorMessage!));
}
final feedback = viewModel.data?.data?.feedback ?? [];
if (feedback.isEmpty) {
return const Center(child: Text('چیزی برای نمایش نیست'));
}
final itemCount =
feedback.length + (viewModel.isLoadingMore ? 1 : 0);
return RefreshIndicator(
onRefresh: () async {
await viewModel.getLikedTenders(reset: true);
},
child: ListView.builder(
controller: _scrollController,
padding: EdgeInsets.symmetric(
horizontal: 24.0.w(),
vertical: 15.0.h(),
),
itemCount: itemCount,
itemBuilder: (context, index) {
if (index < feedback.length) {
final item = feedback[index];
return Padding(
padding: EdgeInsets.only(bottom: 20.0.h()),
child: Dismissible(
key: ValueKey(item.id ?? index),
direction: DismissDirection.endToStart,
background: Container(
alignment: Alignment.centerRight,
padding: EdgeInsets.symmetric(
horizontal: 20.0.w(),
),
child: SvgPicture.asset(
AssetsManager.trash,
),
),
onDismissed: (_) {
if (item.id != null) {
viewModel.removeTenderById(item.id!);
}
},
child: LikedListItem(tender: item.tender!),
),
);
} else {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Center(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
color: AppColors.secondary50,
strokeWidth: 2,
),
),
),
);
}
},
),
); },
),
),
],
),
),
),
);
}
}