diff --git a/api-server.nix b/api-server.nix index 67c8d77..bacfe12 100644 --- a/api-server.nix +++ b/api-server.nix @@ -13,6 +13,7 @@ let databaseSetup = ./server/database/create-role-and-database.sql; tableSetup = pkgs.writeText "setup.sql" '' ${builtins.readFile ./server/database/create-tables.sql} + ${builtins.readFile ./server/database/create-materialized-views.sql} ${mockData} ''; in @@ -63,6 +64,25 @@ in wantedBy = [ "default.target" ]; }; + systemd.services.maptogether-refresh-views = { + description = "Refresh the MapTogether views"; + serviceConfig = { + Type = "oneshot"; + User = "maptogether"; + Group = "maptogether"; + ExecStart = "${pkgs.postgresql}/bin/psql -d maptogether -f ${./server/database/refresh-materialized-views.sql}"; + }; + requires = [ "maptogether-database-setup.service" "postgresql.service" ]; + after = [ "maptogether-database-setup.service" "postgresql.service" ]; + wantedBy = [ "default.target" ]; + }; + + systemd.timers.maptogether-refresh-views = { + description = "Timer to trigger refresh"; + timerConfig.OnCalendar = "*:*:0"; # once a minute + wantedBy = [ "timers.target" ]; + }; + users.groups.maptogether.gid = 1005; users.users.maptogether = { isSystemUser = true; diff --git a/client/lib/database.dart b/client/lib/database.dart deleted file mode 100644 index cb23c5e..0000000 --- a/client/lib/database.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:client/widgets/social_menu_widgets/Leaderboard.dart'; -import 'package:client/widgets/social_menu_widgets/User.dart'; -import 'package:flutter/foundation.dart'; - -//We get the current user through their username, usernames are unique, -//for the purpose of testing we are Simon - -class DummyDatabase with ChangeNotifier{ - - String loginURL = ""; - String currentUserName; - User currentUser; - - List globalUsers; - List leaderboards; - List followingNames; - List following; - - DummyDatabase() { - currentUserName = "Simon"; - - //This is the list of all users to ever use the app, it makes up the global leaderboard. - globalUsers = [ - User("Thomas", 40, 25, 15, "kid.png", "UK"), - User("Sebba", 250, 150, 55, "clean.png", "DK"), - User("Simon", 25, 15, 5, "business.png", "DK"), - User("Phillip", 55, 35, 15, "arthas.png", "DK"), - User("Hartvig", 10, 10, 0, "anime.png", "JP"), - User("Fjeldsø", 20, 20, 10, "wolf.png", "DK"), - ]; - - followingNames = [ - "Thomas", - "Hartvig", - "Sebba", - "Fjeldsø", - "Phillip", - ]; - - //currentUser found in the global list through - currentUser = globalUsers.firstWhere((user) => - user.name == currentUserName); - - //Setting up Leaderboards - LeaderBoard national = new LeaderBoard(currentUser.nationality, - globalUsers.where((user) => user.nationality == currentUser.nationality) - .toList()); - - following = globalUsers.where((user) => - followingNames.contains(user.name)).toList(); - - LeaderBoard world = new LeaderBoard("World", globalUsers); - - LeaderBoard followLB = new LeaderBoard("Follow", following); - - leaderboards = [world, national, followLB]; - - - } - - void givePoints(int x){ - currentUser.total += x; - notifyListeners(); - } - - void followNew(String toFollow){ - User userToFollow = globalUsers.firstWhere((user) => user.name == toFollow, orElse: () => null); - if(userToFollow == null) - print("User does not exist"); - - else if(followingNames.contains(toFollow)) - print("Already following"); - - else { - followingNames.add(toFollow); - following.add(userToFollow); - } - - notifyListeners(); - } - -} diff --git a/client/lib/location_handler.dart b/client/lib/location_handler.dart index e44da9a..23dc2af 100644 --- a/client/lib/location_handler.dart +++ b/client/lib/location_handler.dart @@ -5,7 +5,6 @@ import 'package:latlong/latlong.dart'; import 'package:location/location.dart'; class LocationHandler extends ChangeNotifier { - // From location lib Location _locationService = Location(); @@ -18,9 +17,9 @@ class LocationHandler extends ChangeNotifier { LocationHandler() { mapController = MapController(); - rotationStream = mapController.mapEventStream - .map((_) => mapController.rotation) - .map((d) => (d / 360.0) * (2.0 * math.pi)); + rotationStream = mapController.mapEventStream + .map((_) => mapController.rotation) + .map((d) => (d / 360.0) * (2.0 * math.pi)); initLocationService(); } diff --git a/client/lib/screens/login_screen.dart b/client/lib/login_flow.dart similarity index 81% rename from client/lib/screens/login_screen.dart rename to client/lib/login_flow.dart index 040856b..421bff2 100644 --- a/client/lib/screens/login_screen.dart +++ b/client/lib/login_flow.dart @@ -1,13 +1,15 @@ import 'dart:async'; -import 'package:client/login_handler.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:webview_flutter/webview_flutter.dart'; +import 'package:client/login_handler.dart'; +import 'package:client/widgets/app_bar.dart'; +import 'package:client/widgets/future_loader.dart'; + class LoginWebView extends StatelessWidget { final String url; - void Function(BuildContext) onVerified; LoginWebView(this.url); @@ -34,9 +36,8 @@ class LoginWebView extends StatelessWidget { final verifier = u.split('verifier=')[1]; print('Verifier: $verifier'); loginHandler.authorize(verifier).then((_) { - onVerified(context); + Navigator.of(context).pop(true); }); - Navigator.of(context).pop(true); } }, ), @@ -48,17 +49,12 @@ class LoginWebView extends StatelessWidget { } MaterialPageRoute loginPage() => MaterialPageRoute( - builder: (context) => FutureBuilder( + builder: (context) => FutureLoader( future: context.watch().loginUrl(), - builder: (BuildContext context, AsyncSnapshot snapshot) => - (snapshot.hasData) - ? LoginWebView(snapshot.data) - : (snapshot.hasError) - ? Text('Error: ${snapshot.error}') - : CircularProgressIndicator( - semanticsLabel: 'Getting auth url'))); + builder: (BuildContext context, String url) => LoginWebView(url))); -Future requestLogin(BuildContext context, {bool social}) => request( +Future requestLogin(BuildContext context, {bool social = false}) => + request( context, title: social ? 'You must login to access social features' @@ -70,8 +66,9 @@ Future requestLogin(BuildContext context, {bool social}) => request( no: 'No Thanks', ).then((answer) async { if (answer == false) return false; - - return await Navigator.push(context, loginPage()); + final login = await Navigator.push(context, loginPage()); + if (login && social) await context.read().optIn(); + return login; }); Future request(BuildContext context, diff --git a/client/lib/login_handler.dart b/client/lib/login_handler.dart index c4a50dc..f39519c 100644 --- a/client/lib/login_handler.dart +++ b/client/lib/login_handler.dart @@ -9,24 +9,23 @@ const _ckey = String.fromEnvironment('CKEY'); const _csec = String.fromEnvironment('CSEC'); class LoginHandler extends ChangeNotifier { - // OSM things final _env = osm.ApiEnv.dev('master'); final osm.Auth _auth = osm.Auth(_ckey, _csec, osm.ApiEnv.dev('master')); osm.Api _osmApi = null; var _tempToken = null; - osm.Api osmApi() { - if (!loggedIntoOSM()) - throw Exception('Cannot get OSM Api before logging in'); - if (_osmApi == null) - _osmApi = osm.Api( - 'MapTogether v0.1.0pre', - _auth.getClient(_auth.createCredentials(_accessToken(), _accessSecret())), - _env - ); - return _osmApi; - } + osm.Api osmApi() { + if (!loggedIntoOSM()) + throw Exception('Cannot get OSM Api before logging in'); + if (_osmApi == null) + _osmApi = osm.Api( + 'MapTogether v0.1.0pre', + _auth.getClient( + _auth.createCredentials(_accessToken(), _accessSecret())), + _env); + return _osmApi; + } Future loginUrl() async { _tempToken = (await _auth.getTemporaryToken()).credentials; @@ -37,24 +36,26 @@ class LoginHandler extends ChangeNotifier { final creds = await _auth .getAccessToken(_tempToken, verifier) .then((res) => res.credentials); - login(creds.token, creds.tokenSecret); + await login(creds.token, creds.tokenSecret); return true; } - // MT things - + mt.Api _mtApi = null; mt.Api mtApi() { - if (!loggedIntoSocial()) - throw Exception('Cannot get MT Api before logging in'); - if (_mtApi == null) - _mtApi = mt.Api(_accessToken()); - return _mtApi; + try { + if (!loggedIntoSocial()) + throw Exception('Cannot get MT Api before logging in'); + } catch (e, s) { + print("$s"); + throw Exception("$e"); + } + if (_mtApi == null) _mtApi = mt.Api(_accessToken()); + return _mtApi; } - // General things SharedPreferences _prefs = null; @@ -88,22 +89,30 @@ class LoginHandler extends ChangeNotifier { bool loggedIntoSocial() => socialOptIn() && loggedIntoOSM(); - optIn() { - prefs().setBool('socialOptIn', true); - osmApi().userId().then((id) => - mtApi().createUser(id, _accessSecret(), _ckey, _csec).then((_) => - notifyListeners() - ) - ); + int _userId = null; + Future userId() async { + if (_userId == null) _userId = await osmApi().userId(); + return _userId; + } + + Future user() async => mtApi().user(await userId()); + + optIn() async { + print('Opting in to social features'); + await prefs().setBool('socialOptIn', true); + await userId().then((id) => mtApi() + .createUser(id, _accessSecret(), _ckey, _csec) + .then((_) => notifyListeners())); } - login(token, secret) { - prefs().setString('accessToken', token); - prefs().setString('accessSecret', secret); + login(token, secret) async { + await prefs().setString('accessToken', token); + await prefs().setString('accessSecret', secret); notifyListeners(); } logout() { + _userId = null; prefs().setString('accessToken', ''); prefs().setString('accessSecret', ''); notifyListeners(); diff --git a/client/lib/main.dart b/client/lib/main.dart index 73c1d28..986ecaa 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -4,12 +4,10 @@ import 'package:client/screens/map_screen.dart'; import 'package:client/login_handler.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; - -import 'database.dart'; +import 'package:maptogether_api/maptogether_api.dart'; void main() => runApp( MultiProvider(providers: [ - ChangeNotifierProvider(create: (_) => DummyDatabase()), ChangeNotifierProvider(create: (_) => LoginHandler()), ChangeNotifierProvider(create: (_) => LocationHandler()), ChangeNotifierProvider(create: (_) => QuestHandler()), @@ -20,7 +18,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: '${context.watch().currentUserName}', + title: 'MapTogether', theme: ThemeData( primaryColor: Colors.lightGreen, primarySwatch: Colors.lightGreen, diff --git a/client/lib/screens/map_screen.dart b/client/lib/screens/map_screen.dart index dafdfdb..3037912 100644 --- a/client/lib/screens/map_screen.dart +++ b/client/lib/screens/map_screen.dart @@ -1,15 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:latlong/latlong.dart'; +import 'package:provider/provider.dart'; + import 'package:client/location_handler.dart'; +import 'package:client/login_flow.dart'; import 'package:client/login_handler.dart'; import 'package:client/quests/quest_handler.dart'; -import 'package:client/screens/login_screen.dart'; import 'package:client/screens/social_screen.dart'; -import 'package:client/widgets/map_widgets/map.dart'; -import 'package:client/widgets/map_widgets/map_screen_button_widgets/button_row.dart'; -import 'package:client/widgets/map_widgets/map_screen_button_widgets/map_screen_button.dart'; -import 'package:client/widgets/map_widgets/map_screen_button_widgets/pup_up_menu.dart'; -import 'package:flutter/material.dart'; -import 'package:latlong/latlong.dart'; -import 'package:provider/provider.dart'; +import 'package:client/widgets/map/buttons/button_row.dart'; +import 'package:client/widgets/map/buttons/map_screen_button.dart'; +import 'package:client/widgets/map/buttons/pup_up_menu.dart'; +import 'package:client/widgets/map/map.dart'; launchSocial(BuildContext context) => Navigator.of(context) .push(MaterialPageRoute(builder: (context) => SocialScreen())); diff --git a/client/lib/screens/new_activity_screen.dart b/client/lib/screens/new_activity_screen.dart index 3d69f9a..5a6df22 100644 --- a/client/lib/screens/new_activity_screen.dart +++ b/client/lib/screens/new_activity_screen.dart @@ -6,7 +6,6 @@ class NewActivityScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: MapTogetherAppBar(title: 'New Activity'), - ); } } diff --git a/client/lib/screens/page2.dart b/client/lib/screens/page2.dart deleted file mode 100644 index d154127..0000000 --- a/client/lib/screens/page2.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/material.dart'; - -class Page2 extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Scaffold( - floatingActionButton: FloatingActionButton( - onPressed: (){ - Navigator.pop(context, true); - }, - ), - body: Container( - child: Center( - child: Text('Page 2', - style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.bold)), - ), - ), - ); - } -} \ No newline at end of file diff --git a/client/lib/screens/settings.dart b/client/lib/screens/settings_screen.dart similarity index 100% rename from client/lib/screens/settings.dart rename to client/lib/screens/settings_screen.dart diff --git a/client/lib/screens/social_screen.dart b/client/lib/screens/social_screen.dart index 3f7fd8f..1f63c63 100644 --- a/client/lib/screens/social_screen.dart +++ b/client/lib/screens/social_screen.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:client/database.dart'; import 'package:client/widgets/app_bar.dart'; -import 'package:client/widgets/social_menu_widgets/friends.dart'; -import 'package:client/widgets/social_menu_widgets/groups.dart'; -import 'package:client/widgets/social_menu_widgets/history.dart'; -import 'package:client/widgets/social_menu_widgets/overview.dart'; -import 'package:client/widgets/social_menu_widgets/user_overview.dart'; -import 'package:client/screens/login_screen.dart'; +import 'package:client/widgets/social/friends.dart'; +import 'package:client/widgets/social/groups.dart'; +import 'package:client/widgets/social/history.dart'; +import 'package:client/widgets/social/overview.dart'; +import 'package:client/widgets/social/user_overview.dart'; +import 'package:client/login_flow.dart'; import 'package:client/login_handler.dart'; +import 'package:maptogether_api/maptogether_api.dart'; class SocialScreen extends StatefulWidget { @override @@ -19,25 +19,31 @@ class SocialScreen extends StatefulWidget { class _SocialScreenState extends State { int menuIndex = 0; - List menuItems = [ - Overview(), - Friends(), - Groups(), - History(), - ]; + Future user = null; + List menuItems = null; @override Widget build(BuildContext context) { - final loginHandler = context.watch(); + if (user == null) { + user = context.read().user(); + menuItems = [ + Overview(user), + Friends(user), + Groups(), + History(), + ]; + } return Scaffold( appBar: MapTogetherAppBar( - title: 'Social menu', + title: 'Social', actions: [ - TextButton(child: Text("Log out", style: TextStyle(color: Colors.white)), onPressed: () { - loginHandler.logout(); - Navigator.pop(context); - }), + TextButton( + child: Text("Log out", style: TextStyle(color: Colors.white)), + onPressed: () { + context.read().logout(); + Navigator.pop(context); + }), ], ), body: Container( diff --git a/client/lib/widgets/app_bar.dart b/client/lib/widgets/app_bar.dart index 2552711..353ea16 100644 --- a/client/lib/widgets/app_bar.dart +++ b/client/lib/widgets/app_bar.dart @@ -1,10 +1,7 @@ import 'package:flutter/material.dart'; class MapTogetherAppBar extends StatelessWidget implements PreferredSizeWidget { - MapTogetherAppBar( - {Key key, - @required this.title, - this.actions}) + MapTogetherAppBar({Key key, @required this.title, this.actions}) : super(key: key); String title; diff --git a/client/lib/widgets/error.dart b/client/lib/widgets/error.dart new file mode 100644 index 0000000..820ef54 --- /dev/null +++ b/client/lib/widgets/error.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class Error extends StatelessWidget { + final error; + Error(this.error) { + print(this.error); + } + + @override + build(BuildContext context) => + Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Icon( + Icons.error_outline, + color: Colors.red, + size: 60, + ), + Text(error.toString()), + ]); +} diff --git a/client/lib/widgets/future_loader.dart b/client/lib/widgets/future_loader.dart new file mode 100644 index 0000000..5b1d411 --- /dev/null +++ b/client/lib/widgets/future_loader.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +import 'package:client/widgets/error.dart'; +import 'package:client/widgets/loading.dart'; + +class FutureLoader extends StatelessWidget { + Future future; + Widget Function(BuildContext, F) builder; + + FutureLoader({@required this.future, @required this.builder}); + + @override + build(BuildContext context) => FutureBuilder( + future: future, + builder: (BuildContext context, AsyncSnapshot snapshot) => + snapshot.hasData + ? builder(context, snapshot.data) + : snapshot.hasError + ? Error(snapshot.error) + : Loading()); +} diff --git a/client/lib/widgets/loading.dart b/client/lib/widgets/loading.dart new file mode 100644 index 0000000..be5bc54 --- /dev/null +++ b/client/lib/widgets/loading.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class Loading extends StatelessWidget { + @override + build(BuildContext context) => SizedBox( + child: CircularProgressIndicator(), + width: 60, + height: 60, + ); +} diff --git a/client/lib/widgets/map_widgets/map_screen_button_widgets/button_row.dart b/client/lib/widgets/map/buttons/button_row.dart similarity index 99% rename from client/lib/widgets/map_widgets/map_screen_button_widgets/button_row.dart rename to client/lib/widgets/map/buttons/button_row.dart index cb9ba0f..4f48a84 100644 --- a/client/lib/widgets/map_widgets/map_screen_button_widgets/button_row.dart +++ b/client/lib/widgets/map/buttons/button_row.dart @@ -21,4 +21,4 @@ class ButtonRow extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/client/lib/widgets/map_widgets/map_screen_button_widgets/map_screen_button.dart b/client/lib/widgets/map/buttons/map_screen_button.dart similarity index 86% rename from client/lib/widgets/map_widgets/map_screen_button_widgets/map_screen_button.dart rename to client/lib/widgets/map/buttons/map_screen_button.dart index a57d276..12754eb 100644 --- a/client/lib/widgets/map_widgets/map_screen_button_widgets/map_screen_button.dart +++ b/client/lib/widgets/map/buttons/map_screen_button.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; class MapScreenButton extends StatelessWidget { MapScreenButton( {Key key, - @required this.child, - @required this.onPressed, - this.height, - this.width}) + @required this.child, + @required this.onPressed, + this.height, + this.width}) : super(key: key); final Widget child; @@ -33,4 +33,4 @@ class MapScreenButton extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/client/lib/widgets/map_widgets/map_screen_button_widgets/pup_up_menu.dart b/client/lib/widgets/map/buttons/pup_up_menu.dart similarity index 90% rename from client/lib/widgets/map_widgets/map_screen_button_widgets/pup_up_menu.dart rename to client/lib/widgets/map/buttons/pup_up_menu.dart index e864d10..dd235da 100644 --- a/client/lib/widgets/map_widgets/map_screen_button_widgets/pup_up_menu.dart +++ b/client/lib/widgets/map/buttons/pup_up_menu.dart @@ -1,6 +1,6 @@ import 'package:client/screens/new_activity_screen.dart'; -import 'package:client/screens/settings.dart'; -import 'package:client/screens/login_screen.dart'; +import 'package:client/screens/settings_screen.dart'; +import 'package:client/login_flow.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -29,12 +29,10 @@ class _PopUpMenuState extends State { MapScreenButton( child: Icon(Icons.add), onPressed: () { - Navigator.push( - context, MaterialPageRoute(builder: (context) => NewActivityScreen()) - ); + Navigator.push(context, + MaterialPageRoute(builder: (context) => NewActivityScreen())); closeMenu(); }), - MapScreenButton( child: Icon(Icons.location_history), onPressed: () { @@ -42,7 +40,6 @@ class _PopUpMenuState extends State { context, MaterialPageRoute(builder: (context) => Settings())); closeMenu(); }), - MapScreenButton( child: Icon(Icons.settings), onPressed: () { diff --git a/client/lib/widgets/map_widgets/map.dart b/client/lib/widgets/map/map.dart similarity index 98% rename from client/lib/widgets/map_widgets/map.dart rename to client/lib/widgets/map/map.dart index cddd5db..3869cae 100644 --- a/client/lib/widgets/map_widgets/map.dart +++ b/client/lib/widgets/map/map.dart @@ -1,6 +1,3 @@ -import 'package:client/location_handler.dart'; -import 'package:client/quests/quest_handler.dart'; -import 'package:client/widgets/quest/marker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/plugin_api.dart'; @@ -10,6 +7,13 @@ import 'package:osm_api/osm_api.dart' as osm; import 'package:provider/provider.dart'; import 'package:time_range_picker/time_range_picker.dart'; +import 'package:client/location_handler.dart'; +import 'package:client/login_flow.dart'; +import 'package:client/login_handler.dart'; +import 'package:client/quests/quest.dart'; +import 'package:client/quests/quest_handler.dart'; +import 'package:client/widgets/quest/marker.dart'; + class PointOfInterest { // TODO: I think this should be moved to some model package String name; diff --git a/client/lib/widgets/social_menu_widgets/newFriend.dart b/client/lib/widgets/social/add_friend.dart similarity index 63% rename from client/lib/widgets/social_menu_widgets/newFriend.dart rename to client/lib/widgets/social/add_friend.dart index 33ce418..2112aa1 100644 --- a/client/lib/widgets/social_menu_widgets/newFriend.dart +++ b/client/lib/widgets/social/add_friend.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:client/widgets/app_bar.dart'; import 'friends.dart'; -import 'package:client/database.dart'; import 'package:provider/provider.dart'; -class NewFriend extends StatelessWidget { +class AddFriend extends StatelessWidget { TextEditingController nameController = TextEditingController(); @override @@ -14,23 +13,23 @@ class NewFriend extends StatelessWidget { title: 'Follow New', actions: [], ), - body: Center(child: Column(children: [ + body: Center( + child: Column(children: [ Container( child: Column( children: [ Container( - alignment: Alignment.center, - padding: EdgeInsets.all(10), - margin: const EdgeInsets.only(top: 50), - child: Text( - 'Find via Username', - style: TextStyle( - color: Colors.green, - fontWeight: FontWeight.w500, - fontSize: 30), - ), + alignment: Alignment.center, + padding: EdgeInsets.all(10), + margin: const EdgeInsets.only(top: 50), + child: Text( + 'Find via Username', + style: TextStyle( + color: Colors.green, + fontWeight: FontWeight.w500, + fontSize: 30), + ), ), - Container( padding: EdgeInsets.all(10), child: TextField( @@ -41,29 +40,23 @@ class NewFriend extends StatelessWidget { ), ), ), - Container( height: 50, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), child: ElevatedButton( style: TextButton.styleFrom( - primary: Colors.white, - backgroundColor: Colors.green - ), + primary: Colors.white, backgroundColor: Colors.green), child: Text('Follow'), onPressed: () { //Add friend to backend for user here - context.read().followNew(nameController.text); + print("FOLLOW SOMEONE"); print(nameController.text); Navigator.pop(context); }, )), - ], ), ), - ] - )) - ); + ]))); } -} \ No newline at end of file +} diff --git a/client/lib/widgets/social_menu_widgets/newGroup.dart b/client/lib/widgets/social/create_group.dart similarity index 94% rename from client/lib/widgets/social_menu_widgets/newGroup.dart rename to client/lib/widgets/social/create_group.dart index 0285916..6d35389 100644 --- a/client/lib/widgets/social_menu_widgets/newGroup.dart +++ b/client/lib/widgets/social/create_group.dart @@ -1,7 +1,7 @@ import 'package:client/widgets/app_bar.dart'; import 'package:flutter/material.dart'; -class NewGroup extends StatelessWidget { +class CreateGroup extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( diff --git a/client/lib/widgets/social/friends.dart b/client/lib/widgets/social/friends.dart new file mode 100644 index 0000000..801d1d3 --- /dev/null +++ b/client/lib/widgets/social/friends.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:maptogether_api/maptogether_api.dart' as mt; + +import 'package:client/widgets/social/leaderboard.dart'; +import 'package:client/widgets/social/add_friend.dart'; +import 'package:client/widgets/future_loader.dart'; + +//TODO: move friends list to a seperate file or server +class Friends extends StatelessWidget { + Future user; + Friends(this.user); + + static const Widget seperator = const Divider(thickness: 2, height: 2); + + Widget friendItem(BuildContext context, mt.SimpleUser user) => ListTile( + onLongPress: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Container( + height: 100, + color: Colors.orange, + child: Center( + child: TextButton( + child: Text("Unfollow"), + style: TextButton.styleFrom( + primary: Colors.white, backgroundColor: Colors.red), + onPressed: () { + print("UNFOLLOW SOMEONE"); + Navigator.pop(context); + }, + ), + ), + ); + }); + }, + title: Text(user.name), + leading: CircleAvatar( + backgroundImage: AssetImage('assets/business.png'), + ), + ); + + @override + Widget build(BuildContext context) => Container( + child: FutureLoader( + future: user, + builder: (BuildContext context, mt.User user) => Column( + children: [ + Expanded( + flex: 14, + child: ListView.separated( + separatorBuilder: (_, __) => seperator, + itemCount: user.following.length, + itemBuilder: (context, index) => + friendItem(context, user.following[index]), + )), + Expanded( + flex: 2, + child: TextButton( + child: Text( + 'Follow New', + style: TextStyle(fontSize: 20.0, color: Colors.white), + ), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddFriend())); + }, + ), + ), + ], + ))); +} diff --git a/client/lib/widgets/social_menu_widgets/groups.dart b/client/lib/widgets/social/groups.dart similarity index 78% rename from client/lib/widgets/social_menu_widgets/groups.dart rename to client/lib/widgets/social/groups.dart index d6553f6..3081584 100644 --- a/client/lib/widgets/social_menu_widgets/groups.dart +++ b/client/lib/widgets/social/groups.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'newGroup.dart'; +import 'package:client/widgets/social/create_group.dart'; class Groups extends StatelessWidget { @override @@ -7,13 +7,8 @@ class Groups extends StatelessWidget { return Container( margin: EdgeInsets.all(40.0), child: Column( - children: [ - Expanded( - flex: 10, - child: Text("Groups") - ), - + Expanded(flex: 10, child: Text("Groups")), Expanded( flex: 1, child: Container( @@ -25,7 +20,7 @@ class Groups extends StatelessWidget { ), onPressed: () { Navigator.push(context, - MaterialPageRoute(builder: (context) => NewGroup())); + MaterialPageRoute(builder: (context) => CreateGroup())); }, ), ), diff --git a/client/lib/widgets/social_menu_widgets/history.dart b/client/lib/widgets/social/history.dart similarity index 87% rename from client/lib/widgets/social_menu_widgets/history.dart rename to client/lib/widgets/social/history.dart index eded1e4..6255aba 100644 --- a/client/lib/widgets/social_menu_widgets/history.dart +++ b/client/lib/widgets/social/history.dart @@ -2,24 +2,24 @@ import 'package:flutter/material.dart'; import 'dart:async'; import 'package:intl/intl.dart'; -class Event{ +class Event { String type; String time; Icon icon; - Event(String _type, DateTime _time, Icon i){ + Event(String _type, DateTime _time, Icon i) { type = _type; time = DateFormat('yyyy-MM-dd kk:mm:ss').format(_time).toString(); icon = i; } } + class History extends StatefulWidget { @override _HistoryState createState() => _HistoryState(); } class _HistoryState extends State { - List events = [ Event("Marker Add", DateTime.now(), Icon(Icons.add_location_alt)), Event("Marker Delete", DateTime.now(), Icon(Icons.remove)), @@ -50,34 +50,26 @@ class _HistoryState extends State { Event("Marker Delete", DateTime.now(), Icon(Icons.remove)) ]; - - Widget _buildSuggestions(){ - - } + Widget _buildSuggestions() {} @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.all(40.0), child: Column( - children: [ - Expanded( - flex: 1, - child: Text("History")), + Expanded(flex: 1, child: Text("History")), Expanded( flex: 7, child: ListView.builder( itemCount: events.length, itemBuilder: (context, index) { return ListTile( - leading: events[index].icon, - title: Text(events[index].type + " - " + events[index].time) - ); - } - ) - ), + leading: events[index].icon, + title: Text( + events[index].type + " - " + events[index].time)); + })), ], ), ); } -} \ No newline at end of file +} diff --git a/client/lib/widgets/social/leaderboard.dart b/client/lib/widgets/social/leaderboard.dart new file mode 100644 index 0000000..772f73e --- /dev/null +++ b/client/lib/widgets/social/leaderboard.dart @@ -0,0 +1,45 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:maptogether_api/maptogether_api.dart'; +import 'package:provider/provider.dart'; + +import 'package:client/widgets/app_bar.dart'; +import 'package:client/widgets/future_loader.dart'; + +class LeaderboardWidget extends StatelessWidget { + Future leaderboard; + + LeaderboardWidget({Key key, @required this.leaderboard}) : super(key: key); + + Widget scoreWidget(int placement, String name, int score) => Card( + child: ListTile( + title: Text("#$placement $name : $score"), + leading: CircleAvatar( + backgroundImage: AssetImage('assets/business.png'), + ), + ), + ); + + Widget leaderboardWidget(Leaderboard leaderboard) => Scaffold( + appBar: MapTogetherAppBar( + title: "Leaderboard for ", + actions: [], + ), + body: Column(children: [ + Expanded( + child: ListView.builder( + itemCount: leaderboard.entries.length, + itemBuilder: (context, index) => scoreWidget( + index + 1, + leaderboard.entries[index].user.name, + leaderboard.entries[index].score)), + ), + ])); + + @override + Widget build(BuildContext context) => FutureLoader( + future: leaderboard, + builder: (BuildContext context, Leaderboard leaderboard) => + leaderboardWidget(leaderboard)); +} diff --git a/client/lib/widgets/social/overview.dart b/client/lib/widgets/social/overview.dart new file mode 100644 index 0000000..6053939 --- /dev/null +++ b/client/lib/widgets/social/overview.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:maptogether_api/maptogether_api.dart'; +import 'leaderboard.dart'; +import 'package:provider/provider.dart'; + +import 'package:client/login_handler.dart'; +import 'package:client/widgets/social/user_overview.dart'; +import 'package:client/widgets/future_loader.dart'; + +class Overview extends StatelessWidget { + Future user; + Overview(this.user); + + @override + Widget build(BuildContext context) => Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded(flex: 2, child: UserOverview(user)), + Expanded( + flex: 7, + child: DefaultTabController( + length: 3, + child: Scaffold( + appBar: TabBar( + indicatorColor: Colors.green, + labelColor: Colors.lightGreen, + unselectedLabelColor: Colors.black, + isScrollable: false, + tabs: [ + Tab(text: "All Time"), + Tab(text: "Monthly"), + Tab(text: "Weekly"), + ]), + body: TabBarView( + children: [ + leaderBoardWidget(LeaderboardType.all_time), + leaderBoardWidget(LeaderboardType.monthly), + leaderBoardWidget(LeaderboardType.weekly), + ], + ), + ), + ), + ), + ], + ); + + //TODO: make future builder dependent + Widget leaderBoardWidget(LeaderboardType type) => FutureLoader( + future: + user, // TODO: actually use the users leaderboards instead of just the constant + builder: (BuildContext context, User user) => ListView.builder( + itemCount: 1, + itemBuilder: (context, index) => + leaderboardItem(context, type, "global")), + ); + + Widget leaderboardItem( + BuildContext context, LeaderboardType type, String name) => + Card( + child: ListTile( + leading: Text(name), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LeaderboardWidget( + leaderboard: context + .watch() + .mtApi() + .leaderboard(type, name)), + )); + }, + )); +} diff --git a/client/lib/widgets/social/user_overview.dart b/client/lib/widgets/social/user_overview.dart new file mode 100644 index 0000000..362033d --- /dev/null +++ b/client/lib/widgets/social/user_overview.dart @@ -0,0 +1,60 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:maptogether_api/maptogether_api.dart'; + +import 'package:client/widgets/future_loader.dart'; + +class UserOverview extends StatelessWidget { + Future user; + UserOverview(this.user); + + @override + Widget build(BuildContext context) => FutureLoader( + future: user, + builder: (BuildContext context, User user) => Container( + padding: EdgeInsets.all(10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Container( + width: 100, + height: 100, + decoration: BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + image: AssetImage('assets/business.png'), + ))), + Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text(user.name, + style: TextStyle( + fontFamily: 'RobotoMono', + fontSize: 22, + color: Colors.lightGreen, + fontWeight: FontWeight.bold)), + Text("Score : ${user.scoreAllTime}", + style: TextStyle( + fontFamily: 'RobotoMono', + fontSize: 18, + color: Colors.lightGreen, + fontWeight: FontWeight.bold)), + Text("Monthly Score : ${user.scoreMonthly}", + style: TextStyle( + fontFamily: 'RobotoMono', + fontSize: 18, + color: Colors.lightGreen, + fontWeight: FontWeight.bold)), + Text("Weekly Score : ${user.scoreWeekly}", + style: TextStyle( + fontFamily: 'RobotoMono', + fontSize: 18, + color: Colors.lightGreen, + fontWeight: FontWeight.bold)), + ], + ) + ], + ), + )); +} diff --git a/client/lib/widgets/social_menu_widgets/Leaderboard.dart b/client/lib/widgets/social_menu_widgets/Leaderboard.dart deleted file mode 100644 index ba1a175..0000000 --- a/client/lib/widgets/social_menu_widgets/Leaderboard.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:client/widgets/app_bar.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:client/database.dart'; -import 'package:provider/provider.dart'; - -import 'User.dart'; - -class LeaderBoard{ - String name; - List users; - - LeaderBoard(this.name, this.users){ - users.sort((a, b) => b.total.compareTo(a.total)); - } -} - - -class LeaderBoardView extends StatelessWidget{ - int leaderboardIndex; - LeaderBoardView({Key key, @required this.leaderboardIndex}) : super(key: key); - - @override - Widget build(BuildContext context){ - var curLeaderboard = context.watch().leaderboards[leaderboardIndex]; - //we sort the list whenever we open the list, such that it is in correct order in case of updates to the database - curLeaderboard.users.sort((a, b) => b.total.compareTo(a.total)); - - return Scaffold( - appBar: MapTogetherAppBar( - title: "Leaderboard for " + curLeaderboard.name, - actions: [], - ), - body: Center( - child: Container( - margin: EdgeInsets.all(40.0), - child: Column( - children: [ - Expanded( - child: ListView.builder( - itemCount: curLeaderboard.users.length, - itemBuilder: (context, index){ - return Card( - child: ListTile( - title: Text("#" - + (index+1).toString() - + " " - + curLeaderboard.users[index].name - + " : " - + curLeaderboard.users[index].total.toString() - + " points"), - - leading: CircleAvatar( - backgroundImage: - AssetImage('assets/${curLeaderboard.users[index].pfp}'), - - ), - ), - ); - }), - ), - TextButton( - onPressed: (){ - for(int x = 0; x < curLeaderboard.users.length; x++) - if(curLeaderboard.users[x].name == context.read().currentUserName) - Provider.of(context, listen: false).givePoints(10); - curLeaderboard.users.sort((a, b) => b.total.compareTo(a.total)); - }, - child: Text("+++++") - ) - ] - ) - ) - ) - ); - } -} - diff --git a/client/lib/widgets/social_menu_widgets/User.dart b/client/lib/widgets/social_menu_widgets/User.dart deleted file mode 100644 index d8229e5..0000000 --- a/client/lib/widgets/social_menu_widgets/User.dart +++ /dev/null @@ -1,13 +0,0 @@ -class User { - String name; //name of user - int total; - int weekly; - int daily; - String pfp; - String nationality; - //Eventuelt gem på tasks brugere laver i en task class, her samt et time stamp, vi kan så få - //weekly/daily score ved bare at tage ting i et specifikt interval - - - User(this.name, this.total, this.weekly, this.daily, this.pfp, this.nationality); -} \ No newline at end of file diff --git a/client/lib/widgets/social_menu_widgets/friends.dart b/client/lib/widgets/social_menu_widgets/friends.dart deleted file mode 100644 index 1a8963a..0000000 --- a/client/lib/widgets/social_menu_widgets/friends.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:client/widgets/social_menu_widgets/newFriend.dart'; -import 'package:flutter/material.dart'; -import 'User.dart'; -import 'package:client/database.dart'; -import 'package:provider/provider.dart'; - -//TODO: move friends list to a seperate file or server -class Friends extends StatelessWidget { - - @override - Widget build(BuildContext context) { - return Container( - margin: EdgeInsets.all(40.0), - child: Column( - children: [ - Expanded(flex: 1, child: Text("Following")), - Expanded( - flex: 7, - child: ListView.builder( - itemCount: context.watch().following.length, - itemBuilder: (context, index) { - return Card( - child: ListTile( - onLongPress: () { - showModalBottomSheet ( - context: context, - builder: (BuildContext context) { - return Container( - height: 100, - color: Colors.orange, - child: Center( - child: TextButton( - child: Text("Unfollow"), - style: TextButton.styleFrom( - primary: Colors.white, - backgroundColor: Colors.red - ), - onPressed: (){ - context.read().followingNames.remove(context.read().following[index].name); - context.read().following.removeAt(index); - Navigator.pop(context); - }, - ), - ), - ); - } - ); - }, - title: Text(context.watch().following[index].name), - leading: CircleAvatar( - backgroundImage: - AssetImage('assets/${context.watch().following[index].pfp}'), - ), - ), - ); - })), - Expanded( - flex: 1, - child: Padding( - padding: const EdgeInsets.all(10), - child: Container( - color: Colors.lightGreen, - child: TextButton( - child: Text( - 'Follow New', - style: TextStyle(fontSize: 20.0, color: Colors.white), - ), - onPressed: () { - Navigator.push(context, - MaterialPageRoute(builder: (context) => NewFriend())); - }, - ), - ), - ), - ), - ], - ), - ); - } -} diff --git a/client/lib/widgets/social_menu_widgets/overview.dart b/client/lib/widgets/social_menu_widgets/overview.dart deleted file mode 100644 index 73ca754..0000000 --- a/client/lib/widgets/social_menu_widgets/overview.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:client/widgets/social_menu_widgets/user_overview.dart'; -import 'package:flutter/material.dart'; -import 'Leaderboard.dart'; -import 'User.dart'; -import 'package:client/database.dart'; -import 'package:provider/provider.dart'; - - -class Overview extends StatefulWidget { - @override - _OverviewView createState() => _OverviewView(); -} - -class _OverviewView extends State with TickerProviderStateMixin{ - //We get All, Weekly and Daily from the index of the tabcontroller, changes depending on which leaderboards we are interested in, 0 = daily, 1 = weekly, 2 = all - TabController _nestedTabController; - - @override - void initState(){ - super.initState(); - - _nestedTabController = new TabController(length: 3, vsync: this); - } - - @override - void dispose(){ - super.dispose(); - _nestedTabController.dispose(); - } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - flex: 2, - child: UserOverView() - ), - - TabBar( - controller: _nestedTabController, - indicatorColor: Colors.green, - labelColor: Colors.lightGreen, - unselectedLabelColor: Colors.black, - isScrollable: false, - - tabs: [ - Tab( - text: "Daily", - ), - Tab( - text: "Weekly", - ), - Tab( - text: "All Time", - ), - ] - ), - - Expanded( - flex: 7, - child: TabBarView( - controller: _nestedTabController, - children: [ - leaderBoardWidget("1"), - leaderBoardWidget("2"), - leaderBoardWidget("3"), - ]) - ) - ], - ); - } - - - Widget leaderBoardWidget(String type) => Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: ListView.builder( - itemCount: context.watch().leaderboards.length, - itemBuilder: (context, index){ - return Card( - child: ListTile( - title: Text("#" - + ((context.watch().leaderboards[index].users.indexOf(context.watch().currentUser)+1).toString()) - + "/" - + (context.watch().leaderboards[index].users.length).toString()), - leading: Text(context.watch().leaderboards[index].name), - onTap: (){ - Navigator.push(context, - MaterialPageRoute( - builder: (context) => LeaderBoardView(leaderboardIndex: index),)); - }, - - ) - ); - } - ) - ) - ], - ); -} - - - diff --git a/client/lib/widgets/social_menu_widgets/user_overview.dart b/client/lib/widgets/social_menu_widgets/user_overview.dart deleted file mode 100644 index 7d36f70..0000000 --- a/client/lib/widgets/social_menu_widgets/user_overview.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:client/database.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'Leaderboard.dart'; - -class UserOverView extends StatelessWidget { - @override - Widget build(BuildContext context) { - for(LeaderBoard l in context.watch().leaderboards) { - l.users.sort((a, b) => b.total.compareTo(a.total)); - } - return Container( - padding: EdgeInsets.all(10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Container( - width: 100, - height: 100, - decoration: BoxDecoration( - shape: BoxShape.circle, - image: DecorationImage( - image: AssetImage('assets/${context.watch().currentUser.pfp}'), - ) - ) - - ), - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text("Daily : " + context.watch().currentUser.daily.toString(), - style: TextStyle(fontFamily: 'RobotoMono', fontSize: 18, color: Colors.lightGreen, fontWeight: FontWeight.bold), - ), - Text("Weekly : " + context.watch().currentUser.weekly.toString(), - style: TextStyle(fontFamily: 'RobotoMono', fontSize: 18, color: Colors.lightGreen, fontWeight: FontWeight.bold), - ), - Text("All time : " + context.watch().currentUser.total.toString(), - style: TextStyle(fontFamily: 'RobotoMono', fontSize: 18, color:Colors.lightGreen, fontWeight: FontWeight.bold - ) - ) - ], - ) - ], - ), - ); - } -} diff --git a/client/pubspec.lock b/client/pubspec.lock index cebb234..d2ca2fa 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -91,7 +91,7 @@ packages: name: file url: "https://pub.dartlang.org" source: hosted - version: "6.1.0" + version: "6.1.1" flutter: dependency: "direct main" description: flutter @@ -489,7 +489,7 @@ packages: name: webview_flutter url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.0.7" win32: dependency: transitive description: diff --git a/client/test/widget_test.dart b/client/test/widget_test.dart index 59e8ba5..bc42b6c 100644 --- a/client/test/widget_test.dart +++ b/client/test/widget_test.dart @@ -11,8 +11,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:client/main.dart'; void main() { - test('True is true', (){ + test('True is true', () { expect(true, true); }); - } diff --git a/maptogether_api/example/maptogether_api_example.dart b/maptogether_api/example/maptogether_api_example.dart index b91b9cd..6d5d2a2 100644 --- a/maptogether_api/example/maptogether_api_example.dart +++ b/maptogether_api/example/maptogether_api_example.dart @@ -1,7 +1,23 @@ +import 'dart:math'; + import 'package:maptogether_api/maptogether_api.dart'; +import '../lib/src/data.dart'; + void main() { final api = MapTogetherApi(); - api.user(1).then(print); + /*api.user(1).then(print); api.globalLeaderboard(LeaderboardType.all_time).then(print); + + api.globalLeaderboard(LeaderboardType.all_time).then((l) { + print(l.type); + for(var t in l.entries) + print(t.score); + + print("æøå ÆØÅ"); + });*/ + + api.leaderboard("global", LeaderboardType.all_time).then((l) { + print(l.entries[1].user.name); + }); } diff --git a/maptogether_api/lib/maptogether_api.dart b/maptogether_api/lib/maptogether_api.dart index b660ec4..8fb7a13 100644 --- a/maptogether_api/lib/maptogether_api.dart +++ b/maptogether_api/lib/maptogether_api.dart @@ -1,3 +1,4 @@ library maptogether_api; export 'src/api.dart'; +export 'src/data.dart'; diff --git a/maptogether_api/lib/src/api.dart b/maptogether_api/lib/src/api.dart index 8204b69..5ae1448 100644 --- a/maptogether_api/lib/src/api.dart +++ b/maptogether_api/lib/src/api.dart @@ -2,19 +2,6 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'data.dart' as data; -enum LeaderboardType { weekly, montly, all_time } -String stringify(LeaderboardType type) { - switch (type) { - case LeaderboardType.weekly: - return 'weekly'; - case LeaderboardType.montly: - return 'montly'; - case LeaderboardType.all_time: - return 'all_time'; - } - throw 'Leaderboard type does not exsist'; -} - class InvalidRegionException implements Exception { final String _message; InvalidRegionException( @@ -62,31 +49,26 @@ class Api { Future user(int id) => _get('user/$id').then(_checkRequest('getting user')).then(_decodeUser); - Future leaderboard(String base, LeaderboardType type) => - _get('leaderboard/$base/${stringify(type)}') - .then(_checkRequest('getting leaderboard')) - .then(_decodeLeaderboard); + leaderboardByPath(String path) => _get('leaderboard/$path') + .then(_checkRequest('getting leaderboard')) + .then(_decodeLeaderboard); - Future personalLeaderboard(LeaderboardType type) => - leaderboard('personal', type); + Future leaderboard( + data.LeaderboardType type, String name) => + leaderboardByPath('${type.stringify()}/$name'); - Future globalLeaderboard(LeaderboardType type) => - leaderboard('global', type); + Future personalLeaderboard( + data.LeaderboardType type, int id) => + leaderboard(type, 'personal/${id}'); - Future regionalLeaderboard( - String region, LeaderboardType type) { - if (region == 'personal' || region == 'global') - throw InvalidRegionException(); - else - return leaderboard(region, type); - } + Future globalLeaderboard(data.LeaderboardType type) => + leaderboard(type, 'global'); // Push Endpoints Future createUser( int id, String secret, String clientToken, String clientSecret) => - _put('user/$id', - auth: 'Basic $_access $secret $clientToken $clientSecret') + _put('user/$id', auth: '$_access $secret $clientToken $clientSecret') .then(_checkRequest('creating user')); Future follow(int id, int other) => diff --git a/maptogether_api/lib/src/data.dart b/maptogether_api/lib/src/data.dart index eddca11..1b26284 100644 --- a/maptogether_api/lib/src/data.dart +++ b/maptogether_api/lib/src/data.dart @@ -1,26 +1,34 @@ import 'package:json_annotation/json_annotation.dart'; +import 'package:maptogether_api/src/api.dart'; part 'data.g.dart'; @JsonSerializable() class User { - final int id, score; + final int id; + @JsonKey(name: "score_all_time") + final int scoreAllTime; + @JsonKey(name: "score_monthly") + final int scoreMonthly; + @JsonKey(name: "score_weekly") + final int scoreWeekly; final String name; - @JsonKey(defaultValue: null) final List achievements; - @JsonKey(defaultValue: null) final List followers; - @JsonKey(defaultValue: null) final List following; + final List leaderboards; User( {required this.id, - required this.score, + required this.scoreAllTime, + required this.scoreMonthly, + required this.scoreWeekly, required this.name, required this.achievements, required this.followers, - required this.following}); + required this.following, + required this.leaderboards}); factory User.fromJson(Map json) => _$UserFromJson(json); Map toJson() => _$UserToJson(this); @@ -96,3 +104,52 @@ class Leaderboard { factory Leaderboard.fromJson(List json) => Leaderboard( json.map((e) => LeaderboardEntry.fromJson(e)).toList(growable: false)); } + +@JsonSerializable() +class LeaderboardSummary { + final String path, name; + final int rank, total; + + @JsonKey(fromJson: LeaderboardTypeExtension.fromString) + final LeaderboardType type; + + LeaderboardSummary( + {required this.path, + required this.name, + required this.rank, + required this.total, + required this.type}); + + factory LeaderboardSummary.fromJson(Map json) => + _$LeaderboardSummaryFromJson(json); + Map toJson() => _$LeaderboardSummaryToJson(this); +} + +enum LeaderboardType { weekly, monthly, all_time } + +extension LeaderboardTypeExtension on LeaderboardType { + String stringify() { + switch (this) { + case LeaderboardType.weekly: + return "weekly"; + case LeaderboardType.monthly: + return "monthly"; + case LeaderboardType.all_time: + return 'all_time'; + } + throw 'Leaderboard type does not exsist'; + } + + static LeaderboardType fromString(String type) { + switch (type) { + case "weekly": + return LeaderboardType.weekly; + case "monthly": + return LeaderboardType.monthly; + case "all_time": + return LeaderboardType.all_time; + default: + return LeaderboardType.all_time; + } + } +} diff --git a/maptogether_api/lib/src/data.g.dart b/maptogether_api/lib/src/data.g.dart index e97a721..d748c61 100644 --- a/maptogether_api/lib/src/data.g.dart +++ b/maptogether_api/lib/src/data.g.dart @@ -9,7 +9,9 @@ part of 'data.dart'; User _$UserFromJson(Map json) { return User( id: json['id'] as int, - score: json['score'] as int, + scoreAllTime: json['score_all_time'] as int, + scoreMonthly: json['score_monthly'] as int, + scoreWeekly: json['score_weekly'] as int, name: json['name'] as String, achievements: (json['achievements'] as List) .map((e) => Achievement.fromJson(e as Map)) @@ -20,16 +22,22 @@ User _$UserFromJson(Map json) { following: (json['following'] as List) .map((e) => SimpleUser.fromJson(e as Map)) .toList(), + leaderboards: (json['leaderboards'] as List) + .map((e) => LeaderboardSummary.fromJson(e as Map)) + .toList(), ); } Map _$UserToJson(User instance) => { 'id': instance.id, - 'score': instance.score, + 'score_all_time': instance.scoreAllTime, + 'score_monthly': instance.scoreMonthly, + 'score_weekly': instance.scoreWeekly, 'name': instance.name, 'achievements': instance.achievements, 'followers': instance.followers, 'following': instance.following, + 'leaderboards': instance.leaderboards, }; Achievement _$AchievementFromJson(Map json) { @@ -89,3 +97,28 @@ Map _$LeaderboardEntryToJson(LeaderboardEntry instance) => 'user': instance.user, 'score': instance.score, }; + +LeaderboardSummary _$LeaderboardSummaryFromJson(Map json) { + return LeaderboardSummary( + path: json['path'] as String, + name: json['name'] as String, + rank: json['rank'] as int, + total: json['total'] as int, + type: LeaderboardTypeExtension.fromString(json['type'] as String), + ); +} + +Map _$LeaderboardSummaryToJson(LeaderboardSummary instance) => + { + 'path': instance.path, + 'name': instance.name, + 'rank': instance.rank, + 'total': instance.total, + 'type': _$LeaderboardTypeEnumMap[instance.type], + }; + +const _$LeaderboardTypeEnumMap = { + LeaderboardType.weekly: 'weekly', + LeaderboardType.monthly: 'monthly', + LeaderboardType.all_time: 'all_time', +}; diff --git a/maptogether_api/pubspec.yaml b/maptogether_api/pubspec.yaml index 1aac057..f9b11fe 100644 --- a/maptogether_api/pubspec.yaml +++ b/maptogether_api/pubspec.yaml @@ -1,6 +1,6 @@ name: maptogether_api description: A library for accessing the maptogether api -version: 0.2.0 +version: 0.3.0 homepage: https://github.com/SEbbaDK/p8 environment: diff --git a/maptogether_api/test/api_test.dart b/maptogether_api/test/api_test.dart index e9397cb..c98ca67 100644 --- a/maptogether_api/test/api_test.dart +++ b/maptogether_api/test/api_test.dart @@ -1,5 +1,5 @@ import 'package:test/test.dart'; -import 'package:maptogether_api/src/api.dart' as mt; +import 'package:maptogether_api/maptogether_api.dart' as mt; void main() { group('Maptogether reads', () { diff --git a/maptogether_api/test/data_test.dart b/maptogether_api/test/data_test.dart index 3c356e5..3e0cdfb 100644 --- a/maptogether_api/test/data_test.dart +++ b/maptogether_api/test/data_test.dart @@ -3,19 +3,35 @@ import 'package:maptogether_api/src/data.dart' as data; void main() { group('User', () { - var user = data.User(id: 1, score: 12, name: 'Jens', achievements: [ - data.Achievement( - name: 'fdf', - description: 'Stuff', - ) - ], followers: [ - data.SimpleUser( + var user = data.User( id: 1, - name: 'Hej', - ) - ], following: [ - data.SimpleUser(id: 2, name: 'Med') - ]); + scoreAllTime: 12, + scoreMonthly: 5, + scoreWeekly: 2, + name: 'Jens', + achievements: [ + data.Achievement( + name: 'fdf', + description: 'Stuff', + ) + ], + followers: [ + data.SimpleUser( + id: 1, + name: 'Hej', + ) + ], + following: [ + data.SimpleUser(id: 2, name: 'Med') + ], + leaderboards: [ + data.LeaderboardSummary( + path: "/leaderboard/all_time/global", + name: "Global", + rank: 1, + total: 20, + type: data.LeaderboardType.all_time) + ]); test('have', () => expect(user.followers.length, 1)); }); diff --git a/server.nix b/server.nix index 3eaa28b..b29c5aa 100644 --- a/server.nix +++ b/server.nix @@ -30,5 +30,6 @@ in containers.api = { config = import ./api-server.nix { port = apiPort; inherit pkgs; }; autoStart = true; + ephemeral = true; }; } diff --git a/server/README.md b/server/README.md index d9d7868..3750e50 100644 --- a/server/README.md +++ b/server/README.md @@ -16,27 +16,49 @@ Responds with json object of a user like this: "description": }, ... - ] + ], "followers": [ { "id": , "name": }, ... - ] + ], "following": [ { "id": , "name": }, ... - ] + ], + "leaderboards": [ + { + "path": , + "name": , + "type": , + "rank": , + "total": + }, + ... + ], } ``` -### `/leaderboard/global/all_time` Get Global All-time Leaderboard +### `/leaderboard/