Skip to content

Commit 3ea1c8b

Browse files
Implement auto-update checking logic and dashboard banner UI
1 parent 552a2a9 commit 3ea1c8b

2 files changed

Lines changed: 157 additions & 1 deletion

File tree

lib/controllers/billing_controller.dart

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class BillingController extends ChangeNotifier {
3838
BillingController() {
3939
_initDatabase();
4040
_checkPrinterStatus();
41+
checkForUpdates();
4142
}
4243

4344
Future<void> _initDatabase() async {
@@ -454,4 +455,96 @@ class BillingController extends ChangeNotifier {
454455
),
455456
) ?? false;
456457
}
458+
459+
// 🚀 Auto-Update Feature Variables
460+
static const String appVersion = "1.2.0"; // Current App Version
461+
462+
bool _hasUpdate = false;
463+
bool get hasUpdate => _hasUpdate;
464+
465+
bool _isCheckingUpdate = false;
466+
bool get isCheckingUpdate => _isCheckingUpdate;
467+
468+
String _latestVersion = "";
469+
String get latestVersion => _latestVersion;
470+
471+
String _updateDownloadUrl = "";
472+
String get updateDownloadUrl => _updateDownloadUrl;
473+
474+
String _updateReleaseNotes = "";
475+
String get updateReleaseNotes => _updateReleaseNotes;
476+
477+
Future<void> checkForUpdates() async {
478+
_isCheckingUpdate = true;
479+
notifyListeners();
480+
481+
try {
482+
// First try cartsnap repository URL
483+
var response = await http.get(
484+
Uri.parse('https://api.github.com/repos/1bitVscoder/cartsnap/releases/latest'),
485+
).timeout(const Duration(seconds: 5));
486+
487+
// Fallback to old smart_billing_app URL if cartsnap repository doesn't exist yet
488+
if (response.statusCode == 404) {
489+
response = await http.get(
490+
Uri.parse('https://api.github.com/repos/1bitVscoder/smart_billing_app/releases/latest'),
491+
).timeout(const Duration(seconds: 5));
492+
}
493+
494+
if (response.statusCode == 200) {
495+
final data = jsonDecode(response.body);
496+
final String tag = data['tag_name'] ?? "";
497+
498+
if (tag.isNotEmpty && _isNewerVersion(tag, appVersion)) {
499+
_hasUpdate = true;
500+
_latestVersion = tag;
501+
_updateReleaseNotes = data['body'] ?? "No release notes provided.";
502+
503+
// Try to find apk asset in the assets list
504+
final assets = data['assets'] as List?;
505+
if (assets != null && assets.isNotEmpty) {
506+
final apkAsset = assets.firstWhere(
507+
(asset) => asset['name'].toString().toLowerCase().endsWith('.apk'),
508+
orElse: () => null,
509+
);
510+
if (apkAsset != null) {
511+
_updateDownloadUrl = apkAsset['browser_download_url'] ?? "";
512+
}
513+
}
514+
515+
// If no specific APK found, fall back to the release HTML page
516+
if (_updateDownloadUrl.isEmpty) {
517+
_updateDownloadUrl = data['html_url'] ?? "";
518+
}
519+
}
520+
}
521+
} catch (e) {
522+
debugPrint("Auto-update check exception: $e");
523+
} finally {
524+
_isCheckingUpdate = false;
525+
notifyListeners();
526+
}
527+
}
528+
529+
bool _isNewerVersion(String latest, String current) {
530+
final cleanLatest = latest.replaceAll(RegExp(r'[a-zA-Z]'), '').trim();
531+
final cleanCurrent = current.replaceAll(RegExp(r'[a-zA-Z]'), '').trim();
532+
533+
List<int> latestParts = cleanLatest.split('.').map((e) => int.tryParse(e) ?? 0).toList();
534+
List<int> currentParts = cleanCurrent.split('.').map((e) => int.tryParse(e) ?? 0).toList();
535+
536+
int maxLength = latestParts.length > currentParts.length ? latestParts.length : currentParts.length;
537+
for (int i = 0; i < maxLength; i++) {
538+
int latestVal = i < latestParts.length ? latestParts[i] : 0;
539+
int currentVal = i < currentParts.length ? currentParts[i] : 0;
540+
if (latestVal > currentVal) return true;
541+
if (latestVal < currentVal) return false;
542+
}
543+
return false;
544+
}
545+
546+
void dismissUpdateNotification() {
547+
_hasUpdate = false;
548+
notifyListeners();
549+
}
457550
}

lib/views/dashboard_view.dart

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ class _DashboardViewState extends State<DashboardView> with SingleTickerProvider
115115
'What are we\nselling today?',
116116
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, height: 1.2, letterSpacing: -1),
117117
),
118-
const SizedBox(height: 32),
118+
const SizedBox(height: 16),
119+
_buildUpdateBanner(billingController),
120+
const SizedBox(height: 16),
119121

120122
Expanded(
121123
child: GridView.count(
@@ -480,6 +482,67 @@ class _DashboardViewState extends State<DashboardView> with SingleTickerProvider
480482
);
481483
}
482484

485+
Widget _buildUpdateBanner(BillingController controller) {
486+
if (!controller.hasUpdate) return const SizedBox.shrink();
487+
488+
return Container(
489+
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
490+
decoration: BoxDecoration(
491+
gradient: const LinearGradient(
492+
colors: [Color(0xFF2F80ED), Color(0xFF56CCF2)],
493+
begin: Alignment.topLeft,
494+
end: Alignment.bottomRight,
495+
),
496+
borderRadius: BorderRadius.circular(20),
497+
boxShadow: [
498+
BoxShadow(
499+
color: const Color(0xFF2F80ED).withOpacity(0.3),
500+
blurRadius: 10,
501+
offset: const Offset(0, 4),
502+
),
503+
],
504+
),
505+
child: Row(
506+
children: [
507+
const Icon(Icons.rocket_launch_rounded, color: Colors.white, size: 24),
508+
const SizedBox(width: 12),
509+
Expanded(
510+
child: Column(
511+
crossAxisAlignment: CrossAxisAlignment.start,
512+
children: [
513+
Text(
514+
"Update Available! (${controller.latestVersion})",
515+
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14),
516+
),
517+
const SizedBox(height: 2),
518+
Text(
519+
"New features are ready. Tap to download.",
520+
style: TextStyle(color: Colors.white.withOpacity(0.9), fontSize: 11),
521+
),
522+
],
523+
),
524+
),
525+
ElevatedButton(
526+
onPressed: () => _redirectToPlatform(controller.updateDownloadUrl),
527+
style: ElevatedButton.styleFrom(
528+
backgroundColor: Colors.white,
529+
foregroundColor: const Color(0xFF2F80ED),
530+
elevation: 0,
531+
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
532+
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
533+
),
534+
child: const Text("Get", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12)),
535+
),
536+
const SizedBox(width: 4),
537+
IconButton(
538+
icon: const Icon(Icons.close_rounded, color: Colors.white70, size: 20),
539+
onPressed: () => controller.dismissUpdateNotification(),
540+
),
541+
],
542+
),
543+
);
544+
}
545+
483546
Widget _buildBentoCard({
484547
required String title,
485548
required String value,

0 commit comments

Comments
 (0)