Compare commits

..

No commits in common. "6120f39e76a8ece1b1ae2609ed1a1c75d1883d95" and "cdf32454e6c1074c3bdc2edd6c3cebfcad5487b9" have entirely different histories.

29 changed files with 997 additions and 2280 deletions

View file

@ -1,18 +0,0 @@
name: Tag Action
on:
push:
tags:
- v*
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: apt-get update && apt-get install jq -y
- uses: subosito/flutter-action@v2
with:
flutter-version: 3.19.1
- run: flutter --version
- run: flutter build apk

View file

@ -1,11 +1,11 @@
# This file tracks properties of this Flutter project. # This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc. # Used by Flutter tool to assess capabilities and perform upgrades etc.
# #
# This file should be version controlled and should not be manually edited. # This file should be version controlled.
version: version:
revision: "1751123cde4ffad08ae27bdee4f8ddebd033fe76" revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
channel: "beta" channel: unknown
project_type: app project_type: app
@ -13,11 +13,26 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: 1751123cde4ffad08ae27bdee4f8ddebd033fe76 create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
base_revision: 1751123cde4ffad08ae27bdee4f8ddebd033fe76 base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
- platform: android - platform: android
create_revision: 1751123cde4ffad08ae27bdee4f8ddebd033fe76 create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
base_revision: 1751123cde4ffad08ae27bdee4f8ddebd033fe76 base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
- platform: ios
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
- platform: linux
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
- platform: macos
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
- platform: web
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
- platform: windows
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
# User provided section # User provided section

View file

@ -16,19 +16,3 @@ This app uses /l10n/ according to the official flutter
We use [weblate](https://translate.codeberg.org/engage/outbag/) to manage translations. We use [weblate](https://translate.codeberg.org/engage/outbag/) to manage translations.
[![Translation status](https://translate.codeberg.org/widget/outbag/outbag-app/multi-auto.svg)](https://translate.codeberg.org/engage/outbag/) [![Translation status](https://translate.codeberg.org/widget/outbag/outbag-app/multi-auto.svg)](https://translate.codeberg.org/engage/outbag/)
## Contributing
To keep the code formatted properly,
we use git hooks to format files before committing.
You can easily add git-hooks using the following command:
``` sh
git config core.hooksPath ./hooks
```
Afterwards the `pre-commit` hook will automatically be run,
when you commit your changes:
``` sh
git add # some files
git commit
```

View file

@ -1,9 +1,3 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties() def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties') def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) { if (localPropertiesFile.exists()) {
@ -12,6 +6,11 @@ if (localPropertiesFile.exists()) {
} }
} }
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) { if (flutterVersionCode == null) {
flutterVersionCode = '1' flutterVersionCode = '1'
@ -22,9 +21,12 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0' flutterVersionName = '1.0'
} }
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
namespace "com.example.app" compileSdkVersion flutter.compileSdkVersion
compileSdk flutter.compileSdkVersion
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
@ -42,11 +44,11 @@ android {
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.app" applicationId "com.example.outbag_app"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdk flutter.minSdkVersion minSdkVersion flutter.minSdkVersion
targetSdk flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }
@ -64,4 +66,6 @@ flutter {
source '../..' source '../..'
} }
dependencies {} dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

View file

@ -1,3 +1,16 @@
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects { allprojects {
repositories { repositories {
google() google()
@ -13,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
tasks.register("clean", Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

View file

@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx4G org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true

View file

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip

View file

@ -1,25 +1,11 @@
pluginManagement { include ':app'
def flutterSdkPath = {
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties() def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk") def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties" assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}
include ":app"

View file

@ -1,12 +0,0 @@
#!/bin/sh
# adapted from https://prettier.io/docs/en/precommit.html#option-5-shell-script
FILES=$(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g' | grep '.dart$')
[ -z "$FILES" ] && exit 0
# Format all selected files
echo "$FILES" | xargs dart format
# Add back the formatted files to staging
echo "$FILES" | xargs git add
exit 0

View file

@ -1,4 +1,3 @@
---
arb-dir: lib/l10n arb-dir: lib/l10n
template-arb-file: app_en.arb template-arb-file: app_en.arb
output-localization-file: app_localizations.dart output-localization-file: app_localizations.dart

View file

@ -363,30 +363,20 @@ class RoomInfo {
class RoomCategory { class RoomCategory {
final int? id; final int? id;
String name; final String name;
ColorSwatch<int> color; final ColorSwatch<int> color;
final String room;
final String server;
RoomCategory( const RoomCategory(
{required this.id, {required this.id, required this.name, required this.color});
required this.name,
required this.color,
required this.server,
required this.room});
factory RoomCategory.fromJSON(String server, String room, dynamic json) { factory RoomCategory.fromJSON(dynamic json) {
return RoomCategory( return RoomCategory(
server: server,
room: room,
id: json['id'], id: json['id'],
name: json['title'], name: json['title'],
color: colorFromString(json['color'])); color: colorFromString(json['color']));
} }
factory RoomCategory.other(String server, String room, BuildContext context) { factory RoomCategory.other(BuildContext context) {
return RoomCategory( return RoomCategory(
server: server,
room: room,
id: null, id: null,
name: AppLocalizations.of(context)!.categoryNameOther, name: AppLocalizations.of(context)!.categoryNameOther,
color: Colors.grey); color: Colors.grey);
@ -408,69 +398,6 @@ class RoomCategory {
"purple-acc", "purple-acc",
].map((txt) => colorFromString(txt)).toList(); ].map((txt) => colorFromString(txt)).toList();
} }
// get list of all categories in a given room
static Future<List<RoomCategory>> list(String server, String room) async {
final db = Localstore.instance;
final rooms = (await db.collection('categories:$room@$server').get()) ?? {};
List<RoomCategory> builder = [];
for (MapEntry entry in rooms.entries) {
try {
builder.add(RoomCategory.fromMap(entry.value));
} catch (e) {
// skip invalid entries
// NOTE: might want to autodelete them in the future
// although keeping them might be ok,
// in case we ever get a new dataset to fix the current state
}
}
return builder;
}
// listen to room change
static listen(
String server, String room, Function(Map<String, dynamic>) cb) async {
final db = Localstore.instance;
final stream = db.collection('categories:$room@$server').stream;
stream.listen(cb);
}
factory RoomCategory.fromMap(Map<String, dynamic> map) {
return RoomCategory(
server: map['server'],
room: map['room'],
id: map['id'],
name: map['name'],
color: colorFromString(map['color']));
}
Map<String, dynamic> toMap() {
return {
'server': server,
'room': room,
'id': id,
'name': name,
'color': colorIdFromColor(color)
};
}
Future<void> toDisk() async {
final db = Localstore.instance;
await db.collection('categories:$room@$server').doc('$id').set(toMap());
}
Future<void> removeDisk() async {
final db = Localstore.instance;
await db.collection('categories:$room@$server').doc('$id').delete();
}
static Future<RoomCategory> fromDisk(
{required int id, required String server, required String room}) async {
final db = Localstore.instance;
final raw =
await db.collection('categories:$room@$server').doc('$id').get();
return RoomCategory.fromMap(raw!);
}
} }
ColorSwatch<int> colorFromString(String text) { ColorSwatch<int> colorFromString(String text) {
@ -547,7 +474,7 @@ String colorIdFromColor(ColorSwatch<int> color) {
} }
class RoomProduct { class RoomProduct {
final int id; int id;
String name; String name;
String description; String description;
// category ID // category ID
@ -563,14 +490,9 @@ class RoomProduct {
// parent product ID // parent product ID
int? parent; int? parent;
final String server;
final String room;
RoomProduct( RoomProduct(
{required this.id, {required this.id,
required this.name, required this.name,
required this.server,
required this.room,
this.description = '', this.description = '',
this.category = -1, this.category = -1,
this.defaultUnit = 0, this.defaultUnit = 0,
@ -578,10 +500,8 @@ class RoomProduct {
this.ean, this.ean,
this.parent}); this.parent});
factory RoomProduct.fromJSON(String server, String room, dynamic json) { factory RoomProduct.fromJSON(dynamic json) {
return RoomProduct( return RoomProduct(
server: server,
room: room,
id: json['listProdID'], id: json['listProdID'],
name: json['title'], name: json['title'],
description: json['description'], description: json['description'],
@ -591,82 +511,10 @@ class RoomProduct {
ean: json['ean'], ean: json['ean'],
parent: json['parent']); parent: json['parent']);
} }
// get list of all categories in a given room
static Future<List<RoomProduct>> list(String server, String room) async {
final db = Localstore.instance;
final rooms = (await db.collection('products:$room@$server').get()) ?? {};
List<RoomProduct> builder = [];
for (MapEntry entry in rooms.entries) {
try {
builder.add(RoomProduct.fromMap(entry.value));
} catch (e) {
// skip invalid entries
// NOTE: might want to autodelete them in the future
// although keeping them might be ok,
// in case we ever get a new dataset to fix the current state
}
}
return builder;
}
// listen to room change
static listen(
String server, String room, Function(Map<String, dynamic>) cb) async {
final db = Localstore.instance;
final stream = db.collection('products:$room@$server').stream;
stream.listen(cb);
}
factory RoomProduct.fromMap(Map<String, dynamic> map) {
return RoomProduct(
server: map['server'],
room: map['room'],
id: map['id'],
name: map['name'],
description: map['description'],
category: map['category'],
defaultUnit: map['default_unit'],
defaultValue: map['default_value'],
ean: map['ean'],
parent: map['parent']);
}
Map<String, dynamic> toMap() {
return {
'server': server,
'room': room,
'id': id,
'name': name,
'description': description,
'category': category,
'default_unit': defaultUnit,
'default_value': defaultValue,
'ean': ean,
'parent': parent
};
}
Future<void> toDisk() async {
final db = Localstore.instance;
await db.collection('products:$room@$server').doc('$id').set(toMap());
}
Future<void> removeDisk() async {
final db = Localstore.instance;
await db.collection('products:$room@$server').doc('$id').delete();
}
static Future<RoomProduct> fromDisk(
{required int id, required String server, required String room}) async {
final db = Localstore.instance;
final raw = await db.collection('products:$room@$server').doc('$id').get();
return RoomProduct.fromMap(raw!);
}
} }
class RoomItem { class RoomItem {
final int id; int id;
int state; int state;
String name; String name;
String description; String description;
@ -679,14 +527,9 @@ class RoomItem {
// may link to a product // may link to a product
int? link; int? link;
final String server;
final String room;
RoomItem( RoomItem(
{required this.id, {required this.id,
required this.name, required this.name,
required this.server,
required this.room,
this.description = '', this.description = '',
this.state = 0, this.state = 0,
this.category = -1, this.category = -1,
@ -694,12 +537,10 @@ class RoomItem {
this.value = '', this.value = '',
this.link}); this.link});
factory RoomItem.fromJSON(String server, String room, dynamic json) { factory RoomItem.fromJSON(dynamic json) {
return RoomItem( return RoomItem(
id: json['listItemID'], id: json['listItemID'],
name: json['title'], name: json['title'],
server: server,
room: room,
description: json['description'], description: json['description'],
category: json['listCatID'], category: json['listCatID'],
state: json['state'], state: json['state'],
@ -712,84 +553,10 @@ class RoomItem {
return RoomItem( return RoomItem(
id: id, id: id,
name: name, name: name,
server: server,
room: room,
description: description, description: description,
category: category, category: category,
unit: unit, unit: unit,
value: value, value: value,
link: link); link: link);
} }
// get list of all categories in a given room
static Future<List<RoomItem>> list(String server, String room) async {
final db = Localstore.instance;
final rooms = (await db.collection('items:$room@$server').get()) ?? {};
List<RoomItem> builder = [];
for (MapEntry entry in rooms.entries) {
try {
builder.add(RoomItem.fromMap(entry.value));
} catch (e) {
// skip invalid entries
// NOTE: might want to autodelete them in the future
// although keeping them might be ok,
// in case we ever get a new dataset to fix the current state
}
}
return builder;
}
// listen to room change
static listen(
String server, String room, Function(Map<String, dynamic>) cb) async {
final db = Localstore.instance;
final stream = db.collection('items:$room@$server').stream;
stream.listen(cb);
}
factory RoomItem.fromMap(Map<String, dynamic> map) {
return RoomItem(
server: map['server'],
room: map['room'],
id: map['id'],
name: map['name'],
description: map['description'],
category: map['category'],
state: map['state'],
unit: map['unit'],
value: map['value'],
link: map['product']);
}
Map<String, dynamic> toMap() {
return {
'server': server,
'room': room,
'id': id,
'name': name,
'description': description,
'category': category,
'state': state,
'unit': unit,
'value': value,
'product': link
};
}
Future<void> toDisk() async {
final db = Localstore.instance;
await db.collection('items:$room@$server').doc('$id').set(toMap());
}
Future<void> removeDisk() async {
final db = Localstore.instance;
await db.collection('items:$room@$server').doc('$id').delete();
}
static Future<RoomProduct> fromDisk(
{required int id, required String server, required String room}) async {
final db = Localstore.instance;
final raw = await db.collection('items:$room@$server').doc('$id').get();
return RoomProduct.fromMap(raw!);
}
} }

View file

@ -3,20 +3,15 @@ import 'package:outbag_app/backend/room.dart';
class CategoryChip extends StatelessWidget { class CategoryChip extends StatelessWidget {
final RoomCategory? category; final RoomCategory? category;
final String server;
final String room;
const CategoryChip( const CategoryChip({super.key, this.category});
{super.key, required this.server, required this.room, this.category});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ActionChip( return ActionChip(
avatar: Icon(Icons.square_rounded, avatar: Icon(Icons.square_rounded,
color: category?.color ?? color: category?.color ?? RoomCategory.other(context).color),
RoomCategory.other(server, room, context).color), label: Text(category?.name ?? RoomCategory.other(context).name),
label: Text(
category?.name ?? RoomCategory.other(server, room, context).name),
); );
} }
} }

View file

@ -11,14 +11,9 @@ class CategoryPicker extends StatelessWidget {
final String? hint; final String? hint;
final String? label; final String? label;
final String server;
final String room;
const CategoryPicker( const CategoryPicker(
{super.key, {super.key,
required this.categories, required this.categories,
required this.server,
required this.room,
this.selected, this.selected,
this.onSelect, this.onSelect,
this.hint, this.hint,
@ -36,7 +31,7 @@ class CategoryPicker extends StatelessWidget {
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.category)), prefixIcon: const Icon(Icons.category)),
value: selected, value: selected,
items: [...categories, RoomCategory.other(server, room, context)] items: [...categories, RoomCategory.other(context)]
.map((category) => DropdownMenuItem<int?>( .map((category) => DropdownMenuItem<int?>(
value: category.id, value: category.id,
child: Row( child: Row(

View file

@ -424,17 +424,5 @@
"moveItemToCartTitle": "Move to Cart", "moveItemToCartTitle": "Move to Cart",
"moveItemToCartSubtitle": "Mark item as in-cart, so others know, you bought it", "moveItemToCartSubtitle": "Mark item as in-cart, so others know, you bought it",
"removeItemFromCartTitle": "Remove from Cart", "removeItemFromCartTitle": "Remove from Cart",
"removeItemFromCartSubtitle": "Remove item from shopping cart, so others know, that you still need it", "removeItemFromCartSubtitle": "Remove item from shopping cart, so others know, that you still need it"
"newItemQueryEmpty": "Type to show quick access buttons",
"newItemQueryHint": "Type item or product name",
"newItemQuickAccessPrefix": "Create:",
"newItemQuickProduct": "product: {text}",
"deleteItem": "Delete Item",
"deleteItemConfirm": "Do you really want to remove the item named {item}",
"deleteProductTitle": "Delete Product",
"deleteProductSubtitle": "Remove the product from list of known products",
"deleteProduct": "Delete Product",
"deleteProductConfirm": "DO you really want to remove the product named {product}"
} }

View file

@ -6,7 +6,6 @@ import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/backend/request.dart'; import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/screens/room/categories/edit.dart'; import 'package:outbag_app/screens/room/categories/edit.dart';
import 'package:outbag_app/screens/room/items/edit.dart'; import 'package:outbag_app/screens/room/items/edit.dart';
import 'package:outbag_app/screens/room/items/new.dart';
import 'package:outbag_app/screens/room/products/edit.dart'; import 'package:outbag_app/screens/room/products/edit.dart';
import 'package:outbag_app/screens/room/products/view.dart'; import 'package:outbag_app/screens/room/products/view.dart';
@ -307,7 +306,7 @@ class _OutbagAppState extends State {
GoRoute( GoRoute(
name: 'new-item', name: 'new-item',
path: 'new-item', path: 'new-item',
builder: (context, state) => NewItemPage( builder: (context, state) => EditItemPage(
server: server:
state.params['server'] ?? '', state.params['server'] ?? '',
room: state.params['id'] ?? '', room: state.params['id'] ?? '',

View file

@ -123,12 +123,7 @@ class _HomePageState extends State<HomePage> {
) )
], ],
), ),
body: Center( body: ListView.builder(
child: Padding(
padding: const EdgeInsets.all(14),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView.builder(
itemCount: rooms.length, itemCount: rooms.length,
itemBuilder: (ctx, i) { itemBuilder: (ctx, i) {
final room = rooms[i]; final room = rooms[i];
@ -139,32 +134,27 @@ class _HomePageState extends State<HomePage> {
child: InkWell( child: InkWell(
onTap: () { onTap: () {
// open room // open room
context.goNamed('room', params: { context.goNamed('room',
'server': room.serverTag, params: {'server': room.serverTag, 'id': room.id});
'id': room.id
});
}, },
onLongPress: () { onLongPress: () {
// open bottom sheet // open bottom sheet
// NOTE: feature yet to be confirmed // NOTE: feature yet to be confirmed
}, },
child: Container( child: Container(
padding: padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
const EdgeInsets.fromLTRB(10, 5, 5, 10),
child: ListTile( child: ListTile(
title: Text(room.name), title: Text(room.name),
visualDensity: visualDensity: const VisualDensity(vertical: 3),
const VisualDensity(vertical: 3),
subtitle: Text(room.description), subtitle: Text(room.description),
leading: AspectRatio( leading: AspectRatio(
aspectRatio: 1 / 1, aspectRatio: 1 / 1,
child: child: SvgPicture.asset("${room.icon?.img}"),
SvgPicture.asset("${room.icon?.img}"),
), ),
hoverColor: Colors.transparent, hoverColor: Colors.transparent,
)))); ))));
}, },
)))), ),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
label: Text(AppLocalizations.of(context)!.addRoom), label: Text(AppLocalizations.of(context)!.addRoom),
icon: const Icon(Icons.add), icon: const Icon(Icons.add),

View file

@ -202,13 +202,8 @@ class _EditCategoryPageState extends State<EditCategoryPage> {
final id = body['data']['catID']; final id = body['data']['catID'];
final cat = RoomCategory( final cat = RoomCategory(
server: widget.server, id: id, name: _ctrName.text, color: _ctrColor);
room: widget.tag, // TODO: cache category
id: id,
name: _ctrName.text,
color: _ctrColor);
// cache category
await cat.toDisk();
// go back // go back
router.pop(); router.pop();
@ -234,13 +229,11 @@ class _EditCategoryPageState extends State<EditCategoryPage> {
}), }),
onOK: (body) async { onOK: (body) async {
final cat = RoomCategory( final cat = RoomCategory(
server: widget.server,
room: widget.tag,
id: widget.id!, id: widget.id!,
name: _ctrName.text, name: _ctrName.text,
color: _ctrColor); color: _ctrColor);
// cache category // TODO: cache category
await cat.toDisk();
// go back // go back
router.pop(); router.pop();
return; return;

View file

@ -14,13 +14,10 @@ class EditItemPage extends StatefulWidget {
final String room; final String room;
final String server; final String server;
final int item; final int? item;
const EditItemPage( const EditItemPage(
{super.key, {super.key, required this.room, required this.server, this.item});
required this.room,
required this.server,
required this.item});
@override @override
State<StatefulWidget> createState() => _EditItemPageState(); State<StatefulWidget> createState() => _EditItemPageState();
@ -34,26 +31,18 @@ class _EditItemPageState extends State<EditItemPage> {
int _ctrUnit = 0; int _ctrUnit = 0;
String _ctrValue = ''; String _ctrValue = '';
int? _ctrLink; int? _ctrLink;
int _ctrState = 0;
// data cache // data cache
List<RoomCategory> categories = []; List<RoomCategory> categories = [];
List<RoomProduct> products = []; List<RoomProduct> products = [];
RoomItem? item; RoomItem? item;
void fetchCategories() async { void fetchCategories() {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached categories // TODO: load cached categories first
final cache = await RoomCategory.list(widget.server, widget.room);
if (mounted) {
setState(() {
categories = cache;
});
}
doNetworkRequest(scaffmgr, doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -61,8 +50,7 @@ class _EditItemPageState extends State<EditItemPage> {
body: {'room': widget.room, 'server': widget.server}), body: {'room': widget.room, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomCategory>((raw) => .map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
RoomCategory.fromJSON(widget.server, widget.room, raw))
.toList(); .toList();
setState(() { setState(() {
@ -71,19 +59,12 @@ class _EditItemPageState extends State<EditItemPage> {
}); });
} }
void fetchProducts() async { void fetchProducts() {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached products first // TODO: load cached products first
final cache = await RoomProduct.list(widget.server, widget.room);
if (mounted) {
setState(() {
products = cache;
});
}
doNetworkRequest(scaffmgr, doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -91,8 +72,7 @@ class _EditItemPageState extends State<EditItemPage> {
body: {'room': widget.room, 'server': widget.server}), body: {'room': widget.room, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomProduct>((raw) => .map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
RoomProduct.fromJSON(widget.server, widget.room, raw))
.toList(); .toList();
setState(() { setState(() {
@ -101,19 +81,12 @@ class _EditItemPageState extends State<EditItemPage> {
}); });
} }
void fetchItem() async { void fetchItem() {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached item first // TODO: load cached item first
try {
await RoomItem.fromDisk(
id: widget.item, server: widget.server, room: widget.room);
} catch (_) {
// cache miss
}
doNetworkRequest(scaffmgr, doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -124,17 +97,9 @@ class _EditItemPageState extends State<EditItemPage> {
'listItemID': widget.item 'listItemID': widget.item
}), }),
onOK: (body) async { onOK: (body) async {
final resp = final resp = RoomItem.fromJSON(body['data']);
RoomItem.fromJSON(widget.server, widget.room, body['data']);
setState(() { setState(() {
item = resp; item = resp;
_ctrName.text = resp.name;
_ctrDescription.text = resp.description;
_ctrValue = resp.value;
_ctrCategory = resp.category;
_ctrLink = resp.link;
_ctrUnit = resp.unit;
_ctrState = resp.state;
}); });
}); });
} }
@ -143,31 +108,13 @@ class _EditItemPageState extends State<EditItemPage> {
void initState() { void initState() {
super.initState(); super.initState();
// wait for background room product changes
RoomProduct.listen(widget.server, widget.room, (_) async {
try {
final updated = await RoomProduct.list(widget.server, widget.room);
setState(() {
products = updated;
});
} catch (_) {}
});
// wait for background room category changes
RoomCategory.listen(widget.server, widget.room, (_) async {
try {
final updated = await RoomCategory.list(widget.server, widget.room);
setState(() {
categories = updated;
});
} catch (_) {}
});
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchCategories(); fetchCategories();
fetchProducts(); fetchProducts();
if (widget.item != null) {
fetchItem(); fetchItem();
}
}); });
} }
@ -175,14 +122,16 @@ class _EditItemPageState extends State<EditItemPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context)!.editItem), title: Text((widget.item == null)
? AppLocalizations.of(context)!.createItem
: AppLocalizations.of(context)!.editItem),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Center( child: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600), constraints: const BoxConstraints(maxWidth: 400),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
@ -249,8 +198,6 @@ class _EditItemPageState extends State<EditItemPage> {
}, },
), ),
CategoryPicker( CategoryPicker(
server: widget.server,
room: widget.room,
label: AppLocalizations.of(context)! label: AppLocalizations.of(context)!
.selectCategoryLabel, .selectCategoryLabel,
hint: AppLocalizations.of(context)! hint: AppLocalizations.of(context)!
@ -280,6 +227,27 @@ class _EditItemPageState extends State<EditItemPage> {
final user = context.read<User>(); final user = context.read<User>();
if (widget.item == null) {
doNetworkRequest(scaffMgr,
req: () => postWithCreadentials(
credentials: user,
target: user.server,
path: 'addItem',
body: {
'room': widget.room,
'server': widget.server,
'state': 0,
'title': _ctrName.text,
'description': _ctrDescription.text,
'listCatID': _ctrCategory,
'unit': _ctrUnit,
'value': _ctrValue,
'listProdID': _ctrLink
}),
onOK: (_) async {
nav.pop();
});
} else {
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
@ -290,33 +258,21 @@ class _EditItemPageState extends State<EditItemPage> {
'room': widget.room, 'room': widget.room,
'server': widget.server, 'server': widget.server,
'title': _ctrName.text, 'title': _ctrName.text,
'state': _ctrState,
'description': _ctrDescription.text, 'description': _ctrDescription.text,
'listCatID': _ctrCategory, 'listCatID': _ctrCategory,
'unit': _ctrUnit, 'defUnit': _ctrUnit,
'value': _ctrValue, 'defValue': _ctrValue,
'listProdID': _ctrLink 'listProdID': _ctrLink
}), }),
onOK: (_) async { onOK: (_) async {
// cache item
final item = RoomItem(
id: widget.item,
server: widget.server,
room: widget.room,
name: _ctrName.text,
state: _ctrState,
description: _ctrDescription.text,
category: _ctrCategory,
unit: _ctrUnit,
value: _ctrValue,
link: _ctrLink);
await item.toDisk();
nav.pop(); nav.pop();
}); });
}
}, },
label: Text(AppLocalizations.of(context)!.editItemShort), label: Text(widget.item != null
icon: const Icon(Icons.edit)), ? AppLocalizations.of(context)!.editItemShort
: AppLocalizations.of(context)!.createItemShort),
icon: Icon(widget.item != null ? Icons.edit : Icons.add)),
); );
} }
} }

View file

@ -1,321 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:go_router/go_router.dart';
import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/room.dart';
import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/components/category_chip.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:provider/provider.dart';
class NewItemPage extends StatefulWidget {
final String room;
final String server;
final int? item;
const NewItemPage(
{super.key, required this.room, required this.server, this.item});
@override
State<StatefulWidget> createState() => _NewItemPageState();
}
class _NewItemPageState extends State<NewItemPage> {
// input controllers
final _ctrInput = TextEditingController();
// data cache
List<RoomCategory> categories = [];
List<RoomProduct> products = [];
void fetchCategories() async {
final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached categories
final cache = await RoomCategory.list(widget.server, widget.room);
if (mounted) {
setState(() {
categories = cache;
});
}
doNetworkRequest(scaffmgr,
req: () => postWithCreadentials(
credentials: user,
target: user.server,
path: 'getCategories',
body: {'room': widget.room, 'server': widget.server}),
onOK: (body) async {
final resp = body['data']
.map<RoomCategory>((raw) =>
RoomCategory.fromJSON(widget.server, widget.room, raw))
.toList();
setState(() {
categories = resp;
});
});
}
void fetchProducts() async {
final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached products first
final cache = await RoomProduct.list(widget.server, widget.room);
if (mounted) {
setState(() {
products = cache;
});
}
doNetworkRequest(scaffmgr,
req: () => postWithCreadentials(
credentials: user,
target: user.server,
path: 'getProducts',
body: {'room': widget.room, 'server': widget.server}),
onOK: (body) async {
final resp = body['data']
.map<RoomProduct>((raw) =>
RoomProduct.fromJSON(widget.server, widget.room, raw))
.toList();
setState(() {
products = resp;
});
});
}
@override
void initState() {
super.initState();
// wait for background room product changes
RoomProduct.listen(widget.server, widget.room, (_) async {
try {
final updated = await RoomProduct.list(widget.server, widget.room);
setState(() {
products = updated;
});
} catch (_) {}
});
// wait for background room category changes
RoomCategory.listen(widget.server, widget.room, (_) async {
try {
final updated = await RoomCategory.list(widget.server, widget.room);
setState(() {
categories = updated;
});
} catch (_) {}
});
WidgetsBinding.instance.addPostFrameCallback((_) {
fetchCategories();
fetchProducts();
});
}
String _query = "";
void createItem(BuildContext ctx, String name, int? productID) {
final scaffMgr = ScaffoldMessenger.of(context);
final router = GoRouter.of(context);
final user = context.read<User>();
doNetworkRequest(scaffMgr,
req: () => postWithCreadentials(
target: user.server,
credentials: user,
path: 'addItem',
body: {
'room': widget.room,
'server': widget.server,
'state': 0,
'title': name,
'description': '',
'listCatID': null,
'unit': 0,
'value': '',
'listProdID': productID
}),
onOK: (body) async {
final id = body["data"]["listItemID"];
// cache item
final item = RoomItem(
id: id,
room: widget.room,
server: widget.server,
state: 0,
name: name,
description: '',
category: null,
unit: 0,
value: '',
link: productID);
await item.toDisk();
// launch edit item screen
router.pushReplacementNamed('edit-item', params: {
'server': widget.server,
'id': widget.room,
'item': id.toString()
});
});
}
void createProduct(BuildContext ctx, String name, Function(int) cb) {
final scaffMgr = ScaffoldMessenger.of(context);
final user = context.read<User>();
doNetworkRequest(scaffMgr,
req: () => postWithCreadentials(
target: user.server,
credentials: user,
path: 'addProduct',
body: {
'room': widget.room,
'server': widget.server,
'title': name,
'description': '',
'listCatID': null,
'defUnit': 0,
'defValue': '',
'ean': '',
'parent': null
}),
onOK: (body) async {
final id = body["data"]["listProdID"];
// cache product
final prod = RoomProduct(
id: id,
name: name,
server: widget.server,
room: widget.room,
description: '',
category: null,
defaultUnit: 0,
defaultValue: '',
ean: '',
parent: null);
await prod.toDisk();
cb(id);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text((widget.item == null)
? AppLocalizations.of(context)!.createItem
: AppLocalizations.of(context)!.editItem),
),
body: SingleChildScrollView(
child: Center(
child: Padding(
padding: const EdgeInsets.all(14),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SearchBar(
controller: _ctrInput,
leading: const Icon(Icons.search),
hintText:
AppLocalizations.of(context)!.newItemQueryHint,
onChanged: (text) {
setState(() {
_query = text;
});
},
),
const Divider(),
// button bar
...((_query == "")
? ([
Text(AppLocalizations.of(context)!
.newItemQueryEmpty)
])
: ([
Wrap(
spacing: 20,
runSpacing: 10,
alignment: WrapAlignment.center,
crossAxisAlignment:
WrapCrossAlignment.center,
children: [
Text(AppLocalizations.of(context)!
.newItemQuickAccessPrefix),
// new item
FilledButton.icon(
onPressed: () {
// create new named item
// launch edit item screen once done
createItem(context, _query, null);
},
icon: const Icon(Icons.add),
label: Text(_query)),
// new product
FilledButton.icon(
onPressed: () {
// create new product with name,
// create new item with name
// and link to the created product
// launch edit item screen once done
createProduct(
context,
_query,
(p0) => createItem(
context, _query, p0));
},
icon: const Icon(Icons.add),
label: Text(AppLocalizations.of(
context)!
.newItemQuickProduct(_query))),
])
])),
const Divider(),
// link products search
...((products
// show all products if query is empty
// when query isn't empty show products
// that contain the query in the title
// or description
.where((element) =>
(_query == "") ||
element.name.contains(_query) ||
element.description.contains(_query))
.map((e) => ListTile(
title: Text(e.name),
subtitle: Text(e.description),
trailing: CategoryChip(
server: widget.server,
room: widget.room,
category: categories
.where((element) =>
element.id == e.category)
.firstOrNull ??
RoomCategory.other(widget.server,
widget.room, context),
),
onTap: () {
// create new item and link it to the product
// launch edit item screen once done
createItem(
context,
// use productname as item name
e.name,
e.id);
},
))))
],
))))),
);
}
}

View file

@ -105,7 +105,7 @@ class _JoinRoomPageState extends State {
icon: const Icon(Icons.search), icon: const Icon(Icons.search),
tooltip: AppLocalizations.of(context)!.search, tooltip: AppLocalizations.of(context)!.search,
onPressed: () { onPressed: () {
// TODO: show searchbar // show searchbar
// NOTE: location currently unknown // NOTE: location currently unknown
}, },
), ),
@ -170,8 +170,7 @@ class _JoinRoomPageState extends State {
return BottomSheet( return BottomSheet(
onClosing: () {}, onClosing: () {},
builder: (ctx) { builder: (ctx) {
return SingleChildScrollView( return Column(
child: Column(
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.center, CrossAxisAlignment.center,
mainAxisAlignment: mainAxisAlignment:
@ -283,7 +282,7 @@ class _JoinRoomPageState extends State {
)) ))
]) ])
], ],
)); );
}, },
); );
}); });

View file

@ -33,10 +33,6 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
return SingleChildScrollView( return SingleChildScrollView(
child: Center( child: Center(
child: Padding(
padding: const EdgeInsets.all(14),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: Column(children: [ child: Column(children: [
// room meta display // room meta display
...(widget.room != null) ...(widget.room != null)
@ -69,8 +65,7 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
showSelectedIcon: true, showSelectedIcon: true,
multiSelectionEnabled: false, multiSelectionEnabled: false,
emptySelectionAllowed: false, emptySelectionAllowed: false,
segments: segments: RoomVisibility.list().map((vis) {
RoomVisibility.list().map((vis) {
return ButtonSegment<int>( return ButtonSegment<int>(
value: vis.type, value: vis.type,
label: Text(vis.text(context)), label: Text(vis.text(context)),
@ -81,14 +76,10 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
// only show confirm dialog when user // only show confirm dialog when user
// is admin, owner or has CHANGE_ADMIN permission // is admin, owner or has CHANGE_ADMIN permission
if (widget.info == null || if (widget.info == null ||
(!(widget.info?.isAdmin ?? (!(widget.info?.isAdmin ?? false) &&
false) && !(widget.info?.isOwner ?? false) &&
!(widget.info?.isOwner ?? ((widget.info?.permissions)! &
false) && RoomPermission.ota ==
((widget.info
?.permissions)! &
RoomPermission
.ota ==
0))) { 0))) {
// action not permitted // action not permitted
// NOTE: no error dialog should be shown // NOTE: no error dialog should be shown
@ -96,65 +87,46 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
return; return;
} }
final vis = final vis = RoomVisibility(vset.first);
RoomVisibility(vset.first);
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: Text(AppLocalizations title: Text(AppLocalizations.of(context)!
.of(context)!
.changeRoomVisibilityTitle), .changeRoomVisibilityTitle),
content: Text(AppLocalizations content: Text(
.of(context)! AppLocalizations.of(context)!
.changeRoomVisibilitySubtitle( .changeRoomVisibilitySubtitle(
vis.text( vis.text(context))),
context))),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
context.pop(); context.pop();
}, },
child: Text( child: Text(
AppLocalizations.of( AppLocalizations.of(context)!
context)!
.cancel), .cancel),
), ),
FilledButton( FilledButton(
onPressed: () async { onPressed: () async {
final scaffMgr = final scaffMgr =
ScaffoldMessenger ScaffoldMessenger.of(context);
.of(context); final nav = Navigator.of(context);
final nav = final user = context.read<User>();
Navigator.of(
context);
final user = context
.read<User>();
doNetworkRequest( doNetworkRequest(scaffMgr,
scaffMgr, req: () => postWithCreadentials(
req: () => path: 'setVisibility',
postWithCreadentials( target: user.server,
path:
'setVisibility',
target: user
.server,
body: { body: {
'room': widget 'room': widget.room?.id,
.room
?.id,
'server': (widget 'server': (widget
.room .room?.serverTag)!,
?.serverTag)!, 'visibility': vset.first
'visibility':
vset.first
}, },
credentials: credentials: user),
user),
onOK: (_) { onOK: (_) {
Room r = widget Room r = widget.room!;
.room!; r.visibility = vis;
r.visibility =
vis;
r.toDisk(); r.toDisk();
}, },
after: () { after: () {
@ -162,18 +134,13 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
}); });
}, },
child: Text( child: Text(
AppLocalizations.of( AppLocalizations.of(context)!.ok),
context)!
.ok),
) )
], ],
)); ));
}), }),
selected: { selected: {(widget.room?.visibility?.type)!},
(widget.room?.visibility?.type)! selectedIcon: Icon((widget.room?.visibility?.icon)!),
},
selectedIcon: Icon(
(widget.room?.visibility?.icon)!),
)), )),
], ],
), ),
@ -194,13 +161,10 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
0))) 0)))
? [ ? [
ListTile( ListTile(
trailing: trailing: const Icon(Icons.chevron_right),
const Icon(Icons.chevron_right),
title: Text( title: Text(
AppLocalizations.of(context)! AppLocalizations.of(context)!.editRoomMetadata),
.editRoomMetadata), subtitle: Text(AppLocalizations.of(context)!
subtitle: Text(
AppLocalizations.of(context)!
.editRoomMetadataSubtitle), .editRoomMetadataSubtitle),
onTap: () { onTap: () {
// show edit room screen // show edit room screen
@ -215,10 +179,9 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
// open members view // open members view
ListTile( ListTile(
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
title: Text(AppLocalizations.of(context)! title: Text(AppLocalizations.of(context)!.showRoomMembers),
.showRoomMembers), subtitle:
subtitle: Text(AppLocalizations.of(context)! Text(AppLocalizations.of(context)!.showRoomMembersSubtitle),
.showRoomMembersSubtitle),
onTap: () { onTap: () {
// open member view screen // open member view screen
context.goNamed('room-members', params: { context.goNamed('room-members', params: {
@ -236,20 +199,15 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
0))) 0)))
? [ ? [
ListTile( ListTile(
trailing: trailing: const Icon(Icons.chevron_right),
const Icon(Icons.chevron_right),
title: Text( title: Text(
AppLocalizations.of(context)! AppLocalizations.of(context)!.editRoomPermissions),
.editRoomPermissions), subtitle: Text(AppLocalizations.of(context)!
subtitle: Text(
AppLocalizations.of(context)!
.editRoomPermissionsSubtitle), .editRoomPermissionsSubtitle),
onTap: () { onTap: () {
// show checkbox screen // show checkbox screen
context.goNamed('room-permissions', context.goNamed('room-permissions', params: {
params: { 'server': (widget.room?.serverTag)!,
'server':
(widget.room?.serverTag)!,
'id': (widget.room?.id)! 'id': (widget.room?.id)!
}); });
}, },
@ -259,18 +217,14 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
...(widget.info != null && ...(widget.info != null &&
((widget.info?.isAdmin ?? false) || ((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) || (widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! & ((widget.info?.permissions)! & RoomPermission.ota !=
RoomPermission.ota !=
0))) 0)))
? [ ? [
ListTile( ListTile(
trailing: trailing: const Icon(Icons.chevron_right),
const Icon(Icons.chevron_right), title:
title: Text( Text(AppLocalizations.of(context)!.manageRoomOTA),
AppLocalizations.of(context)! subtitle: Text(AppLocalizations.of(context)!
.manageRoomOTA),
subtitle: Text(
AppLocalizations.of(context)!
.manageRoomOTASubtitle), .manageRoomOTASubtitle),
onTap: () { onTap: () {
// show manage ota screen // show manage ota screen
@ -281,18 +235,14 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
}, },
), ),
ListTile( ListTile(
trailing: trailing: const Icon(Icons.chevron_right),
const Icon(Icons.chevron_right),
title: Text( title: Text(
AppLocalizations.of(context)! AppLocalizations.of(context)!.manageRoomInvites),
.manageRoomInvites), subtitle: Text(AppLocalizations.of(context)!
subtitle: Text(
AppLocalizations.of(context)!
.manageRoomInvitesSubtitle), .manageRoomInvitesSubtitle),
onTap: () { onTap: () {
// show manage ota screen // show manage ota screen
context context.goNamed('room-invite', params: {
.goNamed('room-invite', params: {
'server': (widget.room?.serverTag)!, 'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)! 'id': (widget.room?.id)!
}); });
@ -309,30 +259,20 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: FilledButton.tonal( child: FilledButton.tonal(
child: Text(((widget.info?.isOwner)!) child: Text(((widget.info?.isOwner)!)
? AppLocalizations.of(context)! ? AppLocalizations.of(context)!.deleteRoom
.deleteRoom : AppLocalizations.of(context)!.leaveRoom),
: AppLocalizations.of(context)!
.leaveRoom),
onPressed: () { onPressed: () {
// show confirm dialog // show confirm dialog
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: Text( title: Text(((widget.info?.isOwner)!)
((widget.info?.isOwner)!) ? AppLocalizations.of(context)!.deleteRoom
? AppLocalizations.of( : AppLocalizations.of(context)!.leaveRoom),
context)! content: Text(((widget.info?.isOwner)!)
.deleteRoom ? AppLocalizations.of(context)!
: AppLocalizations.of(
context)!
.leaveRoom),
content: Text(
((widget.info?.isOwner)!)
? AppLocalizations.of(
context)!
.deleteRoomConfirm .deleteRoomConfirm
: AppLocalizations.of( : AppLocalizations.of(context)!
context)!
.leaveRoomConfirm), .leaveRoomConfirm),
actions: [ actions: [
TextButton( TextButton(
@ -341,67 +281,47 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
Navigator.of(ctx).pop(); Navigator.of(ctx).pop();
}, },
child: Text( child: Text(
AppLocalizations.of( AppLocalizations.of(context)!.cancel),
context)!
.cancel),
), ),
FilledButton( FilledButton(
onPressed: () async { onPressed: () async {
// send request // send request
final scaffMgr = final scaffMgr =
ScaffoldMessenger.of( ScaffoldMessenger.of(ctx);
ctx); final nav = Navigator.of(ctx);
final nav = final router = GoRouter.of(context);
Navigator.of(ctx); final user = context.read<User>();
final router =
GoRouter.of(context);
final user =
context.read<User>();
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
req: () => req: () => postWithCreadentials(
postWithCreadentials( path: ((widget.info?.isOwner)!)
path: ((widget
.info
?.isOwner)!)
? 'deleteRoom' ? 'deleteRoom'
: 'leaveRoom', : 'leaveRoom',
target: user target: user.server,
.server,
body: { body: {
'room': widget 'room': widget.room?.id,
.room 'server':
?.id, (widget.room?.serverTag)!,
'server': (widget
.room
?.serverTag)!,
}, },
credentials: credentials: user),
user),
onOK: (_) async { onOK: (_) async {
// try delete room from disk // try delete room from disk
try { try {
await widget.room await widget.room?.removeDisk();
?.removeDisk();
} catch (_) {} } catch (_) {}
// go back home // go back home
router router.pushReplacementNamed('home');
.pushReplacementNamed(
'home');
}, },
after: () { after: () {
// close popup // close popup
nav.pop(); nav.pop();
}); });
}, },
child: Text(((widget child: Text(((widget.info?.isOwner)!)
.info?.isOwner)!) ? AppLocalizations.of(context)!
? AppLocalizations.of(
context)!
.deleteRoomShort .deleteRoomShort
: AppLocalizations.of( : AppLocalizations.of(context)!
context)!
.leaveRoomShort), .leaveRoomShort),
) )
], ],
@ -410,6 +330,6 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
)) ))
] ]
: [], : [],
]))))); ])));
} }
} }

View file

@ -25,18 +25,6 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
void initState() { void initState() {
super.initState(); super.initState();
// wait for background room category changes
RoomCategory.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
(_) async {
try {
final updated = await RoomCategory.list(
widget.room?.serverTag ?? "", widget.room?.id ?? "");
setState(() {
list = updated;
});
} catch (_) {}
});
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchCategories(); fetchCategories();
}); });
@ -44,18 +32,10 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
void fetchCategories() async { void fetchCategories() async {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached categories // TODO: load cached rooms
final cache = await RoomCategory.list(
widget.room?.serverTag ?? "", widget.room?.id ?? "");
if (mounted) {
setState(() {
list = cache;
});
}
doNetworkRequest(scaffmgr, doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -63,12 +43,8 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}), body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
onOK: (json) { onOK: (json) {
final resp = json['data'] final resp = json['data']
.map<RoomCategory>((raw) => RoomCategory.fromJSON( .map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
widget.room?.serverTag ?? "", widget.room?.id ?? "", raw))
.toList(); .toList();
for (RoomCategory ce in resp) {
ce.toDisk();
}
if (mounted) { if (mounted) {
setState(() { setState(() {
@ -85,12 +61,7 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
.apply(displayColor: Theme.of(context).colorScheme.onSurface); .apply(displayColor: Theme.of(context).colorScheme.onSurface);
return Scaffold( return Scaffold(
body: Center( body: ReorderableListView.builder(
child: Padding(
padding: const EdgeInsets.all(14),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ReorderableListView.builder(
buildDefaultDragHandles: false, buildDefaultDragHandles: false,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = list[index]; final item = list[index];
@ -104,8 +75,7 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
RoomPermission.editRoomContent != RoomPermission.editRoomContent !=
0)) 0))
? ReorderableDragStartListener( ? ReorderableDragStartListener(
index: index, index: index, child: const Icon(Icons.drag_handle))
child: const Icon(Icons.drag_handle))
: null, : null,
title: Text(item.name), title: Text(item.name),
onTap: () { onTap: () {
@ -129,24 +99,20 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Row( child: Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.center,
MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [ children: [
Icon(Icons.square_rounded, Icon(Icons.square_rounded,
size: 48.0, color: item.color), size: 48.0, color: item.color),
Text(item.name, Text(item.name, style: textTheme.titleLarge)
style: textTheme.titleLarge)
], ],
)), )),
// edit category // edit category
ListTile( ListTile(
leading: const Icon(Icons.edit), leading: const Icon(Icons.edit),
title: Text(AppLocalizations.of(context)! title: Text(AppLocalizations.of(context)!.editCategory),
.editCategory), subtitle:
subtitle: Text(AppLocalizations.of(context)! Text(AppLocalizations.of(context)!.editCategoryLong),
.editCategoryLong),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () { onTap: () {
// close the modal bottom sheet // close the modal bottom sheet
@ -165,10 +131,9 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
// delete category // delete category
ListTile( ListTile(
leading: const Icon(Icons.delete), leading: const Icon(Icons.delete),
title: Text(AppLocalizations.of(context)! title: Text(AppLocalizations.of(context)!.deleteCategory),
.deleteCategory), subtitle: Text(
subtitle: Text(AppLocalizations.of(context)! AppLocalizations.of(context)!.deleteCategoryLong),
.deleteCategoryLong),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () { onTap: () {
// show popup // show popup
@ -176,13 +141,10 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
icon: const Icon(Icons.delete), icon: const Icon(Icons.delete),
title: Text( title: Text(AppLocalizations.of(context)!
AppLocalizations.of(context)!
.deleteCategory), .deleteCategory),
content: Text( content: Text(AppLocalizations.of(context)!
AppLocalizations.of(context)! .deleteCategoryConfirm(item.name)),
.deleteCategoryConfirm(
item.name)),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
@ -190,46 +152,32 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
Navigator.of(ctx).pop(); Navigator.of(ctx).pop();
}, },
child: Text( child: Text(
AppLocalizations.of( AppLocalizations.of(context)!.cancel),
context)!
.cancel),
), ),
FilledButton( FilledButton(
onPressed: () async { onPressed: () async {
// send request // send request
final scaffMgr = final scaffMgr =
ScaffoldMessenger.of( ScaffoldMessenger.of(ctx);
ctx);
// popup context // popup context
final navInner = final navInner = Navigator.of(ctx);
Navigator.of(ctx);
// bottomsheet context // bottomsheet context
final nav = final nav = Navigator.of(context);
Navigator.of(context); final user = context.read<User>();
final user =
context.read<User>();
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
req: () => req: () => postWithCreadentials(
postWithCreadentials( path: 'deleteCategory',
path: target: user.server,
'deleteCategory',
target:
user.server,
body: { body: {
'room': widget 'room': widget.room?.id,
.room?.id, 'server':
'server': widget widget.room?.serverTag,
.room 'listCatID': item.id
?.serverTag,
'listCatID':
item.id
}, },
credentials: credentials: user),
user),
onOK: (_) async { onOK: (_) async {
// remove cached category // TODO: remove cached category
item.removeDisk();
fetchCategories(); fetchCategories();
}, },
after: () { after: () {
@ -239,9 +187,7 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
nav.pop(); nav.pop();
}); });
}, },
child: Text( child: Text(AppLocalizations.of(context)!
AppLocalizations.of(
context)!
.deleteCategory), .deleteCategory),
) )
], ],
@ -259,8 +205,7 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
onReorder: (int oldIndex, int newIndex) { onReorder: (int oldIndex, int newIndex) {
if (!((widget.info?.isAdmin ?? false) || if (!((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) || (widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! & ((widget.info?.permissions)! & RoomPermission.editRoomContent !=
RoomPermission.editRoomContent !=
0))) { 0))) {
// user is not allowed to edit or delete categories // user is not allowed to edit or delete categories
return; return;
@ -283,12 +228,11 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
body: { body: {
'room': widget.room?.id, 'room': widget.room?.id,
'server': widget.room?.serverTag, 'server': widget.room?.serverTag,
'listCatIDs': 'listCatIDs': list.map((item) => item.id).toList()
list.map((item) => item.id).toList()
})); }));
}); });
}, },
)))), ),
floatingActionButton: (widget.info != null && floatingActionButton: (widget.info != null &&
((widget.info?.isAdmin ?? false) || ((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) || (widget.info?.isOwner ?? false) ||

View file

@ -5,7 +5,9 @@ import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/room.dart'; import 'package:outbag_app/backend/room.dart';
import 'package:outbag_app/backend/user.dart'; import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/components/category_chip.dart'; import 'package:outbag_app/components/category_chip.dart';
import 'package:outbag_app/components/category_picker.dart';
import 'package:outbag_app/components/labeled_divider.dart'; import 'package:outbag_app/components/labeled_divider.dart';
import 'package:outbag_app/components/product_picker.dart';
import 'package:outbag_app/components/value_unit_input.dart'; import 'package:outbag_app/components/value_unit_input.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart'; import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -28,37 +30,12 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
Map<int?, RoomCategory> categories = {}; Map<int?, RoomCategory> categories = {};
List<RoomProduct> products = []; List<RoomProduct> products = [];
void fetchItems() async { void fetchItems() {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached items first // TODO: load cached items first
final cache = await RoomItem.list(
widget.room?.serverTag ?? "", widget.room?.id ?? "");
final List<RoomItem> l = []; doNetworkRequest(ScaffoldMessenger.of(context),
final List<RoomItem> c = [];
for (RoomItem item in cache) {
if (item.state == 0) {
l.add(item);
} else {
c.add(item);
}
// cache items
await item.toDisk();
}
if (mounted) {
setState(() {
list = l;
cart = c;
sortAll();
});
}
doNetworkRequest(scaffmgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -66,8 +43,7 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}), body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomItem>((raw) => RoomItem.fromJSON( .map<RoomItem>((raw) => RoomItem.fromJSON(raw))
widget.room?.serverTag ?? "", widget.room?.id ?? "", raw))
.toList(); .toList();
final List<RoomItem> l = []; final List<RoomItem> l = [];
@ -79,10 +55,10 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
} else { } else {
c.add(item); c.add(item);
} }
// cache items
await item.toDisk();
} }
// TODO: cache items
if (mounted) { if (mounted) {
setState(() { setState(() {
list = l; list = l;
@ -132,31 +108,12 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
} }
} }
void fetchCategories() async { void fetchCategories() {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached categories // TODO: load cached categories first
final resp = await RoomCategory.list(
widget.room?.serverTag ?? "", widget.room?.id ?? "");
if (mounted) {
Map<int, int> map = {};
Map<int?, RoomCategory> cat = {};
for (int i = 0; i < resp.length; i++) {
map[resp[i].id ?? 0] = i;
cat[resp[i].id ?? 0] = resp[i];
}
if (mounted) { doNetworkRequest(ScaffoldMessenger.of(context),
setState(() {
weights = map;
categories = cat;
sortAll();
});
}
}
doNetworkRequest(scaffmgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -164,8 +121,7 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}), body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomCategory>((raw) => RoomCategory.fromJSON( .map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
widget.room?.serverTag ?? "", widget.room?.id ?? "", raw))
.toList(); .toList();
Map<int, int> map = {}; Map<int, int> map = {};
@ -185,20 +141,12 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
}); });
} }
void fetchProducts() async { void fetchProducts() {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached products first // TODO: load cached products first
final cache = await RoomProduct.list(
widget.room?.serverTag ?? "", widget.room?.id ?? "");
if (mounted) {
setState(() {
products = cache;
});
}
doNetworkRequest(scaffmgr, doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -206,8 +154,7 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}), body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomProduct>((raw) => RoomProduct.fromJSON( .map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
widget.room!.serverTag, widget.room!.id, raw))
.toList(); .toList();
if (mounted) { if (mounted) {
@ -222,68 +169,6 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
void initState() { void initState() {
super.initState(); super.initState();
// wait for background room item changes
RoomItem.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
(_) async {
try {
final updated = await RoomItem.list(
widget.room?.serverTag ?? "", widget.room?.id ?? "");
final List<RoomItem> l = [];
final List<RoomItem> c = [];
for (RoomItem item in updated) {
if (item.state == 0) {
l.add(item);
} else {
c.add(item);
}
}
if (mounted) {
setState(() {
list = l;
cart = c;
sortAll();
});
}
} catch (_) {}
});
// wait for background room product changes
RoomProduct.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
(_) async {
try {
final updated = await RoomProduct.list(
widget.room?.serverTag ?? "", widget.room?.id ?? "");
setState(() {
products = updated;
});
} catch (_) {}
});
// wait for background room category changes
RoomCategory.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
(_) async {
try {
final resp = await RoomCategory.list(
widget.room?.serverTag ?? "", widget.room?.id ?? "");
Map<int, int> map = {};
Map<int?, RoomCategory> cat = {};
for (int i = 0; i < resp.length; i++) {
map[resp[i].id ?? 0] = i;
cat[resp[i].id ?? 0] = resp[i];
}
if (mounted) {
setState(() {
weights = map;
categories = cat;
sortAll();
});
}
} catch (_) {}
});
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchItems(); fetchItems();
fetchCategories(); fetchCategories();
@ -309,12 +194,7 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: Center( body: ListView(children: [
child: Padding(
padding: const EdgeInsets.all(14),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(children: [
LabeledDivider(AppLocalizations.of(context)!.shoppingList), LabeledDivider(AppLocalizations.of(context)!.shoppingList),
ListView.builder( ListView.builder(
shrinkWrap: true, shrinkWrap: true,
@ -322,13 +202,10 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
itemCount: list.length, itemCount: list.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = list[index]; final item = list[index];
final cat = categories[item.category] ?? final cat =
RoomCategory.other(widget.room?.serverTag ?? "", categories[item.category] ?? RoomCategory.other(context);
widget.room?.id ?? "", context);
return ShoppingListItem( return ShoppingListItem(
name: item.name, name: item.name,
server: widget.room!.serverTag,
room: widget.room!.id,
description: item.description, description: item.description,
category: cat, category: cat,
key: Key(item.id.toString()), key: Key(item.id.toString()),
@ -376,13 +253,10 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = cart[index]; final item = cart[index];
final cat = categories[item.category] ?? final cat =
RoomCategory.other(widget.room!.serverTag, categories[item.category] ?? RoomCategory.other(context);
widget.room!.id, context);
return ShoppingListItem( return ShoppingListItem(
server: widget.room!.serverTag,
room: widget.room!.id,
name: item.name, name: item.name,
description: item.description, description: item.description,
category: cat, category: cat,
@ -423,7 +297,7 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
}); });
}, },
) )
])))), ]),
floatingActionButton: (widget.info != null && floatingActionButton: (widget.info != null &&
((widget.info?.isAdmin ?? false) || ((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) || (widget.info?.isOwner ?? false) ||
@ -455,16 +329,12 @@ class ShoppingListItem extends StatelessWidget {
final Key _key; final Key _key;
final Function()? onDismiss; final Function()? onDismiss;
final Function()? onTap; final Function()? onTap;
final String server;
final String room;
const ShoppingListItem( const ShoppingListItem(
{required this.name, {required this.name,
required this.category, required this.category,
required this.inCart, required this.inCart,
required this.description, required this.description,
required this.server,
required this.room,
required key, required key,
this.onDismiss, this.onDismiss,
this.onTap}) this.onTap})
@ -497,8 +367,6 @@ class ShoppingListItem extends StatelessWidget {
title: Text(name), title: Text(name),
subtitle: Text(description), subtitle: Text(description),
trailing: CategoryChip( trailing: CategoryChip(
server: server,
room: room,
category: category, category: category,
), ),
onTap: () { onTap: () {
@ -542,8 +410,6 @@ class ShoppingListItemInfo extends StatelessWidget {
Text(item.name, style: textTheme.headlineLarge), Text(item.name, style: textTheme.headlineLarge),
Text(item.description, style: textTheme.titleMedium), Text(item.description, style: textTheme.titleMedium),
CategoryChip( CategoryChip(
server: server,
room: room,
category: category, category: category,
), ),
Text(Unit.fromId(item.unit).display(context, item.value)) Text(Unit.fromId(item.unit).display(context, item.value))
@ -579,14 +445,11 @@ class ShoppingListItemInfo extends StatelessWidget {
subtitle: Text(AppLocalizations.of(context)!.editItemLong), subtitle: Text(AppLocalizations.of(context)!.editItemLong),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () { onTap: () {
context.pushNamed('edit-item', params: { context.pushNamed('edit-product', params: {
'server': server, 'server': server,
'id': room, 'id': room,
'item': item.id.toString() 'item': item.id.toString()
}); });
final navInner = Navigator.of(context);
navInner.pop();
}, },
), ),
ListTile( ListTile(
@ -596,70 +459,16 @@ class ShoppingListItemInfo extends StatelessWidget {
AppLocalizations.of(context)!.deleteItemSubtitle), AppLocalizations.of(context)!.deleteItemSubtitle),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () { onTap: () {
// show popup // TODO: show confirm dialog
showDialog(
context: context,
builder: (ctx) => AlertDialog(
icon: const Icon(Icons.delete),
title: Text(
AppLocalizations.of(context)!.deleteItem),
content: Text(AppLocalizations.of(context)!
.deleteItemConfirm(item.name)),
actions: [
TextButton(
onPressed: () {
// close popup
Navigator.of(ctx).pop();
},
child: Text(
AppLocalizations.of(context)!.cancel),
),
FilledButton(
onPressed: () async {
// send request
final scaffMgr =
ScaffoldMessenger.of(ctx);
// popup context
final navInner = Navigator.of(ctx);
// bottomsheet context
final nav = Navigator.of(context);
final user = context.read<User>();
doNetworkRequest(scaffMgr,
req: () => postWithCreadentials(
path: 'deleteItem',
target: user.server,
body: {
'room': room,
'server': server,
'listItemID': item.id
},
credentials: user),
onOK: (_) async {
// remove cached item
await item.removeDisk();
},
after: () {
// close popup
navInner.pop();
// close modal bottom sheet
nav.pop();
});
},
child: Text(AppLocalizations.of(context)!
.deleteItem),
)
],
));
}), }),
] ]
: [], : [],
ListTile( ListTile(
title: Text(item.state == 0 title: Text(item.state == 0
? AppLocalizations.of(context)!.moveItemToCartTitle ? AppLocalizations.of(context)!.moveItemToCartTitle
: AppLocalizations.of(context)!.removeItemFromCartTitle), : AppLocalizations.of(context)!.moveItemToCartSubtitle),
subtitle: Text(item.state == 0 subtitle: Text(item.state == 0
? AppLocalizations.of(context)!.moveItemToCartSubtitle ? AppLocalizations.of(context)!.removeItemFromCartTitle
: AppLocalizations.of(context)!.removeItemFromCartSubtitle), : AppLocalizations.of(context)!.removeItemFromCartSubtitle),
onTap: () { onTap: () {
// flip state // flip state
@ -675,12 +484,7 @@ class ShoppingListItemInfo extends StatelessWidget {
'server': server, 'server': server,
'listItemID': item.id, 'listItemID': item.id,
'state': item.state 'state': item.state
}), }));
onOK: (_) async {
final navInner = Navigator.of(context);
await item.toDisk();
navInner.pop();
});
}) })
], ],
), ),

View file

@ -21,20 +21,10 @@ class RoomProductsPage extends StatefulWidget {
class _RoomProductsPageState extends State<RoomProductsPage> { class _RoomProductsPageState extends State<RoomProductsPage> {
List<RoomProduct> products = []; List<RoomProduct> products = [];
void fetchProducts() async { void fetchProducts() {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached products first doNetworkRequest(ScaffoldMessenger.of(context),
final cache = await RoomProduct.list(
widget.room?.serverTag ?? "", widget.room?.id ?? "");
if (mounted) {
setState(() {
products = cache;
});
}
doNetworkRequest(scaffmgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -42,13 +32,10 @@ class _RoomProductsPageState extends State<RoomProductsPage> {
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}), body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomProduct>((raw) => RoomProduct.fromJSON( .map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
widget.room!.serverTag, widget.room!.id, raw))
.toList(); .toList();
for (RoomProduct prod in resp) { // TODO: cache products
prod.toDisk();
}
if (mounted) { if (mounted) {
setState(() { setState(() {
@ -62,30 +49,13 @@ class _RoomProductsPageState extends State<RoomProductsPage> {
void initState() { void initState() {
super.initState(); super.initState();
// wait for background room product changes
RoomProduct.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
(_) async {
try {
final updated = await RoomProduct.list(
widget.room?.serverTag ?? "", widget.room?.id ?? "");
setState(() {
products = updated;
});
} catch (_) {}
});
WidgetsBinding.instance.addPostFrameCallback((_) => fetchProducts()); WidgetsBinding.instance.addPostFrameCallback((_) => fetchProducts());
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: Center( body: ListView.builder(
child: Padding(
padding: const EdgeInsets.all(14),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView.builder(
itemCount: products.length, itemCount: products.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = products[index]; final item = products[index];
@ -110,7 +80,7 @@ class _RoomProductsPageState extends State<RoomProductsPage> {
}, },
); );
}, },
)))), ),
floatingActionButton: (widget.info != null && floatingActionButton: (widget.info != null &&
((widget.info?.isAdmin ?? false) || ((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) || (widget.info?.isOwner ?? false) ||

View file

@ -36,19 +36,12 @@ class _EditProductPageState extends State<EditProductPage> {
List<RoomCategory> categories = []; List<RoomCategory> categories = [];
List<RoomProduct> products = []; List<RoomProduct> products = [];
void fetchCategories() async { void fetchCategories() {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached categories // TODO: load cached categories first
final cache = await RoomCategory.list(widget.server, widget.room);
if (mounted) {
setState(() {
categories = cache;
});
}
doNetworkRequest(scaffmgr, doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -56,8 +49,7 @@ class _EditProductPageState extends State<EditProductPage> {
body: {'room': widget.room, 'server': widget.server}), body: {'room': widget.room, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomCategory>((raw) => .map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
RoomCategory.fromJSON(widget.server, widget.room, raw))
.toList(); .toList();
setState(() { setState(() {
@ -66,19 +58,12 @@ class _EditProductPageState extends State<EditProductPage> {
}); });
} }
void fetchProducts() async { void fetchProducts() {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached products first // TODO: load cached products first
final cache = await RoomProduct.list(widget.server, widget.room);
if (mounted) {
setState(() {
products = cache;
});
}
doNetworkRequest(scaffmgr, doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -86,8 +71,7 @@ class _EditProductPageState extends State<EditProductPage> {
body: {'room': widget.room, 'server': widget.server}), body: {'room': widget.room, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomProduct>((raw) => .map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
RoomProduct.fromJSON(widget.server, widget.room, raw))
.toList(); .toList();
if (widget.product != null) { if (widget.product != null) {
@ -118,16 +102,6 @@ class _EditProductPageState extends State<EditProductPage> {
void initState() { void initState() {
super.initState(); super.initState();
// wait for background room category changes
RoomCategory.listen(widget.server, widget.room, (_) async {
try {
final updated = await RoomCategory.list(widget.server, widget.room);
setState(() {
categories = updated;
});
} catch (_) {}
});
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchCategories(); fetchCategories();
fetchProducts(); fetchProducts();
@ -218,8 +192,6 @@ class _EditProductPageState extends State<EditProductPage> {
}, },
), ),
CategoryPicker( CategoryPicker(
server: widget.server,
room: widget.room,
label: AppLocalizations.of(context)! label: AppLocalizations.of(context)!
.selectCategoryLabel, .selectCategoryLabel,
hint: AppLocalizations.of(context)! hint: AppLocalizations.of(context)!
@ -280,22 +252,7 @@ class _EditProductPageState extends State<EditProductPage> {
'ean': _ctrEAN.text, 'ean': _ctrEAN.text,
'parent': _ctrParent 'parent': _ctrParent
}), }),
onOK: (body) async { onOK: (_) async {
// cache product
final id = body["data"]["listProdID"];
final prod = RoomProduct(
id: id,
name: _ctrName.text,
server: widget.server,
room: widget.room,
description: _ctrDescription.text,
category: _ctrCategory,
defaultUnit: _ctrUnit,
defaultValue: _ctrValue,
ean: _ctrEAN.text,
parent: _ctrParent);
await prod.toDisk();
nav.pop(); nav.pop();
}); });
} else { } else {
@ -317,20 +274,6 @@ class _EditProductPageState extends State<EditProductPage> {
'parent': _ctrParent 'parent': _ctrParent
}), }),
onOK: (_) async { onOK: (_) async {
// cache product
final prod = RoomProduct(
id: widget.product!,
name: _ctrName.text,
server: widget.server,
room: widget.room,
description: _ctrDescription.text,
category: _ctrCategory,
defaultUnit: _ctrUnit,
defaultValue: _ctrValue,
ean: _ctrEAN.text,
parent: _ctrParent);
await prod.toDisk();
nav.pop(); nav.pop();
}); });
} }

View file

@ -53,24 +53,12 @@ class _ViewProductPageState extends State<ViewProductPage> {
); );
} }
void fetchCategories() async { void fetchCategories() {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached categories // TODO: load cached categories first
final cache = await RoomCategory.list(widget.server, widget.room);
if (mounted) {
Map<int?, RoomCategory> map = {};
for (RoomCategory cat in cache) { doNetworkRequest(ScaffoldMessenger.of(context),
map[cat.id] = cat;
}
setState(() {
categories = map;
});
}
doNetworkRequest(scaffmgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -78,8 +66,7 @@ class _ViewProductPageState extends State<ViewProductPage> {
body: {'room': widget.room, 'server': widget.server}), body: {'room': widget.room, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomCategory>((raw) => .map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
RoomCategory.fromJSON(widget.server, widget.room, raw))
.toList(); .toList();
Map<int?, RoomCategory> map = {}; Map<int?, RoomCategory> map = {};
@ -93,19 +80,12 @@ class _ViewProductPageState extends State<ViewProductPage> {
}); });
} }
void fetchProducts() async { void fetchProducts() {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// load cached products first // TODO: load cached products first
final cache = await RoomProduct.list(widget.server, widget.room);
if (mounted) {
setState(() {
products = cache;
});
}
doNetworkRequest(scaffmgr, doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -113,8 +93,7 @@ class _ViewProductPageState extends State<ViewProductPage> {
body: {'room': widget.room, 'server': widget.server}), body: {'room': widget.room, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomProduct>((raw) => .map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
RoomProduct.fromJSON(widget.server, widget.room, raw))
.toList(); .toList();
for (RoomProduct prod in resp) { for (RoomProduct prod in resp) {
@ -136,39 +115,6 @@ class _ViewProductPageState extends State<ViewProductPage> {
void initState() { void initState() {
super.initState(); super.initState();
// wait for background room product changes
RoomProduct.listen(widget.server, widget.room, (_) async {
try {
final updated = await RoomProduct.list(widget.server, widget.room);
for (RoomProduct prod in updated) {
// load product info
// for current product
if (prod.id == widget.product) {
setState(() {
product = prod;
});
}
}
setState(() {
products = updated;
});
} catch (_) {}
});
// wait for background room category changes
RoomCategory.listen(widget.server, widget.room, (_) async {
try {
final updated = await RoomCategory.list(widget.server, widget.room);
Map<int?, RoomCategory> map = {};
for (RoomCategory cat in updated) {
map[cat.id] = cat;
}
setState(() {
categories = map;
});
} catch (_) {}
});
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchCategories(); fetchCategories();
fetchProducts(); fetchProducts();
@ -185,11 +131,6 @@ class _ViewProductPageState extends State<ViewProductPage> {
title: Text(product?.name ?? ''), title: Text(product?.name ?? ''),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Center(
child: Padding(
padding: const EdgeInsets.all(14),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: Column(children: [ child: Column(children: [
// display product into // display product into
Center( Center(
@ -199,20 +140,14 @@ class _ViewProductPageState extends State<ViewProductPage> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text(product?.name ?? '', Text(product?.name ?? '', style: textTheme.headlineLarge),
style: textTheme.headlineLarge),
Text(product?.description ?? '', Text(product?.description ?? '',
style: textTheme.titleMedium), style: textTheme.titleMedium),
Text(product?.ean ?? ''), Text(product?.ean ?? ''),
CategoryChip( CategoryChip(category: categories[product?.category]),
server: widget.server,
room: widget.room,
category:
categories[product?.category]),
Text(product != null Text(product != null
? Unit.fromId(product!.defaultUnit) ? Unit.fromId(product!.defaultUnit)
.display( .display(context, product!.defaultValue)
context, product!.defaultValue)
: '') : '')
], ],
))), ))),
@ -222,15 +157,12 @@ class _ViewProductPageState extends State<ViewProductPage> {
...(info != null && ...(info != null &&
(info!.isAdmin || (info!.isAdmin ||
info!.isOwner || info!.isOwner ||
(info!.permissions & (info!.permissions & RoomPermission.editRoomContent != 0)))
RoomPermission.editRoomContent !=
0)))
? [ ? [
ListTile( ListTile(
title: Text(AppLocalizations.of(context)! title: Text(AppLocalizations.of(context)!.editProductTitle),
.editProductTitle), subtitle:
subtitle: Text(AppLocalizations.of(context)! Text(AppLocalizations.of(context)!.editProductSubtitle),
.editProductSubtitle),
onTap: () { onTap: () {
context.pushNamed('edit-product', params: { context.pushNamed('edit-product', params: {
'server': widget.server, 'server': widget.server,
@ -246,10 +178,10 @@ class _ViewProductPageState extends State<ViewProductPage> {
...(product?.parent != null) ...(product?.parent != null)
? [ ? [
ListTile( ListTile(
title: Text(AppLocalizations.of(context)! title: Text(
.viewParentProductTitle), AppLocalizations.of(context)!.viewParentProductTitle),
subtitle: Text(AppLocalizations.of(context)! subtitle: Text(
.viewParentProductSubtitle), AppLocalizations.of(context)!.viewParentProductSubtitle),
onTap: () { onTap: () {
context.pushNamed('view-product', params: { context.pushNamed('view-product', params: {
'server': widget.server, 'server': widget.server,
@ -263,10 +195,9 @@ class _ViewProductPageState extends State<ViewProductPage> {
: [], : [],
// show/manage children // show/manage children
ListTile( ListTile(
title: Text(AppLocalizations.of(context)! title: Text(AppLocalizations.of(context)!.viewProductChildrenTitle),
.viewProductChildrenTitle), subtitle:
subtitle: Text(AppLocalizations.of(context)! Text(AppLocalizations.of(context)!.viewProductChildrenSubtitle),
.viewProductChildrenSubtitle),
onTap: () { onTap: () {
context.pushNamed('view-product-children', params: { context.pushNamed('view-product-children', params: {
'server': widget.server, 'server': widget.server,
@ -276,101 +207,7 @@ class _ViewProductPageState extends State<ViewProductPage> {
}, },
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
), ),
...(info != null && ])),
((info?.isAdmin ?? false) ||
(info?.isOwner ?? false) ||
((info?.permissions)! &
RoomPermission.editRoomContent !=
0)))
? [
// delete product
ListTile(
title: Text(AppLocalizations.of(context)!
.deleteProductTitle),
subtitle: Text(AppLocalizations.of(context)!
.deleteProductSubtitle),
onTap: () {
// show popup
showDialog(
context: context,
builder: (ctx) => AlertDialog(
icon: const Icon(Icons.delete),
title: Text(
AppLocalizations.of(context)!
.deleteProduct),
content: Text(
AppLocalizations.of(context)!
.deleteProductConfirm(
product?.name ?? "")),
actions: [
TextButton(
onPressed: () {
// close popup
Navigator.of(ctx).pop();
},
child: Text(
AppLocalizations.of(
context)!
.cancel),
),
FilledButton(
onPressed: () async {
// send request
final scaffMgr =
ScaffoldMessenger.of(
ctx);
// popup context
final navInner =
Navigator.of(ctx);
// bottomsheet context
final nav =
Navigator.of(context);
final user =
context.read<User>();
doNetworkRequest(scaffMgr,
req: () =>
postWithCreadentials(
path:
'deleteProduct',
target:
user.server,
body: {
'room': widget
.room,
'server': widget
.server,
'listProdID':
product?.id ??
""
},
credentials:
user),
onOK: (_) async {
// remove cached product
await product!
.removeDisk();
},
after: () {
// close popup
navInner.pop();
// close modal bottom sheet
nav.pop();
});
},
child: Text(
AppLocalizations.of(
context)!
.deleteProduct),
)
],
));
},
trailing: const Icon(Icons.chevron_right),
),
]
: []
]))))),
); );
} }
} }

View file

@ -71,6 +71,6 @@ Future<void> doNetworkRequest(ScaffoldMessengerState? sm,
} }
if (after != null) { if (after != null) {
await after(); after();
} }
} }

View file

@ -5,26 +5,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.9" version: "3.3.6"
args: args:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.4.0"
async: async:
dependency: transitive dependency: transitive
description: description:
name: async name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.0" version: "2.10.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -37,18 +37,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.2.1"
checked_yaml: checked_yaml:
dependency: transitive dependency: transitive
description: description:
name: checked_yaml name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" version: "2.0.2"
cli_util: cli_util:
dependency: transitive dependency: transitive
description: description:
@ -69,10 +69,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.17.0"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -85,10 +85,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: crypto name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.2"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -101,18 +101,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.0.1"
file: file:
dependency: transitive dependency: transitive
description: description:
name: file name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "6.1.4"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -130,10 +130,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" version: "2.0.1"
flutter_localizations: flutter_localizations:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -143,10 +143,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_svg name: flutter_svg
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c sha256: "12006889e2987c549c4c1ec1a5ba4ec4b24d34d2469ee5f9476c926dcecff266"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.9" version: "2.0.4"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -161,18 +161,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: go_router name: go_router
sha256: bd7e671d26fd39c78cba82070fa34ef1f830b0e7ed1aeebccabc6561302a7ee5 sha256: "432409518740645ce7f28802171b78783197d01149fad44f9b8ae55f40277139"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.5.9" version: "6.5.0"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.6" version: "0.13.5"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -185,66 +185,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image name: image
sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" sha256: "483a389d6ccb292b570c31b3a193779b1b0178e7eb571986d9a49904b6861227"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.3" version: "4.0.15"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
name: intl name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.18.1" version: "0.17.0"
js: js:
dependency: transitive dependency: transitive
description: description:
name: js name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.7" version: "0.6.5"
json_annotation: json_annotation:
dependency: transitive dependency: transitive
description: description:
name: json_annotation name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.1" version: "4.8.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: cdd14e3836065a1f6302a236ec8b5f700695c803c57ae11a1c84df31e6bcf831
url: "https://pub.dev"
source: hosted
version: "10.0.3"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "9b2ef90589911d665277464e0482b209d39882dffaaf4ef69a3561a3354b2ebc"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: fd3cd66cb2bcd7b50dcd3b413af49d78051f809c8b3f6e047962765c15a0d23d
url: "https://pub.dev"
source: hosted
version: "3.0.0"
lints: lints:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.0.1"
localstore: localstore:
dependency: "direct main" dependency: "direct main"
description: description:
@ -257,34 +233,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: logging name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.1.1"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16+1" version: "0.12.13"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.0" version: "0.2.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.8.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -297,10 +273,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.8.2"
path_parsing: path_parsing:
dependency: transitive dependency: transitive
description: description:
@ -313,146 +289,154 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider name: path_provider
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa sha256: "04890b994ee89bfa80bf3080bfec40d5a92c5c7a785ebb02c13084a099d2b6f9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.0.13"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 sha256: "019f18c9c10ae370b08dce1f3e3b73bc9f58e7f087bb5e921f06529438ac0ae7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.0.24"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
name: path_provider_foundation name: path_provider_foundation
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" sha256: "12eee51abdf4d34c590f043f45073adbb45514a108bd9db4491547a2fd891059"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.2.0"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
name: path_provider_linux name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.1.10"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: path_provider_platform_interface name: path_provider_platform_interface
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.0.6"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.1.5"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
name: petitparser name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.2" version: "5.1.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.3" version: "3.1.0"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.7" version: "2.1.4"
pointycastle: pointycastle:
dependency: transitive dependency: transitive
description: description:
name: pointycastle name: pointycastle
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" sha256: ae73e842cdd27a3467a71d70cefd9b198538aab4fc7dde1d0e8c78c96225abf0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.3" version: "3.7.1"
process:
dependency: transitive
description:
name: process
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
url: "https://pub.dev"
source: hosted
version: "4.2.4"
provider: provider:
dependency: "direct main" dependency: "direct main"
description: description:
name: provider name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.1" version: "6.0.5"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" sha256: "78528fd87d0d08ffd3e69551173c026e8eacc7b7079c82eb6a77413957b7e394"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.0.20"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" sha256: ad423a80fe7b4e48b50d6111b3ea1027af0e959e49d485712e134863d9c1c521
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.0.17"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" sha256: "1e755f8583229f185cfca61b1d80fb2344c9d660e1c69ede5450d8f478fa5310"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.4" version: "2.1.5"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_linux name: shared_preferences_linux
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" sha256: "3a59ed10890a8409ad0faad7bb2957dab4b92b8fbe553257b05d30ed8af2c707"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.1.5"
shared_preferences_platform_interface: shared_preferences_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_platform_interface name: shared_preferences_platform_interface
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a sha256: "824bfd02713e37603b2bdade0842e47d56e7db32b1dcdd1cae533fb88e2913fc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.1.1"
shared_preferences_web: shared_preferences_web:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" sha256: "0dc2633f215a3d4aa3184c9b2c5766f4711e4e5a6b256e62aafee41f89f1bfb8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.0.6"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_windows name: shared_preferences_windows
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" sha256: "71bcd669bb9cdb6b39f22c4a7728b6d49e934f6cba73157ffa5a54f1eed67436"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.1.5"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -462,26 +446,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.9.1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.1" version: "1.11.0"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.1"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
@ -502,42 +486,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.0" version: "0.4.16"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.2" version: "1.3.1"
vector_graphics: vector_graphics:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics name: vector_graphics
sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" sha256: "4cf8e60dbe4d3a693d37dff11255a172594c0793da542183cbfe7fe978ae4aaa"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.9+1" version: "1.1.4"
vector_graphics_codec: vector_graphics_codec:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_codec name: vector_graphics_codec
sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" sha256: "278ad5f816f58b1967396d1f78ced470e3e58c9fe4b27010102c0a595c764468"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.9+1" version: "1.1.4"
vector_graphics_compiler: vector_graphics_compiler:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_compiler name: vector_graphics_compiler
sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 sha256: "0bf61ad56e6fd6688a2865d3ceaea396bc6a0a90ea0d7ad5049b1b76c09d6163"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.9+1" version: "1.1.4"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -546,54 +530,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: a2662fb1f114f4296cf3f5a50786a2d888268d7776cf681aa17d660ffa23b246
url: "https://pub.dev"
source: hosted
version: "14.0.0"
web:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev"
source: hosted
version: "0.3.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.1" version: "3.1.3"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.3" version: "1.0.0"
xml: xml:
dependency: transitive dependency: transitive
description: description:
name: xml name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.5.0" version: "6.2.2"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
name: yaml name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.2" version: "3.1.1"
sdks: sdks:
dart: ">=3.2.0 <4.0.0" dart: ">=2.19.3 <3.0.0"
flutter: ">=3.18.0-18.0.pre.54" flutter: ">=3.7.0-0"

View file

@ -1,4 +1,3 @@
---
name: outbag_app name: outbag_app
description: Official Outbag App description: Official Outbag App
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to