Compare commits

..

13 Commits

33 changed files with 930 additions and 808 deletions

3
api/.gitignore vendored
View File

@ -7,4 +7,5 @@ __pycache__/
config-prod.yml
envfile
firebase.json
.DS_Store
.DS_Store
migration_projects/

View File

@ -29,6 +29,9 @@ def get(user, id):
owner = user and (user.get('_id') == proj['user'])
if not owner and proj['visibility'] != 'public':
raise util.errors.BadRequest('Forbidden')
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']))
del obj['preview']
return obj
def copy_to_project(user, id, project_id):

View File

@ -122,6 +122,9 @@ def get_objects(user, username, path):
for obj in objs:
if obj['type'] == 'file' and 'storedName' in obj:
obj['url'] = uploads.get_presigned_url('projects/{0}/{1}'.format(project['_id'], obj['storedName']))
if obj['type'] == 'pattern' and 'preview' in obj and '.png' in obj['preview']:
obj['previewUrl'] = uploads.get_presigned_url('projects/{0}/{1}'.format(project['_id'], obj['preview']))
del obj['preview']
return objs
def create_object(user, username, path, data):

View File

@ -91,6 +91,9 @@ def explore(page = 1):
objects = list(db.objects.find({'project': {'$in': all_public_project_ids}, 'name': {'$not': re.compile('untitled pattern', re.IGNORECASE)}, 'preview': {'$exists': True}}, {'project': 1, 'name': 1, 'createdAt': 1, 'preview': 1}).sort('createdAt', pymongo.DESCENDING).skip((page - 1) * per_page).limit(per_page))
for object in objects:
object['projectObject'] = project_map.get(object['project'])
if 'preview' in object and '.png' in object['preview']:
object['previewUrl'] = uploads.get_presigned_url('projects/{0}/{1}'.format(object['project'], object['preview']))
del object['preview']
authors = list(db.users.find({'_id': {'$in': list(map(lambda o: o.get('projectObject', {}).get('user'), objects))}}, {'username': 1, 'avatar': 1, 'isSilverSupporter': 1, 'isGoldSupporter': 1}))
for a in authors:
if 'avatar' in a:

View File

@ -4,7 +4,7 @@ from bson.objectid import ObjectId
import boto3
from botocore.client import Config
import blurhash
from util import database
from util import database, util
def sanitise_filename(s):
bad_chars = re.compile('[^a-zA-Z0-9_.]')

View File

@ -0,0 +1,21 @@
# Script to migrate from the old data: string URLs for images to image files directly on S3.
from pymongo import MongoClient
import base64, os
db = MongoClient('mongodb://USER:PASS@db/admin')['treadl']
os.makedirs('migration_projects/projects', exist_ok=True)
for obj in db.objects.find({'preview': {'$regex': '^data:'}}, {'preview': 1, 'project': 1}):
preview = obj['preview']
preview = preview.replace('data:image/png;base64,', '')
imgdata = base64.b64decode(preview)
filename = 'some_image.png'
os.makedirs('migration_projects/projects/'+str(obj['project']), exist_ok=True)
with open('migration_projects/projects/'+str(obj['project'])+'/preview_'+str(obj['_id'])+'.png' , 'wb') as f:
f.write(imgdata)
db.objects.update_one({'_id': obj['_id']}, {'$set': {'previewNew': 'preview_'+str(obj['_id'])+'.png'}})
#exit()

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
<string>11.0</string>
</dict>
</plist>

View File

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@ -1,108 +1,91 @@
PODS:
- Firebase/CoreOnly (8.11.0):
- FirebaseCore (= 8.11.0)
- Firebase/Messaging (8.11.0):
- Firebase/CoreOnly (10.9.0):
- FirebaseCore (= 10.9.0)
- Firebase/Messaging (10.9.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 8.11.0)
- firebase_core (1.13.1):
- Firebase/CoreOnly (= 8.11.0)
- FirebaseMessaging (~> 10.9.0)
- firebase_core (2.13.1):
- Firebase/CoreOnly (= 10.9.0)
- Flutter
- firebase_messaging (11.2.8):
- Firebase/Messaging (= 8.11.0)
- firebase_messaging (14.6.2):
- Firebase/Messaging (= 10.9.0)
- firebase_core
- Flutter
- FirebaseCore (8.11.0):
- FirebaseCoreDiagnostics (~> 8.0)
- GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/Logger (~> 7.7)
- FirebaseCoreDiagnostics (8.12.0):
- GoogleDataTransport (~> 9.1)
- GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/Logger (~> 7.7)
- nanopb (~> 2.30908.0)
- FirebaseInstallations (8.12.0):
- FirebaseCore (~> 8.0)
- GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/UserDefaults (~> 7.7)
- PromisesObjC (< 3.0, >= 1.2)
- FirebaseMessaging (8.11.0):
- FirebaseCore (~> 8.0)
- FirebaseInstallations (~> 8.0)
- GoogleDataTransport (~> 9.1)
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
- GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/Reachability (~> 7.7)
- GoogleUtilities/UserDefaults (~> 7.7)
- nanopb (~> 2.30908.0)
- FirebaseCore (10.9.0):
- FirebaseCoreInternal (~> 10.0)
- GoogleUtilities/Environment (~> 7.8)
- GoogleUtilities/Logger (~> 7.8)
- FirebaseCoreInternal (10.10.0):
- "GoogleUtilities/NSData+zlib (~> 7.8)"
- FirebaseInstallations (10.10.0):
- FirebaseCore (~> 10.0)
- GoogleUtilities/Environment (~> 7.8)
- GoogleUtilities/UserDefaults (~> 7.8)
- PromisesObjC (~> 2.1)
- FirebaseMessaging (10.9.0):
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleDataTransport (~> 9.2)
- GoogleUtilities/AppDelegateSwizzler (~> 7.8)
- GoogleUtilities/Environment (~> 7.8)
- GoogleUtilities/Reachability (~> 7.8)
- GoogleUtilities/UserDefaults (~> 7.8)
- nanopb (< 2.30910.0, >= 2.30908.0)
- Flutter (1.0.0)
- fluttertoast (0.0.2):
- Flutter
- Toast
- GoogleDataTransport (9.1.2):
- GoogleUtilities/Environment (~> 7.2)
- nanopb (~> 2.30908.0)
- GoogleDataTransport (9.2.3):
- GoogleUtilities/Environment (~> 7.7)
- nanopb (< 2.30910.0, >= 2.30908.0)
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/AppDelegateSwizzler (7.7.0):
- GoogleUtilities/AppDelegateSwizzler (7.11.1):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Environment (7.7.0):
- GoogleUtilities/Environment (7.11.1):
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/Logger (7.7.0):
- GoogleUtilities/Logger (7.11.1):
- GoogleUtilities/Environment
- GoogleUtilities/Network (7.7.0):
- GoogleUtilities/Network (7.11.1):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (7.7.0)"
- GoogleUtilities/Reachability (7.7.0):
- "GoogleUtilities/NSData+zlib (7.11.1)"
- GoogleUtilities/Reachability (7.11.1):
- GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (7.7.0):
- GoogleUtilities/UserDefaults (7.11.1):
- GoogleUtilities/Logger
- image_picker (0.0.1):
- image_picker_ios (0.0.1):
- Flutter
- nanopb (2.30908.0):
- nanopb/decode (= 2.30908.0)
- nanopb/encode (= 2.30908.0)
- nanopb/decode (2.30908.0)
- nanopb/encode (2.30908.0)
- PromisesObjC (2.0.0)
- shared_preferences_ios (0.0.1):
- nanopb (2.30909.0):
- nanopb/decode (= 2.30909.0)
- nanopb/encode (= 2.30909.0)
- nanopb/decode (2.30909.0)
- nanopb/encode (2.30909.0)
- PromisesObjC (2.2.0)
- shared_preferences_foundation (0.0.1):
- Flutter
- Toast (4.0.0)
- FlutterMacOS
- url_launcher_ios (0.0.1):
- Flutter
- video_player_avfoundation (0.0.1):
- Flutter
- wakelock (0.0.1):
- Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
DEPENDENCIES:
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
- Flutter (from `Flutter`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- image_picker (from `.symlinks/plugins/image_picker/ios`)
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
SPEC REPOS:
trunk:
- Firebase
- FirebaseCore
- FirebaseCoreDiagnostics
- FirebaseCoreInternal
- FirebaseInstallations
- FirebaseMessaging
- GoogleDataTransport
- GoogleUtilities
- nanopb
- PromisesObjC
- Toast
EXTERNAL SOURCES:
firebase_core:
@ -111,43 +94,30 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/firebase_messaging/ios"
Flutter:
:path: Flutter
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
image_picker:
:path: ".symlinks/plugins/image_picker/ios"
shared_preferences_ios:
:path: ".symlinks/plugins/shared_preferences_ios/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/ios"
wakelock:
:path: ".symlinks/plugins/wakelock/ios"
webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
SPEC CHECKSUMS:
Firebase: 44dd9724c84df18b486639e874f31436eaa9a20c
firebase_core: 08f6a85f62060111de5e98d6a214810d11365de9
firebase_messaging: 36238f3d0b933af8c919aef608408aae06ba22e8
FirebaseCore: 2f4f85b453cc8fea4bb2b37e370007d2bcafe3f0
FirebaseCoreDiagnostics: 3b40dfadef5b90433a60ae01f01e90fe87aa76aa
FirebaseInstallations: 25764cf322e77f99449395870a65b2bef88e1545
FirebaseMessaging: 02e248e8997f71fa8cc9d78e9d49ec1a701ba14a
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037
GoogleDataTransport: 629c20a4d363167143f30ea78320d5a7eb8bd940
GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1
image_picker: 541dcbb3b9cf32d87eacbd957845d8651d6c62c3
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
webview_flutter_wkwebview: 005fbd90c888a42c5690919a1527ecc6649e1162
Firebase: bd152f0f3d278c4060c5c71359db08ebcfd5a3e2
firebase_core: ce64b0941c6d87c6ef5022ae9116a158236c8c94
firebase_messaging: 42912365e62efc1ea3e00724e5eecba6068ddb88
FirebaseCore: b68d3616526ec02e4d155166bbafb8eca64af557
FirebaseCoreInternal: 971029061d326000d65bfdc21f5502c75c8b0893
FirebaseInstallations: 52153982b057d3afcb4e1fbb3eb0b6d00611e681
FirebaseMessaging: 6b7052cc3da7bc8e5f72bef871243e8f04a14eed
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
GoogleDataTransport: f0308f5905a745f94fb91fea9c6cbaf3831cb1bd
GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431
PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
COCOAPODS: 1.10.1
COCOAPODS: 1.12.0

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@ -232,6 +232,7 @@
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -246,6 +247,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);

View File

@ -46,5 +46,9 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

@ -6,23 +6,23 @@ import 'package:shared_preferences/shared_preferences.dart';
class Api {
String _token;
String? _token;
final String apiBase = 'https://api.treadl.com';
//final String apiBase = 'http://localhost:2001';
//final String apiBase = 'http://192.168.5.86:2001';
Future<String> loadToken() async {
Future<String?> loadToken() async {
if (_token != null) {
return _token;
return _token!;
}
SharedPreferences prefs = await SharedPreferences.getInstance();
final String token = prefs.getString('apiToken');
String? token = prefs.getString('apiToken');
return token;
}
Future<Map<String,String>> getHeaders(method) async {
Map<String,String> headers = {};
String token = await loadToken();
String? token = await loadToken();
if (token != null) {
headers['Authorization'] = 'Bearer ' + token;
headers['Authorization'] = 'Bearer ' + token!;
}
if (method == 'POST' || method == 'DELETE') {
headers['Content-Type'] = 'application/json';
@ -34,17 +34,23 @@ class Api {
http.Client client = http.Client();
return await client.get(url, headers: await getHeaders('GET'));
}
Future<http.Response> _post(Uri url, Map<String, dynamic> data) async {
String json = jsonEncode(data);
Future<http.Response> _post(Uri url, Map<String, dynamic>? data) async {
String? json = null;
if (data != null) {
json = jsonEncode(data!);
}
http.Client client = http.Client();
return await client.post(url, headers: await getHeaders('POST'), body: json);
}
Future<http.Response> _put(Uri url, Map<String, dynamic> data) async {
String json = jsonEncode(data);
Future<http.Response> _put(Uri url, Map<String, dynamic>? data) async {
String? json = null;
if (data != null) {
json = jsonEncode(data!);
}
http.Client client = http.Client();
return await client.put(url, headers: await getHeaders('POST'), body: json);
}
Future<http.Response> _delete(Uri url, [Map<String, dynamic> data]) async {
Future<http.Response> _delete(Uri url, [Map<String, dynamic>? data]) async {
http.Client client = http.Client();
if (data != null) {
String json = jsonEncode(data);
@ -54,10 +60,10 @@ class Api {
}
}
Future<Map<String, dynamic>> request(String method, String path, [Map<String, dynamic> data]) async {
Future<Map<String, dynamic>> request(String method, String path, [Map<String, dynamic>? data]) async {
String url = apiBase + path;
Uri uri = Uri.parse(url);
http.Response response;
http.Response? response;
if (method == 'POST') {
response = await _post(uri, data);
}
@ -70,16 +76,19 @@ class Api {
if (method == 'DELETE') {
response = await _delete(uri, data);
}
int status = response.statusCode;
if (response == null) {
return {'success': false, 'message': 'No response for your request'};
}
int status = response!.statusCode;
if (status == 200) {
print('SUCCESS');
Map<String, dynamic> respData = jsonDecode(response.body);
Map<String, dynamic> respData = jsonDecode(response!.body);
return {'success': true, 'payload': respData};
}
else {
print('ERROR');
Map<String, dynamic> respData = jsonDecode(response.body);
return {'success': false, 'code': response.statusCode, 'message': respData['message']};
Map<String, dynamic> respData = jsonDecode(response!.body);
return {'success': false, 'code': status, 'message': respData['message']};
}
}

View File

@ -13,8 +13,8 @@ class Alert extends StatelessWidget {
final String title;
final String description;
final String actionText;
final Widget descriptionWidget;
final Function action;
final Widget? descriptionWidget;
final Function? action;
Alert({this.type = 'info', this.title = '', this.description = '', this.descriptionWidget = null, this.actionText = 'Click here', this.action}) {}
@override
@ -39,7 +39,7 @@ class Alert extends StatelessWidget {
color: accentColor,
borderRadius: new BorderRadius.all(Radius.circular(10.0)),
boxShadow: [
BoxShadow(color: Colors.grey[50], spreadRadius: 5),
BoxShadow(color: Colors.grey[50]!, spreadRadius: 5),
],
),
child: Column(
@ -48,12 +48,12 @@ class Alert extends StatelessWidget {
Icon(icon, color: color),
SizedBox(height: 20),
Text(description, textAlign: TextAlign.center),
descriptionWidget,
descriptionWidget != null ? descriptionWidget! : Text(""),
action != null ? CupertinoButton(
child: Text(actionText),
onPressed: action,
) : null
].where((o) => o != null).toList()
onPressed: () => action!(),
) : Text("")
]
)
);
}
@ -61,8 +61,8 @@ class Alert extends StatelessWidget {
class NoticeboardPost extends StatefulWidget {
final Map<String,dynamic> _entry;
final Function onDelete;
final Function onReply;
final Function? onDelete;
final Function? onReply;
NoticeboardPost(this._entry, {this.onDelete = null, this.onReply = null});
_NoticeboardPostState createState() => _NoticeboardPostState(_entry, onDelete: onDelete, onReply: onReply);
}
@ -70,8 +70,8 @@ class _NoticeboardPostState extends State<NoticeboardPost> {
final Map<String,dynamic> _entry;
final Util utils = new Util();
final Api api = new Api();
final Function onDelete;
final Function onReply;
final Function? onDelete;
final Function? onReply;
final TextEditingController _replyController = TextEditingController();
bool _isReplying = false;
bool _replying = false;
@ -84,7 +84,9 @@ class _NoticeboardPostState extends State<NoticeboardPost> {
if (data['success'] == true) {
_replyController.value = TextEditingValue(text: '');
FocusScope.of(context).requestFocus(FocusNode());
onReply(data['payload']);
if (onReply != null) {
onReply!(data['payload']);
}
setState(() {
_replying = false;
_isReplying = false;
@ -95,7 +97,9 @@ class _NoticeboardPostState extends State<NoticeboardPost> {
void _deletePost() async {
var data = await api.request('DELETE', '/groups/' + _entry['group'] + '/entries/' + _entry['_id']);
if (data['success'] == true) {
onDelete(_entry);
if (onDelete != null) {
onDelete!(_entry);
}
Navigator.of(context).pop();
}
}
@ -104,17 +108,17 @@ class _NoticeboardPostState extends State<NoticeboardPost> {
Widget build(BuildContext context) {
var createdAt = DateTime.parse(_entry['createdAt']);
bool isReply = _entry['inReplyTo'] != null;
int replyCount = _entry['replies'] == null ? 0 : _entry['replies'].length;
int replyCount = _entry['replies'] == null ? 0 : _entry['replies']!.length;
String replyText = 'Write a reply...';
if (replyCount == 1) replyText = '1 Reply';
if (replyCount > 1) replyText = replyCount.toString() + ' replies';
if (_isReplying) replyText = 'Cancel reply';
List<Widget> replyWidgets = [];
if (_entry['replies'] != null) {
for (int i = 0; i < _entry['replies'].length; i++) {
for (int i = 0; i < _entry['replies']!.length; i++) {
replyWidgets.add(new Container(
key: Key(_entry['replies'][i]['_id']),
child: NoticeboardPost(_entry['replies'][i], onDelete: onDelete)
key: Key(_entry['replies']![i]['_id']),
child: NoticeboardPost(_entry['replies']![i], onDelete: onDelete)
));
}
}
@ -127,20 +131,20 @@ class _NoticeboardPostState extends State<NoticeboardPost> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
color: Colors.orange,
ElevatedButton(
//color: Colors.orange,
onPressed: () {
launch('https://www.treadl.com');
},
child: Text('Report this post'),
),
SizedBox(height: 10),
RaisedButton(
color: Colors.red,
ElevatedButton(
//color: Colors.red,
onPressed: _deletePost,
child: Text('Delete post'),
),
FlatButton(
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
@ -176,18 +180,18 @@ class _NoticeboardPostState extends State<NoticeboardPost> {
!isReply ? GestureDetector(
onTap: () => setState(() => _isReplying = !_isReplying),
child: Text(replyText, style: TextStyle(color: replyCount > 0 ? Colors.pink : Colors.black, fontSize: 10, fontWeight: FontWeight.bold)),
): null,
].where((o) => o != null).toList(),
): SizedBox(width: 0),
],
),
Row(children: [
SizedBox(width: 45),
Expanded(child: Text(_entry['content'], textAlign: TextAlign.left))
]),
_isReplying ? NoticeboardInput(_replyController, _sendReply, _replying, label: 'Reply to this post') : null,
_isReplying ? NoticeboardInput(_replyController, _sendReply, _replying, label: 'Reply to this post') : SizedBox(width: 0),
Column(
children: replyWidgets
),
].where((o) => o != null).toList(),
],
))
);
}
@ -215,7 +219,7 @@ class NoticeboardInput extends StatelessWidget {
),
)),
IconButton(
onPressed: _onPost,
onPressed: () => _onPost!(),
color: Colors.pink,
icon: _posting ? CircularProgressIndicator() : Icon(Icons.send),
)

View File

@ -80,9 +80,9 @@ class _LoginScreenState extends State<LoginScreen> {
)]
),
SizedBox(height: 20),
RaisedButton(
ElevatedButton(
onPressed: () => _submit(context),
color: Colors.pink,
//color: Colors.pink,
child: _loggingIn ? SizedBox(height: 20, width: 20, child:CircularProgressIndicator(backgroundColor: Colors.white)) : Text("Login",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 15)

View File

@ -3,7 +3,7 @@ import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:fluttertoast/fluttertoast.dart';
//import 'package:fluttertoast/fluttertoast.dart';
import 'api.dart';
import 'store.dart';
import 'welcome.dart';
@ -42,7 +42,7 @@ class _AppState extends State<MyApp> {
title: 'Treadl',
theme: ThemeData(
primarySwatch: Colors.pink,
textSelectionColor: Colors.blue,
//textSelectionColor: Colors.blue,
),
home: Startup(),
routes: <String, WidgetBuilder>{
@ -64,12 +64,12 @@ class Startup extends StatelessWidget {
Startup() {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (message.notification != null) {
print(message.notification.body);
print(message.notification!);
String text = '';
if (message.notification != null && message.notification.body != null) {
text = message.notification.body;
if (message.notification != null && message.notification!.body != null) {
text = message.notification!.body!;
}
Fluttertoast.showToast(
/*Fluttertoast.showToast(
msg: text,
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.TOP,
@ -77,7 +77,7 @@ class Startup extends StatelessWidget {
backgroundColor: Colors.grey[100],
textColor: Colors.black,
fontSize: 16.0
);
);*/
}
});
}
@ -86,9 +86,9 @@ class Startup extends StatelessWidget {
if (_handled) return;
_handled = true;
SharedPreferences prefs = await SharedPreferences.getInstance();
final String token = prefs.getString('apiToken');
String? token = prefs.getString('apiToken');
if (token != null) {
Provider.of<Store>(context, listen: false).setToken(token);
Provider.of<Store>(context, listen: false).setToken(token!);
FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
await _firebaseMessaging.requestPermission(
@ -100,16 +100,14 @@ class Startup extends StatelessWidget {
provisional: false,
sound: true,
);
String _pushToken = await _firebaseMessaging.getToken();
String? _pushToken = await _firebaseMessaging.getToken();
if (_pushToken != null) {
print("sending push");
Api api = Api();
api.request('PUT', '/accounts/pushToken', {'pushToken': _pushToken});
api.request('PUT', '/accounts/pushToken', {'pushToken': _pushToken!});
}
print('111');
// Push without including current route in stack:
Navigator.of(context, rootNavigator: true).pushNamedAndRemoveUntil('/home', (Route<dynamic> route) => false);
print('222');
} else {
Navigator.of(context).pushNamedAndRemoveUntil('/welcome', (Route<dynamic> route) => false);
}

View File

@ -6,11 +6,13 @@ import 'package:flutter_html/flutter_html.dart';
import 'api.dart';
class _ObjectScreenState extends State<ObjectScreen> {
final Map<String,dynamic> _object;
final Map<String,dynamic> _project;
Map<String,dynamic> _object;
final Function _onUpdate;
final Function _onDelete;
final Api api = Api();
_ObjectScreenState(this._object, this._onDelete) { }
_ObjectScreenState(this._object, this._project, this._onUpdate, this._onDelete) { }
void _deleteObject(BuildContext context, BuildContext modalContext) async {
var data = await api.request('DELETE', '/objects/' + _object['_id']);
@ -26,7 +28,7 @@ class _ObjectScreenState extends State<ObjectScreen> {
showDialog(
context: modalContext,
builder: (BuildContext context) => CupertinoAlertDialog(
title: new Text('Really delete this object?'),
title: new Text('Really delete this item?'),
content: new Text('This action cannot be undone.'),
actions: <Widget>[
CupertinoDialogAction(
@ -43,6 +45,47 @@ class _ObjectScreenState extends State<ObjectScreen> {
)
);
}
void _renameObject(BuildContext context) {
TextEditingController renameController = TextEditingController();
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Rename this item'),
content: TextField(
autofocus: true,
controller: renameController,
decoration: InputDecoration(hintText: "Enter a new name for the item"),
),
actions: <Widget>[
TextButton(
child: Text('CANCEL'),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text('OK'),
onPressed: () async {
print(renameController.text);
var data = await api.request('PUT', '/objects/' + _object['_id'], {'name': renameController.text});
if (data['success']) {
Navigator.pop(context);
_object['name'] = data['payload']['name'];
_onUpdate(_object['_id'], data['payload']);
setState(() {
_object = _object;
});
}
Navigator.pop(context);
},
),
],
);
},
);
}
void _showSettingsModal(context) {
showCupertinoModalPopup(
@ -55,9 +98,13 @@ class _ObjectScreenState extends State<ObjectScreen> {
child: Text('Cancel')
),
actions: [
CupertinoActionSheetAction(
onPressed: () => _renameObject(context),
child: Text('Rename item'),
),
CupertinoActionSheetAction(
onPressed: () => _confirmDeleteObject(modalContext),
child: Text('Delete object'),
child: Text('Delete item'),
isDestructiveAction: true,
),
]
@ -71,11 +118,22 @@ class _ObjectScreenState extends State<ObjectScreen> {
return Image.network(_object['url']);
}
else if (_object['type'] == 'pattern') {
var dat = Uri.parse(_object['preview']).data;
return Image.memory(dat.contentAsBytes());
if (_object['previewUrl'] != null) {
return Image.network(_object['previewUrl']!);;
}
else {
return Column(
children: [
SizedBox(height: 50),
Icon(Icons.pattern, size: 40),
SizedBox(height: 20),
Text('A preview of this pattern is not yet available'),
],
);
}
}
else {
return RaisedButton(child: Text('View file'), onPressed: () {
return ElevatedButton(child: Text('View file'), onPressed: () {
launch(_object['url']);
});
}
@ -113,8 +171,10 @@ class _ObjectScreenState extends State<ObjectScreen> {
class ObjectScreen extends StatefulWidget {
final Map<String,dynamic> _object;
final Map<String,dynamic> _project;
final Function _onUpdate;
final Function _onDelete;
ObjectScreen(this._object, this._onDelete) { }
ObjectScreen(this._object, this._project, this._onUpdate, this._onDelete) { }
@override
_ObjectScreenState createState() => _ObjectScreenState(_object, _onDelete);
_ObjectScreenState createState() => _ObjectScreenState(_object, _project, _onUpdate, _onDelete);
}

View File

@ -9,7 +9,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
);
final Api api = Api();
bool _loading = false;
String _pushToken;
String? _pushToken;
@override
void dispose() {
@ -36,7 +36,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
);
_pushToken = await _firebaseMessaging.getToken();*/
if (_pushToken != null) {
api.request('PUT', '/accounts/pushToken', {'pushToken': _pushToken});
api.request('PUT', '/accounts/pushToken', {'pushToken': _pushToken!});
}
setState(() => _loading = false);
_controller.animateToPage(2, duration: Duration(milliseconds: 500), curve: Curves.easeInOut);
@ -63,7 +63,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
SizedBox(height: 10),
Text('You can create as many projects as you like. Upload weaving draft patterns, images, and other files to your projects to store and showcase your work.', style: TextStyle(color: Colors.white, fontSize: 13), textAlign: TextAlign.center),
SizedBox(height: 10),
RaisedButton(
ElevatedButton(
child: Text('OK, I know what projects are!'),
onPressed: () => _controller.animateToPage(1, duration: Duration(milliseconds: 500), curve: Curves.easeInOut),
)
@ -84,12 +84,12 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
SizedBox(height: 10),
Text('We recommend enabling push notifications so you can keep up-to-date with your groups and projects.', style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
SizedBox(height: 10),
RaisedButton(
ElevatedButton(
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
_loading ? CircularProgressIndicator() : null,
_loading ? SizedBox(width: 5) : null,
_loading ? CircularProgressIndicator() : SizedBox(width: 0),
_loading ? SizedBox(width: 5) : SizedBox(width: 0),
Text('What\'s next?'),
].where((o) => o != null).toList()),
]),
onPressed: _requestPushPermissions,
)
]
@ -107,7 +107,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
SizedBox(height: 10),
Text('You\'re ready to get started. If you have any questions or want to get in touch then just send us a quick tweet.', style: TextStyle(color: Colors.white, fontSize: 13), textAlign: TextAlign.center),
SizedBox(height: 10),
RaisedButton(
ElevatedButton(
child: Text('Let\'s go'),
onPressed: () => Navigator.of(context).pushNamedAndRemoveUntil('/home', (Route<dynamic> route) => false),
),

View File

@ -6,86 +6,8 @@ import 'dart:io';
import 'api.dart';
import 'object.dart';
class _ProjectSettingsDialog extends StatelessWidget {
final Map<String,dynamic> _project;
final Function _onDelete;
final Function _onUpdateProject;
final Api api = Api();
_ProjectSettingsDialog(this._project, this._onDelete, this._onUpdateProject) {}
void _toggleVisibility(BuildContext context, bool checked) async {
var data = await api.request('PUT', '/projects/' + _project['owner']['username'] + '/' + _project['path'], {'visibility': checked ? 'private': 'public'});
if (data['success']) {
Navigator.pop(context);
_onUpdateProject(data['payload']);
}
}
void _deleteProject(BuildContext context, BuildContext modalContext) async {
var data = await api.request('DELETE', '/projects/' + _project['owner']['username'] + '/' + _project['path']);
if (data['success']) {
Navigator.pop(context);
Navigator.pop(modalContext);
_onDelete();
}
}
void _confirmDeleteProject(BuildContext modalContext) {
showDialog(
context: modalContext,
builder: (BuildContext context) => CupertinoAlertDialog(
title: new Text('Really delete this project?'),
content: new Text('This will remove any files and objects inside the project. This action cannot be undone.'),
actions: <Widget>[
CupertinoDialogAction(
isDefaultAction: true,
child: Text('No'),
onPressed: () => Navigator.pop(context),
),
CupertinoDialogAction(
isDestructiveAction: true,
child: Text('Yes'),
onPressed: () => _deleteProject(context, modalContext),
),
],
)
);
}
@override
Widget build(BuildContext context) {
return CupertinoActionSheet(
title: Text('Manage this project'),
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
child: Text('Cancel')
),
actions: [
Container(
padding: EdgeInsets.all(5.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CupertinoSwitch(
value: _project['visibility'] == 'private',
onChanged: (c) => _toggleVisibility(context, c),
),
SizedBox(width: 10),
Text('Private project', style: Theme.of(context).textTheme.bodyText1),
]
)
),
CupertinoActionSheetAction(
onPressed: () { _confirmDeleteProject(context); },
child: Text('Delete project'),
isDestructiveAction: true,
),
]
);
}
}
class _ProjectScreenState extends State<ProjectScreen> {
final Function _onUpdate;
final Function _onDelete;
final picker = ImagePicker();
final Api api = Api();
@ -94,7 +16,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
bool _loading = false;
bool _creating = false;
_ProjectScreenState(this._project, this._onDelete) { }
_ProjectScreenState(this._project, this._onUpdate, this._onDelete) { }
@override
initState() {
@ -122,8 +44,20 @@ class _ProjectScreenState extends State<ProjectScreen> {
setState(() {
_project = project;
});
_onUpdate(project['_id'], project);
}
void _onUpdateObject(String id, Map<String,dynamic> update) {
List<dynamic> _newObjects = _objects.map((o) {
if (o['_id'] == id) {
o.addAll(update);
}
return o;
}).toList();
setState(() {
_objects = _newObjects;
});
}
void _onDeleteObject(String id) {
List<dynamic> _newObjects = _objects.where((p) => p['_id'] != id).toList();
setState(() {
@ -196,7 +130,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
showCupertinoModalPopup(context: context, builder: (BuildContext context) => settingsDialog);
}
Widget getImageBox(data, [bool isMemory, bool isNetwork]) {
Widget getMemoryImageBox(data, [bool? isMemory, bool? isNetwork]) {
return new AspectRatio(
aspectRatio: 1 / 1,
child: new Container(
@ -204,7 +138,21 @@ class _ProjectScreenState extends State<ProjectScreen> {
image: new DecorationImage(
fit: BoxFit.fitWidth,
alignment: FractionalOffset.topCenter,
image: isMemory == true ? new MemoryImage(data) : new NetworkImage(data)
image: new MemoryImage(data),
)
),
),
);
}
Widget getNetworkImageBox(String url) {
return new AspectRatio(
aspectRatio: 1 / 1,
child: new Container(
decoration: new BoxDecoration(
image: new DecorationImage(
fit: BoxFit.fitWidth,
alignment: FractionalOffset.topCenter,
image: new NetworkImage(url),
)
),
),
@ -232,17 +180,30 @@ class _ProjectScreenState extends State<ProjectScreen> {
if (object['isImage'] == true) {
type = 'Image';
leader = getImageBox(object['url']);
if (object['url'] != null) {
leader = getNetworkImageBox(object['url']!);
}
else {
leader = getIconBox(Icon(Icons.photo));
}
}
else if (object['type'] == 'pattern' && object['preview'] != null) {
else if (object['type'] == 'pattern') {
type = 'Weaving pattern';
var dat = Uri.parse(object['preview']).data;
leader = getImageBox(dat.contentAsBytes(), true);
if (object['previewUrl'] != null) {
leader = getNetworkImageBox(object['previewUrl']!);
}
else {
leader = getIconBox(Icon(Icons.pattern));
}
}
else if (object['type'] == 'file') {
type = 'File';
leader = getIconBox(Icon(Icons.insert_drive_file));
}
else {
type = 'Unknown';
leader = getIconBox(Icon(Icons.file_present));
}
return new Card(
child: InkWell(
@ -250,7 +211,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ObjectScreen(object, _onDeleteObject),
builder: (context) => ObjectScreen(object, _project, _onUpdateObject, _onDeleteObject),
),
);
},
@ -319,8 +280,129 @@ class _ProjectScreenState extends State<ProjectScreen> {
class ProjectScreen extends StatefulWidget {
final Map<String,dynamic> _project;
final Function _onUpdate;
final Function _onDelete;
ProjectScreen(this._project, this._onDelete) { }
ProjectScreen(this._project, this._onUpdate, this._onDelete) { }
@override
_ProjectScreenState createState() => _ProjectScreenState(_project, _onDelete);
_ProjectScreenState createState() => _ProjectScreenState(_project, _onUpdate, _onDelete);
}
class _ProjectSettingsDialog extends StatelessWidget {
final Map<String,dynamic> _project;
final Function _onDelete;
final Function _onUpdateProject;
final Api api = Api();
_ProjectSettingsDialog(this._project, this._onDelete, this._onUpdateProject) {}
void _renameProject(BuildContext context) async {
TextEditingController renameController = TextEditingController();
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Rename your project'),
content: TextField(
autofocus: true,
controller: renameController,
decoration: InputDecoration(hintText: "Enter a new name for the project"),
),
actions: <Widget>[
TextButton(
child: Text('CANCEL'),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text('OK'),
onPressed: () async {
print(renameController.text);
var data = await api.request('PUT', '/projects/' + _project['owner']['username'] + '/' + _project['path'], {'name': renameController.text});
if (data['success']) {
Navigator.pop(context);
_onUpdateProject(data['payload']);
}
Navigator.pop(context);
},
),
],
);
},
);
}
void _toggleVisibility(BuildContext context, bool checked) async {
var data = await api.request('PUT', '/projects/' + _project['owner']['username'] + '/' + _project['path'], {'visibility': checked ? 'private': 'public'});
if (data['success']) {
Navigator.pop(context);
_onUpdateProject(data['payload']);
}
}
void _deleteProject(BuildContext context, BuildContext modalContext) async {
var data = await api.request('DELETE', '/projects/' + _project['owner']['username'] + '/' + _project['path']);
if (data['success']) {
Navigator.pop(context);
Navigator.pop(modalContext);
_onDelete();
}
}
void _confirmDeleteProject(BuildContext modalContext) {
showDialog(
context: modalContext,
builder: (BuildContext context) => CupertinoAlertDialog(
title: new Text('Really delete this project?'),
content: new Text('This will remove any files and objects inside the project. This action cannot be undone.'),
actions: <Widget>[
CupertinoDialogAction(
isDefaultAction: true,
child: Text('No'),
onPressed: () => Navigator.pop(context),
),
CupertinoDialogAction(
isDestructiveAction: true,
child: Text('Yes'),
onPressed: () => _deleteProject(context, modalContext),
),
],
)
);
}
@override
Widget build(BuildContext context) {
return CupertinoActionSheet(
title: Text('Manage this project'),
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
child: Text('Cancel')
),
actions: [
Container(
padding: EdgeInsets.all(5.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CupertinoSwitch(
value: _project['visibility'] == 'private',
onChanged: (c) => _toggleVisibility(context, c),
),
SizedBox(width: 10),
Text('Private project', style: Theme.of(context).textTheme.bodyText1),
]
)
),
CupertinoActionSheetAction(
onPressed: () { _renameProject(context); },
child: Text('Rename project'),
),
CupertinoActionSheetAction(
onPressed: () { _confirmDeleteProject(context); },
child: Text('Delete project'),
isDestructiveAction: true,
),
]
);
}
}

View File

@ -6,6 +6,164 @@ import 'api.dart';
import 'project.dart';
import 'settings.dart';
class _ProjectsTabState extends State<ProjectsTab> {
List<dynamic> _projects = [];
bool _loading = false;
bool _creatingProject = false;
final Api api = Api();
@override
initState() {
super.initState();
getProjects();
}
void getProjects() async {
setState(() {
_loading = true;
});
var data = await api.request('GET', '/users/me/projects');
if (data['success'] == true) {
setState(() {
_projects = data['payload']['projects'];
_loading = false;
});
}
}
void _onCreatingProject() {
setState(() {
_creatingProject = true;
});
}
void _onCreateProject(newProject) {
List<dynamic> _newProjects = _projects;
_newProjects.insert(0, newProject);
setState(() {
_projects = _newProjects;
_creatingProject = false;
});
}
void _onUpdateProject(String id, Map<String,dynamic> update) {
List<dynamic> _newProjects = _projects.map((p) {
if (p['_id'] == id) {
p.addAll(update);
}
return p;
}).toList();
setState(() {
_projects = _newProjects;
});
}
void _onDeleteProject(String id) {
List<dynamic> _newProjects = _projects.where((p) => p['_id'] != id).toList();
setState(() {
_projects = _newProjects;
});
}
void showNewProjectDialog() async {
Widget simpleDialog = new _NewProjectDialog(_onCreatingProject, _onCreateProject);
showDialog(context: context, builder: (BuildContext context) => simpleDialog);
}
Widget buildProjectCard(Map<String,dynamic> project) {
String description = project['description'] != null ? project['description'].replaceAll("\n", " ") : '';
if (description != null && description.length > 80) {
description = description.substring(0, 77) + '...';
}
if (project['visibility'] == 'public') {
description = "PUBLIC PROJECT\n" + description;
}
else description = "PRIVATE PROJECT\n" + description;
return new Card(
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProjectScreen(project, _onUpdateProject, _onDeleteProject),
),
);
},
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new ListTile(
leading: Icon(Icons.folder_open),
trailing: Icon(Icons.keyboard_arrow_right),
title: Text(project['name'] != null ? project['name'] : 'Untitled project'),
subtitle: Text(description),
),
]
),
)
)
;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Your Projects'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.info_outline),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SettingsScreen(),
),
);
},
),
]
),
body: _loading ?
Container(
margin: const EdgeInsets.all(10.0),
alignment: Alignment.center,
child: CircularProgressIndicator()
)
: Container(
margin: const EdgeInsets.all(10.0),
alignment: Alignment.center,
child: (_projects != null && _projects.length > 0) ?
ListView.builder(
itemCount: _projects.length,
itemBuilder: (BuildContext context, int index) {
return buildProjectCard(_projects[index]);
},
)
:
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Create your first project', style: TextStyle(fontSize: 20), textAlign: TextAlign.center),
Image(image: AssetImage('assets/reading.png'), width: 300),
Text('Projects contain all the files and patterns that make up a piece of work. Create one using the + button below.', textAlign: TextAlign.center),
])
),
floatingActionButton: FloatingActionButton(
onPressed: showNewProjectDialog,
child: _creatingProject ? CircularProgressIndicator(backgroundColor: Colors.white) : Icon(Icons.add),
backgroundColor: Colors.pink[500],
),
);
}
}
class ProjectsTab extends StatefulWidget {
@override
_ProjectsTabState createState() => _ProjectsTabState();
}
class _NewProjectDialogState extends State<_NewProjectDialog> {
final TextEditingController _newProjectNameController = TextEditingController();
final Function _onStart;
@ -81,156 +239,4 @@ class _NewProjectDialog extends StatefulWidget {
_NewProjectDialog(this._onStart, this._onComplete) {}
@override
_NewProjectDialogState createState() => _NewProjectDialogState(_onStart, _onComplete);
}
class _ProjectsTabState extends State<ProjectsTab> {
List<dynamic> _projects = [];
bool _loading = false;
bool _creatingProject = false;
final Api api = Api();
@override
initState() {
super.initState();
getProjects();
}
void getProjects() async {
setState(() {
_loading = true;
});
var data = await api.request('GET', '/users/me/projects');
if (data['success'] == true) {
setState(() {
_projects = data['payload']['projects'];
_loading = false;
});
}
}
void _onCreatingProject() {
setState(() {
_creatingProject = true;
});
}
void _onCreateProject(newProject) {
List<dynamic> _newProjects = _projects;
_newProjects.insert(0, newProject);
setState(() {
_projects = _newProjects;
_creatingProject = false;
});
}
void _onDeleteProject(String id) {
List<dynamic> _newProjects = _projects.where((p) => p['_id'] != id).toList();
setState(() {
_projects = _newProjects;
});
}
void showNewProjectDialog() async {
Widget simpleDialog = new _NewProjectDialog(_onCreatingProject, _onCreateProject);
showDialog(context: context, builder: (BuildContext context) => simpleDialog);
}
Widget buildProjectCard(Map<String,dynamic> project) {
String description = project['description'] != null ? project['description'] : '';
if (description != null && description.length > 80) {
description = description.substring(0, 77) + '...';
}
return new Card(
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProjectScreen(project, _onDeleteProject),
),
);
},
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new ListTile(
leading: Icon(Icons.folder_open),
trailing: Icon(Icons.keyboard_arrow_right),
title: Text(project['name'] != null ? project['name'] : 'Untitled project'),
subtitle: Text(description.replaceAll("\n", " ")),
),
/*ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('VIEW'),
onPressed: () {
}
),
],
),*/
]
),
)
)
;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Your Projects'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.info_outline),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SettingsScreen(),
),
);
},
),
]
),
body: _loading ?
Container(
margin: const EdgeInsets.all(10.0),
alignment: Alignment.center,
child: CircularProgressIndicator()
)
: Container(
margin: const EdgeInsets.all(10.0),
alignment: Alignment.center,
child: (_projects != null && _projects.length > 0) ?
ListView.builder(
itemCount: _projects.length,
itemBuilder: (BuildContext context, int index) {
return buildProjectCard(_projects[index]);
},
)
:
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Create your first project', style: TextStyle(fontSize: 20), textAlign: TextAlign.center),
Image(image: AssetImage('assets/reading.png'), width: 300),
Text('Projects contain all the files and patterns that make up a piece of work. Create one using the + button below.', textAlign: TextAlign.center),
])
),
floatingActionButton: FloatingActionButton(
onPressed: showNewProjectDialog,
child: _creatingProject ? CircularProgressIndicator(backgroundColor: Colors.white) : Icon(Icons.add),
backgroundColor: Colors.pink[500],
),
);
}
}
class ProjectsTab extends StatefulWidget {
@override
_ProjectsTabState createState() => _ProjectsTabState();
}
}

View File

@ -97,9 +97,9 @@ class _RegisterScreenState extends State<RegisterScreen> {
),
),
SizedBox(height: 20),
RaisedButton(
ElevatedButton(
onPressed: () => _submit(context),
color: Colors.pink,
//color: Colors.pink,
child: _registering ? SizedBox(height: 20, width: 20, child:CircularProgressIndicator(backgroundColor: Colors.white)) : Text("Register",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 15)

View File

@ -31,11 +31,11 @@ class SettingsScreen extends StatelessWidget {
),
]),
actions: [
FlatButton(
TextButton(
child: Text('Cancel'),
onPressed: () { Navigator.of(context).pop(); }
),
RaisedButton(
ElevatedButton(
child: Text('Delete Account'),
onPressed: () async {
Api api = Api();

View File

@ -1,9 +1,9 @@
import 'package:flutter/foundation.dart';
class Store extends ChangeNotifier {
String apiToken;
String? apiToken;
void setToken(String newToken) {
void setToken(String? newToken) {
apiToken = newToken;
}
}

View File

@ -33,7 +33,7 @@ class _UserScreenState extends State<UserScreen> {
@override
Widget build(BuildContext context) {
String created;
String? created;
if (_user['createdAt'] != null) {
DateTime createdAt = DateTime.parse(_user['createdAt']);
created = DateFormat('MMMM y').format(createdAt);
@ -68,7 +68,7 @@ class _UserScreenState extends State<UserScreen> {
Text(_user['location'])
]) : SizedBox(height: 1),
SizedBox(height: 10),
Text('Member' + (created != null ? (' since ' + created) : ''),
Text('Member' + (created != null ? (' since ' + created!) : ''),
style: TextStyle(color: Colors.grey[500])
),
SizedBox(height: 10),

View File

@ -1,167 +1,182 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "9ebe81588e666f7e2b21309f2b5653bd9642d7f27fd0a6894278d2ff40cb9481"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
archive:
dependency: transitive
description:
name: archive
url: "https://pub.dartlang.org"
sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
version: "3.3.7"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.4.2"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
url: "https://pub.dev"
source: hosted
version: "2.8.2"
version: "2.10.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.1"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
url: "https://pub.dev"
source: hosted
version: "1.2.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
chewie:
dependency: transitive
description:
name: chewie
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
chewie_audio:
dependency: transitive
description:
name: chewie_audio
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.2.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
url: "https://pub.dev"
source: hosted
version: "1.15.0"
version: "1.17.0"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
cross_file:
dependency: transitive
description:
name: cross_file
url: "https://pub.dartlang.org"
sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9"
url: "https://pub.dev"
source: hosted
version: "0.3.2"
version: "0.3.3+4"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.3"
csslib:
dependency: transitive
description:
name: csslib
url: "https://pub.dartlang.org"
sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f"
url: "https://pub.dev"
source: hosted
version: "0.17.1"
version: "0.17.3"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.dartlang.org"
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.0.5"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
url: "https://pub.dev"
source: hosted
version: "1.1.2"
version: "2.0.2"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
url: "https://pub.dev"
source: hosted
version: "6.1.2"
version: "6.1.4"
firebase_core:
dependency: transitive
description:
name: firebase_core
url: "https://pub.dartlang.org"
sha256: e9b36b391690cf329c6fb1de220045e97c13784c303820cd33962319580a56c6
url: "https://pub.dev"
source: hosted
version: "1.13.1"
version: "2.13.1"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
url: "https://pub.dartlang.org"
sha256: b63e3be6c96ef5c33bdec1aab23c91eb00696f6452f0519401d640938c94cba2
url: "https://pub.dev"
source: hosted
version: "4.2.5"
version: "4.8.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
url: "https://pub.dartlang.org"
sha256: "8c0f4c87d20e2d001a5915df238c1f9c88704231f591324205f5a5d2a7740a45"
url: "https://pub.dev"
source: hosted
version: "1.6.1"
version: "2.5.0"
firebase_messaging:
dependency: "direct main"
description:
name: firebase_messaging
url: "https://pub.dartlang.org"
sha256: a01d7b9eb43a4bad54a411edb2b4124089d88eab029191893e83c39e18ab19f7
url: "https://pub.dev"
source: hosted
version: "11.2.8"
version: "14.6.2"
firebase_messaging_platform_interface:
dependency: transitive
description:
name: firebase_messaging_platform_interface
url: "https://pub.dartlang.org"
sha256: c2fef3e30fbfa3a71d74477df102d1c2f5aad860bb68bb4086b0af3b12abedf3
url: "https://pub.dev"
source: hosted
version: "3.2.1"
version: "4.5.2"
firebase_messaging_web:
dependency: transitive
description:
name: firebase_messaging_web
url: "https://pub.dartlang.org"
sha256: "8d280f0110ca4946b9863e578b9879874066ac486ffa596a609aab329fb6fa7e"
url: "https://pub.dev"
source: hosted
version: "2.2.9"
version: "3.5.2"
flutter:
dependency: "direct main"
description: flutter
@ -171,44 +186,26 @@ packages:
dependency: "direct main"
description:
name: flutter_html
url: "https://pub.dartlang.org"
sha256: "02ad69e813ecfc0728a455e4bf892b9379983e050722b1dce00192ee2e41d1ee"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "3.0.0-beta.2"
flutter_launcher_icons:
dependency: "direct main"
description:
name: flutter_launcher_icons
url: "https://pub.dartlang.org"
sha256: "559c600f056e7c704bd843723c21e01b5fba47e8824bd02422165bcc02a5de1d"
url: "https://pub.dev"
source: hosted
version: "0.9.2"
flutter_layout_grid:
dependency: transitive
description:
name: flutter_layout_grid
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
flutter_math_fork:
dependency: transitive
description:
name: flutter_math_fork
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.0"
version: "0.9.3"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360"
url: "https://pub.dev"
source: hosted
version: "2.0.5"
flutter_svg:
dependency: transitive
description:
name: flutter_svg
url: "https://pub.dartlang.org"
source: hosted
version: "0.23.0+1"
version: "2.0.15"
flutter_test:
dependency: "direct dev"
description: flutter
@ -219,251 +216,270 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
fluttertoast:
dependency: "direct main"
description:
name: fluttertoast
url: "https://pub.dartlang.org"
source: hosted
version: "8.0.9"
html:
dependency: transitive
description:
name: html
url: "https://pub.dartlang.org"
sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8"
url: "https://pub.dev"
source: hosted
version: "0.15.0"
version: "0.15.3"
http:
dependency: "direct main"
description:
name: http
url: "https://pub.dartlang.org"
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
url: "https://pub.dev"
source: hosted
version: "0.13.4"
version: "0.13.6"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "4.0.2"
image:
dependency: transitive
description:
name: image
url: "https://pub.dartlang.org"
sha256: "02bafd3b4f399bfeb10034deba9753d93b55ce41cd0c4d3d8b355626f80e5b32"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
image_picker:
dependency: "direct main"
description:
name: image_picker
url: "https://pub.dartlang.org"
sha256: "9978d3510af4e6a902e545ce19229b926e6de6a1828d6134d3aab2e129a4d270"
url: "https://pub.dev"
source: hosted
version: "0.8.4+10"
version: "0.8.7+5"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
sha256: "3083c3a3245adf9f3eb7bacf0eaa6a1f087dd538fab73a13a2f7907602601692"
url: "https://pub.dev"
source: hosted
version: "0.8.6+19"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
url: "https://pub.dartlang.org"
sha256: "98f50d6b9f294c8ba35e25cc0d13b04bfddd25dbc8d32fa9d566a6572f2c081c"
url: "https://pub.dev"
source: hosted
version: "2.1.6"
version: "2.1.12"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
sha256: d779210bda268a03b57e923fb1e410f32f5c5e708ad256348bcbf1f44f558fd0
url: "https://pub.dev"
source: hosted
version: "0.8.7+4"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
url: "https://pub.dartlang.org"
sha256: "1991219d9dbc42a99aff77e663af8ca51ced592cd6685c9485e3458302d3d4f8"
url: "https://pub.dev"
source: hosted
version: "2.4.4"
version: "2.6.3"
intl:
dependency: "direct main"
description:
name: intl
url: "https://pub.dartlang.org"
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
url: "https://pub.dev"
source: hosted
version: "0.17.0"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
url: "https://pub.dev"
source: hosted
version: "0.6.3"
version: "0.6.5"
list_counter:
dependency: transitive
description:
name: list_counter
sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237
url: "https://pub.dev"
source: hosted
version: "1.0.2"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
url: "https://pub.dev"
source: hosted
version: "0.12.11"
version: "0.12.13"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
url: "https://pub.dev"
source: hosted
version: "0.1.3"
version: "0.2.0"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
url: "https://pub.dev"
source: hosted
version: "1.7.0"
version: "1.8.0"
nested:
dependency: transitive
description:
name: nested
url: "https://pub.dartlang.org"
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
numerus:
dependency: transitive
description:
name: numerus
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
url: "https://pub.dev"
source: hosted
version: "1.8.0"
path_drawing:
dependency: transitive
description:
name: path_drawing
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.1+1"
path_parsing:
dependency: transitive
description:
name: path_parsing
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.1"
version: "1.8.2"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
url: "https://pub.dartlang.org"
sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
url: "https://pub.dev"
source: hosted
version: "2.1.5"
version: "2.1.11"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.dartlang.org"
sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
version: "2.0.6"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
url: "https://pub.dev"
source: hosted
version: "2.0.5"
version: "2.1.7"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4"
url: "https://pub.dev"
source: hosted
version: "4.4.0"
version: "5.1.0"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.4"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
url: "https://pub.dev"
source: hosted
version: "3.7.3"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
url: "https://pub.dev"
source: hosted
version: "4.2.4"
provider:
dependency: "direct main"
description:
name: provider
url: "https://pub.dartlang.org"
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
url: "https://pub.dev"
source: hosted
version: "6.0.2"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1+1"
version: "6.0.5"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
url: "https://pub.dartlang.org"
sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022"
url: "https://pub.dev"
source: hosted
version: "2.0.13"
version: "2.1.1"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
url: "https://pub.dartlang.org"
sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749"
url: "https://pub.dev"
source: hosted
version: "2.0.11"
shared_preferences_ios:
version: "2.1.4"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_ios
url: "https://pub.dartlang.org"
name: shared_preferences_foundation
sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.2.2"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
url: "https://pub.dartlang.org"
sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
version: "2.2.0"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
url: "https://pub.dartlang.org"
sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d
url: "https://pub.dev"
source: hosted
version: "2.0.0"
version: "2.2.0"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
url: "https://pub.dartlang.org"
sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
version: "2.1.0"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
url: "https://pub.dartlang.org"
sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.2.0"
sky_engine:
dependency: transitive
description: flutter
@ -473,254 +489,162 @@ packages:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
url: "https://pub.dev"
source: hosted
version: "1.8.1"
version: "1.9.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.11.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.1"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
url: "https://pub.dev"
source: hosted
version: "0.4.8"
tuple:
dependency: transitive
description:
name: tuple
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "0.4.16"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.3.2"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
url: "https://pub.dartlang.org"
sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3
url: "https://pub.dev"
source: hosted
version: "6.0.20"
version: "6.1.11"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
url: "https://pub.dartlang.org"
sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51
url: "https://pub.dev"
source: hosted
version: "6.0.15"
version: "6.0.35"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
url: "https://pub.dartlang.org"
sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
url: "https://pub.dev"
source: hosted
version: "6.0.15"
version: "6.1.4"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
url: "https://pub.dartlang.org"
sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.5"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.dartlang.org"
sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.5"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
url: "https://pub.dartlang.org"
sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
url: "https://pub.dev"
source: hosted
version: "2.0.5"
version: "2.1.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
url: "https://pub.dartlang.org"
sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab"
url: "https://pub.dev"
source: hosted
version: "2.0.9"
version: "2.0.17"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
url: "https://pub.dartlang.org"
sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.6"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
very_good_analysis:
dependency: transitive
description:
name: very_good_analysis
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
video_player:
dependency: transitive
description:
name: video_player
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.19"
video_player_android:
dependency: transitive
description:
name: video_player_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
video_player_avfoundation:
dependency: transitive
description:
name: video_player_avfoundation
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
video_player_platform_interface:
dependency: transitive
description:
name: video_player_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "5.1.0"
video_player_web:
dependency: transitive
description:
name: video_player_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.7"
wakelock:
dependency: transitive
description:
name: wakelock
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.1+1"
wakelock_macos:
dependency: transitive
description:
name: wakelock_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
wakelock_platform_interface:
dependency: transitive
description:
name: wakelock_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
wakelock_web:
dependency: transitive
description:
name: wakelock_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
wakelock_windows:
dependency: transitive
description:
name: wakelock_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
webview_flutter:
dependency: transitive
description:
name: webview_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.0"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.3"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
url: "https://pub.dartlang.org"
source: hosted
version: "2.7.1"
version: "2.1.4"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4
url: "https://pub.dev"
source: hosted
version: "2.4.1"
version: "3.1.4"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
url: "https://pub.dartlang.org"
sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1
url: "https://pub.dev"
source: hosted
version: "0.2.0+1"
version: "1.0.0"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
sha256: "80d494c09849dc3f899d227a78c30c5b949b985ededf884cb3f3bcd39f4b447a"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
version: "5.4.1"
yaml:
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.1.2"
sdks:
dart: ">=2.16.0 <3.0.0"
flutter: ">=2.10.0"
dart: ">=2.19.0 <3.0.0"
flutter: ">=3.3.0"

View File

@ -15,25 +15,25 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.2+7
version: 1.1.0+8
environment:
sdk: ">=2.7.0 <3.0.0"
sdk: '>=2.17.0 <3.0.0'
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.3
http: ^0.13.3
shared_preferences: ^2.0.11
provider: ^6.0.1
url_launcher: ^6.0.17
flutter_html: ^2.2.1
cupertino_icons: ^1.0.4
http: ^0.13.4
shared_preferences: ^2.0.15
provider: ^6.0.3
url_launcher: ^6.1.2
flutter_html: ^3.0.0-alpha.3
intl: ^0.17.0
image_picker: ^0.8.4
image_picker: ^0.8.5+3
flutter_launcher_icons: ^0.9.0
firebase_messaging: ^11.2.3
fluttertoast: ^8.0.9
firebase_messaging: ^14.4.0
#fluttertoast: ^8.0.9
dev_dependencies:
flutter_test:

View File

@ -6,4 +6,26 @@ export const uploads = {
}, success, fail) {
api.authenticatedRequest('GET', `/uploads/file/request?name=${name}&size=${size}&type=${type}&forType=${forType}&forId=${forId}`, null, success, fail);
},
uploadFile(forType, forId, name, file, success, fail) {
uploads.generateFileUploadRequest({
forType, forId, name, size: file.size, type: file.type || 'image/png',
}, (response) => {
const xhr = new XMLHttpRequest();
xhr.open('PUT', response.signedRequest);
xhr.setRequestHeader('Content-Type', file.type);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// We pass back the original file name so it can be displayed nicely
success && success({ storedName: response.fileName, name: file.name, type: 'file' });
} else if (onError) {
fail && fail({ message: 'Unable to upload file' });
}
}
};
xhr.send(file);
}, (err) => {
fail && fail(err);
});
},
};

View File

@ -7,7 +7,7 @@ export default function PatternCard({ object, project, user }) {
if (!object) return null;
return (
<Card raised key={object._id} style={{ cursor: 'pointer' }} as={Link} to={`/${user?.username}/${project?.path}/${object._id}`}>
<div style={{ height: 200, backgroundImage: `url(${object.preview})`, backgroundSize: 'cover', backgroundPosition: 'top right', position: 'relative' }}>
<div style={{ height: 200, backgroundImage: `url(${object.previewUrl})`, backgroundSize: 'cover', backgroundPosition: 'top right', position: 'relative' }}>
{user &&
<div style={{position: 'absolute', top: 5, left: 5, padding: '3px 6px', background: 'rgba(250,250,250,0.8)', borderRadius: 5}}>
<UserChip user={user} />

View File

@ -8,7 +8,6 @@ import utils from '../../../utils/utils.js';
import DiscoverCard from '../../includes/DiscoverCard';
import PatternCard from '../../includes/PatternCard';
import PatternLoader from '../../includes/PatternLoader';
import DraftPreview from '../projects/objects/DraftPreview';
export default function Explore() {
const [loading, setLoading] = useState(false);

View File

@ -104,9 +104,9 @@ function ObjectList({ compact }) {
{object.isImage &&
<div style={{width:40, height:40, backgroundImage:`url(${utils.resizeUrl(object.url, 50)})`, backgroundSize:'cover', backgroundPosition:'center center'}} />
}
{object.type === 'pattern' && (object.preview
{object.type === 'pattern' && (object.previewUrl
? (
<div style={{ height: 40, width:40, backgroundImage: `url(${object.preview})`, backgroundSize: 'cover', backgroundPosition: 'top right' }}
<div style={{ height: 40, width:40, backgroundImage: `url(${object.previewUrl})`, backgroundSize: 'cover', backgroundPosition: 'top right' }}
/>
)
: <div style={{ height: 40, width:40, backgroundImage: `url(${logoGreyShort})`, backgroundSize: '50px' }} />
@ -145,9 +145,9 @@ function ObjectList({ compact }) {
{filteredObjects.map((object, index) => (
<Card raised key={object._id} style={{ cursor: 'pointer' }} as={Link} to={`/${fullProjectPath}/${object._id}`}>
{object.type === 'pattern'
&& (object.preview
&& (object.previewUrl
? (
<div style={{ height: 200, backgroundImage: `url(${object.preview})`, backgroundSize: 'cover', backgroundPosition: 'top right' }}
<div style={{ height: 200, backgroundImage: `url(${object.previewUrl})`, backgroundSize: 'cover', backgroundPosition: 'top right' }}
/>
)
: <div style={{ height: 200, backgroundImage: `url(${logoGreyShort})`, backgroundSize: '50px' }} />

View File

@ -85,7 +85,7 @@ function ObjectViewer() {
const downloadDrawdownImage = (object) => {
const element = document.createElement('a');
element.setAttribute('href', object.preview);
element.setAttribute('href', object.previewUrl);
element.setAttribute('download', `${object.name.replace(/ /g, '_')}-drawdown.png`);
element.style.display = 'none';
document.body.appendChild(element);
@ -118,10 +118,10 @@ function ObjectViewer() {
<Helmet title={`${object.name || 'Project Item'} | ${project?.name || 'Project'}`} />
<div style={{ display: 'flex', justifyContent: 'end' }}>
{object.type === 'pattern' && (project.user === (user && user._id) || project.openSource || object.preview) && <>
{object.type === 'pattern' && (project.user === (user && user._id) || project.openSource || object.previewUrl) && <>
<Dropdown direction='left' icon={null} trigger={<Button size='small' secondary icon='download' content='Download pattern' loading={downloading} disabled={downloading}/>}>
<Dropdown.Menu>
{object.preview &&
{object.previewUrl &&
<Dropdown.Item onClick={e => downloadDrawdownImage(object)} content='Download drawdown as an image' icon='file outline' />
}
{(project.user === (user && user._id) || project.openSource) &&

View File

@ -7,6 +7,7 @@ import styled from 'styled-components';
import ElementPan from '../../../includes/ElementPan';
import HelpLink from '../../../includes/HelpLink';
import Tour, { ReRunTour } from '../../../includes/Tour';
import util from '../../../../utils/utils.js';
import Warp from './Warp';
import Weft from './Weft';
@ -56,9 +57,10 @@ function Draft() {
const saveObject = () => {
setSaving(true);
const canvas = document.getElementsByClassName('drawdown')[0];
util.generatePatternPreview(object, previewUrl => {
dispatch(actions.objects.update(objectId, 'previewUrl', previewUrl));
});
const newObject = Object.assign({}, object);
newObject.preview = canvas.toDataURL();
newObject.pattern = pattern;
api.objects.update(objectId, newObject, (o) => {
toast.success('Pattern saved');

View File

@ -3,6 +3,7 @@ import { useDispatch } from 'react-redux';
import { Loader } from 'semantic-ui-react';
import actions from '../../../../actions';
import api from '../../../../api';
import util from '../../../../utils/utils.js';
import ElementPan from '../../../includes/ElementPan';
import { StyledPattern } from '../../../main/projects/objects/Draft';
@ -17,18 +18,6 @@ function DraftPreview({ object }) {
const dispatch = useDispatch();
const objectId = object?._id;
const generatePreview = useCallback(() => {
setTimeout(() => {
const c = document.getElementsByClassName('drawdown')[0];
const preview = c?.toDataURL();
if (preview) {
api.objects.update(objectId, { preview }, () => {
dispatch(actions.objects.update(objectId, 'preview', preview));
});
}
}, 1000);
}, [dispatch, objectId]);
useEffect(() => {
dispatch(actions.objects.updateEditor({ tool: 'pan' }));
setLoading(true);
@ -36,10 +25,17 @@ function DraftPreview({ object }) {
setLoading(false);
if (o.pattern && o.pattern.warp) {
setPattern(o.pattern);
if (!o.preview) generatePreview();
// Generate the preview if not yet set (e.g. if from uploaded WIF)
if (!o.previewUrl) {
setTimeout(() => {
util.generatePatternPreview(object, previewUrl => {
dispatch(actions.objects.update(objectId, 'previewUrl', previewUrl));
});
}, 1000);
}
}
}, err => setLoading(false));
}, [dispatch, objectId, generatePreview]);
}, [dispatch, objectId]);
const unifyCanvas = useCallback(() => {
if (!pattern) return;

View File

@ -1,5 +1,6 @@
import { createConfirmation } from 'react-confirm';
import ConfirmModal from '../components/includes/ConfirmModal';
import api from '../api';
import avatar1 from '../images/avatars/1.png';
import avatar2 from '../images/avatars/2.png';
@ -103,6 +104,18 @@ const utils = {
if (IMAGE_SERVER) return `${import.meta.env.VITE_IMAGINARY_URL}/resize?width=${width}&url=${url}`;
return url;
},
generatePatternPreview(object, callback) {
const c = document.getElementsByClassName('drawdown')[0];
c?.toBlob(blob => {
if (blob) {
api.uploads.uploadFile('project', object.project, `preview-${object._id}.png`, blob, response => {
api.objects.update(object._id, { preview: response.storedName }, ({ previewUrl }) => {
callback && callback(previewUrl);
});
});
}
});
}
};
export default utils;