Compare commits

..

No commits in common. "master" and "use_alarm_manager" have entirely different histories.

22 changed files with 49 additions and 127 deletions

View File

@ -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/home.png" width="318"/>
<img src="metadata/en-US/images/phoneScreenshots/edit.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/plans.png" width="318"/>
<img src="metadata/en-US/images/phoneScreenshots/plan-edit.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/plan-start.png" width="318"/>
<img src="metadata/en-US/images/phoneScreenshots/best-view.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/settings.png" width="318"/>
<img src="metadata/en-US/images/phoneScreenshots/drawer.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 # Building from Source

View File

@ -511,7 +511,7 @@ export default function SettingsPage() {
style={{ alignSelf: "flex-start" }} style={{ alignSelf: "flex-start" }}
onPress={async () => { onPress={async () => {
const result = await DocumentPicker.pickDirectory(); const result = await DocumentPicker.pickDirectory();
await NativeModules.BackupModule.exportSets(result.uri); await NativeModules.BackupModule.exportToCSV(result.uri);
toast("Exported sets as CSV."); toast("Exported sets as CSV.");
}} }}
> >
@ -519,21 +519,6 @@ export default function SettingsPage() {
</Button> </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", name: "Import database",
renderItem: (name: string) => ( renderItem: (name: string) => (

View File

@ -1,4 +1,3 @@
import { DateTimePickerAndroid } from "@react-native-community/datetimepicker";
import { RouteProp, useFocusEffect, useRoute } from "@react-navigation/native"; import { RouteProp, useFocusEffect, useRoute } from "@react-navigation/native";
import { format } from "date-fns"; import { format } from "date-fns";
import { useCallback, useEffect, useMemo, useState } from "react"; 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 { IconButton, List } from "react-native-paper";
import Share from "react-native-share"; import Share from "react-native-share";
import { captureScreen } from "react-native-view-shot"; import { captureScreen } from "react-native-view-shot";
import AppInput from "./AppInput";
import AppLineChart from "./AppLineChart";
import { StackParams } from "./AppStack"; import { StackParams } from "./AppStack";
import AppLineChart from "./AppLineChart";
import Select from "./Select"; import Select from "./Select";
import StackHeader from "./StackHeader"; import StackHeader from "./StackHeader";
import { MARGIN, PADDING } from "./constants"; import { MARGIN, PADDING } from "./constants";
@ -17,9 +15,10 @@ import { setRepo, settingsRepo } from "./db";
import GymSet from "./gym-set"; import GymSet from "./gym-set";
import { Metrics } from "./metrics"; import { Metrics } from "./metrics";
import { Periods } from "./periods"; import { Periods } from "./periods";
import Settings from "./settings";
import Volume from "./volume"; 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() { export default function ViewGraph() {
const { params } = useRoute<RouteProp<StackParams, "ViewGraph">>(); const { params } = useRoute<RouteProp<StackParams, "ViewGraph">>();
@ -27,16 +26,20 @@ export default function ViewGraph() {
const [volumes, setVolumes] = useState<Volume[]>(); const [volumes, setVolumes] = useState<Volume[]>();
const [metric, setMetric] = useState(Metrics.OneRepMax); const [metric, setMetric] = useState(Metrics.OneRepMax);
const [period, setPeriod] = useState(Periods.Monthly); const [period, setPeriod] = useState(Periods.Monthly);
const [unit, setUnit] = useState("kg"); const [unit, setUnit] = useState('kg');
const [start, setStart] = useState<Date | null>(null); const [start, setStart] = useState<Date | null>(null)
const [end, setEnd] = useState<Date | null>(null); const [end, setEnd] = useState<Date | null>(null)
const [settings, setSettings] = useState<Settings>({} as Settings); const [settings, setSettings] = useState<Settings>({} as Settings);
useFocusEffect( useFocusEffect(useCallback(() => {
useCallback(() => { settingsRepo.findOne({ where: {} }).then(setSettings)
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(() => { useEffect(() => {
let difference = "-7 days"; let difference = "-7 days";
@ -55,50 +58,34 @@ export default function ViewGraph() {
.select("STRFTIME('%Y-%m-%d', created)", "created") .select("STRFTIME('%Y-%m-%d', created)", "created")
.addSelect("unit") .addSelect("unit")
.where("name = :name", { name: params.name }) .where("name = :name", { name: params.name })
.andWhere("NOT hidden"); .andWhere("NOT hidden")
if (start) builder.andWhere("DATE(created) >= :start", { start }); if (start)
if (end) builder.andWhere("DATE(created) <= :end", { end }); builder.andWhere("DATE(created) >= :start", { start });
if (end)
builder.andWhere("DATE(created) <= :end", { end });
if (difference) if (difference)
builder.andWhere( builder.andWhere("DATE(created) >= DATE('now', 'weekday 0', :difference)", {
"DATE(created) >= DATE('now', 'weekday 0', :difference)", difference,
{ });
difference,
}
);
builder.groupBy("name").addGroupBy(`STRFTIME('${group}', created)`);
builder
.groupBy("name")
.addGroupBy(`STRFTIME('${group}', created)`);
switch (metric) { switch (metric) {
case Metrics.Best: case Metrics.Best:
builder builder
.addSelect("ROUND(MAX(weight), 2)", "weight") .addSelect("ROUND(MAX(weight), 2)", "weight")
.getRawMany() .getRawMany()
.then((newWeights) => .then(newWeights => newWeights.map(set => ({ ...set, weight: convertWeight(set.weight, set.unit, unit) })))
newWeights.map((set) => {
let weight = convert(set.weight, set.unit, unit);
if (isNaN(weight)) weight = 0;
return ({
...set,
weight: weight
});
})
)
.then(setWeights); .then(setWeights);
break; break;
case Metrics.Volume: case Metrics.Volume:
builder builder
.addSelect("ROUND(SUM(weight * reps), 2)", "value") .addSelect("ROUND(SUM(weight * reps), 2)", "value")
.getRawMany() .getRawMany()
.then((newWeights) => .then(newWeights => newWeights.map(set => ({ ...set, value: convertWeight(set.value, set.unit, unit) })))
newWeights.map((set) => {
let weight = convert(set.value, set.unit, unit);
if (isNaN(weight)) weight = 0;
return ({
...set,
value: weight
});
})
)
.then(setVolumes); .then(setVolumes);
break; break;
default: default:
@ -109,20 +96,9 @@ export default function ViewGraph() {
"weight" "weight"
) )
.getRawMany() .getRawMany()
.then((newWeights) => .then(newWeights => newWeights.map(set => ({ ...set, weight: convertWeight(set.weight, set.unit, unit) })))
newWeights.map((set) => {
let weight = convert(set.weight, set.unit, unit);
if (isNaN(weight)) weight = 0;
return ({
...set,
weight: weight,
});
})
)
.then((newWeights) => { .then((newWeights) => {
console.log(`${ViewGraph.name}.oneRepMax:`, { console.log(`${ViewGraph.name}.oneRepMax:`, { weights: newWeights });
weights: newWeights,
});
setWeights(newWeights); setWeights(newWeights);
}); });
} }
@ -161,7 +137,7 @@ export default function ViewGraph() {
DateTimePickerAndroid.open({ DateTimePickerAndroid.open({
value: start || new Date(), value: start || new Date(),
onChange: (event, date) => { onChange: (event, date) => {
if (event.type === "dismissed") return; if (event.type === 'dismissed') return;
if (date === start) return; if (date === start) return;
setStart(date); setStart(date);
setPeriod(Periods.AllTime); setPeriod(Periods.AllTime);
@ -175,7 +151,7 @@ export default function ViewGraph() {
DateTimePickerAndroid.open({ DateTimePickerAndroid.open({
value: end || new Date(), value: end || new Date(),
onChange: (event, date) => { onChange: (event, date) => {
if (event.type === "dismissed") return; if (event.type === 'dismissed') return;
if (date === end) return; if (date === end) return;
setEnd(date); setEnd(date);
setPeriod(Periods.AllTime); setPeriod(Periods.AllTime);
@ -233,7 +209,7 @@ export default function ViewGraph() {
value={period} value={period}
/> />
<View style={{ flexDirection: "row", marginBottom: MARGIN }}> <View style={{ flexDirection: 'row', marginBottom: MARGIN }}>
<AppInput <AppInput
label="Start date" label="Start date"
value={start ? format(start, settings.date || "Pp") : null} value={start ? format(start, settings.date || "Pp") : null}
@ -253,9 +229,9 @@ export default function ViewGraph() {
value={unit} value={unit}
onChange={setUnit} onChange={setUnit}
items={[ items={[
{ label: "Pounds (lb)", value: "lb" }, { label: 'Pounds (lb)', value: 'lb' },
{ label: "Kilograms (kg)", value: "kg" }, { label: 'Kilograms (kg)', value: 'kg' },
{ label: "Stone", value: "stone" }, { label: 'Stone', value: 'stone' },
]} ]}
/> />
<View style={{ paddingTop: PADDING }}> <View style={{ paddingTop: PADDING }}>

View File

@ -87,8 +87,8 @@ android {
applicationId "com.massive" applicationId "com.massive"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 36249 versionCode 36246
versionName "2.34" versionName "2.31"
} }
signingConfigs { signingConfigs {
release { release {

View File

@ -95,22 +95,10 @@ class BackupModule(context: ReactApplicationContext?) :
} }
@ReactMethod @ReactMethod
fun exportPlans(target: String, promise: Promise) { fun exportToCSV(target: String, promise: Promise) {
try { try {
val db = DatabaseHelper(reactApplicationContext) val db = DatabaseHelper(reactApplicationContext)
db.exportPlans(target, reactApplicationContext) db.exportToCSV(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)
promise.resolve("Export successful!") promise.resolve("Export successful!")
} }
catch (e: Exception) { catch (e: Exception) {

View File

@ -18,8 +18,8 @@ class DatabaseHelper(context: Context) :
private const val DATABASE_VERSION = 1 private const val DATABASE_VERSION = 1
} }
fun exportSets(target: String, context: Context) { fun exportToCSV(target: String, context: Context) {
Log.d("DatabaseHelper", "exportSets $target") Log.d("DatabaseHelper", "exportToCSV $target")
val treeUri: Uri = Uri.parse(target) val treeUri: Uri = Uri.parse(target)
val documentFile = context.let { DocumentFile.fromTreeUri(it, treeUri) } val documentFile = context.let { DocumentFile.fromTreeUri(it, treeUri) }
val file = documentFile?.createFile("application/octet-stream", "sets.csv") ?: return 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) { override fun onCreate(db: SQLiteDatabase) {
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "massive", "name": "massive",
"version": "2.33", "version": "2.28",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "massive", "name": "massive",
"version": "2.33", "version": "2.28",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"dependencies": { "dependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-class-properties": "^7.18.6",

View File

@ -1,6 +1,6 @@
{ {
"name": "massive", "name": "massive",
"version": "2.34", "version": "2.31",
"private": true, "private": true,
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"scripts": { "scripts": {

View File

@ -1 +0,0 @@
No user data is collected.