Compare commits
No commits in common. "master" and "use_alarm_manager" have entirely different histories.
master
...
use_alarm_
|
@ -23,14 +23,13 @@ Massive tracks your reps and sets at the gym. No internet connectivity or high s
|
|||
|
||||
<img src="metadata/en-US/images/phoneScreenshots/home.png" width="318"/>
|
||||
<img src="metadata/en-US/images/phoneScreenshots/edit.png" width="318"/>
|
||||
<img src="metadata/en-US/images/phoneScreenshots/timer.png" width="318"/>
|
||||
<img src="metadata/en-US/images/phoneScreenshots/plans.png" width="318"/>
|
||||
<img src="metadata/en-US/images/phoneScreenshots/plan-edit.png" width="318"/>
|
||||
<img src="metadata/en-US/images/phoneScreenshots/plan-start.png" width="318"/>
|
||||
<img src="metadata/en-US/images/phoneScreenshots/best-view.png" width="318"/>
|
||||
<img src="metadata/en-US/images/phoneScreenshots/settings.png" width="318"/>
|
||||
<img src="metadata/en-US/images/phoneScreenshots/drawer.png" width="318"/>
|
||||
<img src="metadata/en-US/images/phoneScreenshots/exercises.png" width="318"/>
|
||||
<img src="metadata/en-US/images/phoneScreenshots/exercise-edit.png" width="318"/>
|
||||
|
||||
# Building from Source
|
||||
|
||||
|
|
|
@ -511,7 +511,7 @@ export default function SettingsPage() {
|
|||
style={{ alignSelf: "flex-start" }}
|
||||
onPress={async () => {
|
||||
const result = await DocumentPicker.pickDirectory();
|
||||
await NativeModules.BackupModule.exportSets(result.uri);
|
||||
await NativeModules.BackupModule.exportToCSV(result.uri);
|
||||
toast("Exported sets as CSV.");
|
||||
}}
|
||||
>
|
||||
|
@ -519,21 +519,6 @@ export default function SettingsPage() {
|
|||
</Button>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Export plans as CSV",
|
||||
renderItem: (name: string) => (
|
||||
<Button
|
||||
style={{ alignSelf: "flex-start" }}
|
||||
onPress={async () => {
|
||||
const result = await DocumentPicker.pickDirectory();
|
||||
await NativeModules.BackupModule.exportPlans(result.uri);
|
||||
toast("Exported plans as CSV.");
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Import database",
|
||||
renderItem: (name: string) => (
|
||||
|
|
100
ViewGraph.tsx
|
@ -1,4 +1,3 @@
|
|||
import { DateTimePickerAndroid } from "@react-native-community/datetimepicker";
|
||||
import { RouteProp, useFocusEffect, useRoute } from "@react-navigation/native";
|
||||
import { format } from "date-fns";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
@ -7,9 +6,8 @@ import { FileSystem } from "react-native-file-access";
|
|||
import { IconButton, List } from "react-native-paper";
|
||||
import Share from "react-native-share";
|
||||
import { captureScreen } from "react-native-view-shot";
|
||||
import AppInput from "./AppInput";
|
||||
import AppLineChart from "./AppLineChart";
|
||||
import { StackParams } from "./AppStack";
|
||||
import AppLineChart from "./AppLineChart";
|
||||
import Select from "./Select";
|
||||
import StackHeader from "./StackHeader";
|
||||
import { MARGIN, PADDING } from "./constants";
|
||||
|
@ -17,9 +15,10 @@ import { setRepo, settingsRepo } from "./db";
|
|||
import GymSet from "./gym-set";
|
||||
import { Metrics } from "./metrics";
|
||||
import { Periods } from "./periods";
|
||||
import Settings from "./settings";
|
||||
import Volume from "./volume";
|
||||
import { convert } from "./conversions";
|
||||
import AppInput from "./AppInput";
|
||||
import { DateTimePickerAndroid } from "@react-native-community/datetimepicker";
|
||||
import Settings from "./settings";
|
||||
|
||||
export default function ViewGraph() {
|
||||
const { params } = useRoute<RouteProp<StackParams, "ViewGraph">>();
|
||||
|
@ -27,16 +26,20 @@ export default function ViewGraph() {
|
|||
const [volumes, setVolumes] = useState<Volume[]>();
|
||||
const [metric, setMetric] = useState(Metrics.OneRepMax);
|
||||
const [period, setPeriod] = useState(Periods.Monthly);
|
||||
const [unit, setUnit] = useState("kg");
|
||||
const [start, setStart] = useState<Date | null>(null);
|
||||
const [end, setEnd] = useState<Date | null>(null);
|
||||
const [unit, setUnit] = useState('kg');
|
||||
const [start, setStart] = useState<Date | null>(null)
|
||||
const [end, setEnd] = useState<Date | null>(null)
|
||||
const [settings, setSettings] = useState<Settings>({} as Settings);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
settingsRepo.findOne({ where: {} }).then(setSettings);
|
||||
}, [])
|
||||
);
|
||||
useFocusEffect(useCallback(() => {
|
||||
settingsRepo.findOne({ where: {} }).then(setSettings)
|
||||
}, []))
|
||||
|
||||
const convertWeight = (weight: number, unitFrom: string, unitTo: string) => {
|
||||
if (unitFrom === unitTo) return weight
|
||||
if (unitFrom === 'lb' && unitTo === 'kg') return weight * 0.453592;
|
||||
if (unitFrom === 'kg' && unitTo === 'lb') return weight * 2.20462;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let difference = "-7 days";
|
||||
|
@ -55,50 +58,34 @@ export default function ViewGraph() {
|
|||
.select("STRFTIME('%Y-%m-%d', created)", "created")
|
||||
.addSelect("unit")
|
||||
.where("name = :name", { name: params.name })
|
||||
.andWhere("NOT hidden");
|
||||
.andWhere("NOT hidden")
|
||||
|
||||
if (start) builder.andWhere("DATE(created) >= :start", { start });
|
||||
if (end) builder.andWhere("DATE(created) <= :end", { end });
|
||||
if (start)
|
||||
builder.andWhere("DATE(created) >= :start", { start });
|
||||
if (end)
|
||||
builder.andWhere("DATE(created) <= :end", { end });
|
||||
if (difference)
|
||||
builder.andWhere(
|
||||
"DATE(created) >= DATE('now', 'weekday 0', :difference)",
|
||||
{
|
||||
difference,
|
||||
}
|
||||
);
|
||||
builder.andWhere("DATE(created) >= DATE('now', 'weekday 0', :difference)", {
|
||||
difference,
|
||||
});
|
||||
|
||||
builder.groupBy("name").addGroupBy(`STRFTIME('${group}', created)`);
|
||||
|
||||
builder
|
||||
.groupBy("name")
|
||||
.addGroupBy(`STRFTIME('${group}', created)`);
|
||||
switch (metric) {
|
||||
case Metrics.Best:
|
||||
builder
|
||||
.addSelect("ROUND(MAX(weight), 2)", "weight")
|
||||
.getRawMany()
|
||||
.then((newWeights) =>
|
||||
newWeights.map((set) => {
|
||||
let weight = convert(set.weight, set.unit, unit);
|
||||
if (isNaN(weight)) weight = 0;
|
||||
return ({
|
||||
...set,
|
||||
weight: weight
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(newWeights => newWeights.map(set => ({ ...set, weight: convertWeight(set.weight, set.unit, unit) })))
|
||||
.then(setWeights);
|
||||
break;
|
||||
case Metrics.Volume:
|
||||
builder
|
||||
.addSelect("ROUND(SUM(weight * reps), 2)", "value")
|
||||
.getRawMany()
|
||||
.then((newWeights) =>
|
||||
newWeights.map((set) => {
|
||||
let weight = convert(set.value, set.unit, unit);
|
||||
if (isNaN(weight)) weight = 0;
|
||||
return ({
|
||||
...set,
|
||||
value: weight
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(newWeights => newWeights.map(set => ({ ...set, value: convertWeight(set.value, set.unit, unit) })))
|
||||
.then(setVolumes);
|
||||
break;
|
||||
default:
|
||||
|
@ -109,20 +96,9 @@ export default function ViewGraph() {
|
|||
"weight"
|
||||
)
|
||||
.getRawMany()
|
||||
.then((newWeights) =>
|
||||
newWeights.map((set) => {
|
||||
let weight = convert(set.weight, set.unit, unit);
|
||||
if (isNaN(weight)) weight = 0;
|
||||
return ({
|
||||
...set,
|
||||
weight: weight,
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(newWeights => newWeights.map(set => ({ ...set, weight: convertWeight(set.weight, set.unit, unit) })))
|
||||
.then((newWeights) => {
|
||||
console.log(`${ViewGraph.name}.oneRepMax:`, {
|
||||
weights: newWeights,
|
||||
});
|
||||
console.log(`${ViewGraph.name}.oneRepMax:`, { weights: newWeights });
|
||||
setWeights(newWeights);
|
||||
});
|
||||
}
|
||||
|
@ -161,7 +137,7 @@ export default function ViewGraph() {
|
|||
DateTimePickerAndroid.open({
|
||||
value: start || new Date(),
|
||||
onChange: (event, date) => {
|
||||
if (event.type === "dismissed") return;
|
||||
if (event.type === 'dismissed') return;
|
||||
if (date === start) return;
|
||||
setStart(date);
|
||||
setPeriod(Periods.AllTime);
|
||||
|
@ -175,7 +151,7 @@ export default function ViewGraph() {
|
|||
DateTimePickerAndroid.open({
|
||||
value: end || new Date(),
|
||||
onChange: (event, date) => {
|
||||
if (event.type === "dismissed") return;
|
||||
if (event.type === 'dismissed') return;
|
||||
if (date === end) return;
|
||||
setEnd(date);
|
||||
setPeriod(Periods.AllTime);
|
||||
|
@ -233,7 +209,7 @@ export default function ViewGraph() {
|
|||
value={period}
|
||||
/>
|
||||
|
||||
<View style={{ flexDirection: "row", marginBottom: MARGIN }}>
|
||||
<View style={{ flexDirection: 'row', marginBottom: MARGIN }}>
|
||||
<AppInput
|
||||
label="Start date"
|
||||
value={start ? format(start, settings.date || "Pp") : null}
|
||||
|
@ -253,9 +229,9 @@ export default function ViewGraph() {
|
|||
value={unit}
|
||||
onChange={setUnit}
|
||||
items={[
|
||||
{ label: "Pounds (lb)", value: "lb" },
|
||||
{ label: "Kilograms (kg)", value: "kg" },
|
||||
{ label: "Stone", value: "stone" },
|
||||
{ label: 'Pounds (lb)', value: 'lb' },
|
||||
{ label: 'Kilograms (kg)', value: 'kg' },
|
||||
{ label: 'Stone', value: 'stone' },
|
||||
]}
|
||||
/>
|
||||
<View style={{ paddingTop: PADDING }}>
|
||||
|
|
|
@ -87,8 +87,8 @@ android {
|
|||
applicationId "com.massive"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 36249
|
||||
versionName "2.34"
|
||||
versionCode 36246
|
||||
versionName "2.31"
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
|
|
|
@ -95,22 +95,10 @@ class BackupModule(context: ReactApplicationContext?) :
|
|||
}
|
||||
|
||||
@ReactMethod
|
||||
fun exportPlans(target: String, promise: Promise) {
|
||||
fun exportToCSV(target: String, promise: Promise) {
|
||||
try {
|
||||
val db = DatabaseHelper(reactApplicationContext)
|
||||
db.exportPlans(target, reactApplicationContext)
|
||||
promise.resolve("Export successful!")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
promise.reject("ERROR", e)
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun exportSets(target: String, promise: Promise) {
|
||||
try {
|
||||
val db = DatabaseHelper(reactApplicationContext)
|
||||
db.exportSets(target, reactApplicationContext)
|
||||
db.exportToCSV(target, reactApplicationContext)
|
||||
promise.resolve("Export successful!")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
|
|
|
@ -18,8 +18,8 @@ class DatabaseHelper(context: Context) :
|
|||
private const val DATABASE_VERSION = 1
|
||||
}
|
||||
|
||||
fun exportSets(target: String, context: Context) {
|
||||
Log.d("DatabaseHelper", "exportSets $target")
|
||||
fun exportToCSV(target: String, context: Context) {
|
||||
Log.d("DatabaseHelper", "exportToCSV $target")
|
||||
val treeUri: Uri = Uri.parse(target)
|
||||
val documentFile = context.let { DocumentFile.fromTreeUri(it, treeUri) }
|
||||
val file = documentFile?.createFile("application/octet-stream", "sets.csv") ?: return
|
||||
|
@ -43,31 +43,6 @@ class DatabaseHelper(context: Context) :
|
|||
}
|
||||
}
|
||||
|
||||
fun exportPlans(target: String, context: Context) {
|
||||
Log.d("DatabaseHelper", "exportPlans $target")
|
||||
val treeUri: Uri = Uri.parse(target)
|
||||
val documentFile = context.let { DocumentFile.fromTreeUri(it, treeUri) }
|
||||
val file = documentFile?.createFile("application/octet-stream", "plans.csv") ?: return
|
||||
|
||||
context.contentResolver.openOutputStream(file.uri).use { outputStream ->
|
||||
val csvWrite = CSVWriter(outputStream?.writer())
|
||||
val db = this.readableDatabase
|
||||
val cursor = db.rawQuery("SELECT * FROM plans", null)
|
||||
csvWrite.writeNext(cursor.columnNames)
|
||||
|
||||
while(cursor.moveToNext()) {
|
||||
val arrStr = arrayOfNulls<String>(cursor.columnCount)
|
||||
for(i in 0 until cursor.columnCount) {
|
||||
arrStr[i] = cursor.getString(i)
|
||||
}
|
||||
csvWrite.writeNext(arrStr)
|
||||
}
|
||||
|
||||
csvWrite.close()
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
}
|
||||
|
||||
|
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 149 KiB |
Before Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 198 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 188 KiB |
BIN
metadata/en-US/images/phoneScreenshots/timer.png
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
metadata/en-US/images/phoneScreenshots/workout.png
Normal file
After Width: | Height: | Size: 184 KiB |
BIN
metadata/en-US/images/phoneScreenshots/workouts.png
Normal file
After Width: | Height: | Size: 123 KiB |
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "massive",
|
||||
"version": "2.33",
|
||||
"version": "2.28",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "massive",
|
||||
"version": "2.33",
|
||||
"version": "2.28",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "massive",
|
||||
"version": "2.34",
|
||||
"version": "2.31",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
No user data is collected.
|