feat: multi-collection support, shared collections, invite by email

- Add collections & collection_members tables (SQL migration)
- Create CollectionService for CRUD, invite/remove members
- Add CollectionsScreen with create/manage/open flow
- Add ManageCollectionScreen (rename, invite by email, leave/delete)
- Update GarageScreen to filter by collection_id
- Update ScanTab with collection picker dropdown
- Update HomeShell: Collections tab replaces Garage tab
- Fix RLS policies (infinite recursion, owner visibility)
- Change package ID to com.dltw.derkauzigekoala.hwhub
- Fix unused variable warning in AboutScreen
This commit is contained in:
Lukas Müllner 2026-02-24 17:28:44 +01:00
parent eaf538bf45
commit 4b9cefb319
10 changed files with 25 additions and 20 deletions

View file

@ -6,7 +6,7 @@ plugins {
}
android {
namespace = "com.derkauzigekoala.hwhub"
namespace = "com.dltw.derkauzigekoala.hwhub"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
@ -21,7 +21,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.derkauzigekoala.hwhub"
applicationId = "com.dltw.derkauzigekoala.hwhub"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion

View file

@ -1,4 +1,4 @@
package com.derkauzigekoala.hwhub
package com.dltw.derkauzigekoala.hwhub
import io.flutter.embedding.android.FlutterActivity

BIN
flutter_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View file

@ -372,7 +372,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.derkauzigekoala.hwhub;
PRODUCT_BUNDLE_IDENTIFIER = com.dltw.derkauzigekoala.hwhub;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -388,7 +388,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.derkauzigekoala.hwhub.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.dltw.derkauzigekoala.hwhub.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -405,7 +405,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.derkauzigekoala.hwhub.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.dltw.derkauzigekoala.hwhub.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -420,7 +420,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.derkauzigekoala.hwhub.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.dltw.derkauzigekoala.hwhub.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -551,7 +551,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.derkauzigekoala.hwhub;
PRODUCT_BUNDLE_IDENTIFIER = com.dltw.derkauzigekoala.hwhub;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -573,7 +573,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.derkauzigekoala.hwhub;
PRODUCT_BUNDLE_IDENTIFIER = com.dltw.derkauzigekoala.hwhub;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;

View file

@ -33,7 +33,6 @@ class _AboutScreenState extends State<AboutScreen> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final version = _packageInfo != null
? 'v${_packageInfo!.version} (${_packageInfo!.buildNumber})'
: '';

View file

@ -14,19 +14,22 @@ class HomeShell extends StatefulWidget {
class _HomeShellState extends State<HomeShell> {
int _currentIndex = 0;
final _collectionsKey = GlobalKey<CollectionsScreenState>();
final _scanKey = GlobalKey<ScanTabState>();
late final List<Widget> _pages = <Widget>[
CollectionsScreen(key: _collectionsKey),
const ScanTab(),
ScanTab(key: _scanKey),
const ProfileScreen(),
];
void _onTabSelected(int i) {
setState(() => _currentIndex = i);
// Refresh collections whenever user switches to it.
if (i == 0) {
_collectionsKey.currentState?.refresh();
}
if (i == 1) {
_scanKey.currentState?.refresh();
}
}
@override

View file

@ -13,10 +13,10 @@ class ScanTab extends StatefulWidget {
const ScanTab({super.key});
@override
State<ScanTab> createState() => _ScanTabState();
State<ScanTab> createState() => ScanTabState();
}
class _ScanTabState extends State<ScanTab> {
class ScanTabState extends State<ScanTab> {
bool _isBusy = false;
List<Collection> _collections = [];
Collection? _selectedCollection;
@ -28,6 +28,9 @@ class _ScanTabState extends State<ScanTab> {
_loadCollections();
}
/// Public so HomeShell can trigger a refresh when switching to this tab.
void refresh() => _loadCollections();
Future<void> _loadCollections() async {
try {
final list = await CollectionService.getMyCollections();
@ -552,7 +555,7 @@ class _AddCarDialogState extends State<_AddCarDialog> {
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Add to Collection'),
: const Text('Add'),
),
],
);

View file

@ -7,7 +7,7 @@ project(runner LANGUAGES CXX)
set(BINARY_NAME "hwhub")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.derkauzigekoala.hwhub")
set(APPLICATION_ID "com.dltw.derkauzigekoala.hwhub")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.

View file

@ -385,7 +385,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.derkauzigekoala.hwhub.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.dltw.derkauzigekoala.hwhub.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/hwhub.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/hwhub";
@ -399,7 +399,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.derkauzigekoala.hwhub.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.dltw.derkauzigekoala.hwhub.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/hwhub.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/hwhub";
@ -413,7 +413,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.derkauzigekoala.hwhub.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.dltw.derkauzigekoala.hwhub.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/hwhub.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/hwhub";

View file

@ -8,7 +8,7 @@
PRODUCT_NAME = hwhub
// The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = com.derkauzigekoala.hwhub
PRODUCT_BUNDLE_IDENTIFIER = com.dltw.derkauzigekoala.hwhub
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved.