Compare commits
No commits in common. "a22c2d7d16d7358103009d5ef77f99736bad1f67" and "6e15952ffc57f0b0c1a5d5cd2cdc446bef9724ba" have entirely different histories.
a22c2d7d16
...
6e15952ffc
@ -29,14 +29,11 @@ def get(user, id):
|
||||
owner = user and (user.get('_id') == proj['user'])
|
||||
if not owner and proj['visibility'] != 'public':
|
||||
raise util.errors.BadRequest('Forbidden')
|
||||
if obj['type'] == 'file' and 'storedName' in obj:
|
||||
obj['url'] = uploads.get_presigned_url('projects/{0}/{1}'.format(proj['_id'], obj['storedName']))
|
||||
if obj['type'] == 'pattern' and 'preview' in obj and '.png' in obj['preview']:
|
||||
obj['previewUrl'] = uploads.get_presigned_url('projects/{0}/{1}'.format(proj['_id'], obj['preview']))
|
||||
del obj['preview']
|
||||
if obj.get('fullPreview'):
|
||||
obj['fullPreviewUrl'] = uploads.get_presigned_url('projects/{0}/{1}'.format(proj['_id'], obj['fullPreview']))
|
||||
obj['projectObject'] = proj
|
||||
return obj
|
||||
|
||||
def copy_to_project(user, id, project_id):
|
||||
|
@ -61,11 +61,8 @@ def discover(user, count = 3):
|
||||
random.shuffle(all_projects)
|
||||
for p in all_projects:
|
||||
if db.objects.find_one({'project': p['_id'], 'name': {'$ne': 'Untitled pattern'}}):
|
||||
owner = db.users.find_one({'_id': p['user']}, {'username': 1, 'avatar': 1})
|
||||
owner = db.users.find_one({'_id': p['user']}, {'username': 1})
|
||||
p['fullName'] = owner['username'] + '/' + p['path']
|
||||
p['owner'] = owner
|
||||
if 'avatar' in p['owner']:
|
||||
p['owner']['avatarUrl'] = uploads.get_presigned_url('users/{0}/{1}'.format(p['owner']['_id'], p['owner']['avatar']))
|
||||
projects.append(p)
|
||||
if len(projects) >= count: break
|
||||
|
||||
@ -98,7 +95,7 @@ def explore(page = 1):
|
||||
all_public_project_ids = list(map(lambda p: p['_id'], all_public_projects))
|
||||
for project in all_public_projects:
|
||||
project_map[project['_id']] = project
|
||||
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, 'type': 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:
|
||||
object['projectObject'] = project_map.get(object['project'])
|
||||
if 'preview' in object and '.png' in object['preview']:
|
||||
|
@ -30,22 +30,13 @@ def get(user, username):
|
||||
if not user or not user['_id'] == fetch_user['_id']:
|
||||
project_query['visibility'] = 'public'
|
||||
|
||||
fetch_user['projects'] = list(db.projects.find(project_query, {'name': 1, 'path': 1, 'description': 1, 'visibility': 1}).limit(15))
|
||||
for project in fetch_user['projects']:
|
||||
project['fullName'] = fetch_user['username'] + '/' + project['path']
|
||||
if 'avatar' in fetch_user:
|
||||
fetch_user['avatarUrl'] = uploads.get_presigned_url('users/{0}/{1}'.format(str(fetch_user['_id']), fetch_user['avatar']))
|
||||
if user:
|
||||
fetch_user['following'] = fetch_user['_id'] in list(map(lambda f: f['user'], user.get('following', [])))
|
||||
|
||||
user_projects = list(db.projects.find(project_query, {'name': 1, 'path': 1, 'description': 1, 'visibility': 1}).limit(15))
|
||||
for project in user_projects:
|
||||
project['fullName'] = fetch_user['username'] + '/' + project['path']
|
||||
project['owner'] = {
|
||||
'_id': fetch_user['_id'],
|
||||
'username': fetch_user['username'],
|
||||
'avatar': fetch_user.get('avatar'),
|
||||
'avatarUrl': fetch_user.get('avatarUrl'),
|
||||
}
|
||||
fetch_user['projects'] = user_projects
|
||||
|
||||
return fetch_user
|
||||
|
||||
def update(user, username, data):
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 56 KiB |
@ -48,7 +48,6 @@
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
9C430D344D81D00E4F8BC572 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BE18F7F22B54707500363B2E /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
BE6C8E7324CDE9B20018AD10 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = "<group>"; };
|
||||
BEA6727A24CCAF5600BBF836 /* RunnerRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerRelease.entitlements; sourceTree = "<group>"; };
|
||||
BEA6727B24CCB04900BBF836 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
@ -117,7 +116,6 @@
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BE18F7F22B54707500363B2E /* Runner.entitlements */,
|
||||
BE6C8E7324CDE9B20018AD10 /* RunnerDebug.entitlements */,
|
||||
BEA6727A24CCAF5600BBF836 /* RunnerRelease.entitlements */,
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
@ -375,7 +373,6 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 38T664W57F;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1520"
|
||||
LastUpgradeVersion = "1430"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -12,8 +12,6 @@
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Treadl</string>
|
||||
<key>FlutterDeepLinkingEnabled</key>
|
||||
<true/>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
|
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:treadl.com</string>
|
||||
<string>applinks:www.treadl.com</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
@ -4,10 +4,5 @@
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:treadl.com</string>
|
||||
<string>applinks:www.treadl.com</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -4,10 +4,5 @@
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:treadl.com</string>
|
||||
<string>applinks:www.treadl.com</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -1,21 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'util.dart';
|
||||
import 'model.dart';
|
||||
|
||||
class Api {
|
||||
|
||||
String? _token;
|
||||
final String apiBase = 'https://api.treadl.com';
|
||||
//final String apiBase = 'http://192.168.5.134:2001';
|
||||
|
||||
Api({token: null}) {
|
||||
if (token != null) _token = token;
|
||||
}
|
||||
//final String apiBase = 'https://api.treadl.com';
|
||||
final String apiBase = 'http://192.168.5.134:2001';
|
||||
|
||||
Future<String?> loadToken() async {
|
||||
if (_token != null) {
|
||||
|
@ -1,89 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'routeArguments.dart';
|
||||
import 'api.dart';
|
||||
import 'util.dart';
|
||||
import 'lib.dart';
|
||||
|
||||
class _ExploreTabState extends State<ExploreTab> {
|
||||
List<dynamic> objects = [];
|
||||
List<dynamic> projects = [];
|
||||
bool loading = false;
|
||||
final Api api = Api();
|
||||
final Util util = Util();
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
getData();
|
||||
}
|
||||
|
||||
void getData() async {
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
var data = await api.request('GET', '/search/explore');
|
||||
if (data['success'] == true) {
|
||||
setState(() {
|
||||
loading = false;
|
||||
objects = data['payload']['objects'];
|
||||
});
|
||||
}
|
||||
var data2 = await api.request('GET', '/search/discover');
|
||||
if (data2['success'] == true) {
|
||||
setState(() {
|
||||
projects = data2['payload']['highlightProjects'];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Explore'),
|
||||
),
|
||||
body: loading ?
|
||||
Container(
|
||||
margin: const EdgeInsets.all(10.0),
|
||||
alignment: Alignment.center,
|
||||
child: CircularProgressIndicator()
|
||||
)
|
||||
: Container(
|
||||
color: Color.fromRGBO(255, 251, 248, 1),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: 10),
|
||||
CustomText('Discover projects', 'h1', margin: 5),
|
||||
Container(
|
||||
height: 130,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: projects.map((p) => ProjectCard(p)).toList()
|
||||
)
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
CustomText('Recent patterns', 'h1', margin: 5),
|
||||
Expanded(child: GridView.count(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 5,
|
||||
crossAxisSpacing: 5,
|
||||
childAspectRatio: 0.9,
|
||||
children: objects.map((object) =>
|
||||
PatternCard(object)
|
||||
).toList(),
|
||||
)),
|
||||
]
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExploreTab extends StatefulWidget {
|
||||
@override
|
||||
_ExploreTabState createState() => _ExploreTabState();
|
||||
}
|
||||
|
@ -6,41 +6,31 @@ import 'group_noticeboard.dart';
|
||||
import 'group_members.dart';
|
||||
|
||||
class _GroupScreenState extends State<GroupScreen> {
|
||||
final String id;
|
||||
Map<String, dynamic>? _group;
|
||||
int _selectedIndex = 0;
|
||||
List<Widget> _widgetOptions = <Widget> [];
|
||||
final Map<String, dynamic> _group;
|
||||
|
||||
_GroupScreenState(this.id) { }
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
fetchGroup();
|
||||
super.initState();
|
||||
_GroupScreenState(this._group) {
|
||||
_widgetOptions = <Widget> [
|
||||
GroupNoticeBoardTab(this._group),
|
||||
GroupMembersTab(this._group)
|
||||
];
|
||||
}
|
||||
|
||||
void fetchGroup() async {
|
||||
Api api = Api();
|
||||
var data = await api.request('GET', '/groups/' + id);
|
||||
if (data['success'] == true) {
|
||||
setState(() {
|
||||
_group = data['payload'];
|
||||
});
|
||||
}
|
||||
void _onItemTapped(int index) {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(_group?['name'] ?? 'Group')
|
||||
title: Text(_group['name'])
|
||||
),
|
||||
body: Center(
|
||||
child: _group != null ?
|
||||
[
|
||||
GroupNoticeBoardTab(_group!),
|
||||
GroupMembersTab(_group!)
|
||||
].elementAt(_selectedIndex)
|
||||
: CircularProgressIndicator(),
|
||||
child: _widgetOptions.elementAt(_selectedIndex),
|
||||
),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
items: const <BottomNavigationBarItem>[
|
||||
@ -55,17 +45,15 @@ class _GroupScreenState extends State<GroupScreen> {
|
||||
],
|
||||
currentIndex: _selectedIndex,
|
||||
selectedItemColor: Colors.pink[600],
|
||||
onTap: (int index) => setState(() {
|
||||
_selectedIndex = index;
|
||||
}),
|
||||
onTap: _onItemTapped,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GroupScreen extends StatefulWidget {
|
||||
final String id;
|
||||
GroupScreen(this.id) { }
|
||||
final Map<String,dynamic> group;
|
||||
GroupScreen(this.group) { }
|
||||
@override
|
||||
_GroupScreenState createState() => _GroupScreenState(id);
|
||||
_GroupScreenState createState() => _GroupScreenState(group);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'api.dart';
|
||||
import 'util.dart';
|
||||
import 'group_noticeboard.dart';
|
||||
import 'user.dart';
|
||||
|
||||
class _GroupMembersTabState extends State<GroupMembersTab> {
|
||||
@ -33,7 +33,14 @@ class _GroupMembersTabState extends State<GroupMembersTab> {
|
||||
|
||||
Widget getMemberCard(member) {
|
||||
return new ListTile(
|
||||
onTap: () => context.push('/' + member['username']),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => UserScreen(member),
|
||||
),
|
||||
);
|
||||
},
|
||||
leading: util.avatarImage(util.avatarUrl(member), size: 40),
|
||||
trailing: Icon(Icons.keyboard_arrow_right),
|
||||
title: Text(member['username'])
|
||||
|
@ -1,11 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'util.dart';
|
||||
import 'api.dart';
|
||||
import 'user.dart';
|
||||
import 'lib.dart';
|
||||
|
||||
class _GroupNoticeBoardTabState extends State<GroupNoticeBoardTab> {
|
||||
final TextEditingController _newEntryController = TextEditingController();
|
||||
final Util utils = new Util();
|
||||
final Api api = Api();
|
||||
Map<String,dynamic> _group;
|
||||
List<dynamic> _entries = [];
|
||||
@ -38,10 +42,8 @@ class _GroupNoticeBoardTabState extends State<GroupNoticeBoardTab> {
|
||||
}
|
||||
|
||||
void _sendPost(context) async {
|
||||
String text = _newEntryController.text;
|
||||
if (text.length == 0) return;
|
||||
setState(() => _posting = true);
|
||||
var data = await api.request('POST', '/groups/' + _group['_id'] + '/entries', {'content': text});
|
||||
var data = await api.request('POST', '/groups/' + _group['_id'] + '/entries', {'content': _newEntryController.text});
|
||||
if (data['success'] == true) {
|
||||
_newEntryController.value = TextEditingValue(text: '');
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
|
@ -1,9 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'group.dart';
|
||||
import 'api.dart';
|
||||
import 'model.dart';
|
||||
import 'lib.dart';
|
||||
|
||||
class _GroupsTabState extends State<GroupsTab> {
|
||||
List<dynamic> _groups = [];
|
||||
@ -16,8 +16,6 @@ class _GroupsTabState extends State<GroupsTab> {
|
||||
}
|
||||
|
||||
void getGroups() async {
|
||||
AppModel model = Provider.of<AppModel>(context, listen: false);
|
||||
if (model.user == null) return;
|
||||
setState(() => _loading = true);
|
||||
Api api = Api();
|
||||
var data = await api.request('GET', '/groups');
|
||||
@ -38,7 +36,14 @@ class _GroupsTabState extends State<GroupsTab> {
|
||||
}
|
||||
return Card(
|
||||
child: InkWell(
|
||||
onTap: () => context.push('/groups/' + group['_id']),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => GroupScreen(group),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.people),
|
||||
trailing: Icon(Icons.keyboard_arrow_right),
|
||||
@ -50,42 +55,38 @@ class _GroupsTabState extends State<GroupsTab> {
|
||||
;
|
||||
}
|
||||
|
||||
Widget getBody() {
|
||||
AppModel model = Provider.of<AppModel>(context);
|
||||
if (model.user == null)
|
||||
return LoginNeeded(text: 'Once logged in, you\'ll find your groups here.');
|
||||
else if (_loading)
|
||||
return CircularProgressIndicator();
|
||||
else if (_groups != null && _groups.length > 0)
|
||||
return ListView.builder(
|
||||
itemCount: _groups.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildGroupCard(_groups[index]);
|
||||
},
|
||||
);
|
||||
else
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('You aren\'t a member of any groups yet', style: TextStyle(fontSize: 20), textAlign: TextAlign.center),
|
||||
Image(image: AssetImage('assets/group.png'), width: 300),
|
||||
Text('Groups let you meet and keep in touch with others in the weaving community.', textAlign: TextAlign.center),
|
||||
Text('Please use our website to join and leave groups.', textAlign: TextAlign.center),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('My Groups'),
|
||||
title: Text('Groups'),
|
||||
),
|
||||
body: Container(
|
||||
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: getBody()
|
||||
)
|
||||
child: (_groups != null && _groups.length > 0) ?
|
||||
ListView.builder(
|
||||
itemCount: _groups.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildGroupCard(_groups[index]);
|
||||
},
|
||||
)
|
||||
:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('You aren\'t a member of any groups yet', style: TextStyle(fontSize: 20), textAlign: TextAlign.center),
|
||||
Image(image: AssetImage('assets/group.png'), width: 300),
|
||||
Text('Groups let you meet and keep in touch with others in the weaving community.', textAlign: TextAlign.center),
|
||||
Text('Please use our website to join and leave groups.', textAlign: TextAlign.center),
|
||||
])
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'explore.dart';
|
||||
import 'projects.dart';
|
||||
import 'groups.dart';
|
||||
|
||||
@ -14,7 +13,6 @@ class HomeScreen extends StatefulWidget {
|
||||
class _MyStatefulWidgetState extends State<HomeScreen> {
|
||||
int _selectedIndex = 0;
|
||||
List<Widget> _widgetOptions = <Widget> [
|
||||
ExploreTab(),
|
||||
ProjectsTab(),
|
||||
GroupsTab()
|
||||
];
|
||||
@ -33,17 +31,13 @@ class _MyStatefulWidgetState extends State<HomeScreen> {
|
||||
),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.explore),
|
||||
label: 'Explore',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.folder),
|
||||
label: 'My Projects',
|
||||
label: 'Projects',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.person),
|
||||
label: 'My Groups',
|
||||
label: 'Groups',
|
||||
),
|
||||
],
|
||||
currentIndex: _selectedIndex,
|
||||
|
@ -4,12 +4,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'api.dart';
|
||||
import 'util.dart';
|
||||
import 'user.dart';
|
||||
import 'object.dart';
|
||||
import 'project.dart';
|
||||
|
||||
class Alert extends StatelessWidget {
|
||||
final String type;
|
||||
@ -103,7 +100,7 @@ class _NoticeboardPostState extends State<NoticeboardPost> {
|
||||
if (onDelete != null) {
|
||||
onDelete!(_entry);
|
||||
}
|
||||
context.pop();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,7 +146,7 @@ class _NoticeboardPostState extends State<NoticeboardPost> {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text('Cancel'),
|
||||
)
|
||||
@ -168,7 +165,11 @@ class _NoticeboardPostState extends State<NoticeboardPost> {
|
||||
Row(
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => context.push('/' + _entry['authorUser']['username']),
|
||||
onTap: () {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) => UserScreen(_entry['authorUser']),
|
||||
));
|
||||
},
|
||||
child: utils.avatarImage(utils.avatarUrl(_entry['authorUser']), size: isReply ? 30 : 40)
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
@ -228,172 +229,3 @@ class NoticeboardInput extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class UserChip extends StatelessWidget {
|
||||
final Map<String,dynamic> user;
|
||||
final Util utils = new Util();
|
||||
UserChip(this.user) {}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => context.push('/' + user['username']),
|
||||
child: Row(
|
||||
children: [
|
||||
utils.avatarImage(utils.avatarUrl(user), size: 20),
|
||||
SizedBox(width: 5),
|
||||
Text(user['username'], style: TextStyle(color: Colors.grey))
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PatternCard extends StatelessWidget {
|
||||
final Map<String,dynamic> object;
|
||||
PatternCard(this.object) {}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
context.push('/' + object['userObject']['username'] + '/' + object['projectObject']['path'] + '/' + object['_id']);
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.cover,
|
||||
image: NetworkImage(object['previewUrl']),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
UserChip(object['userObject']),
|
||||
SizedBox(height: 5),
|
||||
Text(Util.ellipsis(object['name'], 35), style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProjectCard extends StatelessWidget {
|
||||
final Map<String,dynamic> project;
|
||||
ProjectCard(this.project) {}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
context.push('/' + this.project['owner']['username'] + '/' + this.project['path']);
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.folder),
|
||||
SizedBox(height: 10),
|
||||
UserChip(project['owner']),
|
||||
SizedBox(height: 5),
|
||||
Text(Util.ellipsis(project['name'], 35), style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomText extends StatelessWidget {
|
||||
final String text;
|
||||
final String type;
|
||||
final double margin;
|
||||
TextStyle? style;
|
||||
CustomText(this.text, this.type, {this.margin = 0}) {
|
||||
if (this.type == 'h1') {
|
||||
style = TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
|
||||
}
|
||||
else {
|
||||
style = TextStyle();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.all(this.margin),
|
||||
child: Text(text, style: style)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoginNeeded extends StatelessWidget {
|
||||
final String? text;
|
||||
LoginNeeded({this.text}) {}
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('You need to login to see this', style: TextStyle(fontSize: 20), textAlign: TextAlign.center),
|
||||
Image(image: AssetImage('assets/login.png'), width: 300),
|
||||
text != null ? Text(text!, textAlign: TextAlign.center) : SizedBox(height: 10),
|
||||
CupertinoButton(
|
||||
onPressed: () {
|
||||
context.push('/welcome');
|
||||
},
|
||||
child: new Text("Login or register",
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyBox extends StatelessWidget {
|
||||
final String title;
|
||||
final String? description;
|
||||
|
||||
EmptyBox(this.title, {this.description}) {}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(title, style: TextStyle(fontSize: 20), textAlign: TextAlign.center),
|
||||
Image(image: AssetImage('assets/empty.png'), width: 300),
|
||||
description != null ? Text('Add a pattern file, an image, or something else to this project using the + button below.', textAlign: TextAlign.center) : SizedBox(height: 0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'api.dart';
|
||||
import 'model.dart';
|
||||
|
||||
class _LoginScreenState extends State<LoginScreen> {
|
||||
final TextEditingController _emailController = TextEditingController();
|
||||
@ -13,14 +11,15 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
final Api api = Api();
|
||||
bool _loggingIn = false;
|
||||
|
||||
void _submit(BuildContext context) async {
|
||||
void _submit(context) async {
|
||||
setState(() => _loggingIn = true);
|
||||
var data = await api.request('POST', '/accounts/login', {'email': _emailController.text, 'password': _passwordController.text});
|
||||
setState(() => _loggingIn = false);
|
||||
if (data['success'] == true) {
|
||||
AppModel model = Provider.of<AppModel>(context, listen: false);
|
||||
await model.setToken(data['payload']['token']);
|
||||
context.go('/onboarding');
|
||||
String token = data['payload']['token'];
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString('apiToken', token);
|
||||
Navigator.of(context).pushNamedAndRemoveUntil('/onboarding', (Route<dynamic> route) => false);
|
||||
}
|
||||
else {
|
||||
showDialog(
|
||||
@ -32,7 +31,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
child: Text('Try again'),
|
||||
onPressed: () => context.pop(),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
)
|
||||
@ -47,17 +46,19 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
title: Text('Login to Treadl'),
|
||||
),
|
||||
body: Container(
|
||||
margin: const EdgeInsets.only(top: 40, left: 10, right: 10),
|
||||
child: ListView(
|
||||
margin: const EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text('Login with your Treadl account', style: TextStyle(fontSize: 20)),
|
||||
SizedBox(height: 30),
|
||||
Image(image: AssetImage('assets/logo.png'), width: 100),
|
||||
SizedBox(height: 20),
|
||||
Text('Login using your Treadl account.', style: TextStyle(fontSize: 18)),
|
||||
SizedBox(height: 20),
|
||||
TextField(
|
||||
autofocus: true,
|
||||
controller: _emailController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'sam@example.com', labelText: 'Email address or username',
|
||||
border: OutlineInputBorder(),
|
||||
hintText: 'sam@example.com', labelText: 'Email address or username'
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
@ -66,8 +67,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
controller: _passwordController,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Type your password', labelText: 'Your password',
|
||||
border: OutlineInputBorder(),
|
||||
hintText: 'Type your password', labelText: 'Your password'
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
|
@ -3,54 +3,19 @@ import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
//import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'api.dart';
|
||||
import 'model.dart';
|
||||
import 'store.dart';
|
||||
import 'welcome.dart';
|
||||
import 'login.dart';
|
||||
import 'register.dart';
|
||||
import 'onboarding.dart';
|
||||
import 'home.dart';
|
||||
import 'project.dart';
|
||||
import 'object.dart';
|
||||
import 'settings.dart';
|
||||
import 'group.dart';
|
||||
import 'user.dart';
|
||||
|
||||
final router = GoRouter(
|
||||
routes: [
|
||||
GoRoute(path: '/', builder: (context, state) => Startup()),
|
||||
GoRoute(path: '/welcome', pageBuilder: (context, state) {
|
||||
return CustomTransitionPage(
|
||||
key: state.pageKey,
|
||||
child: WelcomeScreen(),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
// Change the opacity of the screen using a Curve based on the the animation's value
|
||||
return FadeTransition(
|
||||
opacity:
|
||||
CurveTween(curve: Curves.easeInOutCirc).animate(animation),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
GoRoute(path: '/login', builder: (context, state) => LoginScreen()),
|
||||
GoRoute(path: '/register', builder: (context, state) => RegisterScreen()),
|
||||
GoRoute(path: '/onboarding', builder: (context, state) => OnboardingScreen()),
|
||||
GoRoute(path: '/home', builder: (context, state) => HomeScreen()),
|
||||
GoRoute(path: '/settings', builder: (context, state) => SettingsScreen()),
|
||||
GoRoute(path: '/groups/:id', builder: (context, state) => GroupScreen(state.pathParameters['id']!)),
|
||||
GoRoute(path: '/:username', builder: (context, state) => UserScreen(state.pathParameters['username']!)),
|
||||
GoRoute(path: '/:username/:path', builder: (context, state) => ProjectScreen(state.pathParameters['username']!, state.pathParameters['path']!)),
|
||||
GoRoute(path: '/:username/:path/:id', builder: (context, state) => ObjectScreen(state.pathParameters['username']!, state.pathParameters['path']!, state.pathParameters['id']!)),
|
||||
],
|
||||
);
|
||||
|
||||
void main() {
|
||||
runApp(
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => AppModel(),
|
||||
create: (context) => Store(),
|
||||
child: MyApp()
|
||||
)
|
||||
);
|
||||
@ -72,19 +37,21 @@ class _AppState extends State<MyApp> {
|
||||
// Initialize FlutterFire:
|
||||
future: _initialization,
|
||||
builder: (context, snapshot) {
|
||||
return MaterialApp.router(
|
||||
routerConfig: router,
|
||||
/*return MaterialApp(*/
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Treadl',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.pink,
|
||||
//textSelectionColor: Colors.blue,
|
||||
),
|
||||
/*home: Startup(),
|
||||
home: Startup(),
|
||||
routes: <String, WidgetBuilder>{
|
||||
|
||||
}*/
|
||||
'/welcome': (BuildContext context) => WelcomeScreen(),
|
||||
'/login': (BuildContext context) => LoginScreen(),
|
||||
'/register': (BuildContext context) => RegisterScreen(),
|
||||
'/onboarding': (BuildContext context) => OnboardingScreen(),
|
||||
'/home': (BuildContext context) => HomeScreen(),
|
||||
}
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -121,8 +88,8 @@ class Startup extends StatelessWidget {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? token = prefs.getString('apiToken');
|
||||
if (token != null) {
|
||||
AppModel model = Provider.of<AppModel>(context, listen: false);
|
||||
await model.setToken(token!);
|
||||
Provider.of<Store>(context, listen: false).setToken(token!);
|
||||
|
||||
FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
|
||||
await _firebaseMessaging.requestPermission(
|
||||
alert: true,
|
||||
@ -139,8 +106,11 @@ class Startup extends StatelessWidget {
|
||||
Api api = Api();
|
||||
api.request('PUT', '/accounts/pushToken', {'pushToken': _pushToken!});
|
||||
}
|
||||
// Push without including current route in stack:
|
||||
Navigator.of(context, rootNavigator: true).pushNamedAndRemoveUntil('/home', (Route<dynamic> route) => false);
|
||||
} else {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil('/welcome', (Route<dynamic> route) => false);
|
||||
}
|
||||
context.go('/home');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1,65 +0,0 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'api.dart';
|
||||
|
||||
class User {
|
||||
final String id;
|
||||
final String username;
|
||||
String? avatar;
|
||||
String? avatarUrl;
|
||||
|
||||
User(this.id, this.username, {this.avatar, this.avatarUrl}) {}
|
||||
|
||||
static User loadJSON(Map<String,dynamic> input) {
|
||||
return User(input['_id'], input['username'], avatar: input['avatar'], avatarUrl: input['avatarUrl']);
|
||||
}
|
||||
}
|
||||
|
||||
class AppModel extends ChangeNotifier {
|
||||
User? user;
|
||||
void setUser(User? u) {
|
||||
user = u;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String? apiToken;
|
||||
Future<void> setToken(String? newToken) async {
|
||||
apiToken = newToken;
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
if (apiToken != null) {
|
||||
Api api = Api(token: apiToken!);
|
||||
prefs.setString('apiToken', apiToken!);
|
||||
var data = await api.request('GET', '/users/me');
|
||||
if (data['success'] == true) {
|
||||
setUser(User.loadJSON(data['payload']));
|
||||
print(data);
|
||||
}
|
||||
} else {
|
||||
prefs.remove('apiToken');
|
||||
}
|
||||
}
|
||||
/*
|
||||
/// Internal, private state of the cart.
|
||||
final List<Item> _items = [];
|
||||
|
||||
/// An unmodifiable view of the items in the cart.
|
||||
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
|
||||
|
||||
/// The current total price of all items (assuming all items cost $42).
|
||||
int get totalPrice => _items.length * 42;
|
||||
|
||||
/// Adds [item] to cart. This and [removeAll] are the only ways to modify the
|
||||
/// cart from the outside.
|
||||
void add(Item item) {
|
||||
_items.add(item);
|
||||
// This call tells the widgets that are listening to this model to rebuild.
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Removes all items from the cart.
|
||||
void removeAll() {
|
||||
_items.clear();
|
||||
// This call tells the widgets that are listening to this model to rebuild.
|
||||
notifyListeners();
|
||||
}*/
|
||||
}
|
@ -3,38 +3,37 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'dart:io';
|
||||
import 'api.dart';
|
||||
import 'util.dart';
|
||||
import 'model.dart';
|
||||
import 'patterns/pattern.dart';
|
||||
import 'patterns/viewer.dart';
|
||||
|
||||
class _ObjectScreenState extends State<ObjectScreen> {
|
||||
final String username;
|
||||
final String projectPath;
|
||||
final String id;
|
||||
Map<String,dynamic>? object;
|
||||
Map<String,dynamic>? pattern;
|
||||
final Map<String,dynamic> _project;
|
||||
Map<String,dynamic> _object;
|
||||
Map<String,dynamic>? _pattern;
|
||||
bool _isLoading = false;
|
||||
final Function _onUpdate;
|
||||
final Function _onDelete;
|
||||
final Api api = Api();
|
||||
final Util util = Util();
|
||||
|
||||
_ObjectScreenState(this.username, this.projectPath, this.id) { }
|
||||
_ObjectScreenState(this._object, this._project, this._onUpdate, this._onDelete) { }
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
fetchObject();
|
||||
if (_object['type'] == 'pattern') {
|
||||
_fetchPattern();
|
||||
}
|
||||
}
|
||||
|
||||
void fetchObject() async {
|
||||
var data = await api.request('GET', '/objects/' + id);
|
||||
void _fetchPattern() async {
|
||||
var data = await api.request('GET', '/objects/' + _object['_id']);
|
||||
if (data['success'] == true) {
|
||||
setState(() {
|
||||
object = data['payload'];
|
||||
pattern = data['payload']['pattern'];
|
||||
_pattern = data['payload']['pattern'];
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -42,14 +41,14 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
||||
void _shareObject() async {
|
||||
setState(() => _isLoading = true);
|
||||
File? file;
|
||||
if (object!['type'] == 'pattern') {
|
||||
var data = await api.request('GET', '/objects/' + id + '/wif');
|
||||
if (_object['type'] == 'pattern') {
|
||||
var data = await api.request('GET', '/objects/' + _object['_id'] + '/wif');
|
||||
if (data['success'] == true) {
|
||||
file = await util.writeFile(object!['name'] + '.wif', data['payload']['wif']);
|
||||
file = await util.writeFile(_object['name'] + '.wif', data['payload']['wif']);
|
||||
}
|
||||
} else {
|
||||
String fileName = Uri.file(object!['url']).pathSegments.last;
|
||||
file = await api.downloadFile(object!['url'], fileName);
|
||||
String fileName = Uri.file(_object['url']).pathSegments.last;
|
||||
file = await api.downloadFile(_object['url'], fileName);
|
||||
}
|
||||
|
||||
if (file != null) {
|
||||
@ -59,9 +58,12 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
||||
}
|
||||
|
||||
void _deleteObject(BuildContext context, BuildContext modalContext) async {
|
||||
var data = await api.request('DELETE', '/objects/' + id);
|
||||
var data = await api.request('DELETE', '/objects/' + _object['_id']);
|
||||
if (data['success']) {
|
||||
context.go('/home');
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(modalContext);
|
||||
Navigator.pop(context);
|
||||
_onDelete(_object['_id']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +77,7 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
child: Text('No'),
|
||||
onPressed: () => context.pop(),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
isDestructiveAction: true,
|
||||
@ -103,21 +105,22 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
||||
TextButton(
|
||||
child: Text('CANCEL'),
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('OK'),
|
||||
onPressed: () async {
|
||||
var data = await api.request('PUT', '/objects/' + id, {'name': renameController.text});
|
||||
var data = await api.request('PUT', '/objects/' + _object['_id'], {'name': renameController.text});
|
||||
if (data['success']) {
|
||||
context.pop();
|
||||
object!['name'] = data['payload']['name'];
|
||||
Navigator.pop(context);
|
||||
_object['name'] = data['payload']['name'];
|
||||
_onUpdate(_object['_id'], data['payload']);
|
||||
setState(() {
|
||||
object = object;
|
||||
_object = _object;
|
||||
});
|
||||
}
|
||||
context.pop();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -133,7 +136,7 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
||||
return CupertinoActionSheet(
|
||||
title: Text('Manage this object'),
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
onPressed: () => modalContext.pop(),
|
||||
onPressed: () => Navigator.of(modalContext).pop(),
|
||||
child: Text('Cancel')
|
||||
),
|
||||
actions: [
|
||||
@ -153,22 +156,15 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
||||
}
|
||||
|
||||
Widget getObjectWidget() {
|
||||
if (object == null) {
|
||||
return Center(child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [CircularProgressIndicator()]
|
||||
));
|
||||
if (_object['isImage'] == true) {
|
||||
return Image.network(_object['url']);
|
||||
}
|
||||
else if (object!['isImage'] == true && object!['url'] != null) {
|
||||
print(object!['url']);
|
||||
return Image.network(object!['url']);
|
||||
}
|
||||
else if (object!['type'] == 'pattern') {
|
||||
if (pattern != null) {
|
||||
return PatternViewer(pattern!, withEditor: true);
|
||||
else if (_object['type'] == 'pattern') {
|
||||
if (_pattern != null) {
|
||||
return PatternViewer(_pattern!, withEditor: true);
|
||||
}
|
||||
else if (object!['previewUrl'] != null) {
|
||||
return Image.network(object!['previewUrl']!);;
|
||||
else if (_object['previewUrl'] != null) {
|
||||
return Image.network(_object['previewUrl']!);;
|
||||
}
|
||||
else {
|
||||
return Column(
|
||||
@ -188,7 +184,7 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
||||
Text('Treadl cannot display this type of item.'),
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(child: Text('View file'), onPressed: () {
|
||||
launch(object!['url']);
|
||||
launch(_object['url']);
|
||||
}),
|
||||
],
|
||||
));
|
||||
@ -197,14 +193,12 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
AppModel model = Provider.of<AppModel>(context);
|
||||
User? user = model.user;
|
||||
String description = '';
|
||||
if (object?['description'] != null)
|
||||
description = object!['description']!;
|
||||
if (_object['description'] != null)
|
||||
description = _object['description'];
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(object?['name'] ?? 'Object'),
|
||||
title: Text(_object['name']),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.ios_share),
|
||||
@ -212,12 +206,12 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
||||
_shareObject();
|
||||
},
|
||||
),
|
||||
Util.canEditProject(user, object?['projectObject']) ? IconButton(
|
||||
IconButton(
|
||||
icon: Icon(Icons.settings),
|
||||
onPressed: () {
|
||||
_showSettingsModal(context);
|
||||
},
|
||||
) : SizedBox(height: 0),
|
||||
),
|
||||
]
|
||||
),
|
||||
body: Container(
|
||||
@ -234,11 +228,12 @@ class _ObjectScreenState extends State<ObjectScreen> {
|
||||
}
|
||||
|
||||
class ObjectScreen extends StatefulWidget {
|
||||
final String username;
|
||||
final String projectPath;
|
||||
final String id;
|
||||
ObjectScreen(this.username, this.projectPath, this.id, ) { }
|
||||
final Map<String,dynamic> _object;
|
||||
final Map<String,dynamic> _project;
|
||||
final Function _onUpdate;
|
||||
final Function _onDelete;
|
||||
ObjectScreen(this._object, this._project, this._onUpdate, this._onDelete) { }
|
||||
@override
|
||||
_ObjectScreenState createState() => _ObjectScreenState(username, projectPath, id);
|
||||
_ObjectScreenState createState() => _ObjectScreenState(_object, _project, _onUpdate, _onDelete);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'api.dart';
|
||||
|
||||
class _OnboardingScreenState extends State<OnboardingScreen> {
|
||||
@ -112,7 +111,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
||||
CupertinoButton(
|
||||
color: Colors.white,
|
||||
child: Text('Get started', style: TextStyle(color: Colors.pink)),
|
||||
onPressed: () => context.go('/home'),
|
||||
onPressed: () => Navigator.of(context).pushNamedAndRemoveUntil('/home', (Route<dynamic> route) => false),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
@ -4,47 +4,29 @@ import 'package:provider/provider.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'dart:io';
|
||||
import 'api.dart';
|
||||
import 'util.dart';
|
||||
import 'model.dart';
|
||||
import 'lib.dart';
|
||||
import 'object.dart';
|
||||
|
||||
class _ProjectScreenState extends State<ProjectScreen> {
|
||||
final String username;
|
||||
final String projectPath;
|
||||
final String fullPath;
|
||||
final Function? onUpdate;
|
||||
final Function? onDelete;
|
||||
final Function _onUpdate;
|
||||
final Function _onDelete;
|
||||
final picker = ImagePicker();
|
||||
final Api api = Api();
|
||||
final Util util = Util();
|
||||
Map<String,dynamic>? project;
|
||||
Map<String,dynamic> _project;
|
||||
List<dynamic> _objects = [];
|
||||
bool _loading = false;
|
||||
bool _creating = false;
|
||||
|
||||
_ProjectScreenState(this.username, this.projectPath, {this.project, this.onUpdate, this.onDelete}) :
|
||||
fullPath = username + '/' + projectPath;
|
||||
_ProjectScreenState(this._project, this._onUpdate, this._onDelete) { }
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
getProject(fullPath);
|
||||
getObjects(fullPath);
|
||||
}
|
||||
|
||||
void getProject(String fullName) async {
|
||||
setState(() => _loading = true);
|
||||
var data = await api.request('GET', '/projects/' + fullName);
|
||||
if (data['success'] == true) {
|
||||
setState(() {
|
||||
project = data['payload'];
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
getObjects(_project['fullName']);
|
||||
}
|
||||
|
||||
void getObjects(String fullName) async {
|
||||
@ -59,18 +41,18 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
||||
}
|
||||
|
||||
void _shareProject() {
|
||||
util.shareUrl('Check out my project on Treadl', util.appUrl(fullPath));
|
||||
util.shareUrl('Check out my project on Treadl', util.appUrl(_project['fullName']));
|
||||
}
|
||||
|
||||
void _onDeleteProject() {
|
||||
context.pop();
|
||||
onDelete!(project!['_id']);
|
||||
Navigator.pop(context);
|
||||
_onDelete(_project['_id']);
|
||||
}
|
||||
void _onUpdateProject(project) {
|
||||
setState(() {
|
||||
project = project;
|
||||
_project = project;
|
||||
});
|
||||
onUpdate!(project!['_id'], project!);
|
||||
_onUpdate(project['_id'], project);
|
||||
}
|
||||
|
||||
void _onUpdateObject(String id, Map<String,dynamic> update) {
|
||||
@ -92,6 +74,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
||||
}
|
||||
|
||||
void _createObject(objectData) async {
|
||||
String fullPath = _project['fullName'];
|
||||
var resp = await api.request('POST', '/projects/$fullPath/objects', objectData);
|
||||
setState(() => _creating = false);
|
||||
if (resp['success']) {
|
||||
@ -114,7 +97,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
||||
|
||||
void _createObjectFromFile(String name, XFile file) async {
|
||||
final int size = await file.length();
|
||||
final String forId = project!['_id'];
|
||||
final String forId = _project['_id'];
|
||||
final String type = file.mimeType ?? 'text/plain';
|
||||
setState(() => _creating = true);
|
||||
var data = await api.request('GET', '/uploads/file/request?name=$name&size=$size&type=$type&forType=project&forId=$forId');
|
||||
@ -156,7 +139,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
||||
if (imageFile == null) return;
|
||||
final f = new DateFormat('yyyy-MM-dd_hh-mm-ss');
|
||||
String time = f.format(new DateTime.now());
|
||||
String name = project!['name'] + ' ' + time + '.' + imageFile.name.split('.').last;
|
||||
String name = _project['name'] + ' ' + time + '.' + imageFile.name.split('.').last;
|
||||
_createObjectFromFile(name, imageFile);
|
||||
}
|
||||
on Exception {
|
||||
@ -169,7 +152,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
child: Text('OK'),
|
||||
onPressed: () => context.pop(),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
)
|
||||
@ -178,7 +161,7 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
||||
}
|
||||
|
||||
void showSettingsModal() {
|
||||
Widget settingsDialog = new _ProjectSettingsDialog(project!, _onDeleteProject, _onUpdateProject);
|
||||
Widget settingsDialog = new _ProjectSettingsDialog(_project, _onDeleteProject, _onUpdateProject);
|
||||
showCupertinoModalPopup(context: context, builder: (BuildContext context) => settingsDialog);
|
||||
}
|
||||
|
||||
@ -253,7 +236,12 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
||||
return new Card(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
context.push('/' + username + '/' + projectPath + '/' + object['_id']);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ObjectScreen(object, _project, _onUpdateObject, _onDeleteObject),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: ListTile(
|
||||
leading: leader,
|
||||
@ -265,27 +253,11 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget getBody() {
|
||||
if (_loading || project == null)
|
||||
return CircularProgressIndicator();
|
||||
else if ((_objects != null && _objects.length > 0) || _creating)
|
||||
return ListView.builder(
|
||||
itemCount: _objects.length + (_creating ? 1 : 0),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return getObjectCard(index);
|
||||
},
|
||||
);
|
||||
else
|
||||
return EmptyBox('This project is currently empty', description: 'If this is your project, you can add a pattern file, an image, or something else to this project using the + button below.');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
AppModel model = Provider.of<AppModel>(context);
|
||||
User? user = model.user;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(project?['name'] ?? 'Project'),
|
||||
title: Text(_project['name']),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.ios_share),
|
||||
@ -293,21 +265,41 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
||||
_shareProject();
|
||||
},
|
||||
),
|
||||
onUpdate != null ? IconButton(
|
||||
IconButton(
|
||||
icon: Icon(Icons.settings),
|
||||
onPressed: () {
|
||||
showSettingsModal();
|
||||
},
|
||||
) : SizedBox(width: 0),
|
||||
),
|
||||
]
|
||||
),
|
||||
body: Container(
|
||||
body: _loading ?
|
||||
Container(
|
||||
margin: const EdgeInsets.all(10.0),
|
||||
alignment: Alignment.center,
|
||||
child: CircularProgressIndicator()
|
||||
)
|
||||
: Container(
|
||||
margin: const EdgeInsets.all(10.0),
|
||||
alignment: Alignment.center,
|
||||
child: getBody(),
|
||||
child: ((_objects != null && _objects.length > 0) || _creating) ?
|
||||
ListView.builder(
|
||||
itemCount: _objects.length + (_creating ? 1 : 0),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return getObjectCard(index);
|
||||
},
|
||||
)
|
||||
:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('This project is currently empty', style: TextStyle(fontSize: 20), textAlign: TextAlign.center),
|
||||
Image(image: AssetImage('assets/empty.png'), width: 300),
|
||||
Text('Add a pattern file, an image, or something else to this project using the + button below.', textAlign: TextAlign.center),
|
||||
])
|
||||
),
|
||||
floatingActionButtonLocation: ExpandableFab.location,
|
||||
floatingActionButton: Util.canEditProject(user, project) ? ExpandableFab(
|
||||
floatingActionButton: ExpandableFab(
|
||||
distance: 70,
|
||||
type: ExpandableFabType.up,
|
||||
openButtonBuilder: RotateFloatingActionButtonBuilder(
|
||||
@ -347,30 +339,26 @@ class _ProjectScreenState extends State<ProjectScreen> {
|
||||
),
|
||||
]),
|
||||
],
|
||||
) : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProjectScreen extends StatefulWidget {
|
||||
final String username;
|
||||
final String projectPath;
|
||||
final Map<String,dynamic>? project;
|
||||
final Function? onUpdate;
|
||||
final Function? onDelete;
|
||||
ProjectScreen(this.username, this.projectPath, {this.project, this.onUpdate, this.onDelete}) { }
|
||||
final Map<String,dynamic> _project;
|
||||
final Function _onUpdate;
|
||||
final Function _onDelete;
|
||||
ProjectScreen(this._project, this._onUpdate, this._onDelete) { }
|
||||
@override
|
||||
_ProjectScreenState createState() => _ProjectScreenState(username, projectPath, project: project, onUpdate: onUpdate, onDelete: onDelete);
|
||||
_ProjectScreenState createState() => _ProjectScreenState(_project, _onUpdate, _onDelete);
|
||||
}
|
||||
|
||||
class _ProjectSettingsDialog extends StatelessWidget {
|
||||
final String fullPath;
|
||||
final Map<String,dynamic> project;
|
||||
final Map<String,dynamic> _project;
|
||||
final Function _onDelete;
|
||||
final Function _onUpdateProject;
|
||||
final Api api = Api();
|
||||
_ProjectSettingsDialog(this.project, this._onDelete, this._onUpdateProject) :
|
||||
fullPath = project['owner']['username'] + '/' + project['path'];
|
||||
_ProjectSettingsDialog(this._project, this._onDelete, this._onUpdateProject) {}
|
||||
|
||||
void _renameProject(BuildContext context) async {
|
||||
TextEditingController renameController = TextEditingController();
|
||||
@ -388,18 +376,18 @@ class _ProjectSettingsDialog extends StatelessWidget {
|
||||
TextButton(
|
||||
child: Text('CANCEL'),
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('OK'),
|
||||
onPressed: () async {
|
||||
var data = await api.request('PUT', '/projects/' + fullPath, {'name': renameController.text});
|
||||
var data = await api.request('PUT', '/projects/' + _project['owner']['username'] + '/' + _project['path'], {'name': renameController.text});
|
||||
if (data['success']) {
|
||||
context.pop();
|
||||
Navigator.pop(context);
|
||||
_onUpdateProject(data['payload']);
|
||||
}
|
||||
context.pop();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -409,18 +397,18 @@ class _ProjectSettingsDialog extends StatelessWidget {
|
||||
}
|
||||
|
||||
void _toggleVisibility(BuildContext context, bool checked) async {
|
||||
var data = await api.request('PUT', '/projects/' + fullPath, {'visibility': checked ? 'private': 'public'});
|
||||
var data = await api.request('PUT', '/projects/' + _project['owner']['username'] + '/' + _project['path'], {'visibility': checked ? 'private': 'public'});
|
||||
if (data['success']) {
|
||||
context.pop();
|
||||
Navigator.pop(context);
|
||||
_onUpdateProject(data['payload']);
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteProject(BuildContext context, BuildContext modalContext) async {
|
||||
var data = await api.request('DELETE', '/projects/' + fullPath);
|
||||
var data = await api.request('DELETE', '/projects/' + _project['owner']['username'] + '/' + _project['path']);
|
||||
if (data['success']) {
|
||||
context.pop();
|
||||
context.pop();
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(modalContext);
|
||||
_onDelete();
|
||||
}
|
||||
}
|
||||
@ -435,7 +423,7 @@ class _ProjectSettingsDialog extends StatelessWidget {
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
child: Text('No'),
|
||||
onPressed: () => context.pop(),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
isDestructiveAction: true,
|
||||
@ -452,7 +440,7 @@ class _ProjectSettingsDialog extends StatelessWidget {
|
||||
return CupertinoActionSheet(
|
||||
title: Text('Manage this project'),
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
onPressed: () => context.pop(),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text('Cancel')
|
||||
),
|
||||
actions: [
|
||||
@ -462,7 +450,7 @@ class _ProjectSettingsDialog extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CupertinoSwitch(
|
||||
value: project?['visibility'] == 'private',
|
||||
value: _project['visibility'] == 'private',
|
||||
onChanged: (c) => _toggleVisibility(context, c),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'routeArguments.dart';
|
||||
import 'api.dart';
|
||||
import 'model.dart';
|
||||
import 'lib.dart';
|
||||
import 'project.dart';
|
||||
import 'settings.dart';
|
||||
|
||||
class _ProjectsTabState extends State<ProjectsTab> {
|
||||
List<dynamic> _projects = [];
|
||||
@ -19,8 +19,6 @@ class _ProjectsTabState extends State<ProjectsTab> {
|
||||
}
|
||||
|
||||
void getProjects() async {
|
||||
AppModel model = Provider.of<AppModel>(context, listen: false);
|
||||
if (model.user == null) return;
|
||||
setState(() {
|
||||
_loading = true;
|
||||
});
|
||||
@ -83,7 +81,12 @@ class _ProjectsTabState extends State<ProjectsTab> {
|
||||
return new Card(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
context.push('/' + project['owner']['username'] + '/' + project['path']);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ProjectScreen(project, _onUpdateProject, _onDeleteProject),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5),
|
||||
@ -107,56 +110,56 @@ class _ProjectsTabState extends State<ProjectsTab> {
|
||||
;
|
||||
}
|
||||
|
||||
Widget getBody() {
|
||||
AppModel model = Provider.of<AppModel>(context);
|
||||
if (model.user == null)
|
||||
return LoginNeeded(text: 'Once logged in, you\'ll find your own projects shown here.');
|
||||
if (_loading)
|
||||
return CircularProgressIndicator();
|
||||
else if (_projects != null && _projects.length > 0)
|
||||
return ListView.builder(
|
||||
itemCount: _projects.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildProjectCard(_projects[index]);
|
||||
},
|
||||
);
|
||||
else return 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),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
AppModel model = Provider.of<AppModel>(context);
|
||||
User? user = model.user;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('My Projects'),
|
||||
title: Text('Your Projects'),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.info_outline),
|
||||
onPressed: () {
|
||||
context.push('/settings');
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SettingsScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
]
|
||||
),
|
||||
body: Container(
|
||||
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: getBody()
|
||||
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: user != null ? FloatingActionButton(
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: showNewProjectDialog,
|
||||
child: _creatingProject ? CircularProgressIndicator(backgroundColor: Colors.white) : Icon(Icons.add),
|
||||
backgroundColor: Colors.pink[500],
|
||||
) : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -189,7 +192,7 @@ class _NewProjectDialogState extends State<_NewProjectDialog> {
|
||||
var data = await api.request('POST', '/projects', {'name': name, 'visibility': priv ? 'private' : 'public'});
|
||||
if (data['success'] == true) {
|
||||
_onComplete(data['payload']);
|
||||
context.pop();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,7 +228,7 @@ class _NewProjectDialogState extends State<_NewProjectDialog> {
|
||||
SizedBox(height: 10),
|
||||
CupertinoButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text('Cancel'),
|
||||
)
|
||||
|
@ -1,11 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'api.dart';
|
||||
import 'model.dart';
|
||||
|
||||
class _RegisterScreenState extends State<RegisterScreen> {
|
||||
final TextEditingController _usernameController = TextEditingController();
|
||||
@ -19,9 +17,10 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||
var data = await api.request('POST', '/accounts/register', {'username': _usernameController.text, 'email': _emailController.text, 'password': _passwordController.text});
|
||||
setState(() => _registering = false);
|
||||
if (data['success'] == true) {
|
||||
AppModel model = Provider.of<AppModel>(context, listen: false);
|
||||
model.setToken(data['payload']['token']);
|
||||
context.go('/onboarding');
|
||||
String token = data['payload']['token'];
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString('apiToken', token);
|
||||
Navigator.of(context).pushNamedAndRemoveUntil('/onboarding', (Route<dynamic> route) => false);
|
||||
}
|
||||
else {
|
||||
showDialog(
|
||||
@ -33,7 +32,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
child: Text('Try again'),
|
||||
onPressed: () => context.pop(),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
)
|
||||
@ -48,59 +47,66 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||
title: Text('Register with Treadl'),
|
||||
),
|
||||
body: Container(
|
||||
margin: const EdgeInsets.only(top: 40, left: 10, right: 10),
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
TextField(
|
||||
autofocus: true,
|
||||
controller: _usernameController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'username', labelText: 'Choose a username',
|
||||
border: OutlineInputBorder(),
|
||||
margin: const EdgeInsets.all(10.0),
|
||||
child: SingleChildScrollView(
|
||||
child:Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Image(image: AssetImage('assets/logo.png'), width: 100),
|
||||
SizedBox(height: 20),
|
||||
Text('Register a free account.', style: TextStyle(fontSize: 18)),
|
||||
SizedBox(height: 20),
|
||||
TextField(
|
||||
autofocus: true,
|
||||
controller: _usernameController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'username', labelText: 'Choose a username',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: _emailController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'sam@example.com', labelText: 'Your email address', helperText: 'For notifications & password resets - we never share this.',
|
||||
border: OutlineInputBorder()
|
||||
SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: _emailController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'sam@example.com', labelText: 'Your email address', helperText: 'For notifications & password resets - we never share this.',
|
||||
border: OutlineInputBorder()
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
TextField(
|
||||
onEditingComplete: () => _submit(context),
|
||||
controller: _passwordController,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Type your password', labelText: 'Choose a strong password',
|
||||
border: OutlineInputBorder()
|
||||
SizedBox(height: 10),
|
||||
TextField(
|
||||
onEditingComplete: () => _submit(context),
|
||||
controller: _passwordController,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Type your password', labelText: 'Choose a strong password',
|
||||
border: OutlineInputBorder()
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
text: 'By registering you agree to Treadl\'s ',
|
||||
style: Theme.of(context).textTheme.bodyText1,
|
||||
children: <TextSpan>[
|
||||
TextSpan(text: 'Terms of Use', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.pink), recognizer: new TapGestureRecognizer()..onTap = () => launch('https://treadl.com/terms-of-use')),
|
||||
TextSpan(text: ' and '),
|
||||
TextSpan(text: 'Privacy Policy', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.pink), recognizer: new TapGestureRecognizer()..onTap = () => launch('https://treadl.com/privacy')),
|
||||
TextSpan(text: '.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () => _submit(context),
|
||||
//color: Colors.pink,
|
||||
child: _registering ? SizedBox(height: 20, width: 20, child:CircularProgressIndicator(backgroundColor: Colors.white)) : Text("Register",
|
||||
SizedBox(height: 20),
|
||||
RichText(
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.white, fontSize: 15)
|
||||
)
|
||||
),
|
||||
]
|
||||
text: TextSpan(
|
||||
text: 'By registering you agree to Treadl\'s ',
|
||||
style: Theme.of(context).textTheme.bodyText1,
|
||||
children: <TextSpan>[
|
||||
TextSpan(text: 'Terms of Use', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.pink), recognizer: new TapGestureRecognizer()..onTap = () => launch('https://treadl.com/terms-of-use')),
|
||||
TextSpan(text: ' and '),
|
||||
TextSpan(text: 'Privacy Policy', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.pink), recognizer: new TapGestureRecognizer()..onTap = () => launch('https://treadl.com/privacy')),
|
||||
TextSpan(text: '.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () => _submit(context),
|
||||
//color: Colors.pink,
|
||||
child: _registering ? SizedBox(height: 20, width: 20, child:CircularProgressIndicator(backgroundColor: Colors.white)) : Text("Register",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.white, fontSize: 15)
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
);
|
||||
|
@ -1,21 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'api.dart';
|
||||
import 'model.dart';
|
||||
|
||||
class SettingsScreen extends StatelessWidget {
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
|
||||
void _logout(BuildContext context) async {
|
||||
AppModel model = Provider.of<AppModel>(context, listen: false);
|
||||
Api api = Api();
|
||||
api.request('POST', '/accounts/logout');
|
||||
model.setToken(null);
|
||||
model.setUser(null);
|
||||
context.pop();
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.remove('apiToken');
|
||||
Navigator.of(context).pushNamedAndRemoveUntil('/welcome', (Route<dynamic> route) => false);
|
||||
}
|
||||
|
||||
void _deleteAccount(BuildContext context) async {
|
||||
@ -36,7 +33,7 @@ class SettingsScreen extends StatelessWidget {
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('Cancel'),
|
||||
onPressed: () => context.pop(),
|
||||
onPressed: () { Navigator.of(context).pop(); }
|
||||
),
|
||||
ElevatedButton(
|
||||
child: Text('Delete Account'),
|
||||
@ -44,10 +41,9 @@ class SettingsScreen extends StatelessWidget {
|
||||
Api api = Api();
|
||||
var data = await api.request('DELETE', '/accounts', {'password': _passwordController.text});
|
||||
if (data['success'] == true) {
|
||||
AppModel model = Provider.of<AppModel>(context, listen: false);
|
||||
model.setToken(null);
|
||||
model.setUser(null);
|
||||
context.go('/home');
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.remove('apiToken');
|
||||
Navigator.of(context).pushNamedAndRemoveUntil('/welcome', (Route<dynamic> route) => false);
|
||||
} else {
|
||||
showDialog(
|
||||
context: context,
|
||||
@ -58,7 +54,7 @@ class SettingsScreen extends StatelessWidget {
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
child: Text('OK'),
|
||||
onPressed: () => context.pop(),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
)
|
||||
@ -79,8 +75,6 @@ class SettingsScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
AppModel model = Provider.of<AppModel>(context);
|
||||
User? user = model.user;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('About Treadl'),
|
||||
@ -99,23 +93,15 @@ class SettingsScreen extends StatelessWidget {
|
||||
|
||||
SizedBox(height: 30),
|
||||
|
||||
user != null ? Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: Icon(Icons.exit_to_app),
|
||||
title: Text('Logout'),
|
||||
onTap: () => _logout(context),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(Icons.delete),
|
||||
title: Text('Delete Account'),
|
||||
onTap: () => _deleteAccount(context),
|
||||
),
|
||||
]
|
||||
) : CupertinoButton(
|
||||
color: Colors.pink,
|
||||
child: Text('Join Treadl', style: TextStyle(color: Colors.white)),
|
||||
onPressed: () => context.push('/welcome'),
|
||||
ListTile(
|
||||
leading: Icon(Icons.exit_to_app),
|
||||
title: Text('Logout'),
|
||||
onTap: () => _logout(context),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(Icons.delete),
|
||||
title: Text('Delete Account'),
|
||||
onTap: () => _deleteAccount(context),
|
||||
),
|
||||
|
||||
SizedBox(height: 30),
|
||||
|
@ -5,22 +5,18 @@ import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'util.dart';
|
||||
import 'api.dart';
|
||||
import 'lib.dart';
|
||||
|
||||
class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateMixin {
|
||||
final String username;
|
||||
class _UserScreenState extends State<UserScreen> {
|
||||
final Util util = new Util();
|
||||
final Api api = Api();
|
||||
TabController? _tabController;
|
||||
Map<String,dynamic>? _user;
|
||||
Map<String,dynamic> _user;
|
||||
bool _loading = false;
|
||||
_UserScreenState(this.username) { }
|
||||
_UserScreenState(this._user) { }
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
_tabController = new TabController(length: 2, vsync: this);
|
||||
getUser(username);
|
||||
getUser(_user['username']);
|
||||
}
|
||||
|
||||
void getUser(String username) async {
|
||||
@ -35,136 +31,85 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
||||
}
|
||||
}
|
||||
|
||||
Widget getBody() {
|
||||
if (_loading)
|
||||
return CircularProgressIndicator();
|
||||
else if (_user != null && _tabController != null) {
|
||||
var u = _user!;
|
||||
String? created;
|
||||
if (u['createdAt'] != null) {
|
||||
DateTime createdAt = DateTime.parse(u['createdAt']!);
|
||||
created = DateFormat('MMMM y').format(createdAt);
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(children: [
|
||||
util.avatarImage(util.avatarUrl(u), size: 120),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(u['username'], style: Theme.of(context).textTheme.titleLarge),
|
||||
SizedBox(height: 5),
|
||||
u['location'] != null ?
|
||||
Row(children: [
|
||||
Icon(CupertinoIcons.location),
|
||||
SizedBox(width: 10),
|
||||
Text(u['location'])
|
||||
]) : SizedBox(height: 1),
|
||||
SizedBox(height: 10),
|
||||
Text('Member' + (created != null ? (' since ' + created!) : ''),
|
||||
style: TextStyle(color: Colors.grey[500])
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
u['website'] != null ?
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
String url = u['website'];
|
||||
if (!url.startsWith('http')) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
launch(url);
|
||||
},
|
||||
child: Text(u['website'],
|
||||
style: TextStyle(color: Colors.pink))
|
||||
) : SizedBox(height: 1),
|
||||
]
|
||||
)
|
||||
)
|
||||
]),
|
||||
SizedBox(height: 10),
|
||||
TabBar(
|
||||
unselectedLabelColor: Colors.black,
|
||||
labelColor: Colors.pink,
|
||||
tabs: [
|
||||
Tab(
|
||||
text: 'Profile',
|
||||
icon: Icon(Icons.person),
|
||||
),
|
||||
Tab(
|
||||
text: 'Projects',
|
||||
icon: Icon(Icons.folder),
|
||||
)
|
||||
],
|
||||
controller: _tabController!,
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 30),
|
||||
Text(u['bio'] != null ? u['bio'] : 'The user doesn\'t have any more profile information.'),
|
||||
]
|
||||
),
|
||||
(u['projects'] != null && u['projects'].length > 0) ?
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 10),
|
||||
child: GridView.count(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 5,
|
||||
crossAxisSpacing: 5,
|
||||
childAspectRatio: 1.3,
|
||||
children: u['projects'].map<Widget>((p) =>
|
||||
ProjectCard(p)
|
||||
).toList()
|
||||
),
|
||||
) :
|
||||
Container(
|
||||
margin: EdgeInsets.all(10),
|
||||
child: EmptyBox('This user doesn\'t have any public projects'),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
else
|
||||
return Text('User not found');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
String? created;
|
||||
if (_user['createdAt'] != null) {
|
||||
DateTime createdAt = DateTime.parse(_user['createdAt']);
|
||||
created = DateFormat('MMMM y').format(createdAt);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(username),
|
||||
title: Text(_user['username']),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.person),
|
||||
onPressed: () {
|
||||
launch('https://www.treadl.com/' + username);
|
||||
launch('https://www.treadl.com/' + _user['username']);
|
||||
},
|
||||
),
|
||||
]
|
||||
),
|
||||
body: Container(
|
||||
margin: const EdgeInsets.all(10.0),
|
||||
alignment: Alignment.center,
|
||||
child: getBody()
|
||||
body: _loading ?
|
||||
Container(
|
||||
margin: const EdgeInsets.all(10.0),
|
||||
alignment: Alignment.center,
|
||||
child: CircularProgressIndicator()
|
||||
)
|
||||
: Container(
|
||||
padding: EdgeInsets.all(10),
|
||||
margin: EdgeInsets.only(top: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(children: [
|
||||
util.avatarImage(util.avatarUrl(_user), size: 120),
|
||||
Expanded(child: Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(_user['username'], style: Theme.of(context).textTheme.titleLarge),
|
||||
SizedBox(height: 5),
|
||||
_user['location'] != null ?
|
||||
Row(children: [
|
||||
Icon(CupertinoIcons.location),
|
||||
SizedBox(width: 10),
|
||||
Text(_user['location'])
|
||||
]) : SizedBox(height: 1),
|
||||
SizedBox(height: 10),
|
||||
Text('Member' + (created != null ? (' since ' + created!) : ''),
|
||||
style: TextStyle(color: Colors.grey[500])
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
_user['website'] != null ?
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
String url = _user['website'];
|
||||
if (!url.startsWith('http')) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
launch(url);
|
||||
},
|
||||
child: Text(_user['website'],
|
||||
style: TextStyle(color: Colors.pink))
|
||||
) : SizedBox(height: 1),
|
||||
]
|
||||
)
|
||||
))
|
||||
]),
|
||||
SizedBox(height: 30),
|
||||
Text(_user['bio'] != null ? _user['bio'] : '')
|
||||
]
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UserScreen extends StatefulWidget {
|
||||
final String username;
|
||||
UserScreen(this.username) { }
|
||||
final Map<String,dynamic> user;
|
||||
UserScreen(this.user) { }
|
||||
@override
|
||||
_UserScreenState createState() => _UserScreenState(username);
|
||||
_UserScreenState createState() => _UserScreenState(user);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'model.dart';
|
||||
import 'api.dart';
|
||||
|
||||
String APP_URL = 'https://www.treadl.com';
|
||||
|
||||
@ -80,15 +80,4 @@ class Util {
|
||||
void shareUrl(String text, String url) async {
|
||||
await Share.share('$text: $url');
|
||||
}
|
||||
|
||||
static String ellipsis(String input, int cutoff) {
|
||||
return (input.length <= cutoff)
|
||||
? input
|
||||
: '${input.substring(0, cutoff)}...';
|
||||
}
|
||||
|
||||
static bool canEditProject(User? user, Map<String,dynamic>? project) {
|
||||
if (user == null || project == null) return false;
|
||||
return project['user'] == user.id;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'store.dart';
|
||||
import 'login.dart';
|
||||
|
||||
class WelcomeScreen extends StatelessWidget {
|
||||
void _login(BuildContext context) {
|
||||
context.push('/login');
|
||||
Navigator.of(context).pushNamed('/login');
|
||||
}
|
||||
void _register(BuildContext context) {
|
||||
context.push('/register');
|
||||
Navigator.of(context).pushNamed('/register');
|
||||
}
|
||||
|
||||
@override
|
||||
@ -37,20 +36,11 @@ class WelcomeScreen extends StatelessWidget {
|
||||
SizedBox(height: 15),
|
||||
CupertinoButton(
|
||||
onPressed: () => _register(context),
|
||||
color: Colors.pink[400],
|
||||
child: new Text("Register",
|
||||
style: TextStyle(color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
),
|
||||
SizedBox(height: 35),
|
||||
CupertinoButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: new Text("Cancel",
|
||||
style: TextStyle(color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
),
|
||||
]),
|
||||
))
|
||||
);
|
||||
|
@ -264,14 +264,6 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
go_router:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: "3b40e751eaaa855179b416974d59d29669e750d2e50fcdb2b37f1cb0ca8c803a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.0.1"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -392,14 +384,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -37,7 +37,6 @@ dependencies:
|
||||
path_provider: ^2.1.1
|
||||
share_plus: ^7.2.1
|
||||
flutter_expandable_fab: ^2.0.0
|
||||
go_router: ^13.0.1
|
||||
|
||||
#fluttertoast: ^8.0.9
|
||||
|
||||
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"applinks": {
|
||||
"apps": [],
|
||||
"details": [
|
||||
{
|
||||
"appID": "38T664W57F.com.treadl",
|
||||
"paths": ["*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user