Compare commits

..

No commits in common. "bad485ac1d4f1da9f3d5d37456e2eae2a279705d" and "7647542421324a79310f0aa823a66396e0df87ed" have entirely different histories.

12 changed files with 126 additions and 153 deletions

View File

@ -29,8 +29,6 @@ def get(user, id):
owner = user and (user.get('_id') == proj['user']) owner = user and (user.get('_id') == proj['user'])
if not owner and proj['visibility'] != 'public': if not owner and proj['visibility'] != 'public':
raise util.errors.BadRequest('Forbidden') raise util.errors.BadRequest('Forbidden')
if obj['type'] == 'file' and 'storedName' in obj:
obj['url'] = uploads.get_presigned_url('projects/{0}/{1}'.format(proj['_id'], obj['storedName']))
if obj['type'] == 'pattern' and 'preview' in obj and '.png' in obj['preview']: if obj['type'] == 'pattern' and 'preview' in obj and '.png' in obj['preview']:
obj['previewUrl'] = uploads.get_presigned_url('projects/{0}/{1}'.format(proj['_id'], obj['preview'])) obj['previewUrl'] = uploads.get_presigned_url('projects/{0}/{1}'.format(proj['_id'], obj['preview']))
del obj['preview'] del obj['preview']

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,41 +6,31 @@ import 'group_noticeboard.dart';
import 'group_members.dart'; import 'group_members.dart';
class _GroupScreenState extends State<GroupScreen> { class _GroupScreenState extends State<GroupScreen> {
final String id;
Map<String, dynamic>? _group;
int _selectedIndex = 0; int _selectedIndex = 0;
List<Widget> _widgetOptions = <Widget> [];
final Map<String, dynamic> _group;
_GroupScreenState(this.id) { } _GroupScreenState(this._group) {
_widgetOptions = <Widget> [
@override GroupNoticeBoardTab(this._group),
void initState() { GroupMembersTab(this._group)
fetchGroup(); ];
super.initState();
} }
void fetchGroup() async { void _onItemTapped(int index) {
Api api = Api(); setState(() {
var data = await api.request('GET', '/groups/' + id); _selectedIndex = index;
if (data['success'] == true) { });
setState(() {
_group = data['payload'];
});
}
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(_group?['name'] ?? 'Group') title: Text(_group['name'])
), ),
body: Center( body: Center(
child: _group != null ? child: _widgetOptions.elementAt(_selectedIndex),
[
GroupNoticeBoardTab(_group!),
GroupMembersTab(_group!)
].elementAt(_selectedIndex)
: CircularProgressIndicator(),
), ),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[ items: const <BottomNavigationBarItem>[
@ -55,17 +45,15 @@ class _GroupScreenState extends State<GroupScreen> {
], ],
currentIndex: _selectedIndex, currentIndex: _selectedIndex,
selectedItemColor: Colors.pink[600], selectedItemColor: Colors.pink[600],
onTap: (int index) => setState(() { onTap: _onItemTapped,
_selectedIndex = index;
}),
), ),
); );
} }
} }
class GroupScreen extends StatefulWidget { class GroupScreen extends StatefulWidget {
final String id; final Map<String,dynamic> group;
GroupScreen(this.id) { } GroupScreen(this.group) { }
@override @override
_GroupScreenState createState() => _GroupScreenState(id); _GroupScreenState createState() => _GroupScreenState(group);
} }

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:go_router/go_router.dart'; import 'group.dart';
import 'api.dart'; import 'api.dart';
import 'model.dart'; import 'model.dart';
import 'lib.dart'; import 'lib.dart';
@ -16,8 +16,6 @@ class _GroupsTabState extends State<GroupsTab> {
} }
void getGroups() async { void getGroups() async {
AppModel model = Provider.of<AppModel>(context, listen: false);
if (model.user == null) return;
setState(() => _loading = true); setState(() => _loading = true);
Api api = Api(); Api api = Api();
var data = await api.request('GET', '/groups'); var data = await api.request('GET', '/groups');
@ -38,7 +36,14 @@ class _GroupsTabState extends State<GroupsTab> {
} }
return Card( return Card(
child: InkWell( child: InkWell(
onTap: () => context.push('/groups/' + group['_id']), onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GroupScreen(group),
),
);
},
child: ListTile( child: ListTile(
leading: Icon(Icons.people), leading: Icon(Icons.people),
trailing: Icon(Icons.keyboard_arrow_right), trailing: Icon(Icons.keyboard_arrow_right),

View File

@ -263,7 +263,12 @@ class PatternCard extends StatelessWidget {
), ),
child: InkWell( child: InkWell(
onTap: () { onTap: () {
context.push('/' + object['userObject']['username'] + '/' + object['projectObject']['path'] + '/' + object['_id']); Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ObjectScreen(object, object['projectObject']),
),
);
}, },
child: Column( child: Column(
children: [ children: [

View File

@ -13,9 +13,7 @@ import 'register.dart';
import 'onboarding.dart'; import 'onboarding.dart';
import 'home.dart'; import 'home.dart';
import 'project.dart'; import 'project.dart';
import 'object.dart';
import 'settings.dart'; import 'settings.dart';
import 'group.dart';
final router = GoRouter( final router = GoRouter(
routes: [ routes: [
@ -39,9 +37,7 @@ final router = GoRouter(
GoRoute(path: '/onboarding', builder: (context, state) => OnboardingScreen()), GoRoute(path: '/onboarding', builder: (context, state) => OnboardingScreen()),
GoRoute(path: '/home', builder: (context, state) => HomeScreen()), GoRoute(path: '/home', builder: (context, state) => HomeScreen()),
GoRoute(path: '/settings', builder: (context, state) => SettingsScreen()), GoRoute(path: '/settings', builder: (context, state) => SettingsScreen()),
GoRoute(path: '/groups/:id', builder: (context, state) => GroupScreen(state.pathParameters['id']!)), GoRoute(path: '/:username/:id', builder: (context, state) => ProjectScreen(state.pathParameters['username']!, state.pathParameters['id']!)),
GoRoute(path: '/:username/:path', builder: (context, state) => ProjectScreen(state.pathParameters['username']!, state.pathParameters['path']!)),
GoRoute(path: '/:username/:path/:id', builder: (context, state) => ObjectScreen(state.pathParameters['username']!, state.pathParameters['path']!, state.pathParameters['id']!)),
], ],
); );
@ -118,7 +114,9 @@ class Startup extends StatelessWidget {
_handled = true; _handled = true;
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
String? token = prefs.getString('apiToken'); String? token = prefs.getString('apiToken');
print('Nooo');
if (token != null) { if (token != null) {
print('HEE');
AppModel model = Provider.of<AppModel>(context, listen: false); AppModel model = Provider.of<AppModel>(context, listen: false);
await model.setToken(token!); await model.setToken(token!);
FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance; FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;

View File

@ -3,7 +3,6 @@ import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/flutter_html.dart';
import 'package:go_router/go_router.dart';
import 'dart:io'; import 'dart:io';
import 'api.dart'; import 'api.dart';
import 'util.dart'; import 'util.dart';
@ -11,32 +10,30 @@ import 'patterns/pattern.dart';
import 'patterns/viewer.dart'; import 'patterns/viewer.dart';
class _ObjectScreenState extends State<ObjectScreen> { class _ObjectScreenState extends State<ObjectScreen> {
final String username; final Map<String,dynamic> _project;
final String projectPath; Map<String,dynamic> _object;
final String id; Map<String,dynamic>? _pattern;
final Map<String,dynamic>? project;
Map<String,dynamic>? object;
Map<String,dynamic>? pattern;
bool _isLoading = false; bool _isLoading = false;
final Function? onUpdate; final Function? onUpdate;
final Function? onDelete; final Function? onDelete;
final Api api = Api(); final Api api = Api();
final Util util = Util(); final Util util = Util();
_ObjectScreenState(this.username, this.projectPath, this.id, {this.object, this.project, this.onUpdate, this.onDelete}) { } _ObjectScreenState(this._object, this._project, {this.onUpdate, this.onDelete}) { }
@override @override
initState() { initState() {
super.initState(); super.initState();
fetchObject(); if (_object['type'] == 'pattern') {
_fetchPattern();
}
} }
void fetchObject() async { void _fetchPattern() async {
var data = await api.request('GET', '/objects/' + id); var data = await api.request('GET', '/objects/' + _object['_id']);
if (data['success'] == true) { if (data['success'] == true) {
setState(() { setState(() {
object = data['payload']; _pattern = data['payload']['pattern'];
pattern = data['payload']['pattern'];
}); });
} }
} }
@ -44,14 +41,14 @@ class _ObjectScreenState extends State<ObjectScreen> {
void _shareObject() async { void _shareObject() async {
setState(() => _isLoading = true); setState(() => _isLoading = true);
File? file; File? file;
if (object!['type'] == 'pattern') { if (_object['type'] == 'pattern') {
var data = await api.request('GET', '/objects/' + id + '/wif'); var data = await api.request('GET', '/objects/' + _object['_id'] + '/wif');
if (data['success'] == true) { if (data['success'] == true) {
file = await util.writeFile(object!['name'] + '.wif', data['payload']['wif']); file = await util.writeFile(_object['name'] + '.wif', data['payload']['wif']);
} }
} else { } else {
String fileName = Uri.file(object!['url']).pathSegments.last; String fileName = Uri.file(_object['url']).pathSegments.last;
file = await api.downloadFile(object!['url'], fileName); file = await api.downloadFile(_object['url'], fileName);
} }
if (file != null) { if (file != null) {
@ -61,10 +58,12 @@ class _ObjectScreenState extends State<ObjectScreen> {
} }
void _deleteObject(BuildContext context, BuildContext modalContext) async { void _deleteObject(BuildContext context, BuildContext modalContext) async {
var data = await api.request('DELETE', '/objects/' + id); var data = await api.request('DELETE', '/objects/' + _object['_id']);
if (data['success']) { if (data['success']) {
context.go('/home'); Navigator.pop(context);
onDelete!(id); Navigator.pop(modalContext);
Navigator.pop(context);
onDelete!(_object['_id']);
} }
} }
@ -78,7 +77,7 @@ class _ObjectScreenState extends State<ObjectScreen> {
CupertinoDialogAction( CupertinoDialogAction(
isDefaultAction: true, isDefaultAction: true,
child: Text('No'), child: Text('No'),
onPressed: () => context.pop(), onPressed: () => Navigator.pop(context),
), ),
CupertinoDialogAction( CupertinoDialogAction(
isDestructiveAction: true, isDestructiveAction: true,
@ -106,22 +105,22 @@ class _ObjectScreenState extends State<ObjectScreen> {
TextButton( TextButton(
child: Text('CANCEL'), child: Text('CANCEL'),
onPressed: () { onPressed: () {
context.pop(); Navigator.pop(context);
}, },
), ),
TextButton( TextButton(
child: Text('OK'), child: Text('OK'),
onPressed: () async { onPressed: () async {
var data = await api.request('PUT', '/objects/' + id, {'name': renameController.text}); var data = await api.request('PUT', '/objects/' + _object['_id'], {'name': renameController.text});
if (data['success']) { if (data['success']) {
context.pop(); Navigator.pop(context);
object!['name'] = data['payload']['name']; _object['name'] = data['payload']['name'];
onUpdate!(id, data['payload']); onUpdate!(_object['_id'], data['payload']);
setState(() { setState(() {
object = object; _object = _object;
}); });
} }
context.pop(); Navigator.pop(context);
}, },
), ),
], ],
@ -137,7 +136,7 @@ class _ObjectScreenState extends State<ObjectScreen> {
return CupertinoActionSheet( return CupertinoActionSheet(
title: Text('Manage this object'), title: Text('Manage this object'),
cancelButton: CupertinoActionSheetAction( cancelButton: CupertinoActionSheetAction(
onPressed: () => modalContext.pop(), onPressed: () => Navigator.of(modalContext).pop(),
child: Text('Cancel') child: Text('Cancel')
), ),
actions: [ actions: [
@ -157,22 +156,15 @@ class _ObjectScreenState extends State<ObjectScreen> {
} }
Widget getObjectWidget() { Widget getObjectWidget() {
if (object == null) { if (_object['isImage'] == true) {
return Center(child: Column( return Image.network(_object['url']);
crossAxisAlignment: CrossAxisAlignment.center,
children: [CircularProgressIndicator()]
));
} }
else if (object!['isImage'] == true && object!['url'] != null) { else if (_object['type'] == 'pattern') {
print(object!['url']); if (_pattern != null) {
return Image.network(object!['url']); return PatternViewer(_pattern!, withEditor: true);
}
else if (object!['type'] == 'pattern') {
if (pattern != null) {
return PatternViewer(pattern!, withEditor: true);
} }
else if (object!['previewUrl'] != null) { else if (_object['previewUrl'] != null) {
return Image.network(object!['previewUrl']!);; return Image.network(_object['previewUrl']!);;
} }
else { else {
return Column( return Column(
@ -192,7 +184,7 @@ class _ObjectScreenState extends State<ObjectScreen> {
Text('Treadl cannot display this type of item.'), Text('Treadl cannot display this type of item.'),
SizedBox(height: 20), SizedBox(height: 20),
ElevatedButton(child: Text('View file'), onPressed: () { ElevatedButton(child: Text('View file'), onPressed: () {
launch(object!['url']); launch(_object['url']);
}), }),
], ],
)); ));
@ -202,11 +194,11 @@ class _ObjectScreenState extends State<ObjectScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String description = ''; String description = '';
if (object?['description'] != null) if (_object['description'] != null)
description = object!['description']!; description = _object['description'];
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(object?['name'] ?? 'Object'), title: Text(_object['name']),
actions: <Widget>[ actions: <Widget>[
IconButton( IconButton(
icon: Icon(Icons.ios_share), icon: Icon(Icons.ios_share),
@ -236,15 +228,12 @@ class _ObjectScreenState extends State<ObjectScreen> {
} }
class ObjectScreen extends StatefulWidget { class ObjectScreen extends StatefulWidget {
final String username; final Map<String,dynamic> _object;
final String projectPath; final Map<String,dynamic> _project;
final String id;
final Map<String,dynamic>? object;
final Map<String,dynamic>? project;
final Function? onUpdate; final Function? onUpdate;
final Function? onDelete; final Function? onDelete;
ObjectScreen(this.username, this.projectPath, this.id, {this.object, this.project, this.onUpdate, this.onDelete}) { } ObjectScreen(this._object, this._project, {this.onUpdate, this.onDelete}) { }
@override @override
_ObjectScreenState createState() => _ObjectScreenState(username, projectPath, id, object: object, project: project, onUpdate: onUpdate, onDelete: onDelete); _ObjectScreenState createState() => _ObjectScreenState(_object, _project, onUpdate: onUpdate, onDelete: onDelete);
} }

View File

@ -112,7 +112,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
CupertinoButton( CupertinoButton(
color: Colors.white, color: Colors.white,
child: Text('Get started', style: TextStyle(color: Colors.pink)), child: Text('Get started', style: TextStyle(color: Colors.pink)),
onPressed: () => context.go('/home'), onPressed: () => context.go('/'),
), ),
] ]
) )

View File

@ -9,7 +9,7 @@ import 'package:intl/intl.dart';
import 'dart:io'; import 'dart:io';
import 'api.dart'; import 'api.dart';
import 'util.dart'; import 'util.dart';
import 'model.dart'; import 'object.dart';
class _ProjectScreenState extends State<ProjectScreen> { class _ProjectScreenState extends State<ProjectScreen> {
final String username; final String username;
@ -252,7 +252,12 @@ class _ProjectScreenState extends State<ProjectScreen> {
return new Card( return new Card(
child: InkWell( child: InkWell(
onTap: () { onTap: () {
context.push('/' + username + '/' + projectPath + '/' + object['_id']); Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ObjectScreen(object, project!, onUpdate: _onUpdateObject, onDelete: _onDeleteObject),
),
);
}, },
child: ListTile( child: ListTile(
leading: leader, leading: leader,
@ -264,31 +269,8 @@ class _ProjectScreenState extends State<ProjectScreen> {
); );
} }
Widget getBody() {
if (_loading || project == null)
return CircularProgressIndicator();
else if ((_objects != null && _objects.length > 0) || _creating)
return ListView.builder(
itemCount: _objects.length + (_creating ? 1 : 0),
itemBuilder: (BuildContext context, int index) {
return getObjectCard(index);
},
);
else
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('This project is currently empty', style: TextStyle(fontSize: 20), textAlign: TextAlign.center),
Image(image: AssetImage('assets/empty.png'), width: 300),
Text('Add a pattern file, an image, or something else to this project using the + button below.', textAlign: TextAlign.center),
]);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
AppModel model = Provider.of<AppModel>(context);
User? user = model.user;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(project?['name'] ?? 'Project'), title: Text(project?['name'] ?? 'Project'),
@ -307,13 +289,33 @@ class _ProjectScreenState extends State<ProjectScreen> {
) : SizedBox(width: 0), ) : SizedBox(width: 0),
] ]
), ),
body: Container( body: _loading ?
Container(
margin: const EdgeInsets.all(10.0),
alignment: Alignment.center,
child: CircularProgressIndicator()
)
: Container(
margin: const EdgeInsets.all(10.0), margin: const EdgeInsets.all(10.0),
alignment: Alignment.center, child: ((_objects != null && _objects.length > 0) || _creating) ?
child: getBody(), ListView.builder(
itemCount: _objects.length + (_creating ? 1 : 0),
itemBuilder: (BuildContext context, int index) {
return getObjectCard(index);
},
)
:
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('This project is currently empty', style: TextStyle(fontSize: 20), textAlign: TextAlign.center),
Image(image: AssetImage('assets/empty.png'), width: 300),
Text('Add a pattern file, an image, or something else to this project using the + button below.', textAlign: TextAlign.center),
])
), ),
floatingActionButtonLocation: ExpandableFab.location, floatingActionButtonLocation: ExpandableFab.location,
floatingActionButton: user != null ? ExpandableFab( floatingActionButton: ExpandableFab(
distance: 70, distance: 70,
type: ExpandableFabType.up, type: ExpandableFabType.up,
openButtonBuilder: RotateFloatingActionButtonBuilder( openButtonBuilder: RotateFloatingActionButtonBuilder(
@ -353,7 +355,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
), ),
]), ]),
], ],
) : null, ),
); );
} }
} }

View File

@ -19,8 +19,6 @@ class _ProjectsTabState extends State<ProjectsTab> {
} }
void getProjects() async { void getProjects() async {
AppModel model = Provider.of<AppModel>(context, listen: false);
if (model.user == null) return;
setState(() { setState(() {
_loading = true; _loading = true;
}); });
@ -133,8 +131,6 @@ class _ProjectsTabState extends State<ProjectsTab> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
AppModel model = Provider.of<AppModel>(context);
User? user = model.user;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('My Projects'), title: Text('My Projects'),
@ -152,11 +148,11 @@ class _ProjectsTabState extends State<ProjectsTab> {
alignment: Alignment.center, alignment: Alignment.center,
child: getBody() child: getBody()
), ),
floatingActionButton: user != null ? FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: showNewProjectDialog, onPressed: showNewProjectDialog,
child: _creatingProject ? CircularProgressIndicator(backgroundColor: Colors.white) : Icon(Icons.add), child: _creatingProject ? CircularProgressIndicator(backgroundColor: Colors.white) : Icon(Icons.add),
backgroundColor: Colors.pink[500], backgroundColor: Colors.pink[500],
) : null, ),
); );
} }
} }

View File

@ -79,8 +79,6 @@ class SettingsScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
AppModel model = Provider.of<AppModel>(context);
User? user = model.user;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('About Treadl'), title: Text('About Treadl'),
@ -98,24 +96,16 @@ class SettingsScreen extends StatelessWidget {
), ),
SizedBox(height: 30), SizedBox(height: 30),
user != null ? Column( ListTile(
children: [ leading: Icon(Icons.exit_to_app),
ListTile( title: Text('Logout'),
leading: Icon(Icons.exit_to_app), onTap: () => _logout(context),
title: Text('Logout'), ),
onTap: () => _logout(context), ListTile(
), leading: Icon(Icons.delete),
ListTile( title: Text('Delete Account'),
leading: Icon(Icons.delete), onTap: () => _deleteAccount(context),
title: Text('Delete Account'),
onTap: () => _deleteAccount(context),
),
]
) : CupertinoButton(
color: Colors.pink,
child: Text('Join Treadl', style: TextStyle(color: Colors.white)),
onPressed: () => context.push('/welcome'),
), ),
SizedBox(height: 30), SizedBox(height: 30),