price policy
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:open_file/open_file.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import '../../../../core/constants/ui_constants.dart';
|
||||
import '../../../../core/network/dio_client.dart';
|
||||
import '../../../../core/theme/colors.dart';
|
||||
import '../../domain/entities/price_document.dart';
|
||||
import '../providers/price_documents_provider.dart';
|
||||
@@ -55,36 +60,55 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
labelColor: AppColors.white,
|
||||
unselectedLabelColor: AppColors.grey900,
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
indicator: BoxDecoration(
|
||||
color: AppColors.primaryBlue,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
labelStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
unselectedLabelStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
tabs: const [
|
||||
Tab(text: 'Chính sách giá'),
|
||||
Tab(text: 'Bảng giá'),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
body: Column(
|
||||
children: [
|
||||
// Policy tab
|
||||
_buildDocumentList(DocumentCategory.policy),
|
||||
// Price list tab
|
||||
_buildDocumentList(DocumentCategory.priceList),
|
||||
// TabBar with padding
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grey100,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
labelColor: AppColors.white,
|
||||
unselectedLabelColor: AppColors.grey900,
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
indicator: BoxDecoration(
|
||||
color: AppColors.primaryBlue,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
dividerColor: Colors.transparent,
|
||||
labelStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
unselectedLabelStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
tabs: const [
|
||||
Tab(text: 'Chính sách giá'),
|
||||
Tab(text: 'Bảng giá'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// TabBarView
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
// Policy tab
|
||||
_buildDocumentList(DocumentCategory.policy),
|
||||
// Price list tab
|
||||
_buildDocumentList(DocumentCategory.priceList),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -96,7 +120,7 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
|
||||
return documentsAsync.when(
|
||||
data: (documents) {
|
||||
if (documents.isEmpty) {
|
||||
return Center(
|
||||
return const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
@@ -105,7 +129,7 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
|
||||
size: 64,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
'Chưa có tài liệu',
|
||||
style: TextStyle(fontSize: 16, color: AppColors.grey500),
|
||||
@@ -118,8 +142,9 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
// Refresh documents from repository
|
||||
ref.invalidate(filteredPriceDocumentsProvider(category));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 500));
|
||||
await ref
|
||||
.read(filteredPriceDocumentsProvider(category).notifier)
|
||||
.refresh();
|
||||
},
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.all(AppSpacing.md),
|
||||
@@ -130,7 +155,7 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
|
||||
final document = documents[index];
|
||||
return DocumentCard(
|
||||
document: document,
|
||||
onDownload: () => _handleDownload(document),
|
||||
onDownload: () => _handleDownload(document, category),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -150,7 +175,9 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
ref.invalidate(filteredPriceDocumentsProvider(category));
|
||||
ref
|
||||
.read(filteredPriceDocumentsProvider(category).notifier)
|
||||
.refresh();
|
||||
},
|
||||
child: const Text('Thử lại'),
|
||||
),
|
||||
@@ -160,22 +187,95 @@ class _PricePolicyPageState extends ConsumerState<PricePolicyPage>
|
||||
);
|
||||
}
|
||||
|
||||
void _handleDownload(PriceDocument document) {
|
||||
// In real app, this would trigger actual download
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Đang tải: ${document.title}'),
|
||||
duration: const Duration(seconds: 2),
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
Future<void> _handleDownload(
|
||||
PriceDocument document,
|
||||
DocumentCategory category,
|
||||
) async {
|
||||
try {
|
||||
// Check if file already downloaded and exists
|
||||
if (document.filePath != null) {
|
||||
final file = File(document.filePath!);
|
||||
if (await file.exists()) {
|
||||
// File exists, just open it
|
||||
final result = await OpenFile.open(document.filePath!);
|
||||
if (result.type != ResultType.done && mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Không thể mở file: ${result.message}'),
|
||||
backgroundColor: AppColors.danger,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate download
|
||||
// TODO: Implement actual file download functionality
|
||||
// - Use url_launcher or dio to download file
|
||||
// - Show progress indicator
|
||||
// - Save to device storage
|
||||
// File not downloaded yet, show loading snackbar
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Đang tải: ${document.title}'),
|
||||
duration: const Duration(seconds: 2),
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
|
||||
// Get DioClient
|
||||
final dioClient = await ref.read(dioClientProvider.future);
|
||||
|
||||
// Get download directory
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
|
||||
// Extract filename from URL or use title
|
||||
final uri = Uri.parse(document.fileUrl);
|
||||
final filename = uri.pathSegments.isNotEmpty
|
||||
? uri.pathSegments.last
|
||||
: '${document.title}.${document.documentType == DocumentType.pdf ? "pdf" : "xlsx"}';
|
||||
|
||||
final savePath = '${directory.path}/$filename';
|
||||
|
||||
// Download file with authentication headers (automatically added by AuthInterceptor)
|
||||
await dioClient.downloadFile(
|
||||
document.fileUrl,
|
||||
savePath,
|
||||
onReceiveProgress: (received, total) {
|
||||
// Progress tracking available here if needed: (received / total * 100)
|
||||
},
|
||||
);
|
||||
|
||||
// Update document with file path in provider
|
||||
ref
|
||||
.read(filteredPriceDocumentsProvider(category).notifier)
|
||||
.updateDocumentFilePath(document.title, savePath);
|
||||
|
||||
// Show success message
|
||||
if (mounted) {
|
||||
// Clear any existing snackbars
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
|
||||
// Show success message
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Tải thành công: ${document.title}'),
|
||||
backgroundColor: AppColors.success,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Show error message
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Lỗi tải file: ${e.toString()}'),
|
||||
backgroundColor: AppColors.danger,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showInfoDialog() {
|
||||
|
||||
@@ -1,38 +1,54 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:worker/features/price_policy/data/datasources/price_policy_local_datasource.dart';
|
||||
import 'package:worker/core/network/dio_client.dart';
|
||||
import 'package:worker/features/price_policy/data/datasources/price_policy_remote_datasource.dart';
|
||||
import 'package:worker/features/price_policy/data/repositories/price_policy_repository_impl.dart';
|
||||
import 'package:worker/features/price_policy/domain/entities/price_document.dart';
|
||||
import 'package:worker/features/price_policy/domain/repositories/price_policy_repository.dart';
|
||||
|
||||
part 'price_documents_provider.g.dart';
|
||||
|
||||
/// Provider for local data source
|
||||
@riverpod
|
||||
PricePolicyLocalDataSource pricePolicyLocalDataSource(Ref ref) {
|
||||
return PricePolicyLocalDataSource();
|
||||
}
|
||||
|
||||
/// Provider for price policy repository
|
||||
@riverpod
|
||||
PricePolicyRepository pricePolicyRepository(Ref ref) {
|
||||
final localDataSource = ref.watch(pricePolicyLocalDataSourceProvider);
|
||||
|
||||
return PricePolicyRepositoryImpl(localDataSource: localDataSource);
|
||||
Future<PricePolicyRepository> pricePolicyRepository(Ref ref) async {
|
||||
final dioClient = await ref.watch(dioClientProvider.future);
|
||||
final remoteDataSource = PricePolicyRemoteDataSourceImpl(dioClient);
|
||||
return PricePolicyRepositoryImpl(remoteDataSource: remoteDataSource);
|
||||
}
|
||||
|
||||
/// Provider for all price policy documents
|
||||
@riverpod
|
||||
Future<List<PriceDocument>> priceDocuments(Ref ref) async {
|
||||
final repository = ref.watch(pricePolicyRepositoryProvider);
|
||||
final repository = await ref.watch(pricePolicyRepositoryProvider.future);
|
||||
return repository.getAllDocuments();
|
||||
}
|
||||
|
||||
/// Provider for filtered documents by category
|
||||
/// Provider for filtered documents by category with file path management
|
||||
@riverpod
|
||||
Future<List<PriceDocument>> filteredPriceDocuments(
|
||||
Ref ref,
|
||||
DocumentCategory category,
|
||||
) async {
|
||||
final repository = ref.watch(pricePolicyRepositoryProvider);
|
||||
return repository.getDocumentsByCategory(category);
|
||||
class FilteredPriceDocuments extends _$FilteredPriceDocuments {
|
||||
@override
|
||||
Future<List<PriceDocument>> build(DocumentCategory category) async {
|
||||
final repository = await ref.watch(pricePolicyRepositoryProvider.future);
|
||||
return repository.getDocumentsByCategory(category);
|
||||
}
|
||||
|
||||
/// Update a document's file path after download
|
||||
void updateDocumentFilePath(String documentTitle, String filePath) {
|
||||
state = state.whenData((documents) {
|
||||
return documents.map((doc) {
|
||||
if (doc.title == documentTitle) {
|
||||
return doc.copyWith(filePath: filePath);
|
||||
}
|
||||
return doc;
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
/// Refresh documents
|
||||
Future<void> refresh() async {
|
||||
state = const AsyncValue.loading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
final repository = await ref.read(pricePolicyRepositoryProvider.future);
|
||||
return repository.getDocumentsByCategory(category);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,60 +8,6 @@ part of 'price_documents_provider.dart';
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Provider for local data source
|
||||
|
||||
@ProviderFor(pricePolicyLocalDataSource)
|
||||
const pricePolicyLocalDataSourceProvider =
|
||||
PricePolicyLocalDataSourceProvider._();
|
||||
|
||||
/// Provider for local data source
|
||||
|
||||
final class PricePolicyLocalDataSourceProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
PricePolicyLocalDataSource,
|
||||
PricePolicyLocalDataSource,
|
||||
PricePolicyLocalDataSource
|
||||
>
|
||||
with $Provider<PricePolicyLocalDataSource> {
|
||||
/// Provider for local data source
|
||||
const PricePolicyLocalDataSourceProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'pricePolicyLocalDataSourceProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$pricePolicyLocalDataSourceHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<PricePolicyLocalDataSource> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
PricePolicyLocalDataSource create(Ref ref) {
|
||||
return pricePolicyLocalDataSource(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(PricePolicyLocalDataSource value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<PricePolicyLocalDataSource>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$pricePolicyLocalDataSourceHash() =>
|
||||
r'dd1bee761fa7f050835508cf33bf34a788829483';
|
||||
|
||||
/// Provider for price policy repository
|
||||
|
||||
@ProviderFor(pricePolicyRepository)
|
||||
@@ -72,11 +18,13 @@ const pricePolicyRepositoryProvider = PricePolicyRepositoryProvider._();
|
||||
final class PricePolicyRepositoryProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<PricePolicyRepository>,
|
||||
PricePolicyRepository,
|
||||
PricePolicyRepository,
|
||||
PricePolicyRepository
|
||||
FutureOr<PricePolicyRepository>
|
||||
>
|
||||
with $Provider<PricePolicyRepository> {
|
||||
with
|
||||
$FutureModifier<PricePolicyRepository>,
|
||||
$FutureProvider<PricePolicyRepository> {
|
||||
/// Provider for price policy repository
|
||||
const PricePolicyRepositoryProvider._()
|
||||
: super(
|
||||
@@ -94,26 +42,18 @@ final class PricePolicyRepositoryProvider
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<PricePolicyRepository> $createElement(
|
||||
$FutureProviderElement<PricePolicyRepository> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
PricePolicyRepository create(Ref ref) {
|
||||
FutureOr<PricePolicyRepository> create(Ref ref) {
|
||||
return pricePolicyRepository(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(PricePolicyRepository value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<PricePolicyRepository>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$pricePolicyRepositoryHash() =>
|
||||
r'296555a45936d8e43a28bf5add5e7db40495009c';
|
||||
r'35aa21067e77bbb6b91dd29c4772b1c6707be116';
|
||||
|
||||
/// Provider for all price policy documents
|
||||
|
||||
@@ -159,26 +99,18 @@ final class PriceDocumentsProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$priceDocumentsHash() => r'cf2ccf6bd9aaae0c56ab01529fd034a090d99263';
|
||||
String _$priceDocumentsHash() => r'dffe292742776681c22d0ccdb3e091491290057d';
|
||||
|
||||
/// Provider for filtered documents by category
|
||||
/// Provider for filtered documents by category with file path management
|
||||
|
||||
@ProviderFor(filteredPriceDocuments)
|
||||
@ProviderFor(FilteredPriceDocuments)
|
||||
const filteredPriceDocumentsProvider = FilteredPriceDocumentsFamily._();
|
||||
|
||||
/// Provider for filtered documents by category
|
||||
|
||||
/// Provider for filtered documents by category with file path management
|
||||
final class FilteredPriceDocumentsProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<PriceDocument>>,
|
||||
List<PriceDocument>,
|
||||
FutureOr<List<PriceDocument>>
|
||||
>
|
||||
with
|
||||
$FutureModifier<List<PriceDocument>>,
|
||||
$FutureProvider<List<PriceDocument>> {
|
||||
/// Provider for filtered documents by category
|
||||
$AsyncNotifierProvider<FilteredPriceDocuments, List<PriceDocument>> {
|
||||
/// Provider for filtered documents by category with file path management
|
||||
const FilteredPriceDocumentsProvider._({
|
||||
required FilteredPriceDocumentsFamily super.from,
|
||||
required DocumentCategory super.argument,
|
||||
@@ -202,15 +134,7 @@ final class FilteredPriceDocumentsProvider
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<List<PriceDocument>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<List<PriceDocument>> create(Ref ref) {
|
||||
final argument = this.argument as DocumentCategory;
|
||||
return filteredPriceDocuments(ref, argument);
|
||||
}
|
||||
FilteredPriceDocuments create() => FilteredPriceDocuments();
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
@@ -225,13 +149,16 @@ final class FilteredPriceDocumentsProvider
|
||||
}
|
||||
|
||||
String _$filteredPriceDocumentsHash() =>
|
||||
r'8f5b2ed822694b4dd9523e1a61e202a7ba0c1fbc';
|
||||
r'c06d858ed1027d6408c4b70c29f47a4c4c9eb21c';
|
||||
|
||||
/// Provider for filtered documents by category
|
||||
/// Provider for filtered documents by category with file path management
|
||||
|
||||
final class FilteredPriceDocumentsFamily extends $Family
|
||||
with
|
||||
$FunctionalFamilyOverride<
|
||||
$ClassFamilyOverride<
|
||||
FilteredPriceDocuments,
|
||||
AsyncValue<List<PriceDocument>>,
|
||||
List<PriceDocument>,
|
||||
FutureOr<List<PriceDocument>>,
|
||||
DocumentCategory
|
||||
> {
|
||||
@@ -244,7 +171,7 @@ final class FilteredPriceDocumentsFamily extends $Family
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
/// Provider for filtered documents by category
|
||||
/// Provider for filtered documents by category with file path management
|
||||
|
||||
FilteredPriceDocumentsProvider call(DocumentCategory category) =>
|
||||
FilteredPriceDocumentsProvider._(argument: category, from: this);
|
||||
@@ -252,3 +179,29 @@ final class FilteredPriceDocumentsFamily extends $Family
|
||||
@override
|
||||
String toString() => r'filteredPriceDocumentsProvider';
|
||||
}
|
||||
|
||||
/// Provider for filtered documents by category with file path management
|
||||
|
||||
abstract class _$FilteredPriceDocuments
|
||||
extends $AsyncNotifier<List<PriceDocument>> {
|
||||
late final _$args = ref.$arg as DocumentCategory;
|
||||
DocumentCategory get category => _$args;
|
||||
|
||||
FutureOr<List<PriceDocument>> build(DocumentCategory category);
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(_$args);
|
||||
final ref =
|
||||
this.ref as $Ref<AsyncValue<List<PriceDocument>>, List<PriceDocument>>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<List<PriceDocument>>, List<PriceDocument>>,
|
||||
AsyncValue<List<PriceDocument>>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,23 +120,11 @@ class DocumentCard extends StatelessWidget {
|
||||
document.formattedDateWithPrefix,
|
||||
style: const TextStyle(fontSize: 13, color: AppColors.grey500),
|
||||
),
|
||||
if (document.fileSize != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'•',
|
||||
style: TextStyle(fontSize: 13, color: AppColors.grey500),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
document.fileSize!,
|
||||
style: const TextStyle(fontSize: 13, color: AppColors.grey500),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
document.description,
|
||||
document.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
@@ -150,19 +138,24 @@ class DocumentCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildDownloadButton() {
|
||||
final isDownloaded = document.filePath != null;
|
||||
final buttonColor = isDownloaded ? AppColors.success : AppColors.primaryBlue;
|
||||
final buttonIcon = isDownloaded ? Icons.folder_open : Icons.download;
|
||||
final buttonText = isDownloaded ? 'Mở file' : 'Tải về';
|
||||
|
||||
return ElevatedButton.icon(
|
||||
onPressed: onDownload,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
backgroundColor: buttonColor,
|
||||
foregroundColor: AppColors.white,
|
||||
elevation: 0,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
icon: const Icon(Icons.download, size: 18),
|
||||
label: const Text(
|
||||
'Tải về',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
icon: Icon(buttonIcon, size: 18),
|
||||
label: Text(
|
||||
buttonText,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user