Compare commits

..

13 Commits

33 changed files with 930 additions and 808 deletions

1
api/.gitignore vendored
View File

@ -8,3 +8,4 @@ config-prod.yml
envfile envfile
firebase.json 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']) 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'] == '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 return obj
def copy_to_project(user, id, project_id): def copy_to_project(user, id, project_id):

View File

@ -122,6 +122,9 @@ def get_objects(user, username, path):
for obj in objs: for obj in objs:
if obj['type'] == 'file' and 'storedName' in obj: if obj['type'] == 'file' and 'storedName' in obj:
obj['url'] = uploads.get_presigned_url('projects/{0}/{1}'.format(project['_id'], obj['storedName'])) 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 return objs
def create_object(user, username, path, data): 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)) 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: for object in objects:
object['projectObject'] = project_map.get(object['project']) 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})) 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: for a in authors:
if 'avatar' in a: if 'avatar' in a:

View File

@ -4,7 +4,7 @@ from bson.objectid import ObjectId
import boto3 import boto3
from botocore.client import Config from botocore.client import Config
import blurhash import blurhash
from util import database from util import database, util
def sanitise_filename(s): def sanitise_filename(s):
bad_chars = re.compile('[^a-zA-Z0-9_.]') 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> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>9.0</string> <string>11.0</string>
</dict> </dict>
</plist> </plist>

View File

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project # 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. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -80,9 +80,9 @@ class _LoginScreenState extends State<LoginScreen> {
)] )]
), ),
SizedBox(height: 20), SizedBox(height: 20),
RaisedButton( ElevatedButton(
onPressed: () => _submit(context), onPressed: () => _submit(context),
color: Colors.pink, //color: Colors.pink,
child: _loggingIn ? SizedBox(height: 20, width: 20, child:CircularProgressIndicator(backgroundColor: Colors.white)) : Text("Login", child: _loggingIn ? SizedBox(height: 20, width: 20, child:CircularProgressIndicator(backgroundColor: Colors.white)) : Text("Login",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 15) 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:shared_preferences/shared_preferences.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:fluttertoast/fluttertoast.dart'; //import 'package:fluttertoast/fluttertoast.dart';
import 'api.dart'; import 'api.dart';
import 'store.dart'; import 'store.dart';
import 'welcome.dart'; import 'welcome.dart';
@ -42,7 +42,7 @@ class _AppState extends State<MyApp> {
title: 'Treadl', title: 'Treadl',
theme: ThemeData( theme: ThemeData(
primarySwatch: Colors.pink, primarySwatch: Colors.pink,
textSelectionColor: Colors.blue, //textSelectionColor: Colors.blue,
), ),
home: Startup(), home: Startup(),
routes: <String, WidgetBuilder>{ routes: <String, WidgetBuilder>{
@ -64,12 +64,12 @@ class Startup extends StatelessWidget {
Startup() { Startup() {
FirebaseMessaging.onMessage.listen((RemoteMessage message) { FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (message.notification != null) { if (message.notification != null) {
print(message.notification.body); print(message.notification!);
String text = ''; String text = '';
if (message.notification != null && message.notification.body != null) { if (message.notification != null && message.notification!.body != null) {
text = message.notification.body; text = message.notification!.body!;
} }
Fluttertoast.showToast( /*Fluttertoast.showToast(
msg: text, msg: text,
toastLength: Toast.LENGTH_LONG, toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.TOP, gravity: ToastGravity.TOP,
@ -77,7 +77,7 @@ class Startup extends StatelessWidget {
backgroundColor: Colors.grey[100], backgroundColor: Colors.grey[100],
textColor: Colors.black, textColor: Colors.black,
fontSize: 16.0 fontSize: 16.0
); );*/
} }
}); });
} }
@ -86,9 +86,9 @@ class Startup extends StatelessWidget {
if (_handled) return; if (_handled) return;
_handled = true; _handled = true;
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
final String token = prefs.getString('apiToken'); String? token = prefs.getString('apiToken');
if (token != null) { if (token != null) {
Provider.of<Store>(context, listen: false).setToken(token); Provider.of<Store>(context, listen: false).setToken(token!);
FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance; FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
await _firebaseMessaging.requestPermission( await _firebaseMessaging.requestPermission(
@ -100,16 +100,14 @@ class Startup extends StatelessWidget {
provisional: false, provisional: false,
sound: true, sound: true,
); );
String _pushToken = await _firebaseMessaging.getToken(); String? _pushToken = await _firebaseMessaging.getToken();
if (_pushToken != null) { if (_pushToken != null) {
print("sending push"); print("sending push");
Api api = Api(); 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: // Push without including current route in stack:
Navigator.of(context, rootNavigator: true).pushNamedAndRemoveUntil('/home', (Route<dynamic> route) => false); Navigator.of(context, rootNavigator: true).pushNamedAndRemoveUntil('/home', (Route<dynamic> route) => false);
print('222');
} else { } else {
Navigator.of(context).pushNamedAndRemoveUntil('/welcome', (Route<dynamic> route) => false); 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'; import 'api.dart';
class _ObjectScreenState extends State<ObjectScreen> { 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 Function _onDelete;
final Api api = Api(); 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 { void _deleteObject(BuildContext context, BuildContext modalContext) async {
var data = await api.request('DELETE', '/objects/' + _object['_id']); var data = await api.request('DELETE', '/objects/' + _object['_id']);
@ -26,7 +28,7 @@ class _ObjectScreenState extends State<ObjectScreen> {
showDialog( showDialog(
context: modalContext, context: modalContext,
builder: (BuildContext context) => CupertinoAlertDialog( 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.'), content: new Text('This action cannot be undone.'),
actions: <Widget>[ actions: <Widget>[
CupertinoDialogAction( CupertinoDialogAction(
@ -44,6 +46,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) { void _showSettingsModal(context) {
showCupertinoModalPopup( showCupertinoModalPopup(
context: context, context: context,
@ -55,9 +98,13 @@ class _ObjectScreenState extends State<ObjectScreen> {
child: Text('Cancel') child: Text('Cancel')
), ),
actions: [ actions: [
CupertinoActionSheetAction(
onPressed: () => _renameObject(context),
child: Text('Rename item'),
),
CupertinoActionSheetAction( CupertinoActionSheetAction(
onPressed: () => _confirmDeleteObject(modalContext), onPressed: () => _confirmDeleteObject(modalContext),
child: Text('Delete object'), child: Text('Delete item'),
isDestructiveAction: true, isDestructiveAction: true,
), ),
] ]
@ -71,11 +118,22 @@ class _ObjectScreenState extends State<ObjectScreen> {
return Image.network(_object['url']); return Image.network(_object['url']);
} }
else if (_object['type'] == 'pattern') { else if (_object['type'] == 'pattern') {
var dat = Uri.parse(_object['preview']).data; if (_object['previewUrl'] != null) {
return Image.memory(dat.contentAsBytes()); return Image.network(_object['previewUrl']!);;
} }
else { else {
return RaisedButton(child: Text('View file'), onPressed: () { 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 ElevatedButton(child: Text('View file'), onPressed: () {
launch(_object['url']); launch(_object['url']);
}); });
} }
@ -113,8 +171,10 @@ class _ObjectScreenState extends State<ObjectScreen> {
class ObjectScreen extends StatefulWidget { class ObjectScreen extends StatefulWidget {
final Map<String,dynamic> _object; final Map<String,dynamic> _object;
final Map<String,dynamic> _project;
final Function _onUpdate;
final Function _onDelete; final Function _onDelete;
ObjectScreen(this._object, this._onDelete) { } ObjectScreen(this._object, this._project, this._onUpdate, this._onDelete) { }
@override @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(); final Api api = Api();
bool _loading = false; bool _loading = false;
String _pushToken; String? _pushToken;
@override @override
void dispose() { void dispose() {
@ -36,7 +36,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
); );
_pushToken = await _firebaseMessaging.getToken();*/ _pushToken = await _firebaseMessaging.getToken();*/
if (_pushToken != null) { if (_pushToken != null) {
api.request('PUT', '/accounts/pushToken', {'pushToken': _pushToken}); api.request('PUT', '/accounts/pushToken', {'pushToken': _pushToken!});
} }
setState(() => _loading = false); setState(() => _loading = false);
_controller.animateToPage(2, duration: Duration(milliseconds: 500), curve: Curves.easeInOut); _controller.animateToPage(2, duration: Duration(milliseconds: 500), curve: Curves.easeInOut);
@ -63,7 +63,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
SizedBox(height: 10), 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), 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), SizedBox(height: 10),
RaisedButton( ElevatedButton(
child: Text('OK, I know what projects are!'), child: Text('OK, I know what projects are!'),
onPressed: () => _controller.animateToPage(1, duration: Duration(milliseconds: 500), curve: Curves.easeInOut), onPressed: () => _controller.animateToPage(1, duration: Duration(milliseconds: 500), curve: Curves.easeInOut),
) )
@ -84,12 +84,12 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
SizedBox(height: 10), 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), 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), SizedBox(height: 10),
RaisedButton( ElevatedButton(
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
_loading ? CircularProgressIndicator() : null, _loading ? CircularProgressIndicator() : SizedBox(width: 0),
_loading ? SizedBox(width: 5) : null, _loading ? SizedBox(width: 5) : SizedBox(width: 0),
Text('What\'s next?'), Text('What\'s next?'),
].where((o) => o != null).toList()), ]),
onPressed: _requestPushPermissions, onPressed: _requestPushPermissions,
) )
] ]
@ -107,7 +107,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
SizedBox(height: 10), 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), 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), SizedBox(height: 10),
RaisedButton( ElevatedButton(
child: Text('Let\'s go'), child: Text('Let\'s go'),
onPressed: () => Navigator.of(context).pushNamedAndRemoveUntil('/home', (Route<dynamic> route) => false), onPressed: () => Navigator.of(context).pushNamedAndRemoveUntil('/home', (Route<dynamic> route) => false),
), ),

View File

@ -6,86 +6,8 @@ import 'dart:io';
import 'api.dart'; import 'api.dart';
import 'object.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> { class _ProjectScreenState extends State<ProjectScreen> {
final Function _onUpdate;
final Function _onDelete; final Function _onDelete;
final picker = ImagePicker(); final picker = ImagePicker();
final Api api = Api(); final Api api = Api();
@ -94,7 +16,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
bool _loading = false; bool _loading = false;
bool _creating = false; bool _creating = false;
_ProjectScreenState(this._project, this._onDelete) { } _ProjectScreenState(this._project, this._onUpdate, this._onDelete) { }
@override @override
initState() { initState() {
@ -122,8 +44,20 @@ class _ProjectScreenState extends State<ProjectScreen> {
setState(() { setState(() {
_project = project; _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) { void _onDeleteObject(String id) {
List<dynamic> _newObjects = _objects.where((p) => p['_id'] != id).toList(); List<dynamic> _newObjects = _objects.where((p) => p['_id'] != id).toList();
setState(() { setState(() {
@ -196,7 +130,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
showCupertinoModalPopup(context: context, builder: (BuildContext context) => settingsDialog); showCupertinoModalPopup(context: context, builder: (BuildContext context) => settingsDialog);
} }
Widget getImageBox(data, [bool isMemory, bool isNetwork]) { Widget getMemoryImageBox(data, [bool? isMemory, bool? isNetwork]) {
return new AspectRatio( return new AspectRatio(
aspectRatio: 1 / 1, aspectRatio: 1 / 1,
child: new Container( child: new Container(
@ -204,7 +138,21 @@ class _ProjectScreenState extends State<ProjectScreen> {
image: new DecorationImage( image: new DecorationImage(
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
alignment: FractionalOffset.topCenter, 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) { if (object['isImage'] == true) {
type = 'Image'; type = 'Image';
leader = getImageBox(object['url']); if (object['url'] != null) {
leader = getNetworkImageBox(object['url']!);
} }
else if (object['type'] == 'pattern' && object['preview'] != null) { else {
leader = getIconBox(Icon(Icons.photo));
}
}
else if (object['type'] == 'pattern') {
type = 'Weaving pattern'; type = 'Weaving pattern';
var dat = Uri.parse(object['preview']).data; if (object['previewUrl'] != null) {
leader = getImageBox(dat.contentAsBytes(), true); leader = getNetworkImageBox(object['previewUrl']!);
}
else {
leader = getIconBox(Icon(Icons.pattern));
}
} }
else if (object['type'] == 'file') { else if (object['type'] == 'file') {
type = 'File'; type = 'File';
leader = getIconBox(Icon(Icons.insert_drive_file)); leader = getIconBox(Icon(Icons.insert_drive_file));
} }
else {
type = 'Unknown';
leader = getIconBox(Icon(Icons.file_present));
}
return new Card( return new Card(
child: InkWell( child: InkWell(
@ -250,7 +211,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( 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 { class ProjectScreen extends StatefulWidget {
final Map<String,dynamic> _project; final Map<String,dynamic> _project;
final Function _onUpdate;
final Function _onDelete; final Function _onDelete;
ProjectScreen(this._project, this._onDelete) { } ProjectScreen(this._project, this._onUpdate, this._onDelete) { }
@override @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 'project.dart';
import 'settings.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> { class _NewProjectDialogState extends State<_NewProjectDialog> {
final TextEditingController _newProjectNameController = TextEditingController(); final TextEditingController _newProjectNameController = TextEditingController();
final Function _onStart; final Function _onStart;
@ -82,155 +240,3 @@ class _NewProjectDialog extends StatefulWidget {
@override @override
_NewProjectDialogState createState() => _NewProjectDialogState(_onStart, _onComplete); _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), SizedBox(height: 20),
RaisedButton( ElevatedButton(
onPressed: () => _submit(context), onPressed: () => _submit(context),
color: Colors.pink, //color: Colors.pink,
child: _registering ? SizedBox(height: 20, width: 20, child:CircularProgressIndicator(backgroundColor: Colors.white)) : Text("Register", child: _registering ? SizedBox(height: 20, width: 20, child:CircularProgressIndicator(backgroundColor: Colors.white)) : Text("Register",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 15) style: TextStyle(color: Colors.white, fontSize: 15)

View File

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

View File

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

View File

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

View File

@ -1,167 +1,182 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "9ebe81588e666f7e2b21309f2b5653bd9642d7f27fd0a6894278d2ff40cb9481"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
archive: archive:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
url: "https://pub.dartlang.org" sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.1" version: "3.3.7"
args: args:
dependency: transitive dependency: transitive
description: description:
name: args name: args
url: "https://pub.dartlang.org" sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.4.2"
async: async:
dependency: transitive dependency: transitive
description: description:
name: async name: async
url: "https://pub.dartlang.org" sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
url: "https://pub.dev"
source: hosted source: hosted
version: "2.8.2" version: "2.10.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
name: boolean_selector name: boolean_selector
url: "https://pub.dartlang.org" sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
characters: characters:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
url: "https://pub.dartlang.org" sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
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"
clock: clock:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
url: "https://pub.dartlang.org" sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
url: "https://pub.dev"
source: hosted 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: cross_file:
dependency: transitive dependency: transitive
description: description:
name: cross_file name: cross_file
url: "https://pub.dartlang.org" sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.2" version: "0.3.3+4"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
name: crypto name: crypto
url: "https://pub.dartlang.org" sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.3"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
name: csslib name: csslib
url: "https://pub.dartlang.org" sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.17.1" version: "0.17.3"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
name: cupertino_icons name: cupertino_icons
url: "https://pub.dartlang.org" sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.5"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.3.1"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
url: "https://pub.dartlang.org" sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "2.0.2"
file: file:
dependency: transitive dependency: transitive
description: description:
name: file name: file
url: "https://pub.dartlang.org" sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.2" version: "6.1.4"
firebase_core: firebase_core:
dependency: transitive dependency: transitive
description: description:
name: firebase_core name: firebase_core
url: "https://pub.dartlang.org" sha256: e9b36b391690cf329c6fb1de220045e97c13784c303820cd33962319580a56c6
url: "https://pub.dev"
source: hosted source: hosted
version: "1.13.1" version: "2.13.1"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_platform_interface name: firebase_core_platform_interface
url: "https://pub.dartlang.org" sha256: b63e3be6c96ef5c33bdec1aab23c91eb00696f6452f0519401d640938c94cba2
url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.5" version: "4.8.0"
firebase_core_web: firebase_core_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_web name: firebase_core_web
url: "https://pub.dartlang.org" sha256: "8c0f4c87d20e2d001a5915df238c1f9c88704231f591324205f5a5d2a7740a45"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.6.1" version: "2.5.0"
firebase_messaging: firebase_messaging:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_messaging name: firebase_messaging
url: "https://pub.dartlang.org" sha256: a01d7b9eb43a4bad54a411edb2b4124089d88eab029191893e83c39e18ab19f7
url: "https://pub.dev"
source: hosted source: hosted
version: "11.2.8" version: "14.6.2"
firebase_messaging_platform_interface: firebase_messaging_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_messaging_platform_interface name: firebase_messaging_platform_interface
url: "https://pub.dartlang.org" sha256: c2fef3e30fbfa3a71d74477df102d1c2f5aad860bb68bb4086b0af3b12abedf3
url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.1" version: "4.5.2"
firebase_messaging_web: firebase_messaging_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_messaging_web name: firebase_messaging_web
url: "https://pub.dartlang.org" sha256: "8d280f0110ca4946b9863e578b9879874066ac486ffa596a609aab329fb6fa7e"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.9" version: "3.5.2"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -171,44 +186,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_html name: flutter_html
url: "https://pub.dartlang.org" sha256: "02ad69e813ecfc0728a455e4bf892b9379983e050722b1dce00192ee2e41d1ee"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "3.0.0-beta.2"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_launcher_icons name: flutter_launcher_icons
url: "https://pub.dartlang.org" sha256: "559c600f056e7c704bd843723c21e01b5fba47e8824bd02422165bcc02a5de1d"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.2" version: "0.9.3"
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"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org" sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.5" version: "2.0.15"
flutter_svg:
dependency: transitive
description:
name: flutter_svg
url: "https://pub.dartlang.org"
source: hosted
version: "0.23.0+1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -219,251 +216,270 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
fluttertoast:
dependency: "direct main"
description:
name: fluttertoast
url: "https://pub.dartlang.org"
source: hosted
version: "8.0.9"
html: html:
dependency: transitive dependency: transitive
description: description:
name: html name: html
url: "https://pub.dartlang.org" sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.15.0" version: "0.15.3"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
url: "https://pub.dartlang.org" sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.4" version: "0.13.6"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
url: "https://pub.dartlang.org" sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "4.0.2"
image: image:
dependency: transitive dependency: transitive
description: description:
name: image name: image
url: "https://pub.dartlang.org" sha256: "02bafd3b4f399bfeb10034deba9753d93b55ce41cd0c4d3d8b355626f80e5b32"
url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
image_picker: image_picker:
dependency: "direct main" dependency: "direct main"
description: description:
name: image_picker name: image_picker
url: "https://pub.dartlang.org" sha256: "9978d3510af4e6a902e545ce19229b926e6de6a1828d6134d3aab2e129a4d270"
url: "https://pub.dev"
source: hosted 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: image_picker_for_web:
dependency: transitive dependency: transitive
description: description:
name: image_picker_for_web name: image_picker_for_web
url: "https://pub.dartlang.org" sha256: "98f50d6b9f294c8ba35e25cc0d13b04bfddd25dbc8d32fa9d566a6572f2c081c"
url: "https://pub.dev"
source: hosted 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: image_picker_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: image_picker_platform_interface name: image_picker_platform_interface
url: "https://pub.dartlang.org" sha256: "1991219d9dbc42a99aff77e663af8ca51ced592cd6685c9485e3458302d3d4f8"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.4" version: "2.6.3"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
name: intl name: intl
url: "https://pub.dartlang.org" sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.17.0" version: "0.17.0"
js: js:
dependency: transitive dependency: transitive
description: description:
name: js name: js
url: "https://pub.dartlang.org" sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
url: "https://pub.dev"
source: hosted 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: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
url: "https://pub.dartlang.org" sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.11" version: "0.12.13"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
url: "https://pub.dartlang.org" sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.3" version: "0.2.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.7.0" version: "1.8.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
name: nested name: nested
url: "https://pub.dartlang.org" sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
numerus:
dependency: transitive
description:
name: numerus
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
url: "https://pub.dartlang.org" sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.0" version: "1.8.2"
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"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
name: path_provider_linux name: path_provider_linux
url: "https://pub.dartlang.org" sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.1.11"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: path_provider_platform_interface name: path_provider_platform_interface
url: "https://pub.dartlang.org" sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" version: "2.0.6"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
url: "https://pub.dartlang.org" sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.5" version: "2.1.7"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
name: petitparser name: petitparser
url: "https://pub.dartlang.org" sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4"
url: "https://pub.dev"
source: hosted source: hosted
version: "4.4.0" version: "5.1.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
url: "https://pub.dartlang.org" sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
url: "https://pub.dartlang.org" sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
url: "https://pub.dev"
source: hosted 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: process:
dependency: transitive dependency: transitive
description: description:
name: process name: process
url: "https://pub.dartlang.org" sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.4" version: "4.2.4"
provider: provider:
dependency: "direct main" dependency: "direct main"
description: description:
name: provider name: provider
url: "https://pub.dartlang.org" sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.2" version: "6.0.5"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1+1"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
url: "https://pub.dartlang.org" sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.13" version: "2.1.1"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
url: "https://pub.dartlang.org" sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.11" version: "2.1.4"
shared_preferences_ios: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_ios name: shared_preferences_foundation
url: "https://pub.dartlang.org" sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.2.2"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_linux name: shared_preferences_linux
url: "https://pub.dartlang.org" sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.2.0"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
shared_preferences_platform_interface: shared_preferences_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_platform_interface name: shared_preferences_platform_interface
url: "https://pub.dartlang.org" sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.2.0"
shared_preferences_web: shared_preferences_web:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
url: "https://pub.dartlang.org" sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" version: "2.1.0"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_windows name: shared_preferences_windows
url: "https://pub.dartlang.org" sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.2.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -473,254 +489,162 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
url: "https://pub.dartlang.org" sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.1" version: "1.9.1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
url: "https://pub.dartlang.org" sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.11.0"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
url: "https://pub.dartlang.org" sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.2.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.8" version: "0.4.16"
tuple:
dependency: transitive
description:
name: tuple
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
url: "https://pub.dartlang.org" sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.2"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
url: "https://pub.dartlang.org" sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3
url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.20" version: "6.1.11"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
url: "https://pub.dartlang.org" sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51
url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.15" version: "6.0.35"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
url: "https://pub.dartlang.org" sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.15" version: "6.1.4"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_linux name: url_launcher_linux
url: "https://pub.dartlang.org" sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.5"
url_launcher_macos: url_launcher_macos:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
url: "https://pub.dartlang.org" sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e"
url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.5"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_platform_interface name: url_launcher_platform_interface
url: "https://pub.dartlang.org" sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.5" version: "2.1.2"
url_launcher_web: url_launcher_web:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
url: "https://pub.dartlang.org" sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.9" version: "2.0.17"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_windows name: url_launcher_windows
url: "https://pub.dartlang.org" sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771"
url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.6"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.4"
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"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
url: "https://pub.dartlang.org" sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4
url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" version: "3.1.4"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
url: "https://pub.dartlang.org" sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1
url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0+1" version: "1.0.0"
xml: xml:
dependency: transitive dependency: transitive
description: description:
name: xml name: xml
url: "https://pub.dartlang.org" sha256: "80d494c09849dc3f899d227a78c30c5b949b985ededf884cb3f3bcd39f4b447a"
url: "https://pub.dev"
source: hosted source: hosted
version: "5.3.1" version: "5.4.1"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
name: yaml name: yaml
url: "https://pub.dartlang.org" sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.1.2"
sdks: sdks:
dart: ">=2.16.0 <3.0.0" dart: ">=2.19.0 <3.0.0"
flutter: ">=2.10.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. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.2+7 version: 1.1.0+8
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: '>=2.17.0 <3.0.0'
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: ^1.0.3 cupertino_icons: ^1.0.4
http: ^0.13.3 http: ^0.13.4
shared_preferences: ^2.0.11 shared_preferences: ^2.0.15
provider: ^6.0.1 provider: ^6.0.3
url_launcher: ^6.0.17 url_launcher: ^6.1.2
flutter_html: ^2.2.1 flutter_html: ^3.0.0-alpha.3
intl: ^0.17.0 intl: ^0.17.0
image_picker: ^0.8.4 image_picker: ^0.8.5+3
flutter_launcher_icons: ^0.9.0 flutter_launcher_icons: ^0.9.0
firebase_messaging: ^11.2.3 firebase_messaging: ^14.4.0
fluttertoast: ^8.0.9 #fluttertoast: ^8.0.9
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -6,4 +6,26 @@ export const uploads = {
}, success, fail) { }, success, fail) {
api.authenticatedRequest('GET', `/uploads/file/request?name=${name}&size=${size}&type=${type}&forType=${forType}&forId=${forId}`, null, 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; if (!object) return null;
return ( return (
<Card raised key={object._id} style={{ cursor: 'pointer' }} as={Link} to={`/${user?.username}/${project?.path}/${object._id}`}> <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 && {user &&
<div style={{position: 'absolute', top: 5, left: 5, padding: '3px 6px', background: 'rgba(250,250,250,0.8)', borderRadius: 5}}> <div style={{position: 'absolute', top: 5, left: 5, padding: '3px 6px', background: 'rgba(250,250,250,0.8)', borderRadius: 5}}>
<UserChip user={user} /> <UserChip user={user} />

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import { useDispatch } from 'react-redux';
import { Loader } from 'semantic-ui-react'; import { Loader } from 'semantic-ui-react';
import actions from '../../../../actions'; import actions from '../../../../actions';
import api from '../../../../api'; import api from '../../../../api';
import util from '../../../../utils/utils.js';
import ElementPan from '../../../includes/ElementPan'; import ElementPan from '../../../includes/ElementPan';
import { StyledPattern } from '../../../main/projects/objects/Draft'; import { StyledPattern } from '../../../main/projects/objects/Draft';
@ -17,18 +18,6 @@ function DraftPreview({ object }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const objectId = object?._id; 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(() => { useEffect(() => {
dispatch(actions.objects.updateEditor({ tool: 'pan' })); dispatch(actions.objects.updateEditor({ tool: 'pan' }));
setLoading(true); setLoading(true);
@ -36,10 +25,17 @@ function DraftPreview({ object }) {
setLoading(false); setLoading(false);
if (o.pattern && o.pattern.warp) { if (o.pattern && o.pattern.warp) {
setPattern(o.pattern); 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)); }, err => setLoading(false));
}, [dispatch, objectId, generatePreview]); }, [dispatch, objectId]);
const unifyCanvas = useCallback(() => { const unifyCanvas = useCallback(() => {
if (!pattern) return; if (!pattern) return;

View File

@ -1,5 +1,6 @@
import { createConfirmation } from 'react-confirm'; import { createConfirmation } from 'react-confirm';
import ConfirmModal from '../components/includes/ConfirmModal'; import ConfirmModal from '../components/includes/ConfirmModal';
import api from '../api';
import avatar1 from '../images/avatars/1.png'; import avatar1 from '../images/avatars/1.png';
import avatar2 from '../images/avatars/2.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}`; if (IMAGE_SERVER) return `${import.meta.env.VITE_IMAGINARY_URL}/resize?width=${width}&url=${url}`;
return 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; export default utils;