diff --git a/Gemfile.lock b/Gemfile.lock index 86dccb02..b4b74e3c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,25 +5,25 @@ GEM base64 nkf rexml - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.944.0) - aws-sdk-core (3.197.0) + aws-partitions (1.960.0) + aws-sdk-core (3.201.3) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.84.0) - aws-sdk-core (~> 3, >= 3.197.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.152.3) - aws-sdk-core (~> 3, >= 3.197.0) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.156.0) + aws-sdk-core (~> 3, >= 3.201.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.8) - aws-sigv4 (1.8.0) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.9.1) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) base64 (0.2.0) @@ -38,7 +38,7 @@ GEM domain_name (0.6.20240107) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.110.0) + excon (0.111.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -60,7 +60,7 @@ GEM faraday-httpclient (1.0.1) faraday-multipart (1.0.4) multipart-post (~> 2) - faraday-net_http (1.0.1) + faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) @@ -68,7 +68,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.220.0) + fastlane (2.222.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -153,9 +153,9 @@ GEM httpclient (2.8.3) jmespath (1.6.2) json (2.7.2) - jwt (2.8.1) + jwt (2.8.2) base64 - mini_magick (4.13.1) + mini_magick (4.13.2) mini_mime (1.1.5) multi_json (1.15.0) multipart-post (2.4.1) @@ -165,7 +165,7 @@ GEM optparse (0.5.0) os (1.1.4) plist (3.7.1) - public_suffix (5.1.0) + public_suffix (6.0.1) rake (13.2.1) representable (3.2.0) declarative (< 0.1.0) diff --git a/fastlane/report.xml b/fastlane/report.xml index 87898c06..31b38440 100644 --- a/fastlane/report.xml +++ b/fastlane/report.xml @@ -5,12 +5,12 @@ - + - + diff --git a/lib/api_client.dart b/lib/api_client.dart index d6ff9c40..d7b0d152 100644 --- a/lib/api_client.dart +++ b/lib/api_client.dart @@ -1,42 +1,71 @@ import 'dart:convert'; +import 'dart:io'; + import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'helpers/consts.dart'; final log = Logger('powersync-test'); class ApiClient { final String baseUrl; - ApiClient(this.baseUrl); + const ApiClient(this.baseUrl); Future> authenticate(String username, String password) async { final response = await http.post( - Uri.parse('$baseUrl/api/auth/'), + Uri.parse('$baseUrl/api/v2/login/'), headers: {'Content-Type': 'application/json'}, body: json.encode({'username': username, 'password': password}), ); if (response.statusCode == 200) { + log.log(Level.ALL, response.body); return json.decode(response.body); - } else { - throw Exception('Failed to authenticate'); } + throw Exception('Failed to authenticate'); } - Future> getToken(String userId) async { - final response = await http.get( - Uri.parse('$baseUrl/api/get_powersync_token/'), - headers: {'Content-Type': 'application/json'}, + Future> getWgerJWTToken() async { + final response = await http.post( + Uri.parse('$baseUrl/api/v2/token'), + headers: {HttpHeaders.contentTypeHeader: 'application/json'}, + body: json.encode({'username': 'admin', 'password': 'adminadmin'}), ); if (response.statusCode == 200) { + log.log(Level.ALL, response.body); return json.decode(response.body); - } else { - throw Exception('Failed to fetch token'); } + throw Exception('Failed to fetch token'); + } + + /// Returns a powersync JWT token token + /// + /// Note that at the moment we use the permanent API token for authentication + /// but this should be probably changed to the wger API JWT tokens in the + /// future since they are not permanent and could be easily revoked. + Future> getPowersyncToken() async { + final prefs = await SharedPreferences.getInstance(); + final apiData = json.decode(prefs.getString(PREFS_USER)!); + + final response = await http.get( + Uri.parse('$baseUrl/api/v2/powersync-token'), + headers: { + HttpHeaders.contentTypeHeader: 'application/json', + HttpHeaders.authorizationHeader: 'Token ${apiData["token"]}', + }, + ); + if (response.statusCode == 200) { + log.log(Level.ALL, response.body); + return json.decode(response.body); + } + throw Exception('Failed to fetch token'); } Future upsert(Map record) async { await http.put( - Uri.parse('$baseUrl/api/upload_data/'), + Uri.parse('$baseUrl/api/upload-powersync-data'), headers: {'Content-Type': 'application/json'}, body: json.encode(record), ); @@ -44,7 +73,7 @@ class ApiClient { Future update(Map record) async { await http.patch( - Uri.parse('$baseUrl/api/upload_data/'), + Uri.parse('$baseUrl/api/upload-powersync-data'), headers: {'Content-Type': 'application/json'}, body: json.encode(record), ); @@ -52,7 +81,7 @@ class ApiClient { Future delete(Map record) async { await http.delete( - Uri.parse('$baseUrl/api/upload_data/'), + Uri.parse('$baseUrl/api/v2/upload-powersync-data'), headers: {'Content-Type': 'application/json'}, body: json.encode(record), ); diff --git a/lib/app_config.dart b/lib/app_config.dart index e751f694..78dc420a 100644 --- a/lib/app_config.dart +++ b/lib/app_config.dart @@ -1,4 +1,4 @@ class AppConfig { - static const String djangoUrl = 'http://192.168.2.223:6061'; - static const String powersyncUrl = 'http://192.168.2.223:8080'; + static const String djangoUrl = 'http://10.0.2.2:8000'; + static const String powersyncUrl = 'http://10.0.2.2:8080'; } diff --git a/lib/models/muscle.dart b/lib/models/muscle.dart new file mode 100644 index 00000000..b910341f --- /dev/null +++ b/lib/models/muscle.dart @@ -0,0 +1,37 @@ +import 'package:powersync/sqlite3.dart' as sqlite; +import 'package:wger/models/schema.dart'; +import 'package:wger/powersync.dart'; + +class Muscle { + final String id; + final String name; + final String nameEn; + final bool isFront; + + const Muscle({ + required this.id, + required this.name, + required this.nameEn, + required this.isFront, + }); + + factory Muscle.fromRow(sqlite.Row row) { + return Muscle( + id: row['id'], + name: row['name'], + nameEn: row['name_en'], + isFront: row['is_front'] == 1, + ); + } + + Future delete() async { + await db.execute('DELETE FROM $musclesTable WHERE id = ?', [id]); + } + + /// Watch all lists. + static Stream> watchMuscles() { + return db.watch('SELECT * FROM muscles ORDER BY id').map((results) { + return results.map(Muscle.fromRow).toList(growable: false); + }); + } +} diff --git a/lib/models/schema.dart b/lib/models/schema.dart index b85c5f10..b4c03784 100644 --- a/lib/models/schema.dart +++ b/lib/models/schema.dart @@ -1,24 +1,43 @@ import 'package:powersync/powersync.dart'; const todosTable = 'todos'; +const musclesTable = 'muscles'; // these are the same ones as in postgres, except for 'id' -Schema schema = const Schema(([ - Table(todosTable, [ - Column.text('list_id'), - Column.text('created_at'), - Column.text('completed_at'), - Column.text('description'), - Column.integer('completed'), - Column.text('created_by'), - Column.text('completed_by'), - ], indexes: [ - // Index to allow efficient lookup within a list - Index('list', [IndexedColumn('list_id')]) - ]), - Table('lists', - [Column.text('created_at'), Column.text('name'), Column.text('owner_id')]) -])); +Schema schema = const Schema([ + Table( + todosTable, + [ + Column.text('list_id'), + Column.text('created_at'), + Column.text('completed_at'), + Column.text('description'), + Column.integer('completed'), + Column.text('created_by'), + Column.text('completed_by'), + ], + indexes: [ + // Index to allow efficient lookup within a list + Index('list', [IndexedColumn('list_id')]), + ], + ), + Table( + 'lists', + [ + Column.text('created_at'), + Column.text('name'), + Column.text('owner_id'), + ], + ), + Table( + 'muscles', + [ + Column.text('name'), + Column.text('name_en'), + Column.text('is_front'), + ], + ), +]); // post gres columns: // todos: diff --git a/lib/models/todo_item.dart b/lib/models/todo_item.dart index 8bd10864..1b6e6e4e 100644 --- a/lib/models/todo_item.dart +++ b/lib/models/todo_item.dart @@ -1,7 +1,6 @@ -import 'package:wger/models/schema.dart'; - -import '../powersync.dart'; import 'package:powersync/sqlite3.dart' as sqlite; +import 'package:wger/models/schema.dart'; +import 'package:wger/powersync.dart'; /// TodoItem represents a result row of a query on "todos". /// @@ -14,18 +13,20 @@ class TodoItem { final String? photoId; final bool completed; - TodoItem( - {required this.id, - required this.description, - required this.completed, - required this.photoId}); + TodoItem({ + required this.id, + required this.description, + required this.completed, + required this.photoId, + }); factory TodoItem.fromRow(sqlite.Row row) { return TodoItem( - id: row['id'], - description: row['description'], - photoId: row['photo_id'], - completed: row['completed'] == 1); + id: row['id'], + description: row['description'], + photoId: row['photo_id'], + completed: row['completed'] == 1, + ); } Future toggle() async { diff --git a/lib/models/todo_list.dart b/lib/models/todo_list.dart index 17e848b2..4d965eb4 100644 --- a/lib/models/todo_list.dart +++ b/lib/models/todo_list.dart @@ -1,7 +1,7 @@ import 'package:powersync/sqlite3.dart' as sqlite; +import 'package:wger/powersync.dart'; import 'todo_item.dart'; -import '../powersync.dart'; /// TodoList represents a result row of a query on "lists". /// @@ -24,10 +24,11 @@ class TodoList { factory TodoList.fromRow(sqlite.Row row) { return TodoList( - id: row['id'], - name: row['name'], - completedCount: row['completed_count'], - pendingCount: row['pending_count']); + id: row['id'], + name: row['name'], + completedCount: row['completed_count'], + pendingCount: row['pending_count'], + ); } /// Watch all lists. @@ -55,12 +56,15 @@ class TodoList { /// Create a new list static Future create(String name) async { - final results = await db.execute(''' + final results = await db.execute( + ''' INSERT INTO lists(id, created_at, name, owner_id) VALUES(uuid(), datetime(), ?, ?) RETURNING * - ''', [name, await getUserId()]); + ''', + [name, await getUserId()], + ); return TodoList.fromRow(results.first); } diff --git a/lib/powersync.dart b/lib/powersync.dart index 0eaa6c7b..bdfcdfc9 100644 --- a/lib/powersync.dart +++ b/lib/powersync.dart @@ -28,19 +28,15 @@ class DjangoConnector extends PowerSyncBackendConnector { DjangoConnector(this.db); - final ApiClient apiClient = ApiClient(AppConfig.djangoUrl); + final ApiClient apiClient = const ApiClient(AppConfig.djangoUrl); /// Get a token to authenticate against the PowerSync instance. @override Future fetchCredentials() async { - final prefs = await SharedPreferences.getInstance(); - final userId = prefs.getString('id'); - if (userId == null) { - throw Exception('User does not have session'); - } // Somewhat contrived to illustrate usage, see auth docs here: // https://docs.powersync.com/usage/installation/authentication-setup/custom - final session = await apiClient.getToken(userId); + // final wgerSession = await apiClient.getWgerJWTToken(); + final session = await apiClient.getPowersyncToken(); return PowerSyncCredentials(endpoint: AppConfig.powersyncUrl, token: session['token']); } diff --git a/lib/screens/home_tabs_screen.dart b/lib/screens/home_tabs_screen.dart index 765703be..96faff81 100644 --- a/lib/screens/home_tabs_screen.dart +++ b/lib/screens/home_tabs_screen.dart @@ -16,13 +16,13 @@ * along with this program. If not, see . */ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; import 'package:rive/rive.dart'; +import 'package:wger/powersync.dart'; import 'package:wger/providers/auth.dart'; import 'package:wger/providers/body_weight.dart'; import 'package:wger/providers/exercises.dart'; @@ -39,6 +39,7 @@ import 'package:wger/screens/workout_plans_screen.dart'; class HomeTabsScreen extends StatefulWidget { const HomeTabsScreen(); + static const routeName = '/dashboard2'; @override @@ -72,6 +73,12 @@ class _HomeTabsScreenState extends State with SingleTickerProvid /// Load initial data from the server Future _loadEntries() async { + final connector = DjangoConnector(db); + final credentials = await connector.fetchCredentials(); + print('----------'); + print(credentials); + print('----------'); + final authProvider = context.read(); if (!authProvider.dataInit) { @@ -84,7 +91,7 @@ class _HomeTabsScreenState extends State with SingleTickerProvid final userProvider = context.read(); // Base data - log('Loading base data'); + log.log(Level.FINER, Level.FINER, 'Loading base data'); try { await Future.wait([ authProvider.setServerVersion(), @@ -94,12 +101,12 @@ class _HomeTabsScreenState extends State with SingleTickerProvid exercisesProvider.fetchAndSetInitialData(), ]); } catch (e) { - log('Exception loading base data'); - log(e.toString()); + log.log(Level.FINER, 'Exception loading base data'); + log.log(Level.FINER, e.toString()); } // Plans, weight and gallery - log('Loading plans, weight, measurements and gallery'); + log.log(Level.FINER, 'Loading plans, weight, measurements and gallery'); try { await Future.wait([ galleryProvider.fetchAndSetGallery(), @@ -109,24 +116,24 @@ class _HomeTabsScreenState extends State with SingleTickerProvid measurementProvider.fetchAndSetAllCategoriesAndEntries(), ]); } catch (e) { - log('Exception loading plans, weight, measurements and gallery'); - log(e.toString()); + log.log(Level.FINER, 'Exception loading plans, weight, measurements and gallery'); + log.log(Level.FINER, e.toString()); } // Current nutritional plan - log('Loading current nutritional plan'); + log.log(Level.FINER, 'Loading current nutritional plan'); try { if (nutritionPlansProvider.currentPlan != null) { final plan = nutritionPlansProvider.currentPlan!; await nutritionPlansProvider.fetchAndSetPlanFull(plan.id!); } } catch (e) { - log('Exception loading current nutritional plan'); - log(e.toString()); + log.log(Level.FINER, 'Exception loading current nutritional plan'); + log.log(Level.FINER, e.toString()); } // Current workout plan - log('Loading current workout plan'); + log.log(Level.FINER, 'Loading current workout plan'); if (workoutPlansProvider.activePlan != null) { final planId = workoutPlansProvider.activePlan!.id!; await workoutPlansProvider.fetchAndSetWorkoutPlanFull(planId); diff --git a/lib/widgets/dashboard/widgets.dart b/lib/widgets/dashboard/widgets.dart index 855d6f22..65ee1c39 100644 --- a/lib/widgets/dashboard/widgets.dart +++ b/lib/widgets/dashboard/widgets.dart @@ -49,6 +49,7 @@ import 'package:wger/widgets/workouts/forms.dart'; class DashboardNutritionWidget extends StatefulWidget { const DashboardNutritionWidget(); + @override _DashboardNutritionWidgetState createState() => _DashboardNutritionWidgetState(); } @@ -149,6 +150,7 @@ class _DashboardNutritionWidgetState extends State { class DashboardWeightWidget extends StatefulWidget { const DashboardWeightWidget(); + @override _DashboardWeightWidgetState createState() => _DashboardWeightWidgetState(); } @@ -235,13 +237,14 @@ class _DashboardWeightWidgetState extends State { class DashboardMeasurementWidget extends StatefulWidget { const DashboardMeasurementWidget(); + @override _DashboardMeasurementWidgetState createState() => _DashboardMeasurementWidgetState(); } class _DashboardMeasurementWidgetState extends State { int _current = 0; - final CarouselController _controller = CarouselController(); + final _controller = CarouselSliderController(); @override Widget build(BuildContext context) { @@ -346,6 +349,7 @@ class _DashboardMeasurementWidgetState extends State class DashboardWorkoutWidget extends StatefulWidget { const DashboardWorkoutWidget(); + @override _DashboardWorkoutWidgetState createState() => _DashboardWorkoutWidgetState(); } diff --git a/pubspec.lock b/pubspec.lock index cf227c53..3a67c079 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -173,10 +173,10 @@ packages: dependency: "direct main" description: name: carousel_slider - sha256: "9c695cc963bf1d04a47bd6021f68befce8970bcd61d24938e1fb0918cf5d9c42" + sha256: "7b006ec356205054af5beaef62e2221160ea36b90fb70a35e4deacd49d0349ae" url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "5.0.0" change: dependency: transitive description: @@ -800,18 +800,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -872,18 +872,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -1032,10 +1032,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -1381,10 +1381,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" timing: dependency: transitive description: @@ -1573,10 +1573,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" watcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1ec936fb..cdda63e0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,7 +56,7 @@ dependencies: flutter_barcode_scanner: ^2.0.0 video_player: ^2.8.6 flutter_staggered_grid_view: ^0.7.0 - carousel_slider: ^4.2.1 + carousel_slider: ^5.0.0 multi_select_flutter: ^4.1.3 flutter_svg: ^2.0.10+1 fl_chart: ^0.68.0