Compare commits

...

2 Commits

Author SHA1 Message Date
7335c55703 Fix: image carousel on web 2024-05-31 15:07:52 +03:00
1eeff4209e Add: images slider 2024-05-31 14:55:24 +03:00
4 changed files with 155 additions and 74 deletions

View File

@@ -10,6 +10,7 @@
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:enableOnBackInvokedCallback="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user

View File

@@ -1,5 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:math';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gymlink_module_web/components/app_bar.dart'; import 'package:gymlink_module_web/components/app_bar.dart';
import 'package:gymlink_module_web/components/heading.dart'; import 'package:gymlink_module_web/components/heading.dart';
@@ -27,6 +29,8 @@ class _DetailPageState extends State<DetailPage> {
bool isInCart = false; bool isInCart = false;
int quantity = 0; int quantity = 0;
GymItem? item; GymItem? item;
final CarouselController _carouselController = CarouselController();
int _currentImage = 0;
@override @override
void initState() { void initState() {
@@ -53,6 +57,11 @@ class _DetailPageState extends State<DetailPage> {
setState(() { setState(() {
item = GymItem.fromJson(jsonDecode(utf8.decode(response.bodyBytes))); item = GymItem.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
}); });
WidgetsBinding.instance.addPostFrameCallback((_) {
for (var element in item!.images) {
precacheImage(NetworkImage(element.url), context);
}
});
} }
} }
@@ -154,6 +163,12 @@ class _DetailPageState extends State<DetailPage> {
} }
} }
double _getAspectRatio() {
double width = MediaQuery.sizeOf(context).width;
double height = MediaQuery.sizeOf(context).height;
return max(width, height) / min(width, height);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -173,9 +188,67 @@ class _DetailPageState extends State<DetailPage> {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Image( item!.images.length > 1
image: NetworkImage(item!.images[0].url), ? Column(children: [
), CarouselSlider.builder(
itemCount: item!.images.length,
itemBuilder: (context, index, realIdx) {
return Center(
child: Image.network(
item!.images[realIdx].url,
width: min(
550,
MediaQuery.sizeOf(context)
.width)),
);
},
carouselController: _carouselController,
options: CarouselOptions(
enlargeCenterPage: true,
height: min(
MediaQuery.sizeOf(context).height -
100,
400),
enableInfiniteScroll: false,
onPageChanged: (index, reason) {
setState(() {
_currentImage = index;
});
}),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: item!.images
.asMap()
.entries
.map((entry) {
return GestureDetector(
onTap: () => _carouselController
.animateToPage(entry.key),
child: Container(
width: 12.0,
height: 12.0,
margin: const EdgeInsets.symmetric(
vertical: 8.0, horizontal: 4.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: (Theme.of(context)
.brightness ==
Brightness.dark
? Colors.white
: Colors.black)
.withOpacity(
_currentImage == entry.key
? 0.9
: 0.4)),
),
);
}).toList(),
),
])
: Image.network(item!.images[0].url,
width: min(
550, MediaQuery.sizeOf(context).width)),
item!.description != '' item!.description != ''
? Padding( ? Padding(
padding: const EdgeInsetsDirectional.all(30), padding: const EdgeInsetsDirectional.all(30),

View File

@@ -95,14 +95,16 @@ class _MainPageState extends State<MainPage> {
} }
void _onLoad() async { void _onLoad() async {
setState(() { if (itemViewCount < filteredData.length) {
isLoading = true; setState(() {
}); isLoading = true;
await Future.delayed(const Duration(seconds: 1)); });
setState(() { await Future.delayed(const Duration(seconds: 1));
itemViewCount = min(filteredData.length, itemViewCount + 5); setState(() {
isLoading = false; itemViewCount = min(filteredData.length, itemViewCount + 5);
}); isLoading = false;
});
}
} }
void _onSearch() { void _onSearch() {
@@ -201,72 +203,76 @@ class _MainPageState extends State<MainPage> {
child: LazyLoadScrollView( child: LazyLoadScrollView(
onEndOfPage: _onLoad, onEndOfPage: _onLoad,
isLoading: isLoading, isLoading: isLoading,
child: ListView( child: Scrollbar(
children: [ child: ListView(
items.isEmpty children: [
? const Center(child: CircularProgressIndicator()) items.isEmpty
: filteredData.isEmpty ? const Center(child: CircularProgressIndicator())
? const Center(child: Text('Ничего не найдено')) : filteredData.isEmpty
: GridView.builder( ? const Center(child: Text('Ничего не найдено'))
physics: const AlwaysScrollableScrollPhysics(), : GridView.builder(
shrinkWrap: true, physics: const NeverScrollableScrollPhysics(),
gridDelegate: shrinkWrap: true,
SliverGridDelegateWithFixedCrossAxisCount( gridDelegate:
crossAxisCount: min( SliverGridDelegateWithFixedCrossAxisCount(
(MediaQuery.sizeOf(context).width ~/ 200) crossAxisCount: min(
.toInt(), (MediaQuery.sizeOf(context).width ~/ 200)
8), .toInt(),
), 8),
itemCount: itemViewCount, ),
itemBuilder: (context, index) { itemCount: itemViewCount,
final product = filteredData[index]; itemBuilder: (context, index) {
return ProductCard( final product = filteredData[index];
imagePath: Image( return ProductCard(
image: Image.network(product.images[0].url) imagePath: Image(
.image, image:
width: 50, Image.network(product.images[0].url)
), .image,
name: product.title, width: 50,
price: product.price.toString(), ),
onTap: () => Navigator.of(context).push( name: product.title,
CustomPageRoute( price: product.price.toString(),
builder: (context) => DetailPage( onTap: () => Navigator.of(context).push(
id: product.id, CustomPageRoute(
builder: (context) => DetailPage(
id: product.id,
),
), ),
), ),
), );
); },
}, ),
), itemViewCount > 0
itemViewCount > 0 ? Padding(
? Padding( padding: const EdgeInsets.symmetric(vertical: 10),
padding: const EdgeInsets.symmetric(vertical: 10), child: Center(
child: Center( child: itemViewCount < filteredData.length
child: itemViewCount < filteredData.length ? !isLoading
? !isLoading ? ElevatedButton(
? ElevatedButton( onPressed: _onLoad,
onPressed: _onLoad, style: ElevatedButton.styleFrom(
style: ElevatedButton.styleFrom( backgroundColor:
backgroundColor: Theme.of(context).primaryColor,
Theme.of(context).primaryColor, shape: const RoundedRectangleBorder(
shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(
borderRadius: BorderRadius.all( Radius.circular(50)),
Radius.circular(50)), ),
foregroundColor: Colors.white,
), ),
foregroundColor: Colors.white, child: const Text('Загрузить ещё'),
), )
child: const Text('Загрузить ещё'), : const CircularProgressIndicator()
) : const Text(
: const CircularProgressIndicator() 'Конец списка',
: const Text( style: TextStyle(
'Конец списка', fontSize: 10,
style: TextStyle( color: Color(0x88000000)),
fontSize: 10, color: Color(0x88000000)), ),
), ),
), )
) : const SizedBox.shrink(),
: const SizedBox.shrink(), ],
], ),
), ),
), ),
), ),

View File

@@ -40,6 +40,7 @@ dependencies:
lazy_load_scrollview: ^1.3.0 lazy_load_scrollview: ^1.3.0
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
flutter_svg: ^2.0.10+1 flutter_svg: ^2.0.10+1
carousel_slider: ^4.2.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: