384fbb0573
The edit item screen might be overwhelming at first, and if you only want to add simple items (by name) to the list, it is way easier to simply type the name and click create to create a simple item. After creating the item the user will be redirected (history replacement) to the edit screen, but clicking back will bring them back to the list. This screen also makes linking products easier and allows the user to create new products if they notice they are using the same item multiple times or can't be bothered to switch to the products tab
339 lines
14 KiB
Dart
339 lines
14 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import 'package:outbag_app/backend/themes.dart';
|
|
import 'package:outbag_app/backend/user.dart';
|
|
import 'package:outbag_app/backend/request.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/new.dart';
|
|
import 'package:outbag_app/screens/room/products/edit.dart';
|
|
import 'package:outbag_app/screens/room/products/view.dart';
|
|
|
|
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
|
|
|
import 'package:outbag_app/screens/home.dart';
|
|
import 'package:outbag_app/screens/welcome.dart';
|
|
import 'package:outbag_app/screens/auth.dart';
|
|
|
|
import 'package:outbag_app/screens/room/new.dart';
|
|
import 'package:outbag_app/screens/room/join.dart';
|
|
import 'package:outbag_app/screens/room/main.dart';
|
|
import 'package:outbag_app/screens/room/about/members.dart';
|
|
import 'package:outbag_app/screens/room/about/permissions.dart';
|
|
import 'package:outbag_app/screens/settings/main.dart';
|
|
|
|
import 'package:provider/provider.dart';
|
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|
|
|
void main() {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
runApp(const OutbagApp());
|
|
}
|
|
|
|
final GlobalKey<NavigatorState> _rootNavigatorKey =
|
|
GlobalKey<NavigatorState>(debugLabel: 'root');
|
|
final GlobalKey<NavigatorState> _userShellNavigatorKey =
|
|
GlobalKey<NavigatorState>(debugLabel: 'user');
|
|
|
|
class OutbagApp extends StatefulWidget {
|
|
const OutbagApp({super.key});
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _OutbagAppState();
|
|
}
|
|
|
|
class _OutbagAppState extends State {
|
|
// assume user is logged in
|
|
// unless not userdata is found
|
|
// or the userdata turns out to be wrong
|
|
User? user;
|
|
|
|
AppTheme theme = AppTheme.auto;
|
|
|
|
void loadTheme() async {
|
|
// load theme
|
|
try {
|
|
final theme = await AppTheme.fromDisk();
|
|
setState(() {
|
|
this.theme = theme;
|
|
});
|
|
} catch (_) {}
|
|
}
|
|
|
|
void loadUser() async {
|
|
// load user
|
|
try {
|
|
final user = await User.fromDisk();
|
|
setState(() {
|
|
this.user = user;
|
|
});
|
|
} catch (_) {
|
|
// user unavailable
|
|
// invalid credentials
|
|
// log out
|
|
setState(() {
|
|
user = null;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
loadTheme();
|
|
loadUser();
|
|
});
|
|
}
|
|
|
|
Future<AccountMeta?> fetchInfo(User user) async {
|
|
AccountMeta? info;
|
|
// try to obtain user account information
|
|
// with existing details
|
|
// NOTE: also functions as a way to verify ther data
|
|
await doNetworkRequest(null,
|
|
req: () => postWithCreadentials(
|
|
target: user.server,
|
|
path: 'getMyAccount',
|
|
credentials: user,
|
|
body: {}),
|
|
onOK: (body) async {
|
|
final i = AccountMeta.fromJSON(body['data']);
|
|
info = i;
|
|
},
|
|
onServerErr: (_) {
|
|
info = null;
|
|
|
|
setState(() {
|
|
this.user = null;
|
|
});
|
|
return true;
|
|
},
|
|
onNetworkErr: () {
|
|
info = null;
|
|
// user is currently offline
|
|
// approve login,
|
|
// until user goes back offline
|
|
// NOTE TODO: check user data once online
|
|
return true;
|
|
});
|
|
|
|
return info;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MultiProvider(
|
|
providers: [
|
|
Provider<AppTheme>.value(value: theme),
|
|
],
|
|
child: MaterialApp.router(
|
|
title: "Outbag",
|
|
localizationsDelegates: const [
|
|
GlobalMaterialLocalizations.delegate,
|
|
GlobalWidgetsLocalizations.delegate,
|
|
GlobalCupertinoLocalizations.delegate,
|
|
AppLocalizations.delegate
|
|
],
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
themeMode: theme.mode,
|
|
theme: ThemeData(useMaterial3: true, brightness: Brightness.light),
|
|
darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark),
|
|
routerConfig: GoRouter(
|
|
navigatorKey: _rootNavigatorKey,
|
|
initialLocation: '/',
|
|
redirect: (context, state) async {
|
|
if (user == null) {
|
|
// prelogin
|
|
if (!state.subloc.startsWith('/welcome')) {
|
|
// prevent unauthorized user from accessing home
|
|
return '/welcome';
|
|
}
|
|
} else {
|
|
// post login
|
|
if (state.subloc.startsWith('/welcome')) {
|
|
// prevent authorized user from accessing /welcome
|
|
return '/';
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
routes: <RouteBase>[
|
|
// unauthorized routes
|
|
GoRoute(
|
|
name: 'welcome',
|
|
path: '/welcome',
|
|
builder: (context, state) => const WelcomePage(),
|
|
routes: <RouteBase>[
|
|
GoRoute(
|
|
name: 'signin',
|
|
path: 'signin',
|
|
builder: (context, state) =>
|
|
AuthPage(mode: Mode.signin, refresh: loadUser),
|
|
),
|
|
GoRoute(
|
|
name: 'signup',
|
|
path: 'signup',
|
|
builder: (context, state) =>
|
|
AuthPage(mode: Mode.signup, refresh: loadUser),
|
|
),
|
|
GoRoute(
|
|
name: 'signup-ota',
|
|
path: 'signup-ota',
|
|
builder: (context, state) =>
|
|
AuthPage(mode: Mode.signupOTA, refresh: loadUser),
|
|
),
|
|
]),
|
|
|
|
// authorized routes
|
|
ShellRoute(
|
|
navigatorKey: _userShellNavigatorKey,
|
|
builder: (context, state, child) => Provider.value(
|
|
value: user!,
|
|
child: FutureProvider(
|
|
initialData: null,
|
|
child: child,
|
|
create: (context) => fetchInfo(context.read<User>()),
|
|
)),
|
|
routes: <RouteBase>[
|
|
GoRoute(
|
|
path: '/',
|
|
name: 'home',
|
|
builder: (context, state) => const HomePage(),
|
|
routes: [
|
|
GoRoute(
|
|
name: 'settings',
|
|
path: 'settings',
|
|
builder: (context, state) => SettingsPage(
|
|
refreshTheme: loadTheme,
|
|
refreshUser: loadUser)),
|
|
GoRoute(
|
|
path: 'join-room',
|
|
name: 'add-room',
|
|
builder: (context, state) =>
|
|
const JoinRoomPage(),
|
|
routes: <RouteBase>[
|
|
GoRoute(
|
|
path: 'new',
|
|
name: 'new-room',
|
|
builder: (context, state) =>
|
|
NewRoomPage()),
|
|
]),
|
|
GoRoute(
|
|
name: 'room',
|
|
path: 'r/:server/:id',
|
|
builder: (context, state) => RoomPage(
|
|
state.params['server'] ?? '',
|
|
state.params['id'] ?? ''),
|
|
routes: <RouteBase>[
|
|
GoRoute(
|
|
name: 'edit-room',
|
|
path: 'edit',
|
|
builder: (context, state) => NewRoomPage(
|
|
server: state.params['server'] ?? '',
|
|
tag: state.params['id'] ?? '')),
|
|
GoRoute(
|
|
name: 'room-members',
|
|
path: 'members',
|
|
builder: (context, state) =>
|
|
ManageRoomMembersPage(
|
|
state.params['server'] ?? '',
|
|
state.params['id'] ?? '')),
|
|
GoRoute(
|
|
name: 'room-permissions',
|
|
path: 'roles',
|
|
builder: (context, state) =>
|
|
EditRoomPermissionSetPage(
|
|
state.params['server'] ?? '',
|
|
state.params['id'] ?? '')),
|
|
GoRoute(
|
|
name: 'new-category',
|
|
path: 'new-category',
|
|
builder: (context, state) =>
|
|
EditCategoryPage(
|
|
state.params['server'] ?? '',
|
|
state.params['id'] ?? '')),
|
|
GoRoute(
|
|
name: 'edit-category',
|
|
path: 'edit-category/:category',
|
|
builder: (context, state) =>
|
|
EditCategoryPage(
|
|
state.params['server'] ?? '',
|
|
state.params['id'] ?? '',
|
|
id: int.tryParse(
|
|
state.params['category'] ??
|
|
''))),
|
|
GoRoute(
|
|
name: 'new-product',
|
|
path: 'new-product',
|
|
builder: (context, state) =>
|
|
EditProductPage(
|
|
server:
|
|
state.params['server'] ?? '',
|
|
room: state.params['id'] ?? '',
|
|
)),
|
|
GoRoute(
|
|
name: 'view-product',
|
|
path: 'p/:product',
|
|
builder: (context, state) =>
|
|
ViewProductPage(
|
|
server:
|
|
state.params['server'] ?? '',
|
|
room: state.params['id'] ?? '',
|
|
product: int.tryParse(
|
|
state.params['product'] ??
|
|
'') ??
|
|
0),
|
|
routes: [
|
|
GoRoute(
|
|
name: 'edit-product',
|
|
path: 'edit',
|
|
builder: (context, state) =>
|
|
EditProductPage(
|
|
server: state
|
|
.params['server'] ??
|
|
'',
|
|
room: state.params['id'] ??
|
|
'',
|
|
product: int.tryParse(
|
|
state.params[
|
|
'product'] ??
|
|
''))),
|
|
]),
|
|
GoRoute(
|
|
name: 'new-item',
|
|
path: 'new-item',
|
|
builder: (context, state) => NewItemPage(
|
|
server:
|
|
state.params['server'] ?? '',
|
|
room: state.params['id'] ?? '',
|
|
)),
|
|
GoRoute(
|
|
name: 'edit-item',
|
|
path: 'i/:item',
|
|
builder: (context, state) => EditItemPage(
|
|
server: state.params['server'] ?? '',
|
|
room: state.params['id'] ?? '',
|
|
item: int.tryParse(
|
|
state.params['item'] ?? '') ??
|
|
0),
|
|
)
|
|
])
|
|
]),
|
|
]),
|
|
|
|
// routes that can be accessed
|
|
// with and without an account
|
|
// i.e the about screen
|
|
GoRoute(
|
|
path: '/about',
|
|
name: 'about',
|
|
builder: (context, state) => const Text('About'))
|
|
]),
|
|
));
|
|
}
|
|
}
|