fix homepage news, invoice
This commit is contained in:
@@ -131,7 +131,8 @@ class _HomePageState extends ConsumerState<HomePage> {
|
|||||||
promotions: promotions,
|
promotions: promotions,
|
||||||
onPromotionTap: (promotion) {
|
onPromotionTap: (promotion) {
|
||||||
// Navigate to promotion details
|
// Navigate to promotion details
|
||||||
context.push('/promotions/${promotion.id}');
|
context.push('/news/${promotion.id}');
|
||||||
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ class BuyerInfoModel {
|
|||||||
final String? wardCode;
|
final String? wardCode;
|
||||||
final String? cityName;
|
final String? cityName;
|
||||||
final String? wardName;
|
final String? wardName;
|
||||||
|
final String? customerName;
|
||||||
|
|
||||||
const BuyerInfoModel({
|
const BuyerInfoModel({
|
||||||
this.name,
|
this.name,
|
||||||
@@ -100,6 +101,7 @@ class BuyerInfoModel {
|
|||||||
this.wardCode,
|
this.wardCode,
|
||||||
this.cityName,
|
this.cityName,
|
||||||
this.wardName,
|
this.wardName,
|
||||||
|
this.customerName,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory BuyerInfoModel.fromJson(Map<String, dynamic> json) {
|
factory BuyerInfoModel.fromJson(Map<String, dynamic> json) {
|
||||||
@@ -115,6 +117,7 @@ class BuyerInfoModel {
|
|||||||
wardCode: json['ward_code'] as String?,
|
wardCode: json['ward_code'] as String?,
|
||||||
cityName: json['city_name'] as String?,
|
cityName: json['city_name'] as String?,
|
||||||
wardName: json['ward_name'] as String?,
|
wardName: json['ward_name'] as String?,
|
||||||
|
customerName: json['customer_name'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +133,7 @@ class BuyerInfoModel {
|
|||||||
'ward_code': wardCode,
|
'ward_code': wardCode,
|
||||||
'city_name': cityName,
|
'city_name': cityName,
|
||||||
'ward_name': wardName,
|
'ward_name': wardName,
|
||||||
|
'customer_name': customerName,
|
||||||
};
|
};
|
||||||
|
|
||||||
BuyerInfo toEntity() => BuyerInfo(
|
BuyerInfo toEntity() => BuyerInfo(
|
||||||
@@ -144,6 +148,7 @@ class BuyerInfoModel {
|
|||||||
wardCode: wardCode,
|
wardCode: wardCode,
|
||||||
cityName: cityName,
|
cityName: cityName,
|
||||||
wardName: wardName,
|
wardName: wardName,
|
||||||
|
customerName: customerName,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ class BuyerInfo extends Equatable {
|
|||||||
final String? wardCode;
|
final String? wardCode;
|
||||||
final String? cityName;
|
final String? cityName;
|
||||||
final String? wardName;
|
final String? wardName;
|
||||||
|
final String? customerName;
|
||||||
|
|
||||||
const BuyerInfo({
|
const BuyerInfo({
|
||||||
this.name,
|
this.name,
|
||||||
@@ -88,6 +89,7 @@ class BuyerInfo extends Equatable {
|
|||||||
this.wardCode,
|
this.wardCode,
|
||||||
this.cityName,
|
this.cityName,
|
||||||
this.wardName,
|
this.wardName,
|
||||||
|
this.customerName
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Get formatted full address
|
/// Get formatted full address
|
||||||
@@ -118,6 +120,7 @@ class BuyerInfo extends Equatable {
|
|||||||
wardCode,
|
wardCode,
|
||||||
cityName,
|
cityName,
|
||||||
wardName,
|
wardName,
|
||||||
|
customerName
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -294,10 +294,10 @@ class InvoiceDetailPage extends ConsumerWidget {
|
|||||||
|
|
||||||
List<_InfoLine> _buildBuyerInfoLines(Invoice invoice) {
|
List<_InfoLine> _buildBuyerInfoLines(Invoice invoice) {
|
||||||
return [
|
return [
|
||||||
if (invoice.buyerInfo!.name != null)
|
if (invoice.buyerInfo!.customerName != null)
|
||||||
_InfoLine(label: 'Người mua hàng', value: invoice.buyerInfo!.name!),
|
_InfoLine(label: 'Người mua hàng', value: invoice.buyerInfo!.customerName!),
|
||||||
if (invoice.customerName != null)
|
if (invoice.buyerInfo!.addressTitle != null)
|
||||||
_InfoLine(label: 'Tên đơn vị', value: invoice.customerName!),
|
_InfoLine(label: 'Tên đơn vị', value: invoice.buyerInfo!.addressTitle!),
|
||||||
if (invoice.buyerInfo!.taxCode != null)
|
if (invoice.buyerInfo!.taxCode != null)
|
||||||
_InfoLine(label: 'Mã số thuế', value: invoice.buyerInfo!.taxCode!),
|
_InfoLine(label: 'Mã số thuế', value: invoice.buyerInfo!.taxCode!),
|
||||||
if (invoice.buyerInfo!.fullAddress.isNotEmpty)
|
if (invoice.buyerInfo!.fullAddress.isNotEmpty)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:shimmer/shimmer.dart';
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:worker/core/constants/ui_constants.dart';
|
import 'package:worker/core/constants/ui_constants.dart';
|
||||||
import 'package:worker/core/theme/colors.dart';
|
import 'package:worker/core/theme/colors.dart';
|
||||||
import 'package:worker/features/products/domain/entities/product.dart';
|
import 'package:worker/features/products/domain/entities/product.dart';
|
||||||
@@ -273,17 +274,14 @@ class ProductCard extends ConsumerWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 36.0,
|
height: 36.0,
|
||||||
child: OutlinedButton.icon(
|
child: OutlinedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
// TODO: Open 360 view in browser
|
final url = Uri.parse(product.customLink360!);
|
||||||
// For now, show a message
|
if (await canLaunchUrl(url)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
await launchUrl(
|
||||||
const SnackBar(
|
url,
|
||||||
content: Text(
|
mode: LaunchMode.inAppWebView,
|
||||||
'Đang phát triển tính năng xem 360°',
|
);
|
||||||
),
|
}
|
||||||
duration: Duration(seconds: 2),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: colorScheme.primary,
|
foregroundColor: colorScheme.primary,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:shimmer/shimmer.dart';
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:worker/core/constants/ui_constants.dart';
|
import 'package:worker/core/constants/ui_constants.dart';
|
||||||
import 'package:worker/features/products/domain/entities/product.dart';
|
import 'package:worker/features/products/domain/entities/product.dart';
|
||||||
|
|
||||||
@@ -18,7 +19,6 @@ import 'package:worker/features/products/domain/entities/product.dart';
|
|||||||
/// - Image indicators (dots)
|
/// - Image indicators (dots)
|
||||||
/// - Thumbnail gallery row (horizontal scroll)
|
/// - Thumbnail gallery row (horizontal scroll)
|
||||||
class ImageGallerySection extends StatefulWidget {
|
class ImageGallerySection extends StatefulWidget {
|
||||||
|
|
||||||
const ImageGallerySection({super.key, required this.product});
|
const ImageGallerySection({super.key, required this.product});
|
||||||
final Product product;
|
final Product product;
|
||||||
|
|
||||||
@@ -40,22 +40,15 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_currentImageIndex = index;
|
_currentImageIndex = index;
|
||||||
});
|
});
|
||||||
_pageController.animateToPage(
|
_pageController.animateToPage(index, duration: AppDuration.medium, curve: Curves.easeInOut);
|
||||||
index,
|
|
||||||
duration: AppDuration.medium,
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _open360View() {
|
void _open360View() async {
|
||||||
if (widget.product.customLink360 != null) {
|
if (widget.product.customLink360 != null) {
|
||||||
// TODO: Open in browser
|
final url = Uri.parse(widget.product.customLink360!);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
if (await canLaunchUrl(url)) {
|
||||||
const SnackBar(
|
await launchUrl(url, mode: LaunchMode.inAppWebView);
|
||||||
content: Text('Đang phát triển tính năng xem 360°'),
|
}
|
||||||
duration: Duration(seconds: 2),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,11 +101,7 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
|
|||||||
),
|
),
|
||||||
errorWidget: (context, url, error) => Container(
|
errorWidget: (context, url, error) => Container(
|
||||||
color: colorScheme.surfaceContainerHighest,
|
color: colorScheme.surfaceContainerHighest,
|
||||||
child: Icon(
|
child: Icon(FontAwesomeIcons.image, size: 64, color: colorScheme.onSurfaceVariant),
|
||||||
FontAwesomeIcons.image,
|
|
||||||
size: 64,
|
|
||||||
color: colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -131,10 +120,7 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
|
|||||||
onTap: _open360View,
|
onTap: _open360View,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
horizontal: 12,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -144,11 +130,7 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
|
|||||||
builder: (context, double value, child) {
|
builder: (context, double value, child) {
|
||||||
return Transform.rotate(
|
return Transform.rotate(
|
||||||
angle: value * 2 * 3.14159,
|
angle: value * 2 * 3.14159,
|
||||||
child: Icon(
|
child: Icon(FontAwesomeIcons.arrowsRotate, size: 10, color: colorScheme.surface),
|
||||||
FontAwesomeIcons.arrowsRotate,
|
|
||||||
size: 10,
|
|
||||||
color: colorScheme.surface,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onEnd: () {
|
onEnd: () {
|
||||||
@@ -159,11 +141,7 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
|
|||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
'360°',
|
'360°',
|
||||||
style: TextStyle(
|
style: TextStyle(color: colorScheme.surface, fontSize: 12, fontWeight: FontWeight.w600),
|
||||||
color: colorScheme.surface,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -245,9 +223,7 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isActive
|
color: isActive ? colorScheme.primary : Colors.transparent,
|
||||||
? colorScheme.primary
|
|
||||||
: Colors.transparent,
|
|
||||||
width: 2,
|
width: 2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -256,15 +232,10 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
|
|||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
placeholder: (context, url) =>
|
placeholder: (context, url) => Container(color: colorScheme.surfaceContainerHighest),
|
||||||
Container(color: colorScheme.surfaceContainerHighest),
|
|
||||||
errorWidget: (context, url, error) => Container(
|
errorWidget: (context, url, error) => Container(
|
||||||
color: colorScheme.surfaceContainerHighest,
|
color: colorScheme.surfaceContainerHighest,
|
||||||
child: Icon(
|
child: Icon(FontAwesomeIcons.image, size: 20, color: colorScheme.onSurfaceVariant),
|
||||||
FontAwesomeIcons.image,
|
|
||||||
size: 20,
|
|
||||||
color: colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -285,12 +256,7 @@ class _ImageGallerySectionState extends State<ImageGallerySection> {
|
|||||||
|
|
||||||
/// Image Lightbox for full-screen image viewing
|
/// Image Lightbox for full-screen image viewing
|
||||||
class _ImageLightbox extends StatefulWidget {
|
class _ImageLightbox extends StatefulWidget {
|
||||||
|
const _ImageLightbox({required this.images, required this.imageCaptions, required this.initialIndex});
|
||||||
const _ImageLightbox({
|
|
||||||
required this.images,
|
|
||||||
required this.imageCaptions,
|
|
||||||
required this.initialIndex,
|
|
||||||
});
|
|
||||||
final List<String> images;
|
final List<String> images;
|
||||||
final Map<String, String> imageCaptions;
|
final Map<String, String> imageCaptions;
|
||||||
final int initialIndex;
|
final int initialIndex;
|
||||||
@@ -318,19 +284,13 @@ class _ImageLightboxState extends State<_ImageLightbox> {
|
|||||||
|
|
||||||
void _nextImage() {
|
void _nextImage() {
|
||||||
if (_currentIndex < widget.images.length - 1) {
|
if (_currentIndex < widget.images.length - 1) {
|
||||||
_pageController.nextPage(
|
_pageController.nextPage(duration: AppDuration.medium, curve: Curves.easeInOut);
|
||||||
duration: AppDuration.medium,
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _previousImage() {
|
void _previousImage() {
|
||||||
if (_currentIndex > 0) {
|
if (_currentIndex > 0) {
|
||||||
_pageController.previousPage(
|
_pageController.previousPage(duration: AppDuration.medium, curve: Curves.easeInOut);
|
||||||
duration: AppDuration.medium,
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,11 +332,8 @@ class _ImageLightboxState extends State<_ImageLightbox> {
|
|||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: widget.images[index],
|
imageUrl: widget.images[index],
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
errorWidget: (context, url, error) => Icon(
|
errorWidget: (context, url, error) =>
|
||||||
FontAwesomeIcons.circleExclamation,
|
Icon(FontAwesomeIcons.circleExclamation, color: colorScheme.surface, size: 64),
|
||||||
color: colorScheme.surface,
|
|
||||||
size: 64,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -393,15 +350,9 @@ class _ImageLightboxState extends State<_ImageLightbox> {
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(FontAwesomeIcons.chevronLeft, color: colorScheme.surface, size: 32),
|
||||||
FontAwesomeIcons.chevronLeft,
|
|
||||||
color: colorScheme.surface,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
onPressed: _previousImage,
|
onPressed: _previousImage,
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(backgroundColor: Colors.white.withAlpha(51)),
|
||||||
backgroundColor: Colors.white.withAlpha(51),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -414,15 +365,9 @@ class _ImageLightboxState extends State<_ImageLightbox> {
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(FontAwesomeIcons.chevronRight, color: colorScheme.surface, size: 32),
|
||||||
FontAwesomeIcons.chevronRight,
|
|
||||||
color: colorScheme.surface,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
onPressed: _nextImage,
|
onPressed: _nextImage,
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(backgroundColor: Colors.white.withAlpha(51)),
|
||||||
backgroundColor: Colors.white.withAlpha(51),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1166,13 +1166,13 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
|
|||||||
height: 24,
|
height: 24,
|
||||||
child: CustomLoadingIndicator(color: Colors.white, size: 20),
|
child: CustomLoadingIndicator(color: Colors.white, size: 20),
|
||||||
)
|
)
|
||||||
: const Row(
|
: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
FaIcon(FontAwesomeIcons.paperPlane, size: 16),
|
const FaIcon(FontAwesomeIcons.paperPlane, size: 16),
|
||||||
SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'Gửi đăng ký',
|
isEditing ? 'Cập nhật' : 'Gửi đăng ký',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.0.1+24
|
version: 1.0.1+26
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.10.0
|
sdk: ^3.10.0
|
||||||
|
|||||||
Reference in New Issue
Block a user