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
74 changes: 28 additions & 46 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ on:

jobs:
# ========================================
# Job 1: Create GitHub Release
# Build and Release Android
# ========================================
create-release:
name: Create Release
release-android:
name: Build and Release Android
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}

steps:
- name: 📚 Checkout repository
Expand All @@ -23,29 +21,6 @@ jobs:
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT

- name: 🎉 Create GitHub Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ steps.get_version.outputs.VERSION }}
draft: false
prerelease: false

# ========================================
# Job 2: Build and Release Android
# ========================================
release-android:
name: Release Android
runs-on: ubuntu-latest
needs: create-release

steps:
- name: 📚 Checkout repository
uses: actions/checkout@v6

- name: ☕ Setup Java
uses: actions/setup-java@v4
with:
Expand Down Expand Up @@ -118,29 +93,34 @@ jobs:
# track: production
# status: completed

- name: 📤 Upload APK to Release
uses: actions/upload-release-asset@v1
- name: 🎉 Create Release and Upload APK
uses: softprops/action-gh-release@v2
with:
name: Release ${{ steps.get_version.outputs.VERSION }}
draft: false
prerelease: false
files: |
apps/mobile/build/app/outputs/flutter-apk/app-release.apk
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: apps/mobile/build/app/outputs/flutter-apk/app-release.apk
asset_name: collection-tracker-android.apk
asset_content_type: application/vnd.android.package-archive

# ========================================
# Job 3: Build and Release iOS
# ========================================
# release-ios:
# name: Release iOS
# name: Build and Release iOS
# runs-on: macos-latest
# needs: create-release

# steps:
# - name: 📚 Checkout repository
# uses: actions/checkout@v6

# - name: 🐦 Setup Flutter
# - name: � Extract version from tag
# id: get_version
# run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT

# - name: �🐦 Setup Flutter
# uses: subosito/flutter-action@v2
# with:
# flutter-version: '3.38.x'
Expand Down Expand Up @@ -187,12 +167,14 @@ jobs:
# cd apps/mobile
# flutter build ipa --release --export-options-plist=ios/ExportOptions.plist

# - name: 📤 Upload to App Store
# - name: 🎉 Create Release and Upload IPA
# uses: softprops/action-gh-release@v2
# with:
# name: Release ${{ steps.get_version.outputs.VERSION }}
# draft: false
# prerelease: false
# files: |
# apps/mobile/build/ios/ipa/*.ipa
# fail_on_unmatched_files: true
# env:
# APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
# run: |
# xcrun altool --upload-app \
# --type ios \
# --file apps/mobile/build/ios/ipa/*.ipa \
# --apiKey ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} \
# --apiIssuer ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,9 @@ If you have any questions or issues:

## 🗺️ Roadmap

- [ ] Barcode scanning with camera
- [ ] Image upload and gallery
- [ ] Advanced search and filters
- [x] Barcode scanning with camera
- [x] Image upload and gallery
- [x] Advanced search and filters
- [ ] Cloud synchronization
- [ ] Import/Export data (CSV, JSON)
- [ ] Price tracking and statistics
Expand Down
18 changes: 18 additions & 0 deletions apps/mobile/lib/core/router/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import '../../features/items/presentation/views/add_item_screen.dart';
import '../../features/items/presentation/views/edit_item_screen.dart';
import '../../features/items/presentation/views/item_detail_screen.dart';
import '../../features/items/presentation/views/items_screen.dart';
import '../../features/items/presentation/views/tag_items_screen.dart';
import '../../features/onboarding/presentation/views/onboarding_screen.dart';
import '../../features/scanner/presentation/views/scanner_screen.dart';
import '../../features/search/presentation/views/search_screen.dart';
import '../../features/settings/presentation/views/settings_screen.dart';
import '../../features/statistics/presentation/views/statistics_screen.dart';
import '../../features/items/presentation/views/tag_management_screen.dart';
import 'app_shell.dart';
import 'package:collection_tracker/core/observers/analytics_observer.dart';
import 'routes.dart';
Expand Down Expand Up @@ -125,6 +127,14 @@ GoRouter appRouter(Ref ref) {
path: Routes.settings,
name: 'settings',
builder: (_, _) => const SettingsScreen(),
routes: [
GoRoute(
path: 'tags',
name: 'manage-tags',
parentNavigatorKey: _rootNavigatorKey,
builder: (_, _) => const TagManagementScreen(),
),
],
),
],
),
Expand All @@ -150,6 +160,14 @@ GoRouter appRouter(Ref ref) {
),
],
),
GoRoute(
path: Routes.tagItems,
name: 'tag-items',
builder: (context, state) {
final tag = state.uri.queryParameters['tag'] ?? '';
return TagItemsScreen(tagName: tag);
},
),
GoRoute(
path: Routes.scanner,
name: 'scanner',
Expand Down
8 changes: 4 additions & 4 deletions apps/mobile/lib/core/router/app_shell.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ class ScaffoldWithNavigationBar extends StatelessWidget {
selectedIndex: currentIndex,
destinations: const [
NavigationDestination(
icon: Icon(Icons.collections_outlined),
selectedIcon: Icon(Icons.collections),
icon: Icon(Icons.inventory_2_outlined),
selectedIcon: Icon(Icons.inventory_2),
label: 'Home',
),
NavigationDestination(
Expand Down Expand Up @@ -148,8 +148,8 @@ class ScaffoldWithNavigationRail extends StatelessWidget {
),
destinations: const <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.collections_outlined),
selectedIcon: Icon(Icons.collections),
icon: Icon(Icons.inventory_2_outlined),
selectedIcon: Icon(Icons.inventory_2),
label: Text('Home'),
),
NavigationRailDestination(
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/lib/core/router/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ abstract final class Routes {
static const scanner = '/scanner';
static const statistics = '/statistics';
static const settings = '/settings';
static const settingsTags = '/settings/tags';
static const tagItems = '/tags/items';

// static String bookingWithId(int id) => '$booking/$id';
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class ItemFilterState {
final ItemSortBy sortBy;
final bool sortAscending;
final Set<ItemCondition> conditions;
final Set<String> tags;
final bool showOnlyFavorites;
final bool showOnlyWishlist;

Expand All @@ -29,6 +30,7 @@ class ItemFilterState {
this.sortBy = ItemSortBy.custom,
this.sortAscending = true,
this.conditions = const {},
this.tags = const {},
this.showOnlyFavorites = false,
this.showOnlyWishlist = false,
});
Expand All @@ -38,6 +40,7 @@ class ItemFilterState {
ItemSortBy? sortBy,
bool? sortAscending,
Set<ItemCondition>? conditions,
Set<String>? tags,
bool? showOnlyFavorites,
bool? showOnlyWishlist,
}) {
Expand All @@ -46,6 +49,7 @@ class ItemFilterState {
sortBy: sortBy ?? this.sortBy,
sortAscending: sortAscending ?? this.sortAscending,
conditions: conditions ?? this.conditions,
tags: tags ?? this.tags,
showOnlyFavorites: showOnlyFavorites ?? this.showOnlyFavorites,
showOnlyWishlist: showOnlyWishlist ?? this.showOnlyWishlist,
);
Expand Down Expand Up @@ -81,6 +85,16 @@ class ItemFilter extends _$ItemFilter {
state = state.copyWith(conditions: nextConditions);
}

void toggleTag(String tag) {
final nextTags = Set<String>.from(state.tags);
if (nextTags.contains(tag)) {
nextTags.remove(tag);
} else {
nextTags.add(tag);
}
state = state.copyWith(tags: nextTags);
}

void toggleFavorites() {
state = state.copyWith(showOnlyFavorites: !state.showOnlyFavorites);
}
Expand All @@ -107,7 +121,8 @@ Stream<List<Item>> filteredItemsList(Ref ref, String collectionId) async* {
final query = filter.searchQuery.toLowerCase();
filtered = filtered.where((item) {
return item.title.toLowerCase().contains(query) ||
(item.description?.toLowerCase().contains(query) ?? false);
(item.description?.toLowerCase().contains(query) ?? false) ||
item.tags.any((tag) => tag.toLowerCase().contains(query));
}).toList();
}

Expand All @@ -129,6 +144,16 @@ Stream<List<Item>> filteredItemsList(Ref ref, String collectionId) async* {
}).toList();
}

// Tags (match any selected tag)
if (filter.tags.isNotEmpty) {
filtered = filtered.where((item) {
final itemTags = item.tags.map((tag) => tag.toLowerCase()).toSet();
return filter.tags.any(
(selected) => itemTags.contains(selected.toLowerCase()),
);
}).toList();
}

// Sorting
filtered.sort((a, b) {
final comparison = switch (filter.sortBy) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Future<void> createItem(
String? description,
String? coverImageUrl,
String? coverImagePath,
List<String> tags = const [],
}) async {
final repository = ref.read(itemRepositoryProvider);

Expand All @@ -32,6 +33,7 @@ Future<void> createItem(
description: description,
coverImageUrl: coverImageUrl,
coverImagePath: coverImagePath,
tags: tags,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:collection_tracker/core/providers/providers.dart';
import 'package:domain/domain.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'tag_items_view_model.g.dart';

@riverpod
Stream<List<Item>> tagItems(Ref ref, String tagName) {
final repository = ref.watch(itemRepositoryProvider);
return repository.watchItemsByTag(tagName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:collection_tracker/core/providers/providers.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final tagsWithUsageProvider = StreamProvider<List<(String, int)>>((ref) {
final repository = ref.watch(itemRepositoryProvider);
return repository.watchTagsWithUsage();
});

final renameTagProvider =
FutureProvider.family<void, ({String oldName, String newName})>((
ref,
args,
) async {
final repository = ref.read(itemRepositoryProvider);
final result = await repository.renameTag(
oldName: args.oldName,
newName: args.newName,
);
result.fold((exception) => throw exception, (_) => null);
});

final mergeTagsProvider =
FutureProvider.family<void, ({String sourceName, String targetName})>((
ref,
args,
) async {
final repository = ref.read(itemRepositoryProvider);
final result = await repository.mergeTags(
sourceName: args.sourceName,
targetName: args.targetName,
);
result.fold((exception) => throw exception, (_) => null);
});

final deleteTagProvider = FutureProvider.family<void, String>((
ref,
tagName,
) async {
final repository = ref.read(itemRepositoryProvider);
final result = await repository.deleteTag(tagName);
result.fold((exception) => throw exception, (_) => null);
});
Loading