1.0.2
This commit is contained in:
@@ -0,0 +1,200 @@
|
|||||||
|
# Release Guide - Shooting App
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This guide explains how to create and publish a new release of the Shooting App. The app uses GitHub releases and semantic versioning for update management.
|
||||||
|
|
||||||
|
## Current Setup
|
||||||
|
- **Repository**: https://github.com/Bl3kiiie/shooting_app
|
||||||
|
- **Update Method**: Manual check via Settings button (no automatic prompts)
|
||||||
|
- **Version File**: `pubspec.yaml`
|
||||||
|
- **Update Detection**: Compares app version with GitHub release tags
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step-by-Step Release Process
|
||||||
|
|
||||||
|
### 1. Update Version in pubspec.yaml
|
||||||
|
Open `pubspec.yaml` and update the version number:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 1.0.2+2
|
||||||
|
```
|
||||||
|
|
||||||
|
Format: `MAJOR.MINOR.PATCH+BUILD_NUMBER`
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- `1.0.1+1` → `1.0.2+1` (patch release, new build)
|
||||||
|
- `1.0.2+1` → `1.0.2+2` (same version, new build)
|
||||||
|
- `1.0.2+2` → `1.1.0+1` (minor release)
|
||||||
|
|
||||||
|
**Important**: New tag must be HIGHER than previous version for users to see update prompt.
|
||||||
|
|
||||||
|
### 2. Build Release APK
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Navigate to project directory
|
||||||
|
cd /home/bl3kunja/Documents/Programming/Strelišče/shooting_app
|
||||||
|
|
||||||
|
# Build release APK
|
||||||
|
flutter build apk --release
|
||||||
|
```
|
||||||
|
|
||||||
|
The APK will be generated at: `build/app/outputs/flutter-app/release/app-release.apk`
|
||||||
|
|
||||||
|
### 3. Create GitHub Release (Option A: Web Interface - Recommended)
|
||||||
|
|
||||||
|
1. Go to: https://github.com/Bl3kiiie/shooting_app/releases
|
||||||
|
2. Click **Draft a new release** or **Create a new release**
|
||||||
|
3. Fill in the form:
|
||||||
|
|
||||||
|
**Tag version**: `v1.0.2` (must match your new version number)
|
||||||
|
|
||||||
|
**Release title**: e.g., `Version 1.0.2 - Bug Fixes and Improvements`
|
||||||
|
|
||||||
|
**Description** (example):
|
||||||
|
```
|
||||||
|
## Changes in v1.0.2
|
||||||
|
- Fixed camera calibration issue
|
||||||
|
- Improved bullet detection accuracy
|
||||||
|
- Added German language support
|
||||||
|
- Performance optimizations
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Download the APK from the assets below and install on your device.
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Upload APK**:
|
||||||
|
- Click "Attach binaries by dropping them here or selecting them"
|
||||||
|
- Select: `build/app/outputs/flutter-app/release/app-release.apk`
|
||||||
|
|
||||||
|
5. Click **Publish release**
|
||||||
|
|
||||||
|
### 3. Create GitHub Release (Option B: Command Line)
|
||||||
|
|
||||||
|
If using GitHub CLI (`gh` command):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create tag locally
|
||||||
|
git tag v1.0.2
|
||||||
|
|
||||||
|
# Push tag to GitHub
|
||||||
|
git push origin v1.0.2
|
||||||
|
|
||||||
|
# Create release (interactive)
|
||||||
|
gh release create v1.0.2 \
|
||||||
|
--title "Version 1.0.2" \
|
||||||
|
--notes "Your changelog here"
|
||||||
|
|
||||||
|
# Upload APK to release
|
||||||
|
gh release upload v1.0.2 build/app/outputs/flutter-app/release/app-release.apk
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Commit Version Update (Optional)
|
||||||
|
If you want to track version changes in git:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add pubspec.yaml
|
||||||
|
git commit -m "bump version to 1.0.2+2"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How Users Update
|
||||||
|
|
||||||
|
1. User opens app → Settings screen
|
||||||
|
2. User taps **"Check for Updates"** button
|
||||||
|
3. App compares:
|
||||||
|
- Current version: `1.0.1+1`
|
||||||
|
- Latest GitHub release tag: `v1.0.2`
|
||||||
|
4. Since `v1.0.2 > v1.0.1`, update is available
|
||||||
|
5. Dialog shows **"Update Available"**
|
||||||
|
6. User clicks **"Update"** → Opens GitHub release page
|
||||||
|
7. User downloads and installs new APK
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
### Version Numbering
|
||||||
|
- Always increment version number before creating release
|
||||||
|
- Tag format: `vX.Y.Z` (e.g., `v1.0.2`, `v1.1.0`)
|
||||||
|
- App checks: `tag_version > app_version`
|
||||||
|
|
||||||
|
### GitHub Token
|
||||||
|
- Stored in: `lib/services/update_checker.dart`
|
||||||
|
- Token: `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`
|
||||||
|
- Scopes: `repo` (for private repo access)
|
||||||
|
- Consider rotating this token periodically for security
|
||||||
|
|
||||||
|
### Testing Before Release
|
||||||
|
```bash
|
||||||
|
# Test the build
|
||||||
|
flutter build apk --release
|
||||||
|
|
||||||
|
# Verify APK was created
|
||||||
|
ls -lh build/app/outputs/flutter-app/release/app-release.apk
|
||||||
|
|
||||||
|
# Optionally install on connected device
|
||||||
|
flutter install build/app/outputs/flutter-app/release/app-release.apk
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting Update Detection
|
||||||
|
|
||||||
|
If users don't see update prompt:
|
||||||
|
|
||||||
|
1. Check GitHub release exists: https://github.com/Bl3kiiie/shooting_app/releases
|
||||||
|
2. Verify tag format is `vX.Y.Z` (must start with 'v')
|
||||||
|
3. Confirm new version > current version
|
||||||
|
4. User must tap "Check for Updates" manually
|
||||||
|
5. Check app logs for errors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checklist for Next Release
|
||||||
|
|
||||||
|
- [ ] Update version in `pubspec.yaml`
|
||||||
|
- [ ] Test build: `flutter build apk --release`
|
||||||
|
- [ ] Create GitHub release with tag `vX.Y.Z`
|
||||||
|
- [ ] Upload APK to release
|
||||||
|
- [ ] Write changelog/description
|
||||||
|
- [ ] Publish release
|
||||||
|
- [ ] (Optional) Test update detection in app
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example Release Workflow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Update version in pubspec.yaml (manually edit to 1.0.2+1)
|
||||||
|
|
||||||
|
# 2. Build APK
|
||||||
|
flutter build apk --release
|
||||||
|
|
||||||
|
# 3. Create tag and push
|
||||||
|
git tag v1.0.2
|
||||||
|
git push origin v1.0.2
|
||||||
|
|
||||||
|
# 4. Create release via GitHub web:
|
||||||
|
# - Go to https://github.com/Bl3kiiie/shooting_app/releases
|
||||||
|
# - Draft new release
|
||||||
|
# - Tag: v1.0.2
|
||||||
|
# - Upload APK from build/app/outputs/flutter-app/release/app-release.apk
|
||||||
|
# - Publish
|
||||||
|
|
||||||
|
# 5. Users can now "Check for Updates" and see the new version
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Referenced
|
||||||
|
- `pubspec.yaml` - Version number
|
||||||
|
- `lib/services/update_checker.dart` - GitHub token and update logic
|
||||||
|
- `lib/screens/settings_screen.dart` - "Check for Updates" button
|
||||||
|
- `lib/utils/translations.dart` - Update dialog translations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: November 13, 2025
|
||||||
|
**Current Version**: 1.0.1
|
||||||
|
**Next Planned Version**: 1.0.2
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# This file configures the analyzer, which statically analyzes Dart code to
|
|
||||||
# check for errors, warnings, and lints.
|
|
||||||
#
|
|
||||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
|
||||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
|
||||||
# invoked from the command line by running `flutter analyze`.
|
|
||||||
|
|
||||||
# The following line activates a set of recommended lints for Flutter apps,
|
|
||||||
# packages, and plugins designed to encourage good coding practices.
|
|
||||||
include: package:flutter_lints/flutter.yaml
|
|
||||||
|
|
||||||
linter:
|
|
||||||
# The lint rules applied to this project can be customized in the
|
|
||||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
|
||||||
# included above or to enable additional rules. A list of all available lints
|
|
||||||
# and their documentation is published at https://dart.dev/lints.
|
|
||||||
#
|
|
||||||
# Instead of disabling a lint rule for the entire project in the
|
|
||||||
# section below, it can also be suppressed for a single line of code
|
|
||||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
|
||||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
|
||||||
# producing the lint.
|
|
||||||
rules:
|
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
|
||||||
# https://dart.dev/guides/language/analysis-options
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
description: This file stores settings for Dart & Flutter DevTools.
|
|
||||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
|
||||||
extensions:
|
|
||||||
+11
-1
@@ -36,7 +36,17 @@ class MyApp extends StatelessWidget {
|
|||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.white),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.white),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
home: const HomeScreen(), // Change LoginScreen to HomeScreen
|
darkTheme: ThemeData(
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
seedColor: Colors.blue,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
useMaterial3: true,
|
||||||
|
dialogTheme: DialogThemeData(
|
||||||
|
backgroundColor: Colors.grey[900],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
home: const HomeScreen(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
|||||||
String _bulletColor = 'Blue';
|
String _bulletColor = 'Blue';
|
||||||
String _selectedLanguage = 'English';
|
String _selectedLanguage = 'English';
|
||||||
bool _showContacts = false;
|
bool _showContacts = false;
|
||||||
bool _showDebugPreview = true;
|
bool _showDebugPreview = false;
|
||||||
|
|
||||||
// Members contact list
|
// Members contact list
|
||||||
final List<Contact> _contacts = [
|
final List<Contact> _contacts = [
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:upgrader/upgrader.dart';
|
||||||
import '../utils/translations.dart';
|
import '../utils/translations.dart';
|
||||||
|
|
||||||
class SettingsScreen extends StatefulWidget {
|
class SettingsScreen extends StatefulWidget {
|
||||||
@@ -170,6 +171,224 @@ Future<void> _saveLanguage(String language) async {
|
|||||||
_loadCameraIPs();
|
_loadCameraIPs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _checkForUpdatesManual() async {
|
||||||
|
// Check for updates without the 24-hour throttling
|
||||||
|
final upgrader = Upgrader();
|
||||||
|
try {
|
||||||
|
final isUpdateAvailable = await upgrader.isUpdateAvailable();
|
||||||
|
if (isUpdateAvailable && mounted) {
|
||||||
|
_showCustomUpdateDialog(true);
|
||||||
|
} else if (mounted) {
|
||||||
|
// Show "No updates available" dialog
|
||||||
|
_showNoUpdatesDialog();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(_t('Update check failed'))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showCustomUpdateDialog(bool updateAvailable) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.grey[900],
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.update,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text(
|
||||||
|
_t('Update Available'),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
_t('A new version of the app is available'),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.grey[300],
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => Navigator.of(context).pop(),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[800],
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
_t('Later'),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
// Trigger the update using the upgrader package
|
||||||
|
_triggerUpdate();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
_t('Update'),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showNoUpdatesDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.grey[900],
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.green,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.check_circle,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text(
|
||||||
|
_t('No updates available'),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
_t('You are using the latest version'),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.grey[300],
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => Navigator.of(context).pop(),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
_t('OK'),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _triggerUpdate() {
|
||||||
|
// Use Upgrader to trigger the app store/play store
|
||||||
|
try {
|
||||||
|
Upgrader().sendUserToAppStore();
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(_t('Failed to open app store'))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _editCameraIP(int index) {
|
void _editCameraIP(int index) {
|
||||||
TextEditingController controller = TextEditingController(
|
TextEditingController controller = TextEditingController(
|
||||||
text: _cameraIPs[index],
|
text: _cameraIPs[index],
|
||||||
@@ -314,6 +533,8 @@ Future<void> _saveLanguage(String language) async {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildLanguageSelector(),
|
_buildLanguageSelector(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
_buildCheckUpdateButton(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
_buildBulletColorSelector(),
|
_buildBulletColorSelector(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
@@ -372,6 +593,46 @@ Future<void> _saveLanguage(String language) async {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildCheckUpdateButton() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_t("Updates"),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _checkForUpdatesManual,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[300],
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.system_update, color: Colors.black54),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
_t('Check for Updates'),
|
||||||
|
style: const TextStyle(color: Colors.black87, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(Icons.chevron_right, color: Colors.black54),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildBulletColorSelector() {
|
Widget _buildBulletColorSelector() {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:upgrader/upgrader.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import '../utils/translations.dart';
|
||||||
|
import '../widgets/custom_upgrade_alert.dart';
|
||||||
|
|
||||||
|
// GitHub Configuration
|
||||||
|
const String _githubRepoOwner = 'Bl3kiiie';
|
||||||
|
const String _githubRepoName = 'shooting_app';
|
||||||
|
const String _githubToken =
|
||||||
|
'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'; // Replace with your GitHub Personal Access Token
|
||||||
|
|
||||||
|
class UpdateChecker {
|
||||||
|
static final UpdateChecker _instance = UpdateChecker._internal();
|
||||||
|
|
||||||
|
factory UpdateChecker() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateChecker._internal();
|
||||||
|
|
||||||
|
Future<void> checkForUpdate(BuildContext context, String language) async {
|
||||||
|
// Check if we've already checked today
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final lastCheckTime = prefs.getInt('last_update_check_time') ?? 0;
|
||||||
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
final oneDayInMilliseconds = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
// Only check once per day
|
||||||
|
if (now - lastCheckTime < oneDayInMilliseconds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the last check time
|
||||||
|
await prefs.setInt('last_update_check_time', now);
|
||||||
|
|
||||||
|
final upgrader = Upgrader();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if update is available
|
||||||
|
final isUpdateAvailable = await upgrader.isUpdateAvailable();
|
||||||
|
|
||||||
|
if (isUpdateAvailable && context.mounted) {
|
||||||
|
_showCustomUpdateDialog(context, language);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Silently fail if update check fails
|
||||||
|
print('Update check failed: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showCustomUpdateDialog(BuildContext context, String language) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return CustomUpdateDialog(
|
||||||
|
title: AppTranslations.translate('Update Available', language),
|
||||||
|
message: AppTranslations.translate(
|
||||||
|
'A new version of the app is available', language),
|
||||||
|
updateButtonText: AppTranslations.translate('Update', language),
|
||||||
|
laterButtonText: AppTranslations.translate('Later', language),
|
||||||
|
onUpdate: () {
|
||||||
|
Upgrader().sendUserToAppStore();
|
||||||
|
},
|
||||||
|
onLater: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -584,6 +584,46 @@ class AppTranslations {
|
|||||||
'English': 'copied to clipboard',
|
'English': 'copied to clipboard',
|
||||||
'Slovenian': 'kopirano v odložišče',
|
'Slovenian': 'kopirano v odložišče',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// App Update translations
|
||||||
|
'Update Available': {
|
||||||
|
'English': 'Update Available',
|
||||||
|
'Slovenian': 'Posodobitev je na voljo',
|
||||||
|
},
|
||||||
|
'A new version of the app is available': {
|
||||||
|
'English': 'A new version of the app is available',
|
||||||
|
'Slovenian': 'Nova različica aplikacije je na voljo',
|
||||||
|
},
|
||||||
|
'Update': {
|
||||||
|
'English': 'Update',
|
||||||
|
'Slovenian': 'Posodobi',
|
||||||
|
},
|
||||||
|
'Later': {
|
||||||
|
'English': 'Later',
|
||||||
|
'Slovenian': 'Kasneje',
|
||||||
|
},
|
||||||
|
|
||||||
|
// App Update Check Button
|
||||||
|
'Check for Updates': {
|
||||||
|
'English': 'Check for Updates',
|
||||||
|
'Slovenian': 'Preveri za posodobitve',
|
||||||
|
},
|
||||||
|
'No updates available': {
|
||||||
|
'English': 'No updates available',
|
||||||
|
'Slovenian': 'Ni dostopnih posodobitev',
|
||||||
|
},
|
||||||
|
'You are using the latest version': {
|
||||||
|
'English': 'You are using the latest version',
|
||||||
|
'Slovenian': 'Uporabljate najnovejšo različico',
|
||||||
|
},
|
||||||
|
'Updates': {
|
||||||
|
'English': 'Updates',
|
||||||
|
'Slovenian': 'Posodobitve',
|
||||||
|
},
|
||||||
|
'OK': {
|
||||||
|
'English': 'OK',
|
||||||
|
'Slovenian': 'V redu',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static String translate(String key, String language) {
|
static String translate(String key, String language) {
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:upgrader/upgrader.dart';
|
||||||
|
import '../utils/translations.dart';
|
||||||
|
|
||||||
|
class CustomUpgradeAlert extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
final String language;
|
||||||
|
|
||||||
|
const CustomUpgradeAlert({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
required this.language,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CustomUpgradeAlert> createState() => _CustomUpgradeAlertState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomUpgradeAlertState extends State<CustomUpgradeAlert> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return UpgradeAlert(
|
||||||
|
child: widget.child,
|
||||||
|
upgrader: Upgrader(
|
||||||
|
debugDisplayAlways: true,
|
||||||
|
),
|
||||||
|
dialogStyle: UpgradeDialogStyle.material,
|
||||||
|
shouldPopScope: () => true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomUpdateDialog extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String message;
|
||||||
|
final String updateButtonText;
|
||||||
|
final String laterButtonText;
|
||||||
|
final VoidCallback onUpdate;
|
||||||
|
final VoidCallback onLater;
|
||||||
|
|
||||||
|
const CustomUpdateDialog({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.message,
|
||||||
|
required this.updateButtonText,
|
||||||
|
required this.laterButtonText,
|
||||||
|
required this.onUpdate,
|
||||||
|
required this.onLater,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.grey[900],
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// App Icon
|
||||||
|
Container(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.update,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
// Title
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Message
|
||||||
|
Text(
|
||||||
|
message,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.grey[300],
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
// Buttons
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
// Later Button
|
||||||
|
Expanded(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: onLater,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[800],
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
laterButtonText,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
// Update Button
|
||||||
|
Expanded(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: onUpdate,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
updateButtonText,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -137,6 +137,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.6"
|
version: "3.0.6"
|
||||||
|
csslib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: csslib
|
||||||
|
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -344,6 +352,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.0"
|
version: "1.6.0"
|
||||||
|
html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: html
|
||||||
|
sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.15.6"
|
||||||
http:
|
http:
|
||||||
dependency: "direct overridden"
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
@@ -512,6 +528,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
os_detect:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: os_detect
|
||||||
|
sha256: "7d87c0dd98c6faf110d5aa498e9a6df02ffce4bb78cc9cfc8ad02929be9bb71f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -797,6 +821,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
upgrader:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: upgrader
|
||||||
|
sha256: "4ba3f840691ffa43c6d71a93a6ad3ae01590c7895c3eb33f9f683513f736a9f7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "12.3.0"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -893,6 +925,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
version:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: version
|
||||||
|
sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
visibility_detector:
|
visibility_detector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user