Skip to content

Commit

Permalink
docs(example): add Smolov Jr calculator for Hevy app (#232)
Browse files Browse the repository at this point in the history
  • Loading branch information
GregoryConrad authored Oct 18, 2024
1 parent b015e22 commit f41135c
Show file tree
Hide file tree
Showing 23 changed files with 3,016 additions and 0 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/firebase-hosting-hevy-smolov-jr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Deploy Hevy Smolov Jr to Firebase Hosting

on:
push:
branches:
- main

jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- uses: bluefireteam/melos-action@v2
- name: Run tests
run: melos run test
- name: Build for web
run: flutter build web
working-directory: examples/hevy_smolov_jr
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
entryPoint: examples/hevy_smolov_jr
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_HEVY_SMOLOV_JR }}'
channelId: live
projectId: hevy-smolov-jr
5 changes: 5 additions & 0 deletions examples/hevy_smolov_jr/.firebaserc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"projects": {
"default": "hevy-smolov-jr"
}
}
43 changes: 43 additions & 0 deletions examples/hevy_smolov_jr/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
30 changes: 30 additions & 0 deletions examples/hevy_smolov_jr/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: "2663184aa79047d0a33a14a3b607954f8fdd8730"
channel: "stable"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: macos
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730

# User provided section

# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
7 changes: 7 additions & 0 deletions examples/hevy_smolov_jr/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
targets:
$default:
builders:
json_serializable:
options:
# explicit_to_json: true
field_rename: snake
16 changes: 16 additions & 0 deletions examples/hevy_smolov_jr/firebase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"hosting": {
"public": "build/web",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
181 changes: 181 additions & 0 deletions examples/hevy_smolov_jr/lib/api/raw_hevy_api.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import 'dart:convert';

import 'package:hevy_smolov_jr/shared_prefs.dart';
import 'package:http/http.dart' as http;
import 'package:rearch/experimental.dart';
import 'package:rearch/rearch.dart';

/// [Capsule] representing the user's Hevy API key.
final Capsule<(String, void Function(String))> apiKeyCapsule = capsule((use) {
const sharedPrefsKey = 'api-key';
final sharedPrefs = use(sharedPrefsCapsule);
final (apiKey, setApiKey) =
use.state(sharedPrefs.getString(sharedPrefsKey) ?? '');
return (
apiKey,
(newApiKey) {
sharedPrefs.setString(sharedPrefsKey, newApiKey);
setApiKey(newApiKey);
},
);
});

final Capsule<String> _apiDomainCapsule = capsule((use) => 'api.hevyapp.com');

/// Represents an [Exception] from the Hevy API.
sealed class HevyApiException implements Exception {}

/// Represents an [Exception] while completing a Hevy API request.
final class HevyApiNetworkException implements HevyApiException {
/// Represents an [Exception] while completing a Hevy API request.
const HevyApiNetworkException(this.underlyingException);

/// The underlying [Exception] or [Error] thrown during the request.
final Object underlyingException;

@override
String toString() {
return 'HevyApiNetworkException(underlyingException: $underlyingException)';
}
}

/// Represents an [Exception] regarding the [http.Response].
final class HevyApiResponseException implements HevyApiException {
/// Represents an [Exception] regarding the [http.Response].
const HevyApiResponseException(this.statusCode, this.errorMessage);

/// The HTTP status code returned by the Hevy API.
final int statusCode;

/// The error message returned by the Hevy API.
final String errorMessage;

@override
String toString() {
return 'HevyApiResponseException(statusCode: $statusCode, '
'errorMessage: "$errorMessage")';
}
}

/// Represents an [Exception] parsing the [http.Response] from the Hevy API.
final class HevyApiResponseParseException implements HevyApiException {
/// Represents an [Exception] parsing the [http.Response] from the Hevy API.
HevyApiResponseParseException({
required this.statusCode,
required this.responseBody,
required this.parseException,
});

/// The HTTP status code returned by the Hevy API.
final int statusCode;

/// The response body returned by the Hevy API that could not be parsed.
final String responseBody;

/// The [Exception] or [Error] thrown while parsing [responseBody].
final Object parseException;

@override
String toString() {
return 'HevyApiResponseParseException(statusCode: $statusCode, '
'parseException: $parseException, responseBody: $responseBody)';
}
}

/// Wraps a raw Hevy API HTTP call so that it:
/// - returns the response body as the decoded `Map<String, dynamic>`
/// - throws the appropriate type of [Exception] as needed
final Capsule<Future<Map<String, dynamic>> Function(Future<http.Response>)>
_parseApiRequestAction = capsule((use) {
return (hevyApiRequest) async {
late http.Response response;
try {
response = await hevyApiRequest;
} catch (underlyingException, stackTrace) {
Error.throwWithStackTrace(
HevyApiNetworkException(underlyingException),
stackTrace,
);
}

late Map<String, dynamic> body;
try {
body = json.decode(response.body) as Map<String, dynamic>;

if (response.statusCode < 200 || response.statusCode > 299) {
throw HevyApiResponseException(
response.statusCode,
body['error'] as String,
);
}
} catch (e, stackTrace) {
Error.throwWithStackTrace(
HevyApiResponseParseException(
statusCode: response.statusCode,
responseBody: response.body,
parseException: e,
),
stackTrace,
);
}

return body;
};
});

/// Represents an HTTP GET request that returns JSON.
typedef GetRequest = Future<Map<String, dynamic>> Function({
required String path,
Map<String, String>? queryParams,
});

/// Represents an HTTP GET request to the Hevy API.
final Capsule<GetRequest> apiGetAction = capsule((use) {
final parseRequest = use(_parseApiRequestAction);
final apiDomain = use(_apiDomainCapsule);
final headers = {
'accept': 'application/json',
'api-key': use(apiKeyCapsule).$1,
};

return ({required String path, Map<String, dynamic>? queryParams}) {
return parseRequest(
http.get(
Uri.https(apiDomain, path, queryParams),
headers: headers,
),
);
};
});

/// Represents an HTTP POST request that returns JSON.
typedef PostRequest = Future<Map<String, dynamic>> Function({
required String path,
Object? jsonBody,
Map<String, String>? queryParams,
});

/// Represents an HTTP POST request to the Hevy API.
final Capsule<PostRequest> apiPostAction = capsule((use) {
final parseRequest = use(_parseApiRequestAction);
final apiDomain = use(_apiDomainCapsule);
final headers = {
'accept': 'application/json',
'api-key': use(apiKeyCapsule).$1,
'Content-Type': 'application/json',
};

return ({
required String path,
Object? jsonBody,
Map<String, dynamic>? queryParams,
}) {
return parseRequest(
http.post(
Uri.https(apiDomain, path, queryParams),
headers: headers,
body: json.encode(jsonBody),
),
);
};
});
Loading

0 comments on commit f41135c

Please sign in to comment.