Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: 🐦 Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.38.x'
flutter-version: '3.41.x'
channel: 'stable'
cache: true

Expand Down Expand Up @@ -76,7 +76,7 @@ jobs:
- name: 🐦 Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.38.x'
flutter-version: '3.41.x'
channel: 'stable'
cache: true

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: 🐦 Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.38.x'
flutter-version: '3.41.x'
channel: 'stable'

- name: 📦 Get dependencies
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- name: 🐦 Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.38.x'
flutter-version: '3.41.x'
channel: 'stable'
cache: true

Expand Down Expand Up @@ -123,7 +123,7 @@ jobs:
# - name: �🐦 Setup Flutter
# uses: subosito/flutter-action@v2
# with:
# flutter-version: '3.38.x'
# flutter-version: '3.41.x'
# channel: 'stable'
# cache: true

Expand Down
1 change: 0 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ linter:
rules:
# Error rules
avoid_print: true
avoid_returning_null_for_future: true
cancel_subscriptions: true
close_sinks: true
valid_regexps: true
Expand Down
2 changes: 0 additions & 2 deletions apps/mobile/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,5 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>
7 changes: 5 additions & 2 deletions apps/mobile/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import Flutter
import UIKit

@main
@objc class AppDelegate: FlutterAppDelegate {
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}
48 changes: 33 additions & 15 deletions apps/mobile/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
Expand All @@ -24,6 +26,37 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to scan barcodes for adding items to your collection</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs photo library access to add images to your items</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand All @@ -41,20 +74,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>

<!-- Camera permission for barcode scanning and taking photos -->
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to scan barcodes for adding items to your collection</string>
<!-- Photo library permission for image upload -->
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs photo library access to add images to your items</string>
<key>UIFileSharingEnabled</key>
<true />
<key>LSSupportsOpeningDocumentsInPlace</key>
<true />
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'package:collection_tracker/core/providers/providers.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final itemPriceHistoryProvider =
StreamProvider.family<List<(DateTime, double)>, String>((ref, itemId) {
final repository = ref.watch(itemRepositoryProvider);
return repository.watchPriceHistory(itemId);
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Future<void> createItem(
String? coverImageUrl,
String? coverImagePath,
List<String> tags = const [],
double? purchasePrice,
double? currentValue,
DateTime? purchaseDate,
}) async {
final repository = ref.read(itemRepositoryProvider);

Expand All @@ -34,6 +37,9 @@ Future<void> createItem(
coverImageUrl: coverImageUrl,
coverImagePath: coverImagePath,
tags: tags,
purchasePrice: purchasePrice,
currentValue: currentValue,
purchaseDate: purchaseDate,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
Expand Down
102 changes: 102 additions & 0 deletions apps/mobile/lib/features/items/presentation/views/add_item_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class _AddItemScreenState extends ConsumerState<AddItemScreen> {
final _titleController = TextEditingController();
final _barcodeController = TextEditingController();
final _descriptionController = TextEditingController();
final _purchasePriceController = TextEditingController();
final _currentValueController = TextEditingController();
final _purchaseDateController = TextEditingController();

final _imageStorageService = ImageStorageService();

Expand All @@ -35,12 +38,16 @@ class _AddItemScreenState extends ConsumerState<AddItemScreen> {
String? _imagePath;
String? _coverImageUrl;
List<String> _tags = const [];
DateTime? _selectedPurchaseDate;

@override
void dispose() {
_titleController.dispose();
_barcodeController.dispose();
_descriptionController.dispose();
_purchasePriceController.dispose();
_currentValueController.dispose();
_purchaseDateController.dispose();
super.dispose();
}

Expand Down Expand Up @@ -184,6 +191,63 @@ class _AddItemScreenState extends ConsumerState<AddItemScreen> {
label: 'Tags (optional)',
hintText: 'e.g., Rare, Completed Set',
),
const SizedBox(height: 16),

Row(
children: [
Expanded(
child: TextFormField(
controller: _purchasePriceController,
decoration: const InputDecoration(
labelText: 'Purchase Price',
prefixText: '\$',
prefixIcon: Icon(Icons.attach_money),
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
validator: _validatePriceInput,
),
),
const SizedBox(width: 12),
Expanded(
child: TextFormField(
controller: _currentValueController,
decoration: const InputDecoration(
labelText: 'Current Value',
prefixText: '\$',
prefixIcon: Icon(Icons.show_chart),
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
validator: _validatePriceInput,
),
),
],
),
const SizedBox(height: 16),

TextFormField(
controller: _purchaseDateController,
readOnly: true,
decoration: InputDecoration(
labelText: 'Purchase Date (optional)',
prefixIcon: const Icon(Icons.calendar_today),
suffixIcon: _selectedPurchaseDate == null
? null
: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setState(() {
_selectedPurchaseDate = null;
_purchaseDateController.clear();
});
},
),
),
onTap: _pickPurchaseDate,
),
const SizedBox(height: 24),

// Add button
Expand Down Expand Up @@ -308,6 +372,9 @@ class _AddItemScreenState extends ConsumerState<AddItemScreen> {
coverImageUrl: _coverImageUrl,
coverImagePath: _imagePath,
tags: _tags,
purchasePrice: _parsePriceInput(_purchasePriceController.text),
currentValue: _parsePriceInput(_currentValueController.text),
purchaseDate: _selectedPurchaseDate,
).future,
);

Expand All @@ -334,4 +401,39 @@ class _AddItemScreenState extends ConsumerState<AddItemScreen> {
}
}
}

String? _validatePriceInput(String? value) {
if (value == null || value.trim().isEmpty) return null;
final parsed = _parsePriceInput(value);
if (parsed == null) return 'Invalid price';
if (parsed < 0) return 'Must be positive';
return null;
}

double? _parsePriceInput(String raw) {
final normalized = raw.trim().replaceAll(',', '');
if (normalized.isEmpty) return null;
return double.tryParse(normalized);
}

Future<void> _pickPurchaseDate() async {
final picked = await showDatePicker(
context: context,
initialDate: _selectedPurchaseDate ?? DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime.now().add(const Duration(days: 3650)),
);

if (picked == null || !mounted) return;
setState(() {
_selectedPurchaseDate = picked;
_purchaseDateController.text = _formatDate(picked);
});
}

String _formatDate(DateTime date) {
final month = date.month.toString().padLeft(2, '0');
final day = date.day.toString().padLeft(2, '0');
return '${date.year}-$month-$day';
}
}
Loading