sensors = Preference.getString(context, R.string.preference_hardware_heart_sensor);
47 |
48 | String name = sensors.get();
49 | try {
50 | Constructor> constructor = Class.forName(name).getConstructor(Context.class, Measurement.class, Callback.class);
51 | return (Heart) constructor.newInstance(context, measurement, callback);
52 | } catch (Exception ex) {
53 | Log.e(Coxswain.TAG, "cannot create sensor " + name);
54 | return new Heart(context, measurement, callback);
55 | }
56 | }
57 |
58 | public interface Callback {
59 | void onMeasurement(Measurement measurement);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/rower/wired/usb/Permission.java:
--------------------------------------------------------------------------------
1 | package svenmeier.coxswain.rower.wired.usb;
2 |
3 | import android.app.PendingIntent;
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.IntentFilter;
8 | import android.hardware.usb.UsbDevice;
9 | import android.hardware.usb.UsbManager;
10 |
11 | /**
12 | * Request permission to use a {@link UsbDevice}.
13 | *
14 | * Not used currently, since we restart an activity for intent USB_DEVICE_ATTACHED and
15 | * Android will ask the user for permissions automatically.
16 | */
17 | public class Permission extends BroadcastReceiver {
18 |
19 | private static final String ACTION_USB_PERMISSION = "svenmeier.coxswain.USB_PERMISSION";
20 |
21 | private Context context;
22 |
23 | public Permission(Context context) {
24 | this.context = context;
25 |
26 | IntentFilter filter = new IntentFilter();
27 | filter.addAction(ACTION_USB_PERMISSION);
28 | filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
29 | context.registerReceiver(this, filter);
30 | }
31 |
32 | public void destroy() {
33 | context.unregisterReceiver(this);
34 | context = null;
35 | }
36 |
37 | public void request(UsbDevice device) {
38 | UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
39 |
40 | manager.requestPermission(device, PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0));
41 | }
42 |
43 | public void onReceive(Context context, Intent intent) {
44 | String action = intent.getAction();
45 |
46 | if (ACTION_USB_PERMISSION.equals(action)) {
47 | onRequested(intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
48 | }
49 | }
50 |
51 | protected void onRequested(boolean granted) {
52 | }
53 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/segment.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | -
5 |
6 |
8 |
9 |
10 | -
11 |
12 |
16 |
18 |
19 |
20 | -
21 |
22 |
26 |
28 |
29 |
30 | -
31 |
32 |
36 |
38 |
39 |
40 | -
41 |
42 |
46 |
48 |
49 |
50 | -
51 |
52 |
56 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/view/LevelView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Sven Meier
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package svenmeier.coxswain.view;
17 |
18 | import android.content.Context;
19 | import android.graphics.Canvas;
20 | import android.graphics.drawable.Drawable;
21 | import android.util.AttributeSet;
22 | import android.view.View;
23 |
24 | /**
25 | */
26 | public class LevelView extends View {
27 |
28 | private int level = 0;
29 |
30 | public LevelView(Context context, AttributeSet attrs) {
31 | super(context, attrs);
32 | }
33 |
34 | public LevelView(Context context, AttributeSet attrs, int defStyle) {
35 | super(context, attrs, defStyle);
36 | }
37 |
38 | public void setLevel(int level) {
39 | if (this.level != level) {
40 | this.level = level;
41 |
42 | invalidate();
43 | }
44 | }
45 |
46 | @Override
47 | protected int getSuggestedMinimumHeight() {
48 | return super.getSuggestedMinimumHeight();
49 | }
50 |
51 | @Override
52 | protected void onDraw(Canvas canvas) {
53 |
54 | Drawable background = getBackground();
55 | if (background != null) {
56 | background.setLevel(level);
57 | }
58 |
59 | super.onDraw(canvas);
60 | }
61 | }
--------------------------------------------------------------------------------
/app/src/test/java/svenmeier/coxswain/rower/wired/RatioCalculatorTest.java:
--------------------------------------------------------------------------------
1 | package svenmeier.coxswain.rower.wired;
2 |
3 | import org.junit.Test;
4 |
5 | import svenmeier.coxswain.gym.Measurement;
6 |
7 | import static org.junit.Assert.assertEquals;
8 |
9 | /**
10 | * Test for {@link RatioCalculator}.
11 | */
12 | public class RatioCalculatorTest {
13 |
14 | Measurement measurement = new Measurement();
15 |
16 | @Test
17 | public void test() {
18 | RatioCalculator calculator = new RatioCalculator();
19 |
20 | long now = 100000;
21 | calculator.strokeEnd(measurement, now);
22 | calculator.strokeEnd(measurement, now + 200);
23 |
24 | now += 1000;
25 | calculator.strokeStart(measurement, now);
26 | calculator.strokeStart(measurement, now + 200);
27 |
28 | now += 1000;
29 | calculator.strokeEnd(measurement, now);
30 | calculator.strokeEnd(measurement, now + 200);
31 |
32 | assertEquals(8, measurement.getStrokeRatio());
33 |
34 | now += 2000;
35 | calculator.strokeStart(measurement, now);
36 | calculator.strokeStart(measurement, now + 200);
37 |
38 | now += 1000;
39 | calculator.strokeEnd(measurement, now);
40 | calculator.strokeEnd(measurement, now + 200);
41 |
42 | assertEquals(16, measurement.getStrokeRatio());
43 |
44 | now += 1500;
45 | calculator.strokeStart(measurement, now);
46 | calculator.strokeStart(measurement, now + 200);
47 |
48 | now += 1000;
49 | calculator.strokeEnd(measurement, now);
50 | calculator.strokeEnd(measurement, now + 500);
51 |
52 | assertEquals(12, measurement.getStrokeRatio());
53 |
54 | now += 500;
55 | calculator.strokeStart(measurement, now);
56 | calculator.strokeStart(measurement, now + 200);
57 |
58 | now += 1000;
59 | calculator.strokeEnd(measurement, now);
60 | calculator.strokeEnd(measurement, now + 200);
61 |
62 | assertEquals(4, measurement.getStrokeRatio());
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/view/ExportProgramDialogFragment.java:
--------------------------------------------------------------------------------
1 | package svenmeier.coxswain.view;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.os.Bundle;
7 |
8 | import androidx.fragment.app.DialogFragment;
9 |
10 | import propoid.db.Reference;
11 | import svenmeier.coxswain.Gym;
12 | import svenmeier.coxswain.R;
13 | import svenmeier.coxswain.gym.Program;
14 | import svenmeier.coxswain.io.Export;
15 | import svenmeier.coxswain.io.ProgramExport;
16 |
17 | public class ExportProgramDialogFragment extends DialogFragment {
18 |
19 | private Export export;
20 |
21 | @Override
22 | public Dialog onCreateDialog(Bundle savedInstanceState) {
23 | final Program program = Gym.instance(getActivity()).get(Reference.from(getArguments()));
24 |
25 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
26 |
27 | String[] exports = new String[]{getString(R.string.program_export), getString(R.string.program_export_share)};
28 |
29 | builder.setTitle(R.string.action_export)
30 | .setItems(exports, new DialogInterface.OnClickListener() {
31 | public void onClick(DialogInterface dialog, int which) {
32 | switch (which) {
33 | case 0:
34 | export = new ProgramExport(getActivity(), false);
35 | break;
36 | case 1:
37 | export = new ProgramExport(getActivity(), true);
38 | break;
39 | default:
40 | throw new IndexOutOfBoundsException();
41 | }
42 |
43 | export.start(program, false);
44 | }
45 | });
46 |
47 |
48 | return builder.create();
49 | }
50 |
51 | public static ExportProgramDialogFragment create(Program program) {
52 | ExportProgramDialogFragment fragment = new ExportProgramDialogFragment();
53 |
54 | fragment.setArguments(new Reference<>(program).to(new Bundle()));
55 |
56 | return fragment;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/GymLocator.java:
--------------------------------------------------------------------------------
1 | package svenmeier.coxswain;
2 |
3 | import android.content.Context;
4 | import android.content.res.AssetManager;
5 | import android.content.res.Resources;
6 | import android.database.sqlite.SQLiteDatabase;
7 | import android.os.Environment;
8 | import android.widget.Toast;
9 |
10 | import java.io.File;
11 | import java.io.IOException;
12 |
13 | import propoid.db.Locator;
14 | import propoid.db.locator.FileLocator;
15 | import propoid.util.content.Preference;
16 |
17 | /**
18 | */
19 | class GymLocator implements Locator {
20 |
21 | private static final String NAME = "gym";
22 |
23 | private final Context context;
24 |
25 | private SQLiteDatabase database;
26 |
27 | /**
28 | * Locate the database from the given context.
29 | *
30 | * @param context context
31 | */
32 | public GymLocator(Context context) {
33 | this.context = context;
34 | }
35 |
36 | public SQLiteDatabase open() {
37 | if (database != null) {
38 | throw new IllegalStateException("already open");
39 | }
40 |
41 | if (Preference.getBoolean(context, R.string.preference_data_external).get()) {
42 | try {
43 | database = open(external());
44 | } catch (Exception ex) {
45 | Toast.makeText(context, R.string.gym_repository_extern_failed, Toast.LENGTH_LONG).show();
46 | }
47 | }
48 |
49 | if (database == null) {
50 | database = open(internal());
51 | }
52 |
53 | return database;
54 | }
55 |
56 | private File internal() {
57 | return context.getDatabasePath(NAME);
58 | }
59 |
60 | private File external() {
61 | return new File(Coxswain.getExternalFilesDir(context), NAME);
62 | }
63 |
64 | private SQLiteDatabase open(File file) {
65 | if (!file.exists()) {
66 | file.getParentFile().mkdirs();
67 | }
68 |
69 | return SQLiteDatabase.openOrCreateDatabase(file, null);
70 | }
71 |
72 | @Override
73 | public void close() {
74 | database.close();
75 | database = null;
76 | }
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/util/PermissionActivity.java:
--------------------------------------------------------------------------------
1 | package svenmeier.coxswain.util;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.IntentFilter;
7 | import android.content.pm.PackageManager;
8 | import android.os.Bundle;
9 |
10 | import androidx.core.app.ActivityCompat;
11 |
12 | /**
13 | */
14 | public class PermissionActivity extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback {
15 |
16 | static final String ACTION = "svenmeier.coxswain.util.permission.GRANTED";
17 |
18 | static final String PERMISSIONS = "permissions";
19 |
20 | static final String GRANTED = "granted";
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 |
26 | String[] permissions = getIntent().getStringArrayExtra(PERMISSIONS);
27 |
28 | ActivityCompat.requestPermissions(this, permissions, 1);
29 | }
30 |
31 | @Override
32 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
33 | boolean granted = true;
34 | for (int grantResult : grantResults) {
35 | granted &= (grantResult == PackageManager.PERMISSION_GRANTED);
36 | }
37 |
38 | Intent intent = new Intent();
39 | intent.setAction(ACTION);
40 | intent.putExtra(PERMISSIONS, permissions);
41 | intent.putExtra(GRANTED, granted);
42 | sendBroadcast(intent);
43 |
44 | finish();
45 | }
46 |
47 | public static IntentFilter start(Context context, String[] permissions) {
48 | Intent intent = new Intent(context, PermissionActivity.class);
49 |
50 | // required for activity started from non-activity
51 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
52 |
53 | intent.putExtra(PERMISSIONS, permissions);
54 |
55 | context.startActivity(intent);
56 |
57 | IntentFilter filter = new IntentFilter();
58 | filter.addAction(PermissionActivity.ACTION);
59 | return filter;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/test/java/svenmeier/coxswain/rower/wired/Protocol4Test.java:
--------------------------------------------------------------------------------
1 | package svenmeier.coxswain.rower.wired;
2 |
3 | import org.junit.Test;
4 |
5 | import svenmeier.coxswain.gym.Measurement;
6 | import svenmeier.coxswain.rower.wired.usb.ITransfer;
7 | import svenmeier.coxswain.rower.wired.usb.TestTransfer;
8 |
9 | import static org.junit.Assert.assertEquals;
10 |
11 | /**
12 | */
13 | public class Protocol4Test {
14 |
15 | Measurement measurement = new Measurement();
16 |
17 | @Test
18 | public void test() throws Exception {
19 | TestTransfer transfer = new TestTransfer();
20 | TestTrace trace = new TestTrace();
21 |
22 | Protocol4 protocol = new Protocol4(transfer, trace);
23 | assertEquals(115200, transfer.baudrate);
24 | assertEquals(0, transfer.dataBits);
25 | assertEquals(TestTransfer.PARITY_NONE, transfer.parity);
26 | assertEquals(ITransfer.STOP_BIT_1_0, transfer.stopBits);
27 | assertEquals(false, transfer.tx);
28 | protocol.setThrottle(0);
29 |
30 | assertEquals(Protocol4.VERSION_UNKOWN, protocol.getVersion());
31 |
32 | protocol.transfer(measurement);
33 | transfer.assertOutput("USB\r\n");
34 | assertEquals(Protocol4.VERSION_UNKOWN, protocol.getVersion());
35 |
36 | transfer.setupInput("_WR_\r\n");
37 | protocol.transfer(measurement);
38 | transfer.assertOutput("IV?\r\n");
39 | assertEquals(Protocol4.VERSION_UNKOWN, protocol.getVersion());
40 |
41 | transfer.setupInput("IV42020\r\n");
42 | protocol.transfer(measurement);
43 | assertEquals("42020", protocol.getVersion());
44 |
45 | transfer.setupInput("IDT1E1151515\r\n");
46 | protocol.transfer(measurement);
47 | assertEquals(((15 * 60) + 15)*60 +15, measurement.getDuration());
48 |
49 | transfer.setupInput("IDT08A0003E8\r\n");
50 | protocol.transfer(measurement);
51 | assertEquals(1, measurement.getEnergy());
52 |
53 | // incomplete
54 | transfer.setupInput("IDT08A0003");
55 | protocol.transfer(measurement);
56 |
57 | assertEquals("#protocol 4>USB<_WR_#handshake complete>IV?IRD140IRD057IRD14A>IRS1A9", trace.toString());
58 | }
59 | }
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/rower/wired/RatioCalculator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Sven Meier
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package svenmeier.coxswain.rower.wired;
17 |
18 | import svenmeier.coxswain.gym.Measurement;
19 |
20 | public class RatioCalculator {
21 |
22 | // actually would be 10 for one decimal place, but S4
23 | // reports pulls too late and recover too early -
24 | // thus we reduce the recovering phase
25 | public static final int MULTIPLIER = 8;
26 |
27 | public static final int MAX = 99;
28 |
29 | public boolean pulling = true;
30 |
31 | private long start = 0;
32 |
33 | private long pullDuration;
34 |
35 | private long recoverDuration;
36 |
37 | public void clear(long now) {
38 | pulling = true;
39 | start = now;
40 |
41 | pullDuration = 0;
42 | recoverDuration = 0;
43 | }
44 |
45 | public void strokeStart(Measurement measurement, long now) {
46 | if (pulling == false) {
47 | pulling = true;
48 |
49 | recoverDuration = (now - start);
50 | start = now;
51 | }
52 | }
53 |
54 | public void strokeEnd(Measurement measurement, long now) {
55 | if (pulling) {
56 | pulling = false;
57 |
58 | pullDuration = (now - start);
59 | start = now;
60 |
61 | int ratio = Math.min((int) (MULTIPLIER * recoverDuration / pullDuration), MAX);
62 | measurement.setStrokeRatio(ratio);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/rower/SpeedAdjuster.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Sven Meier
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package svenmeier.coxswain.rower;
17 |
18 | import svenmeier.coxswain.gym.Measurement;
19 |
20 | /**
21 | * Ignore speed and calculate from power instead.
22 | */
23 | public class SpeedAdjuster extends Adjuster {
24 |
25 | private int cms;
26 |
27 | public SpeedAdjuster(Measurement measurement) {
28 | super(measurement);
29 | }
30 |
31 | @Override
32 | public void reset() {
33 | super.reset();
34 |
35 | cms = 0;
36 | }
37 |
38 | @Override
39 | public void setSpeed(int untrustedSpeed) {
40 | // magic formula see:
41 | // http://www.concept2.com/indoor-rowers/training/calculators/watts-calculator
42 | float mps = 0.709492f * (float) Math.pow(getPower(), 1d / 3d);
43 |
44 | super.setSpeed(Math.round(mps * 100));
45 | }
46 |
47 | @Override
48 | public void setDistance(int untrustedDistance) {
49 | if (untrustedDistance == 0) {
50 | super.setDistance(0);
51 | }
52 | }
53 |
54 | @Override
55 | public void setDuration(int newDuration) {
56 | int oldDuration = super.getDuration();
57 |
58 | super.setDuration(newDuration);
59 |
60 | if (newDuration > oldDuration) {
61 | int delta = newDuration - oldDuration;
62 |
63 | cms += delta * getSpeed();
64 |
65 | super.setDistance(cms / 100);
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/rower/wired/usb/UsbConnector.java:
--------------------------------------------------------------------------------
1 | package svenmeier.coxswain.rower.wired.usb;
2 |
3 | import android.app.PendingIntent;
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.IntentFilter;
8 | import android.hardware.usb.UsbDevice;
9 | import android.hardware.usb.UsbManager;
10 |
11 | import java.util.Collection;
12 |
13 | /**
14 | * Created by sven on 24.10.15.
15 | */
16 | public class UsbConnector extends BroadcastReceiver {
17 |
18 | private static final String DEVICE_CONNECT = "svenmeier.coxswain.usb.lister";
19 |
20 | private final Context context;
21 |
22 | private final UsbManager manager;
23 |
24 | public UsbConnector(Context context) {
25 | this.context = context;
26 |
27 | manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
28 |
29 | context.registerReceiver(this, new IntentFilter(DEVICE_CONNECT));
30 | }
31 |
32 | public void destroy() {
33 | context.unregisterReceiver(this);
34 | }
35 |
36 | public Collection list() {
37 | return manager.getDeviceList().values();
38 | }
39 |
40 | /**
41 | * Connect to the given device.
42 | *
43 | * @see #onConnected(UsbDevice)
44 | */
45 | public void connect(UsbDevice device) {
46 |
47 | PendingIntent intent = PendingIntent.getBroadcast(context, 0, new Intent(DEVICE_CONNECT), 0);
48 |
49 | manager.requestPermission(device, intent);
50 |
51 | }
52 |
53 | @Override
54 | public void onReceive(Context context, Intent intent) {
55 | String action = intent.getAction();
56 |
57 | if (DEVICE_CONNECT.equals(action)) {
58 | synchronized (this) {
59 | UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
60 |
61 | if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
62 | onConnected(device);
63 | }
64 | }
65 | }
66 |
67 | }
68 |
69 | /**
70 | * Callback when device was connected.
71 | *
72 | * @see #connect(UsbDevice)
73 | */
74 | protected void onConnected(UsbDevice device) {
75 |
76 | }
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/view/DeleteDialogFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Sven Meier
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package svenmeier.coxswain.view;
17 |
18 | import android.app.AlertDialog;
19 | import android.app.Dialog;
20 | import android.content.DialogInterface;
21 | import android.os.Bundle;
22 |
23 | import androidx.fragment.app.DialogFragment;
24 |
25 | import propoid.core.Propoid;
26 | import propoid.db.Reference;
27 | import svenmeier.coxswain.Gym;
28 | import svenmeier.coxswain.R;
29 |
30 | /**
31 | */
32 | public class DeleteDialogFragment extends DialogFragment implements DialogInterface.OnClickListener {
33 |
34 | @Override
35 | public Dialog onCreateDialog(Bundle savedInstanceState) {
36 | return new AlertDialog.Builder(getActivity())
37 | .setTitle(R.string.action_delete_confirm_title)
38 | .setMessage(R.string.action_delete_confirm_message).setPositiveButton(R.string.action_delete_confirm, this)
39 | .create();
40 | }
41 |
42 | @Override
43 | public void onClick(DialogInterface dialogInterface, int i) {
44 | Propoid propoid = Gym.instance(getActivity()).get(Reference.from(getArguments()));
45 | Gym.instance(getActivity()).delete(propoid);
46 |
47 | dismiss();
48 | }
49 |
50 | public static DeleteDialogFragment create(Propoid propoid) {
51 | DeleteDialogFragment fragment = new DeleteDialogFragment();
52 |
53 | fragment.setArguments(new Reference<>(propoid).to(new Bundle()));
54 |
55 | return fragment;
56 | }
57 | }
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/rower/Energy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Sven Meier
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package svenmeier.coxswain.rower;
17 |
18 | import android.content.Context;
19 |
20 | import propoid.util.content.Preference;
21 | import svenmeier.coxswain.R;
22 |
23 | /**
24 | */
25 | public class Energy {
26 |
27 | private static final float KCAL_TO_WH = 1.163f;
28 |
29 | private static final float KCAL_TO_KJ = 4.185f;
30 |
31 | private Context context;
32 |
33 | private int kcal;
34 |
35 | public String formatted() {
36 | Preference unit = Preference.getString(context, R.string.preference_energy_unit);
37 |
38 | switch (unit.get()) {
39 | case "kJ":
40 | return String.format(context.getString(R.string.energy_kilojoules), kj());
41 | case "Wh":
42 | return String.format(context.getString(R.string.energy_watthours), wh());
43 | default:
44 | return String.format(context.getString(R.string.energy_kilocalories), kcal);
45 | }
46 | }
47 |
48 | public int wh() {
49 | return Math.round(kcal * KCAL_TO_WH);
50 | }
51 |
52 | public int kj() {
53 | return Math.round(KCAL_TO_KJ * kcal);
54 | }
55 |
56 | public int kcal() {
57 | return kcal;
58 | }
59 |
60 | public static Energy kcal(Context context, int kcal) {
61 | Energy energy = new Energy();
62 |
63 | energy.context = context;
64 | energy.kcal = kcal;
65 |
66 | return energy;
67 | }
68 | }
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/view/preference/EditTextPreference.java:
--------------------------------------------------------------------------------
1 | package svenmeier.coxswain.view.preference;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 |
6 | import androidx.preference.Preference;
7 |
8 | /**
9 | * An specialization that substitutes the current text into the summary (as ListPreference does it
10 | * too).
11 | */
12 | public class EditTextPreference extends androidx.preference.EditTextPreference {
13 |
14 | public EditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) {
15 | super(context, attrs, defStyleAttr);
16 |
17 | init(context, attrs);
18 | }
19 |
20 | public EditTextPreference(Context context, AttributeSet attrs) {
21 | super(context, attrs);
22 |
23 | init(context, attrs);
24 | }
25 |
26 | public EditTextPreference(Context context) {
27 | super(context);
28 | }
29 |
30 | private void init(Context context, AttributeSet attrs) {
31 | try {
32 | for (int i=0;i
10 |
11 |
16 |
17 |
24 |
25 |
30 |
31 |
42 |
49 |
50 |
51 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/util/PermissionBlock.java:
--------------------------------------------------------------------------------
1 | package svenmeier.coxswain.util;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.IntentFilter;
7 | import android.content.pm.PackageManager;
8 |
9 | import androidx.core.content.ContextCompat;
10 |
11 | import java.util.Arrays;
12 |
13 | public class PermissionBlock {
14 |
15 | private final Context context;
16 |
17 | private String[] permissions;
18 |
19 | private BroadcastReceiverImpl receiver;
20 |
21 | public PermissionBlock(Context context) {
22 | this.context = context;
23 | }
24 |
25 | public void acquirePermissions(String... permissions) {
26 | unregister();
27 |
28 | this.permissions = permissions;
29 |
30 | for (String permission : permissions) {
31 | if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
32 | requestPermissions();
33 | return;
34 | }
35 | }
36 |
37 | onPermissionsApproved();
38 | }
39 |
40 | protected final void abortPermissions() {
41 | unregister();
42 | }
43 |
44 | protected void onPermissionsApproved() {
45 | }
46 |
47 | protected void onRejected() {
48 | }
49 |
50 | private void requestPermissions() {
51 |
52 | IntentFilter filter = PermissionActivity.start(context, permissions);
53 |
54 | receiver = new BroadcastReceiverImpl();
55 | context.registerReceiver(receiver, filter);
56 | }
57 |
58 | private void unregister() {
59 | if (receiver != null) {
60 | context.unregisterReceiver(receiver);
61 | receiver = null;
62 | }
63 | }
64 |
65 | private class BroadcastReceiverImpl extends BroadcastReceiver {
66 | @Override
67 | public final void onReceive(Context context, Intent intent) {
68 |
69 | String[] permissions = intent.getStringArrayExtra(PermissionActivity.PERMISSIONS);
70 | if (Arrays.equals(PermissionBlock.this.permissions, permissions) == false) {
71 | return;
72 | }
73 |
74 | unregister();
75 |
76 | boolean granted = intent.getBooleanExtra(PermissionActivity.GRANTED, false);
77 | if (granted) {
78 | onPermissionsApproved();
79 | } else {
80 | onRejected();
81 | }
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/io/Program2Json.java:
--------------------------------------------------------------------------------
1 | package svenmeier.coxswain.io;
2 |
3 | import android.location.Location;
4 | import android.util.JsonWriter;
5 |
6 | import java.io.IOException;
7 | import java.io.Writer;
8 | import java.util.List;
9 |
10 | import propoid.core.Property;
11 | import svenmeier.coxswain.gym.Difficulty;
12 | import svenmeier.coxswain.gym.Program;
13 | import svenmeier.coxswain.gym.Segment;
14 | import svenmeier.coxswain.gym.Snapshot;
15 | import svenmeier.coxswain.gym.Workout;
16 |
17 | /**
18 | * Converter for {@link svenmeier.coxswain.gym.Program}s.
19 | */
20 | public class Program2Json {
21 |
22 | private JsonWriter writer;
23 |
24 | public Program2Json(Writer writer) throws IOException {
25 | this.writer = new JsonWriter(writer);
26 | this.writer.setIndent(" ");
27 | }
28 |
29 | public void document(Program program) throws IOException {
30 | program(program);
31 |
32 | writer.flush();
33 | }
34 |
35 | private void program(Program program) throws IOException {
36 | writer.beginObject();
37 |
38 | writer.name("name").value(program.name.get());
39 |
40 | writer.name("segments");
41 | writer.beginArray();
42 | for (Segment segment : program.getSegments()) {
43 | segment(segment);
44 | }
45 | writer.endArray();
46 |
47 | writer.endObject();
48 | }
49 |
50 | private void segment(Segment segment) throws IOException {
51 | writer.beginObject();
52 |
53 | writer.name("difficulty").value(segment.difficulty.get().name());
54 |
55 | target("distance", segment.distance);
56 | target("duration", segment.duration);
57 | target("strokes", segment.strokes);
58 | target("energy", segment.energy);
59 |
60 | limit("speed", segment.speed);
61 | limit("strokeRate", segment.strokeRate);
62 | limit("pulse", segment.pulse);
63 | limit("power", segment.power);
64 |
65 | writer.endObject();
66 | }
67 |
68 | private void target(String name, Property property) throws IOException {
69 | if (property.get() > 0) {
70 | writer.name(name).value(property.get());
71 | }
72 | }
73 |
74 | private void limit(String name, Property property) throws IOException {
75 | if (property.get() > 0) {
76 | writer.name(name).value(property.get());
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/io/ImportIntention.java:
--------------------------------------------------------------------------------
1 | package svenmeier.coxswain.io;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.database.Cursor;
6 | import android.net.Uri;
7 | import android.provider.OpenableColumns;
8 | import android.util.Log;
9 | import android.widget.Toast;
10 |
11 | import svenmeier.coxswain.Coxswain;
12 | import svenmeier.coxswain.R;
13 | import svenmeier.coxswain.garmin.TcxImport;
14 |
15 | /**
16 | */
17 | public class ImportIntention {
18 |
19 | private final Activity activity;
20 |
21 | public ImportIntention(Activity activity) {
22 | this.activity = activity;
23 | }
24 |
25 |
26 | public boolean onIntent(Intent intent) {
27 | Uri uri;
28 | if (Intent.ACTION_VIEW.equals(intent.getAction())) {
29 | uri = intent.getData();
30 | } else if (Intent.ACTION_SEND.equals(intent.getAction())) {
31 | uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
32 | } else {
33 | return false;
34 | }
35 |
36 | return importFrom(uri);
37 | }
38 |
39 | public boolean importFrom(Uri uri) {
40 | Import> importer = null;
41 |
42 | try {
43 | String name = getFileName(uri);
44 | int dot = name.lastIndexOf('.');
45 | String extension = name.substring(dot + 1);
46 |
47 | if ("tcx".equalsIgnoreCase(extension)) {
48 | importer = new TcxImport(activity);
49 | } else if ("coxswain".equalsIgnoreCase(extension)) {
50 | importer = new ProgramImport(activity);
51 | }
52 | } catch (Exception ex) {
53 | Log.e(Coxswain.TAG, ex.getMessage());
54 | }
55 |
56 | if (importer == null) {
57 | Toast.makeText(activity, R.string.import_unknown, Toast.LENGTH_LONG).show();
58 | return false;
59 | }
60 |
61 | importer.start(uri);
62 | return true;
63 | }
64 |
65 | private String getFileName(Uri uri) {
66 | Cursor cursor = activity.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null);
67 |
68 | if (cursor == null) {
69 | return uri.getLastPathSegment();
70 | } else {
71 | try {
72 | int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
73 | cursor.moveToFirst();
74 | return cursor.getString(nameIndex);
75 | } finally {
76 | cursor.close();
77 | }
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/app/src/main/java/svenmeier/coxswain/garmin/TCX2Course.java:
--------------------------------------------------------------------------------
1 | package svenmeier.coxswain.garmin;
2 |
3 | import android.location.Location;
4 |
5 | import java.io.IOException;
6 | import java.io.Reader;
7 | import java.text.ParseException;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | import propoid.util.io.XmlNavigator;
12 |
13 | /**
14 | * Converter for {@code TCX} (Training Center XML).
15 | */
16 | public class TCX2Course {
17 |
18 | private final XmlNavigator navigator;
19 |
20 | private Course course;
21 |
22 | public TCX2Course(Reader reader) throws IOException {
23 | navigator = new XmlNavigator(reader);
24 | }
25 |
26 | public Course getCourse() {
27 | return course;
28 | }
29 |
30 | public TCX2Course course() throws IOException, ParseException {
31 | if (navigator.descent("Course") == false) {
32 | throw new ParseException(" missing", navigator.offset());
33 | }
34 |
35 | if (navigator.descent("Track") == false) {
36 | throw new ParseException("