Skip to content

Commit

Permalink
Release version 4.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
matco committed Jul 25, 2023
2 parents 0df62f4 + 595cd54 commit 2c03ce3
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 8 deletions.
33 changes: 33 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "monkeyc",
"request": "launch",
"name": "Run",
"stopAtLaunch": false,
"device": "${command:GetTargetDevice}"
},
{
"type": "monkeyc",
"request": "launch",
"name": "Run on Fenix 7",
"stopAtLaunch": false,
"device": "fenix7"
},
{
"type": "monkeyc",
"request": "launch",
"name": "Run tests",
"runTests": true,
"device": "${command:GetTargetDevice}"
},
{
"type": "monkeyc",
"request": "launch",
"name": "Run tests on Fenix 7",
"runTests": true,
"device": "fenix7"
},
]
}
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
# Changelog

## 4.1.0
- Display your heart rate
- Add support for Epix 2 pro and Fenix 7 pro series
- Improve display of the court
- Restore the possibility to choose the beginner randomly

## 4.0.0
- Add endless mode to play an unlimited number of sets
- Add support for Forerunner 265 series, Forerunner 955 and Forerunner 965
- Rely on the official support of badminton by Garmin (for devices that support Connect IQ >= 4.1.0)
- Rely on the official support of badminton by Garmin (for devices that support Connect IQ 4.1.0)
- Save data even if the match is ended prematurely
- Drop support for devices that don't support Connect IQ 3.1.0

## 3.6.0
- Display your position in singles and doubles, anytime
- Improve design of the court
- Improve the design of the court
- Display the boundaries of the court according to the type of match
- Add support for Forerunner 255 and Venus Sq 2 series
- Improve settings
Expand Down
7 changes: 7 additions & 0 deletions manifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
<iq:product id="descentmk2"/>
<iq:product id="descentmk2s"/>
<iq:product id="epix2"/>
<iq:product id="epix2pro42mm"/>
<iq:product id="epix2pro47mm"/>
<iq:product id="epix2pro51mm"/>
<iq:product id="fenix5"/>
<iq:product id="fenix5plus"/>
<iq:product id="fenix5s"/>
Expand All @@ -18,8 +21,11 @@
<iq:product id="fenix6spro"/>
<iq:product id="fenix6xpro"/>
<iq:product id="fenix7"/>
<iq:product id="fenix7pro"/>
<iq:product id="fenix7s"/>
<iq:product id="fenix7spro"/>
<iq:product id="fenix7x"/>
<iq:product id="fenix7xpro"/>
<iq:product id="fr245"/>
<iq:product id="fr245m"/>
<iq:product id="fr255"/>
Expand Down Expand Up @@ -55,6 +61,7 @@
<iq:permissions>
<iq:uses-permission id="Fit"/>
<iq:uses-permission id="FitContributor"/>
<iq:uses-permission id="UserProfile"/>
</iq:permissions>
<iq:languages>
<iq:language>deu</iq:language>
Expand Down
4 changes: 4 additions & 0 deletions monkey.jungle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ vivoactive4s.resourcePath = resources;icons/30x30

fenix5s.resourcePath = resources;icons/36x36

epix2pro51mm.resourcePath = resources;icons/60x60
epix2pro47mm.resourcePath = resources;icons/60x60
epix2pro42mm.resourcePath = resources;icons/60x60

fr55.resourcePath = resources;icons/35x35

fr265.resourcePath = resources;icons/60x60
Expand Down
1 change: 1 addition & 0 deletions resources-deu/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@

<string id="settings_enable_sound">Töne einschalten</string>
<string id="settings_display_time">Anzeige der Uhrzeit</string>
<string id="settings_display_heart_rate">Heartbeat-Anzeige aktivieren</string>

<string id="fit_activity_name">Badminton</string>
<string id="fit_set_unit_label">Sätze</string>
Expand Down
1 change: 1 addition & 0 deletions resources-fre/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@

<string id="settings_enable_sound">Activer les sons</string>
<string id="settings_display_time">Afficher l'heure</string>
<string id="settings_display_heart_rate">Afficher la fréquence cardiaque</string>

<string id="fit_activity_name">Badminton</string>
<string id="fit_set_unit_label">sets</string>
Expand Down
1 change: 1 addition & 0 deletions resources/properties.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<property id="absolute_maximum_points" type="number">30</property>
<property id="enable_sound" type="boolean">true</property>
<property id="display_time" type="boolean">false</property>
<property id="display_heart_rate" type="boolean">true</property>
<property id="default_match_type" type="number">0</property>
<property id="default_match_number_of_sets" type="number">0</property>
</properties>
4 changes: 4 additions & 0 deletions resources/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@
<setting propertyKey="@Properties.display_time" title="@Strings.settings_display_time">
<settingConfig type="boolean" />
</setting>

<setting propertyKey="@Properties.display_heart_rate" title="@Strings.settings_display_heart_rate">
<settingConfig type="boolean" />
</setting>
</settings>
1 change: 1 addition & 0 deletions resources/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@

<string id="settings_enable_sound">Enable sound</string>
<string id="settings_display_time">Display time</string>
<string id="settings_display_heart_rate">Display heart rate</string>

<string id="fit_activity_name">Badminton</string>
<string id="fit_set_unit_label">sets</string>
Expand Down
6 changes: 3 additions & 3 deletions source/factories/BeginnerPickerFactory.mc
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ using Toybox.WatchUi;

class BeginnerPickerFactory extends WatchUi.PickerFactory {

var beginners as Array<Player> = [YOU, OPPONENT] as Array<Player>;
var beginners_label as Array<Symbol> = [Rez.Strings.beginner_you, Rez.Strings.beginner_opponent] as Array<Symbol>;
var beginners as Array<Player or Symbol> = [YOU, OPPONENT, :random] as Array<Player or Symbol>;
var beginners_label as Array<Symbol> = [Rez.Strings.beginner_you, Rez.Strings.beginner_opponent, Rez.Strings.beginner_random] as Array<Symbol>;

function initialize() {
PickerFactory.initialize();
Expand All @@ -22,7 +22,7 @@ class BeginnerPickerFactory extends WatchUi.PickerFactory {
}

function getValue(index) {
return beginners[index] as Number;
return beginners[index];
}

function getSize() {
Expand Down
9 changes: 9 additions & 0 deletions source/model/BetterMath.mc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Toybox.Lang;
import Toybox.Math;

module BetterMath {

Expand All @@ -17,4 +18,12 @@ module BetterMath {
function weightedMean(number1 as Float, number2 as Float, weight_ratio as Float) as Float {
return (number1 - number2).abs() * weight_ratio + min(number1, number2);
}

function roundAll(numbers as Array<Numeric>) {
var rounded = new [numbers.size()] as Array<Numeric>;
for(var i = 0; i < numbers.size(); i++) {
rounded[i] = Math.round(numbers[i]);
}
return rounded;
}
}
9 changes: 8 additions & 1 deletion source/views/BeginnerPicker.mc
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,14 @@ class BeginnerPickerDelegate extends WatchUi.PickerDelegate {

function onAccept(values) as Boolean {
//update match configuration
InitialView.config.beginner = values[0] as Player;
var value = values[0];
if(value == :random) {
var number = Math.rand();
InitialView.config.beginner = number % 2 == 0 ? YOU : OPPONENT;
}
else {
InitialView.config.beginner = values[0] as Player;
}
InitialView.config.step++;
//remove picker from view stack to go back to initial view
WatchUi.popView(WatchUi.SLIDE_IMMEDIATE);
Expand Down
97 changes: 95 additions & 2 deletions source/views/MatchView.mc
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import Toybox.WatchUi;
import Toybox.System;
using Toybox.Application;
using Toybox.Application.Properties;
using Toybox.Activity;
using Toybox.UserProfile;

class MatchBoundaries {
static const COURT_WIDTH_RATIO = 0.7; //width of the back compared to the front of the court
static const COURT_WIDTH_RATIO = 0.6; //width of the back compared to the front of the court
static const COURT_SIDELINE_SIZE = 0.1;
static const COURT_LONG_SERVICE_SIZE = 0.05;
static const COURT_SHORT_SERVICE_SIZE = 0.1;
Expand Down Expand Up @@ -105,10 +107,12 @@ class MatchBoundaries {
public var corners as Dictionary<Corner, Array>;
public var board as Array<Array>;

public var hr_coordinates as Dictionary<String, Array or Number>;

function initialize(match as Match, device as DeviceSettings) {
//calculate margins
marginHeight = device.screenHeight * (device.screenShape == System.SCREEN_SHAPE_RECTANGLE ? 0.04 : 0.09);
var margin_width = device.screenWidth * (device.screenShape == System.SCREEN_SHAPE_RECTANGLE ? 0.04 : 0.09);
var margin_width = device.screenWidth * 0.09;

//calculate strategic positions
xCenter = device.screenWidth / 2f;
Expand All @@ -127,14 +131,22 @@ class MatchBoundaries {
var court_margin = SET_BALL_RADIUS * 2 + margin_width;
//rectangular watches
if(device.screenShape == System.SCREEN_SHAPE_RECTANGLE) {
//simulate perspective using the arbitrary court width ratio
front_width = (device.screenWidth / 2) - court_margin;
back_width = front_width * COURT_WIDTH_RATIO;
}
//round watches
else {
var radius = device.screenWidth / 2f;
//use the available space to draw the court
front_width = Geometry.chordLength(radius, yFront - yCenter) / 2f - court_margin;
back_width = Geometry.chordLength(radius, yCenter - yBack) / 2f - court_margin;
//however, this may not result in a good perspective, for example when the current time is displayed
//in this case, the top and bottom margins are the same, resulting in a court that has the shape of a rectangle
//perspective must be created it artificially
if((back_width / front_width) > COURT_WIDTH_RATIO) {
back_width = front_width * COURT_WIDTH_RATIO;
}
}

//perspective is defined by its two side vanishing lines
Expand Down Expand Up @@ -164,6 +176,35 @@ class MatchBoundaries {
var transformed_coordinates = perspective.transform([-0.5, y] as Array<Float>);
board[i] = [transformed_coordinates[0] - SET_BALL_RADIUS * 2, transformed_coordinates[1]];
}

//calculate hear rate position
var hr_center = BetterMath.roundAll(perspective.transform([0.75, 0.6])) as Array<Numeric>;
//size the icon according to the size of the tiny font
var size = Math.round(Graphics.getFontHeight(Graphics.FONT_TINY) * 0.2);
var icon_center = [hr_center[0], hr_center[1] - size * 2];
//the heart icon is composed of two a-little-more-than-half circles, a triangle, and a rectangle to cover the space between the two circles
var angle = Math.PI / 4;
var circle_y_extension = Math.round(size * Math.sin(angle));
var circle_x_extension = Math.round(size * (1 - Math.cos(angle)));
hr_coordinates = {
"center" => hr_center,
"size" => size,
"icon_center" => icon_center,
"circle_y_extension" => circle_y_extension,
"heart_circle_left" => [icon_center[0] - size, icon_center[1]],
"heart_circle_right" => [icon_center[0] + size, icon_center[1]],
"heart_triangle" => [
[icon_center[0] - 2 * size + circle_x_extension, icon_center[1] + circle_y_extension],
[icon_center[0] + 2 * size - circle_x_extension, icon_center[1] + circle_y_extension],
[icon_center[0], icon_center[1] + 2 * size]
],
"heart_rectangle" => [
[icon_center[0] - size / 2, icon_center[1]],
[icon_center[0] + size / 2, icon_center[1]],
[icon_center[0] + size / 2, icon_center[1] + size],
[icon_center[0] - size / 2, icon_center[1] + size]
]
};
}
}

Expand Down Expand Up @@ -301,6 +342,50 @@ class MatchView extends WatchUi.View {
}
}

function drawHeartRate(dc as Dc) as Void {
var rate = Activity.getActivityInfo().currentHeartRate;

if(rate != null) {
var profile = UserProfile.getCurrentSport();
var zones = UserProfile.getHeartRateZones(profile);

//choose color for the heart icon depending on the current user zone
var color = Graphics.COLOR_GREEN;
if(zones != null && zones.size() > 4) {
if(rate > zones[4]) {
color= Graphics.COLOR_RED;
}
else if(rate > zones[3]) {
color = Graphics.COLOR_YELLOW;
}
}

var hr_coordinates = boundaries.hr_coordinates;
var size = hr_coordinates["size"] as Numeric;
var icon_center = hr_coordinates["icon_center"] as Array<Numeric>;
var circle_y_extension = hr_coordinates["circle_y_extension"];
dc.setColor(color, Graphics.COLOR_TRANSPARENT);
//draw half circles by clipping the bottom part of full circles
//add a margin on the top and bottom because rounded coordinates may result in bad clipping
var margin = 1;
dc.setClip(
icon_center[0] as Numeric - size * 2 - margin,
icon_center[1] as Numeric - size - margin,
size * 4 + 2 * margin,
size + circle_y_extension + 2 * margin);
var heart_circle_left = hr_coordinates["heart_circle_left"] as Array<Numeric>;
dc.fillCircle(heart_circle_left[0] as Numeric, heart_circle_left[1] as Numeric, size);
var heart_circle_right = hr_coordinates["heart_circle_right"] as Array<Numeric>;
dc.fillCircle(heart_circle_right[0] as Numeric, heart_circle_right[1] as Numeric, size);
dc.clearClip();
dc.fillPolygon(hr_coordinates["heart_triangle"] as Array<Array>);
dc.fillPolygon(hr_coordinates["heart_rectangle"] as Array<Array>);
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT);
var center = hr_coordinates["center"] as Array<Numeric>;
dc.drawText(center[0], center[1], Graphics.FONT_TINY, rate.toString(), Graphics.TEXT_JUSTIFY_CENTER);
}
}

function drawTimer(dc as Dc, match as Match) as Void {
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT);
dc.drawText(
Expand Down Expand Up @@ -346,6 +431,14 @@ class MatchView extends WatchUi.View {
if(Properties.getValue("display_time")) {
drawTime(dc);
}

if(Properties.getValue("display_heart_rate")) {
//disable anti aliasing to draw a pixel perfect icon
if(dc has :setAntiAlias) {
dc.setAntiAlias(false);
}
drawHeartRate(dc);
}
}
}

Expand Down

0 comments on commit 2c03ce3

Please sign in to comment.