add wishlists screen

This commit is contained in:
cuqmbr 2023-11-25 20:03:36 +02:00
parent 7d89bae2e1
commit 8540a01486
Signed by: cuqmbr
GPG Key ID: 2D72ED98B6CB200F
12 changed files with 521 additions and 180 deletions

4
assets/icons/cart.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.0551 12.257C1.3371 8.904 0.977103 7.227 1.8781 6.114C2.7781 5 4.4931 5 7.9221 5H13.0781C16.5081 5 18.2211 5 19.1221 6.114C20.0221 7.228 19.6631 8.904 18.9451 12.257L18.5161 14.257C18.0291 16.53 17.7861 17.666 16.9611 18.333C16.1361 19 14.9741 19 12.6501 19H8.3501C6.0261 19 4.8641 19 4.0401 18.333C3.2141 17.666 2.9701 16.53 2.4841 14.257L2.0551 12.257Z" stroke="white" stroke-width="1.5"/>
<path d="M1.5 9H19.5M8.5 12H12.5M16.5 7L13.5 1M4.5 7L7.5 1" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 649 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.7911 1.2534C9.91165 0.53012 10.5374 0 11.2707 0H12.7293C13.4626 0 14.0884 0.530119 14.2089 1.2534L14.2724 1.6346C14.3382 2.02925 14.6344 2.34228 15.0159 2.46281C15.5698 2.63782 16.1029 2.85975 16.6105 3.12394C16.9655 3.30869 17.3963 3.29678 17.7219 3.0642L18.0371 2.83909C18.6337 2.41289 19.4511 2.48054 19.9696 2.99903L21.001 4.03043C21.5195 4.54892 21.5871 5.36627 21.1609 5.96295L20.9358 6.2781C20.7032 6.60371 20.6913 7.03455 20.8761 7.38949C21.1403 7.89707 21.3622 8.43016 21.5372 8.98409C21.6577 9.3656 21.9707 9.66179 22.3654 9.72757L22.7466 9.7911C23.4699 9.91165 24 10.5374 24 11.2707V12.7293C24 13.4626 23.4699 14.0884 22.7466 14.2089L22.3654 14.2724C21.9707 14.3382 21.6577 14.6344 21.5372 15.0159C21.3622 15.5698 21.1403 16.1029 20.8761 16.6105C20.6913 16.9654 20.7032 17.3963 20.9358 17.7219L21.1609 18.0371C21.5871 18.6337 21.5195 19.4511 21.001 19.9696L19.9696 21.001C19.4511 21.5195 18.6338 21.5871 18.0371 21.1609L17.7219 20.9358C17.3963 20.7032 16.9655 20.6913 16.6105 20.8761C16.1029 21.1403 15.5698 21.3622 15.0159 21.5372C14.6344 21.6577 14.3382 21.9707 14.2724 22.3654L14.2089 22.7466C14.0884 23.4699 13.4626 24 12.7293 24H11.2707C10.5374 24 9.91165 23.4699 9.7911 22.7466L9.72757 22.3654C9.66179 21.9707 9.36561 21.6577 8.98409 21.5372C8.43016 21.3622 7.89707 21.1403 7.38949 20.8761C7.03455 20.6913 6.60372 20.7032 6.27811 20.9358L5.96293 21.1609C5.36625 21.5871 4.5489 21.5195 4.03041 21.001L2.99901 19.9696C2.48052 19.4511 2.41287 18.6338 2.83907 18.0371L3.0642 17.7219C3.29678 17.3963 3.30869 16.9655 3.12394 16.6105C2.85975 16.1029 2.63782 15.5698 2.46281 15.0159C2.34228 14.6344 2.02925 14.3382 1.6346 14.2724L1.2534 14.2089C0.53012 14.0884 0 13.4626 0 12.7293V11.2707C0 10.5374 0.530119 9.91165 1.2534 9.7911L1.6346 9.72757C2.02925 9.66179 2.34228 9.3656 2.46281 8.98409C2.63782 8.43015 2.85975 7.89706 3.12394 7.38948C3.30869 7.03454 3.29678 6.60371 3.0642 6.2781L2.83909 5.96294C2.41289 5.36626 2.48054 4.54892 2.99903 4.03042L4.03043 2.99903C4.54892 2.48053 5.36627 2.41289 5.96295 2.83909L6.2781 3.0642C6.60371 3.29678 7.03455 3.30868 7.38949 3.12394C7.89707 2.85975 8.43016 2.63782 8.98409 2.46281C9.36561 2.34228 9.66179 2.02926 9.72757 1.6346L9.7911 1.2534ZM18.9291 13C18.4439 16.3923 15.5265 19 12 19C11.1568 19 10.3484 18.8509 9.5997 18.5776L11.6753 14.9826C11.7819 14.9941 11.8903 15 12 15C13.3062 15 14.4175 14.1652 14.8293 13H18.9291ZM18.9291 11H14.8293C14.4175 9.83481 13.3062 9 12 9C11.8903 9 11.782 9.00589 11.6754 9.01736L9.5998 5.42233C10.3484 5.14908 11.1568 5 12 5C15.5265 5 18.4439 7.60771 18.9291 11ZM7.83814 6.37104C6.11624 7.64626 5 9.69278 5 12C5 14.3072 6.1162 16.3537 7.83806 17.6289L9.86885 14.1115C9.33174 13.5694 9 12.8234 9 12C9 11.1765 9.33177 10.4306 9.86893 9.88848L7.83814 6.37104Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,3 @@
<svg width="25" height="26" viewBox="0 0 25 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.9167 0.5H2.08333C1.5308 0.5 1.00089 0.719493 0.610194 1.11019C0.219493 1.50089 0 2.0308 0 2.58333V23.4167C0 23.9692 0.219493 24.4991 0.610194 24.8898C1.00089 25.2805 1.5308 25.5 2.08333 25.5H22.9167C23.4692 25.5 23.9991 25.2805 24.3898 24.8898C24.7805 24.4991 25 23.9692 25 23.4167V2.58333C25 2.0308 24.7805 1.50089 24.3898 1.11019C23.9991 0.719493 23.4692 0.5 22.9167 0.5ZM19.7917 14.0417H13.5417V20.2917C13.5417 20.5679 13.4319 20.8329 13.2366 21.0282C13.0412 21.2236 12.7763 21.3333 12.5 21.3333C12.2237 21.3333 11.9588 21.2236 11.7634 21.0282C11.5681 20.8329 11.4583 20.5679 11.4583 20.2917V14.0417H5.20833C4.93207 14.0417 4.66711 13.9319 4.47176 13.7366C4.27641 13.5412 4.16667 13.2763 4.16667 13C4.16667 12.7237 4.27641 12.4588 4.47176 12.2634C4.66711 12.0681 4.93207 11.9583 5.20833 11.9583H11.4583V5.70833C11.4583 5.43207 11.5681 5.16711 11.7634 4.97176C11.9588 4.77641 12.2237 4.66667 12.5 4.66667C12.7763 4.66667 13.0412 4.77641 13.2366 4.97176C13.4319 5.16711 13.5417 5.43207 13.5417 5.70833V11.9583H19.7917C20.0679 11.9583 20.3329 12.0681 20.5282 12.2634C20.7236 12.4588 20.8333 12.7237 20.8333 13C20.8333 13.2763 20.7236 13.5412 20.5282 13.7366C20.3329 13.9319 20.0679 14.0417 19.7917 14.0417Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

3
assets/icons/trash.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 3C17.2652 3 17.5196 3.10536 17.7071 3.29289C17.8946 3.48043 18 3.73478 18 4C18 4.26522 17.8946 4.51957 17.7071 4.70711C17.5196 4.89464 17.2652 5 17 5H16L15.997 5.071L15.064 18.142C15.0281 18.6466 14.8023 19.1188 14.4321 19.4636C14.0619 19.8083 13.5749 20 13.069 20H4.93C4.42414 20 3.93707 19.8083 3.56688 19.4636C3.1967 19.1188 2.97092 18.6466 2.935 18.142L2.002 5.072C2.00048 5.04803 1.99982 5.02402 2 5H1C0.734784 5 0.48043 4.89464 0.292893 4.70711C0.105357 4.51957 0 4.26522 0 4C0 3.73478 0.105357 3.48043 0.292893 3.29289C0.48043 3.10536 0.734784 3 1 3H17ZM13.997 5H4.003L4.931 18H13.069L13.997 5ZM11 0C11.2652 0 11.5196 0.105357 11.7071 0.292893C11.8946 0.48043 12 0.734784 12 1C12 1.26522 11.8946 1.51957 11.7071 1.70711C11.5196 1.89464 11.2652 2 11 2H7C6.73478 2 6.48043 1.89464 6.29289 1.70711C6.10536 1.51957 6 1.26522 6 1C6 0.734784 6.10536 0.48043 6.29289 0.292893C6.48043 0.105357 6.73478 0 7 0H11Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,4 @@
<svg width="22" height="26" viewBox="0 0 22 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.75 23V24C3.75 25.1046 4.64543 26 5.75 26H19.25C20.3546 26 21.25 25.1046 21.25 24V6C21.25 4.89543 20.3546 4 19.25 4H18.75V21C18.75 22.1046 17.8546 23 16.75 23H3.75Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.75 0C1.64543 0 0.75 0.89543 0.75 2V20C0.75 21.1046 1.64543 22 2.75 22H15.75C16.8546 22 17.75 21.1046 17.75 20V2C17.75 0.895431 16.8546 0 15.75 0H2.75ZM14.6141 9.26478C14.6077 9.20575 14.5939 9.07931 14.5762 8.96456C14.3212 7.57956 13.2812 6.59456 11.9812 6.50956C11.1062 6.44956 10.2462 6.76956 9.47625 7.43456L9.43625 7.46456L9.39625 7.43456C8.60625 6.75456 7.75625 6.43956 6.85125 6.50956C6.56625 6.52956 6.21125 6.62456 5.94125 6.74456C5.07625 7.14456 4.49625 7.93956 4.30625 8.97956C4.23125 9.33956 4.23125 9.90956 4.30625 10.2696C4.42625 10.9196 4.72625 11.5846 5.19125 12.2546C5.98625 13.3946 7.31625 14.6046 8.92625 15.6596L8.93583 15.6657C9.21727 15.8452 9.26325 15.8746 9.44125 15.8746C9.62125 15.8746 9.67125 15.8446 9.92625 15.6796C11.4562 14.6846 12.7012 13.5746 13.5362 12.4696C13.9262 11.9596 14.3062 11.2496 14.4612 10.7496C14.5362 10.4996 14.6062 10.0996 14.6162 9.96956C14.6362 9.74956 14.6362 9.54956 14.6162 9.28456C14.6157 9.27928 14.6149 9.27263 14.6141 9.26478Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,158 +1,134 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:graphql/client.dart';
import 'package:shopping_assistant_mobile_client/network/api_client.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:shopping_assistant_mobile_client/screens/wishlists.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
class MyApp extends StatefulWidget {
const MyApp({super.key});
// This widget is the root of your application.
static const List<String> _pageNameOptions = <String>[
'Wishlists',
'New Chat',
'Settings',
];
static const List<Widget> _widgetOptions = <Widget>[
WishlistsScreen(),
Text(''),
Text(''),
];
static const Color _selectedColor = Color.fromRGBO(36, 36, 36, 1);
static const Color _unselectedColor = Color.fromRGBO(144, 144, 144, 1);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _selectedIndex = 0;
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// TRY THIS: Try running your application with "flutter run". You'll see
// the application has a blue toolbar. Then, without quitting the app,
// try changing the seedColor in the colorScheme below to Colors.green
// and then invoke "hot reload" (save your changes or press the "hot
// reload" button in a Flutter-supported IDE, or press "r" if you used
// the command line to start the app).
//
// Notice that the counter didn't reset back to zero; the application
// state is not lost during the reload. To reset the state, use hot
// restart instead.
//
// This works for code too, not just values: Most code changes can be
// tested with just a hot reload.
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
var client = ApiClient();
Future<void> _incrementCounter() async {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
const String startPersonalWishlistMutations = r'''
mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) {
startPersonalWishlist(dto: $dto) {
createdById, id, name, type
}
}
''';
MutationOptions mutationOptions = MutationOptions(
document: gql(startPersonalWishlistMutations),
variables: const <String, dynamic>{
'dto': {
'firstMessageText': 'Gaming mechanical keyboard',
'type': 'Product'
},
}
);
var result = await client.mutate(mutationOptions);
print(jsonEncode(result));
var wishlistId = result?['startPersonalWishlist']['id'];
var sseStream = client.getServerSentEventStream(
'api/productssearch/search/$wishlistId',
{'text': 'silent wireless mouse'});
await for (var chunk in sseStream) {
print('${chunk.event}: ${chunk.data}');
}
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
// change color while the other colors stay the same.
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
//
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
// action in the IDE, or press "p" in the console), to see the
// wireframe for each widget.
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
appBarTheme: AppBarTheme(),
textTheme: TextTheme(
bodyMedium: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
home: Scaffold(
appBar: AppBar(
title: Text(MyApp._pageNameOptions[_selectedIndex]),
centerTitle: true,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1),
child: Container(
color: Color.fromRGBO(234, 234, 234, 1),
height: 1,
),
),
),
body: MyApp._widgetOptions[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: SvgPicture.asset(
'assets/icons/wishlists.svg',
color: _selectedIndex == 0
? MyApp._selectedColor
: MyApp._unselectedColor,
),
label: 'Wishlists',
),
BottomNavigationBarItem(
icon: SvgPicture.asset(
'assets/icons/start-new-search.svg',
color: _selectedIndex == 1
? MyApp._selectedColor
: MyApp._unselectedColor,
),
label: 'New Chat',
),
BottomNavigationBarItem(
icon: SvgPicture.asset(
'assets/icons/settings.svg',
color: _selectedIndex == 2
? MyApp._selectedColor
: MyApp._unselectedColor,
),
label: 'Settings',
),
],
selectedItemColor: MyApp._selectedColor,
unselectedItemColor: MyApp._unselectedColor,
selectedFontSize: 14,
unselectedFontSize: 14,
currentIndex: _selectedIndex,
onTap: _onItemTapped,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
// Use to seed wishlists for new user
// const String startPersonalWishlistMutations = r'''
// mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) {
// startPersonalWishlist(dto: $dto) {
// createdById, id, name, type
// }
// }
// ''';
//
// MutationOptions mutationOptions = MutationOptions(
// document: gql(startPersonalWishlistMutations),
// variables: const <String, dynamic>{
// 'dto': {
// 'firstMessageText': 'Gaming mechanical keyboard',
// 'type': 'Product'
// },
// });
//
// var client = ApiClient();
// // for (var i = 0; i < 5; i++) {
// // client
// // .mutate(mutationOptions)
// // .then((result) => print(jsonEncode(result)));
// // sleep(Duration(milliseconds: 100));
// // }
//

21
lib/models/wishlist.dart Normal file
View File

@ -0,0 +1,21 @@
class Wishlist {
Wishlist(
{required this.id,
required this.name,
required this.type,
required this.createdById});
String id;
String name;
String type;
String createdById;
Wishlist.fromJson(Map<String, dynamic> json)
: id = json['id'] as String,
name = json['name'] as String,
type = json['type'] as String,
createdById = json['createdById'] as String;
}

View File

@ -12,21 +12,18 @@ class AuthenticationService {
late SharedPreferences prefs;
AuthenticationService() {
SharedPreferences.getInstance().then((result) => {prefs = result});
}
Future<String> getAccessToken() async {
prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString('accessToken');
var refreshToken = prefs.getString('refreshToken');
if (accessToken == null && refreshToken != null) {
print('WTF??');
} else if (accessToken == null && refreshToken == null) {
accessToken = await accessGuest();
accessToken = await _accessGuest();
print('Got new access token $accessToken');
} else if (JwtDecoder.isExpired(accessToken!)) {
accessToken = await refreshAccessToken();
accessToken = await _refreshAccessToken();
print('Refreshed access token $accessToken');
}
@ -35,6 +32,7 @@ class AuthenticationService {
}
Future login(String? email, String? phone, String password) async {
prefs = await SharedPreferences.getInstance();
const String loginQuery = r'''
mutation Login($login: AccessUserModelInput!) {
login(login: $login) {
@ -68,7 +66,8 @@ class AuthenticationService {
prefs.setString('refreshToken', refreshToken);
}
Future<String> accessGuest() async {
Future<String> _accessGuest() async {
prefs = await SharedPreferences.getInstance();
String? guestId = prefs.getString('guestId');
guestId ??= const Uuid().v4();
prefs.setString('guestId', guestId);
@ -106,7 +105,7 @@ class AuthenticationService {
return accessToken;
}
Future<String> refreshAccessToken() async {
Future<String> _refreshAccessToken() async {
var accessToken = prefs.getString('accessToken');
var refreshToken = prefs.getString('refreshToken');

284
lib/screens/wishlists.dart Normal file
View File

@ -0,0 +1,284 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:graphql/client.dart';
import 'package:shopping_assistant_mobile_client/models/wishlist.dart';
import 'package:shopping_assistant_mobile_client/network/api_client.dart';
class WishlistsScreen extends StatefulWidget {
const WishlistsScreen({super.key});
@override
State<WishlistsScreen> createState() => _WishlistsScreenState();
}
class _WishlistsScreenState extends State<WishlistsScreen> {
var client = ApiClient();
late Future _wishlistsFuture;
late List<Wishlist> _wishlists;
@override
void initState() {
super.initState();
_wishlistsFuture = _fetchWishlistPage();
}
Future _fetchWishlistPage() async {
const String personalWishlistsPageQuery = r'''
query personalWishlistsPage($pageNumber: Int!, $pageSize: Int!) {
personalWishlistsPage(pageNumber: $pageNumber, pageSize: $pageSize) {
items {
id, name, type, createdById,
},
hasNextPage, hasPreviousPage, pageNumber, pageSize, totalItems, totalPages,
}
}
''';
QueryOptions queryOptions = QueryOptions(
document: gql(personalWishlistsPageQuery),
variables: const <String, dynamic>{
'pageNumber': 1,
'pageSize': 200,
});
var result = await client.query(queryOptions);
_wishlists = List<Map<String, dynamic>>.from(
result?['personalWishlistsPage']['items'])
.map((e) => Wishlist.fromJson(e))
.toList();
return;
}
void _deleteWishlist(Wishlist wishlist) async {
const String deletePersonalWishlistMutation = r'''
mutation deletePersonalWishlist($wishlistId: String!) {
deletePersonalWishlist(wishlistId: $wishlistId) {
}
}
''';
MutationOptions mutationOptions = MutationOptions(
document: gql(deletePersonalWishlistMutation),
variables: <String, dynamic>{
'wishlistId': wishlist.id,
});
var result = await client.mutate(mutationOptions);
setState(() {
_wishlists.remove(wishlist);
});
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _wishlistsFuture,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text('Error: ${snapshot.error}'),
);
} else if (snapshot.connectionState == ConnectionState.done) {
// Data loaded successfully, display the widget
return Container(
color: Colors.white,
child: _wishlists.length == 0
? Center(
child: Text('No wishlists found'),
)
: ListView.builder(
padding: EdgeInsets.symmetric(vertical: 30),
itemCount: _wishlists.length,
itemBuilder: (context, index) {
return WishlistItem(
wishlist: _wishlists[index],
onDelete: () => _deleteWishlist(_wishlists[index]),
);
},
),
);
}
return Center(
child: CircularProgressIndicator(),
);
},
);
}
}
class WishlistItem extends StatefulWidget {
WishlistItem({
super.key,
required Wishlist wishlist,
required Function() onDelete,
}) : _wishlist = wishlist,
_onDelete = onDelete;
final Wishlist _wishlist;
final Function() _onDelete;
@override
State<WishlistItem> createState() => _WishlistItemState();
}
class _WishlistItemState extends State<WishlistItem> {
double _xOffset = 0;
double _rightBorderRadius = 10.0;
bool _isDeleting = false;
void _transformLeft() {
setState(() {
_xOffset = -70;
_rightBorderRadius = 0;
});
}
void _transformRight() {
setState(() {
_xOffset = 0;
_rightBorderRadius = 10;
});
}
void _onDelete() async {
setState(() {
_isDeleting = true;
});
await widget._onDelete();
setState(() {
_isDeleting = false;
});
_transformRight();
}
@override
Widget build(BuildContext context) {
return Container(
height: 54,
margin: EdgeInsets.only(
bottom: 10,
left: 20,
right: 20,
),
child: Stack(
children: <Widget>[
Positioned(
child: GestureDetector(
onTap: () => _onDelete(),
child: Container(
margin: EdgeInsets.symmetric(
horizontal: .25,
vertical: .25,
),
decoration: BoxDecoration(
color: Color.fromRGBO(0, 82, 204, 1),
borderRadius: BorderRadius.circular(10),
),
alignment: AlignmentDirectional.centerEnd,
child: _isDeleting
? Container(
margin: EdgeInsets.only(
right: 25,
),
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: Container(
padding: EdgeInsets.symmetric(
horizontal: 25,
vertical: 17,
),
child: SvgPicture.asset(
'assets/icons/trash.svg',
color: Colors.white,
width: 20,
),
),
),
),
),
Positioned(
child: GestureDetector(
onTap: () => print(Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
Text('Chat ' + widget._wishlist.id)))),
onHorizontalDragUpdate: (DragUpdateDetails details) => {
if (details.delta.dx < -1)
{_transformLeft()}
else if (details.delta.dx > 1)
{_transformRight()}
},
child: AnimatedContainer(
transform: Matrix4.translationValues(_xOffset, 0, 0),
duration: Duration(milliseconds: 250),
curve: Curves.easeInOut,
decoration: BoxDecoration(
color: Color.fromRGBO(234, 234, 234, 1),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10),
topRight: Radius.circular(_rightBorderRadius),
bottomRight: Radius.circular(_rightBorderRadius),
),
),
alignment: AlignmentDirectional.centerStart,
padding: EdgeInsets.only(
left: 15,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Text(
widget._wishlist.name,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
GestureDetector(
onTap: () => print(Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
Text('Cart ' + widget._wishlist.id)))),
behavior: HitTestBehavior.opaque,
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 17,
vertical: 17,
),
child: SvgPicture.asset(
'assets/icons/cart.svg',
color: Color.fromRGBO(32, 32, 32, 1),
width: 20,
),
),
),
],
),
),
),
),
],
),
);
}
}

View File

@ -134,6 +134,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c
url: "https://pub.dev"
source: hosted
version: "2.0.9"
flutter_test:
dependency: "direct dev"
description: flutter
@ -312,6 +320,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.8.3"
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
url: "https://pub.dev"
source: hosted
version: "1.0.1"
path_provider:
dependency: transitive
description:
@ -517,6 +533,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.7"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43"
url: "https://pub.dev"
source: hosted
version: "1.1.9+1"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7"
url: "https://pub.dev"
source: hosted
version: "1.1.9+1"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26
url: "https://pub.dev"
source: hosted
version: "1.1.9+1"
vector_math:
dependency: transitive
description:

View File

@ -41,6 +41,7 @@ dependencies:
shared_preferences: ^2.2.2
uuid: ^3.0.7
graphql_flutter: ^5.2.0-beta.6
flutter_svg: ^2.0.9
dev_dependencies:
flutter_test:
@ -65,9 +66,12 @@ flutter:
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- assets/icons/cart.svg
- assets/icons/trash.svg
- assets/icons/wishlists.svg
- assets/icons/start-new-search.svg
- assets/icons/settings.svg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware

View File

@ -1,30 +1,30 @@
// This is a basic Flutter widget test.
// // This is a basic Flutter widget test.
// //
// // To perform an interaction with a widget in your test, use the WidgetTester
// // utility in the flutter_test package. For example, you can send tap and scroll
// // gestures. You can also use WidgetTester to find child widgets in the widget
// // tree, read text, and verify that the values of widget properties are correct.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shopping_assistant_mobile_client/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
// import 'package:flutter/material.dart';
// import 'package:flutter_test/flutter_test.dart';
//
// import 'package:shopping_assistant_mobile_client/main.dart';
//
// void main() {
// testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// // Build our app and trigger a frame.
// await tester.pumpWidget(const MyApp());
//
// // Verify that our counter starts at 0.
// expect(find.text('0'), findsOneWidget);
// expect(find.text('1'), findsNothing);
//
// // Tap the '+' icon and trigger a frame.
// await tester.tap(find.byIcon(Icons.add));
// await tester.pump();
//
// // Verify that our counter has incremented.
// expect(find.text('0'), findsNothing);
// expect(find.text('1'), findsOneWidget);
// });
// }