Merge branch 'master' into alarm-module

This commit is contained in:
Brandon Presley 2022-10-28 16:49:39 +13:00
commit 8504f8b811
16 changed files with 472 additions and 545 deletions

View File

@ -28,6 +28,7 @@ export default function EditSet() {
milliseconds,
!!settings.vibrate,
settings.sound,
!!settings.noSound,
);
const nextAlarm = new Date();
nextAlarm.setTime(nextAlarm.getTime() + milliseconds);

View File

@ -5,6 +5,7 @@ import {Divider, List, Menu, Text} from 'react-native-paper';
import {HomePageParams} from './home-page-params';
import Set from './set';
import {deleteSet} from './set.service';
import {format} from './time';
import useDark from './use-dark';
import {useSettings} from './use-settings';
@ -63,7 +64,7 @@ export default function SetItem({
alignSelf: 'center',
color: dark ? '#909090ff' : '#717171ff',
}}>
{item.created?.replace('T', ' ')}
{format(item.created || '', settings.date)}
</Text>
)}
<Menu

View File

@ -33,6 +33,7 @@ export default function SettingsPage() {
showSets,
theme,
alarm,
noSound,
} = settings;
const {color, setColor} = useColor();
const {toast} = useSnackbar();
@ -139,9 +140,19 @@ export default function SettingsPage() {
[toast, update],
);
const changeNoSound = useCallback(
(enabled: boolean) => {
update(enabled, 'noSound');
if (enabled) toast('Disable sound on rest timer alarms.', 4000);
else toast('Enabled sound for rest timer alarms.', 4000);
},
[toast, update],
);
const switches: Input<boolean>[] = [
{name: 'Rest timers', value: !!alarm, onChange: changeAlarmEnabled},
{name: 'Vibrate', value: !!vibrate, onChange: changeVibrate},
{name: 'Disable sound', value: !!noSound, onChange: changeNoSound},
{name: 'Record notifications', value: !!notify, onChange: changeNotify},
{name: 'Show images', value: !!images, onChange: changeImages},
{name: 'Show unit', value: !!showUnit, onChange: changeUnit},
@ -217,16 +228,18 @@ export default function SettingsPage() {
dropdownIconColor={color}
selectedValue={settings.date}
onValueChange={changeDate}>
<Picker.Item value="%Y-%m-%d %H:%M" label="1990-12-24 15:05" />
<Picker.Item value="%Y-%m-%d" label="1990-12-24" />
<Picker.Item value="%d/%m" label="24/12 (dd/MM)" />
<Picker.Item value="%H:%M" label="15:05 (24-hour time)" />
<Picker.Item value="%h:%M %p" label="3:05 PM (12-hour time)" />
<Picker.Item value="%d/%m/%y" label="24/12/1996" />
<Picker.Item value="%A %h:%M %p" label="Monday 3:05 PM" />
<Picker.Item
value="%Y-%m-%d %H:%M"
label="Format date as 1990-12-24 15:05"
value="%d/%m/%y %h:%M %p"
label="24/12/1990 3:05 PM"
/>
<Picker.Item
value="%Y-%m-%d"
label="Format date as 1990-12-24 (YYYY-MM-dd)"
/>
<Picker.Item value="%d/%m" label="Format date as 24/12 (dd/MM)" />
<Picker.Item value="%H:%M" label="Format date as 15:05 (HH:MM)" />
<Picker.Item value="%d/%m %h:%M %p" label="24/12 3:05 PM" />
</Picker>
)}
{'alarm sound'.includes(search.toLowerCase()) && (

View File

@ -11,7 +11,7 @@ import MassiveInput from './MassiveInput';
import {useSnackbar} from './MassiveSnack';
import {PlanPageParams} from './plan-page-params';
import Set from './set';
import {addSet, countManyToday, getDistinctSets} from './set.service';
import {addSet, countMany} from './set.service';
import SetForm from './SetForm';
import StackHeader from './StackHeader';
import {useSettings} from './use-settings';
@ -30,7 +30,6 @@ export default function StartPlan() {
const [selected, setSelected] = useState(0);
const {settings} = useSettings();
const [counts, setCounts] = useState<CountMany[]>();
const [distinctSets, setDistinctSets] = useState<Set[]>();
const weightRef = useRef<TextInput>(null);
const repsRef = useRef<TextInput>(null);
const unitRef = useRef<TextInput>(null);
@ -44,16 +43,10 @@ export default function StartPlan() {
useFocusEffect(
useCallback(() => {
countManyToday().then(newCounts => {
countMany(workouts).then(newCounts => {
setCounts(newCounts);
console.log(`${StartPlan.name}.focus:`, {newCounts});
});
getDistinctSets({limit: 100, offset: 0, search: '%'}).then(
newDistinct => {
setDistinctSets(newDistinct);
console.log(`${StartPlan.name}.focus:`, {newDistinct});
},
);
}, [params]),
);
@ -69,7 +62,7 @@ export default function StartPlan() {
image: set.image,
unit,
});
countManyToday().then(setCounts);
countMany(workouts).then(setCounts);
if (
settings.notify &&
(+weight > best.weight || (+reps > best.reps && +weight === best.weight))
@ -95,10 +88,11 @@ export default function StartPlan() {
const select = useCallback(
async (index: number) => {
setSelected(index);
console.log(`${StartPlan.name}.next:`, {name, workouts});
const workout = workouts[index];
console.log(`${StartPlan.name}.next:`, {name});
if (!counts) return;
const workout = counts[index];
console.log(`${StartPlan.name}.next:`, {workout});
const newBest = await getBestSet(workout);
const newBest = await getBestSet(workout.name);
setMinutes(newBest.minutes);
setSeconds(newBest.seconds);
setName(newBest.name);
@ -110,20 +104,6 @@ export default function StartPlan() {
[name, workouts],
);
const getDescription = useCallback(
(countName: string) => {
const count = counts?.find(c => c.name === countName);
console.log(`${StartPlan.name}:`, {count, countName});
if (!distinctSets) return;
const distinct = distinctSets.find(d => d.name === countName);
console.log(`${StartPlan.name}:`, {distinct});
if (settings.showSets)
return `${count?.total || 0} / ${distinct?.sets || 3}`;
return count?.total || '0';
},
[counts, distinctSets, settings.showSets],
);
return (
<>
<StackHeader title={params.plan.days.replace(/,/g, ', ')} />
@ -157,13 +137,17 @@ export default function StartPlan() {
innerRef={unitRef}
/>
)}
{counts && distinctSets && (
{counts && (
<FlatList
data={workouts}
data={counts}
renderItem={({item, index}) => (
<List.Item
title={item}
description={getDescription(item)}
title={item.name}
description={
settings.showSets
? `${item.total} / ${item.sets ?? 3}`
: item.total.toString()
}
onPress={() => select(index)}
left={() => (
<View

View File

@ -64,7 +64,7 @@ export default function ViewBest() {
yData={volumes.map(v => v.value)}
yFormat={(value: number) =>
`${value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')}${
volumes[0].unit
volumes[0].unit || 'kg'
}`
}
xData={weights}

View File

@ -43,8 +43,8 @@ android {
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 36069
versionName "1.43"
versionCode 36071
versionName "1.45"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {

View File

@ -67,7 +67,7 @@ class AlarmModule internal constructor(context: ReactApplicationContext?) :
@RequiresApi(api = Build.VERSION_CODES.O)
@ReactMethod
fun timer(milliseconds: Int, vibrate: Boolean, sound: String?) {
fun timer(milliseconds: Int, vibrate: Boolean, sound: String?, noSound: Boolean = false) {
Log.d("AlarmModule", "Queue alarm for $milliseconds delay")
val intent = Intent(reactApplicationContext, AlarmModule::class.java)
currentActivity?.startActivityForResult(intent, 0)

View File

@ -21,11 +21,13 @@ class AlarmService : Service(), OnPreparedListener {
return START_STICKY
}
val sound = intent.extras?.getString("sound")
if (sound == null) {
val noSound = intent.extras?.getBoolean("noSound") == true
if (sound == null && !noSound) {
mediaPlayer = MediaPlayer.create(applicationContext, R.raw.argon)
mediaPlayer?.start()
mediaPlayer?.setOnCompletionListener { vibrator?.cancel() }
} else {
} else if (sound != null && !noSound) {
mediaPlayer = MediaPlayer().apply {
setAudioAttributes(
AudioAttributes.Builder()
@ -39,6 +41,7 @@ class AlarmService : Service(), OnPreparedListener {
setOnCompletionListener { vibrator?.cancel() }
}
}
val pattern = longArrayOf(0, 300, 1300, 300, 1300, 300)
vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager =

View File

@ -20,11 +20,13 @@ class TimerService : Service() {
private var endMs: Int = 0
private var currentMs: Long = 0
private var vibrate: Boolean = true
private var noSound: Boolean = false
private var sound: String? = null
@RequiresApi(Build.VERSION_CODES.O)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
vibrate = intent?.extras?.getBoolean("vibrate") == true
noSound = intent?.extras?.getBoolean("noSound") == true
sound = intent?.extras?.getString("sound")
val manager = getManager()
manager.cancel(NOTIFICATION_ID_DONE)
@ -93,9 +95,11 @@ class TimerService : Service() {
val manager = getManager()
manager.notify(NOTIFICATION_ID_DONE, builder.build())
manager.cancel(NOTIFICATION_ID_PENDING)
val alarmIntent = Intent(applicationContext, AlarmService::class.java)
alarmIntent.putExtra("vibrate", vibrate)
alarmIntent.putExtra("sound", sound)
val alarmIntent = Intent(applicationContext, AlarmService::class.java).apply {
putExtra("vibrate", vibrate)
putExtra("sound", sound)
putExtra("noSound", noSound)
}
applicationContext.startService(alarmIntent)
}
}
@ -124,11 +128,13 @@ class TimerService : Service() {
val stopIntent = Intent(context, StopTimer::class.java)
val pendingStop =
PendingIntent.getService(context, 0, stopIntent, PendingIntent.FLAG_IMMUTABLE)
val addIntent = Intent(context, TimerService::class.java)
addIntent.action = "add"
addIntent.putExtra("vibrate", vibrate)
addIntent.putExtra("sound", sound)
addIntent.data = Uri.parse("$currentMs")
val addIntent = Intent(context, TimerService::class.java).apply {
action = "add"
putExtra("vibrate", vibrate)
putExtra("sound", sound)
putExtra("noSound", noSound)
data = Uri.parse("$currentMs")
}
val pendingAdd = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getService(context, 0, addIntent, PendingIntent.FLAG_MUTABLE)
} else {

View File

@ -1,4 +1,5 @@
export default interface CountMany {
name: string;
total: number;
sets?: number;
}

22
db.ts
View File

@ -41,16 +41,16 @@ const migrations = [
)
`,
`
ALTER TABLE sets ADD COLUMN hidden DEFAULT 0
ALTER TABLE sets ADD COLUMN hidden DEFAULT false
`,
`
ALTER TABLE settings ADD COLUMN notify DEFAULT 0
ALTER TABLE settings ADD COLUMN notify DEFAULT false
`,
`
ALTER TABLE sets ADD COLUMN image TEXT NULL
`,
`
ALTER TABLE settings ADD COLUMN images BOOLEAN DEFAULT 1
ALTER TABLE settings ADD COLUMN images BOOLEAN DEFAULT true
`,
`
SELECT * FROM settings LIMIT 1
@ -74,7 +74,7 @@ const migrations = [
ALTER TABLE sets ADD COLUMN seconds INTEGER NOT NULL DEFAULT 30
`,
`
ALTER TABLE settings ADD COLUMN showUnit BOOLEAN DEFAULT 1
ALTER TABLE settings ADD COLUMN showUnit BOOLEAN DEFAULT true
`,
`
ALTER TABLE sets ADD COLUMN steps TEXT NULL
@ -94,10 +94,10 @@ const migrations = [
UPDATE settings SET showUnit = 1
`,
`
ALTER TABLE settings ADD COLUMN workouts BOOLEAN DEFAULT 1
ALTER TABLE settings ADD COLUMN workouts BOOLEAN DEFAULT true
`,
`
ALTER TABLE settings ADD COLUMN steps BOOLEAN DEFAULT 1
ALTER TABLE settings ADD COLUMN steps BOOLEAN DEFAULT true
`,
`
ALTER TABLE settings ADD COLUMN nextAlarm TEXT NULL
@ -109,13 +109,19 @@ const migrations = [
ALTER TABLE settings ADD COLUMN date TEXT NULL
`,
`
ALTER TABLE settings ADD COLUMN showDate BOOLEAN DEFAULT 0
ALTER TABLE settings ADD COLUMN showDate BOOLEAN DEFAULT false
`,
`
ALTER TABLE settings ADD COLUMN theme TEXT
`,
`
ALTER TABLE settings ADD COLUMN showSets BOOLEAN DEFAULT 1
ALTER TABLE settings ADD COLUMN showSets BOOLEAN DEFAULT true
`,
`
CREATE INDEX sets_created ON sets(created)
`,
`
ALTER TABLE settings ADD COLUMN noSound BOOLEAN DEFAULT false
`,
`
CREATE INDEX sets_created ON sets(created)

View File

@ -1,6 +1,6 @@
{
"name": "massive",
"version": "1.43",
"version": "1.45",
"private": true,
"license": "GPL-3.0-only",
"scripts": {
@ -23,19 +23,19 @@
"@types/react-native-vector-icons": "^6.4.12",
"babel-plugin-transform-remove-console": "^6.9.4",
"react": "^18.2.0",
"react-native": "^0.70.1",
"react-native-document-picker": "^8.1.1",
"react-native": "^0.70.4",
"react-native-document-picker": "^8.1.2",
"react-native-file-access": "^2.5.0",
"react-native-gesture-handler": "^2.6.1",
"react-native-gesture-handler": "^2.8.0",
"react-native-linear-gradient": "^2.6.2",
"react-native-pager-view": "^6.0.0",
"react-native-paper": "^4.12.4",
"react-native-reanimated": "^2.10.0",
"react-native-safe-area-context": "^4.3.3",
"react-native-screens": "^3.17.0",
"react-native-share": "^7.9.0",
"react-native-pager-view": "^6.0.1",
"react-native-paper": "^4.12.5",
"react-native-reanimated": "^2.12.0",
"react-native-safe-area-context": "^4.4.1",
"react-native-screens": "^3.18.2",
"react-native-share": "^7.9.1",
"react-native-sqlite-storage": "^6.0.1",
"react-native-svg": "^13.2.0",
"react-native-svg": "^13.4.0",
"react-native-svg-charts": "^5.4.0",
"react-native-vector-icons": "^9.2.0",
"react-native-view-shot": "^3.4.0"
@ -48,9 +48,9 @@
"@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"eslint": "^7.32.0",
"metro-react-native-babel-preset": "^0.70.3",
"typescript": "^4.4.4"
"eslint": "^8.26.0",
"metro-react-native-babel-preset": "^0.73.3",
"typescript": "^4.8.4"
},
"eslintConfig": {
"extends": "react-app",

View File

@ -82,18 +82,13 @@ export const getSets = async ({
}: PageParams): Promise<Set[]> => {
const select = `
SELECT id, name, reps, weight, sets, minutes, seconds,
STRFTIME(?, created) as created, unit, image, steps
created, unit, image, steps
FROM sets
WHERE name LIKE ? AND NOT hidden
ORDER BY STRFTIME('%Y-%m-%d %H:%M', created) DESC
LIMIT ? OFFSET ?
`;
const [result] = await db.executeSql(select, [
format,
`%${search}%`,
limit,
offset,
]);
const [result] = await db.executeSql(select, [`%${search}%`, limit, offset]);
return result.rows.raw();
};
@ -166,14 +161,21 @@ export const countToday = async (name: string): Promise<number> => {
return Number(result.rows.item(0)?.total);
};
export const countManyToday = async (): Promise<CountMany[]> => {
export const countMany = async (names: string[]): Promise<CountMany[]> => {
const questions = names.map(_ => '?').join(',');
console.log({questions, names});
const select = `
SELECT COUNT(*) as total, name FROM sets
WHERE created LIKE strftime('%Y-%m-%d%%', 'now', 'localtime')
AND NOT hidden
GROUP BY name
SELECT workouts.name, COUNT(sets.id) as total, workouts.sets
FROM (
SELECT distinct name, sets FROM sets
WHERE name IN (${questions})
) workouts
LEFT JOIN sets ON sets.name = workouts.name
AND sets.created LIKE STRFTIME('%Y-%m-%d%%', 'now', 'localtime')
AND NOT sets.hidden
GROUP BY workouts.name;
`;
const [result] = await db.executeSql(select);
const [result] = await db.executeSql(select, names);
return result.rows.raw();
};

View File

@ -11,5 +11,5 @@ export default interface Settings {
showDate: number;
theme: 'system' | 'dark' | 'light';
showSets: number;
nextAlarm?: string;
noSound: number;
}

45
time.ts
View File

@ -14,3 +14,48 @@ export function formatMonth(iso: string) {
const mm = (date.getMonth() + 1).toString();
return `${dd}/${mm}`;
}
function twelveHour(twentyFourHour: string) {
const [hourString, minute] = twentyFourHour.split(':');
const hour = +hourString % 24;
return (hour % 12 || 12) + ':' + minute + (hour < 12 ? ' AM' : ' PM');
}
function dayOfWeek(iso: string) {
const date = new Date(iso);
const day = date.getDay();
const target = DAYS[day === 0 ? 0 : day - 1];
return target.slice(0, 3);
}
/**
* @param iso ISO formatted date, e.g. 1996-12-24T14:03:04
* @param kind Intended format for the date, e.g. '%Y-%m-%d %H:%M'
*/
export function format(iso: string, kind: string) {
const split = iso.split('T');
const [year, month, day] = split[0].split('-');
const time = twelveHour(split[1]);
switch (kind) {
case '%Y-%m-%d %H:%M':
return iso.replace('T', ' ').replace(/:\d{2}/, '');
case '%Y-%m-%d':
return split[0];
case '%H:%M':
return split[1].replace(/:\d{2}/, '');
case '%d/%m/%y %h:%M %p':
return `${day}/${month}/${year} ${time}`;
case '%d/%m %h:%M %p':
return `${day}/${month} ${time}`;
case '%d/%m/%y':
return `${day}/${month}/${year}`;
case '%d/%m':
return `${day}/${month}`;
case '%h:%M %p':
return time;
case '%A %h:%M %p':
return dayOfWeek(iso) + ' ' + time;
default:
return iso;
}
}

775
yarn.lock

File diff suppressed because it is too large Load Diff