Compare commits
1 Commits
master
...
react-nati
Author | SHA1 | Date |
---|---|---|
Brandon Presley | 9727418dcd |
2
App.tsx
|
@ -17,6 +17,7 @@ import FatalError from "./FatalError";
|
|||
import { AppDataSource } from "./data-source";
|
||||
import { settingsRepo } from "./db";
|
||||
import { ThemeContext } from "./use-theme";
|
||||
import TimerProgress from "./TimerProgress";
|
||||
|
||||
export const CombinedDefaultTheme = {
|
||||
...NavigationDefaultTheme,
|
||||
|
@ -118,6 +119,7 @@ const App = () => {
|
|||
</NavigationContainer>
|
||||
|
||||
<AppSnack textColor={paperTheme.colors.background} />
|
||||
<TimerProgress />
|
||||
</PaperProvider>
|
||||
);
|
||||
};
|
||||
|
|
141
AppDrawer.tsx
|
@ -3,13 +3,14 @@ import { StackScreenProps } from "@react-navigation/stack";
|
|||
import { IconButton, useTheme, Banner } from "react-native-paper";
|
||||
import { DrawerParams } from "./drawer-params";
|
||||
import ExerciseList from "./ExerciseList";
|
||||
import GraphsList from "./GraphList";
|
||||
import GraphsList from "./GraphsList";
|
||||
import InsightsPage from "./InsightsPage";
|
||||
import PlanList from "./PlanList";
|
||||
import SetList from "./SetList";
|
||||
import SettingsPage from "./SettingsPage";
|
||||
import TimerPage from "./TimerPage";
|
||||
import WeightList from "./WeightList";
|
||||
import Daily from "./Daily";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
|
||||
const Drawer = createDrawerNavigator<DrawerParams>();
|
||||
|
||||
|
@ -23,60 +24,86 @@ export default function AppDrawer({
|
|||
const { dark } = useTheme();
|
||||
|
||||
return (
|
||||
<Drawer.Navigator
|
||||
screenOptions={{
|
||||
headerTintColor: dark ? "white" : "black",
|
||||
swipeEdgeWidth: 1000,
|
||||
headerShown: false,
|
||||
}}
|
||||
initialRouteName={
|
||||
(route.params.startup as keyof DrawerParams) || "History"
|
||||
}
|
||||
>
|
||||
<Drawer.Screen
|
||||
name="History"
|
||||
component={SetList}
|
||||
options={{ drawerIcon: () => <IconButton icon="history" /> }}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Exercises"
|
||||
component={ExerciseList}
|
||||
options={{ drawerIcon: () => <IconButton icon="dumbbell" /> }}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Daily"
|
||||
component={Daily}
|
||||
options={{ drawerIcon: () => <IconButton icon="calendar-outline" /> }}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Plans"
|
||||
component={PlanList}
|
||||
options={{ drawerIcon: () => <IconButton icon="checkbox-multiple-marked-outline" /> }}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Graphs"
|
||||
component={GraphsList}
|
||||
options={{
|
||||
drawerIcon: () => <IconButton icon="chart-bell-curve-cumulative" />,
|
||||
<>
|
||||
{__DEV__ && (
|
||||
<View style={styles.debugBanner}>
|
||||
<Text style={styles.debugText}>DEBUG</Text>
|
||||
</View>
|
||||
)}
|
||||
<Drawer.Navigator
|
||||
screenOptions={{
|
||||
headerTintColor: dark ? "white" : "black",
|
||||
swipeEdgeWidth: 1000,
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Weight"
|
||||
component={WeightList}
|
||||
options={{ drawerIcon: () => <IconButton icon="scale-bathroom" /> }}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Insights"
|
||||
component={InsightsPage}
|
||||
options={{
|
||||
drawerIcon: () => <IconButton icon="lightbulb-on-outline" />,
|
||||
}}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Settings"
|
||||
component={SettingsPage}
|
||||
options={{ drawerIcon: () => <IconButton icon="cog-outline" /> }}
|
||||
/>
|
||||
</Drawer.Navigator>
|
||||
initialRouteName={
|
||||
(route.params.startup as keyof DrawerParams) || "History"
|
||||
}
|
||||
>
|
||||
<Drawer.Screen
|
||||
name="History"
|
||||
component={SetList}
|
||||
options={{ drawerIcon: () => <IconButton icon="history" /> }}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Exercises"
|
||||
component={ExerciseList}
|
||||
options={{ drawerIcon: () => <IconButton icon="dumbbell" /> }}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Plans"
|
||||
component={PlanList}
|
||||
options={{ drawerIcon: () => <IconButton icon="calendar-outline" /> }}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Graphs"
|
||||
component={GraphsList}
|
||||
options={{
|
||||
drawerIcon: () => <IconButton icon="chart-bell-curve-cumulative" />,
|
||||
}}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Timer"
|
||||
component={TimerPage}
|
||||
options={{ drawerIcon: () => <IconButton icon="timer-outline" /> }}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Weight"
|
||||
component={WeightList}
|
||||
options={{ drawerIcon: () => <IconButton icon="scale-bathroom" /> }}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Insights"
|
||||
component={InsightsPage}
|
||||
options={{
|
||||
drawerIcon: () => <IconButton icon="lightbulb-on-outline" />,
|
||||
}}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="Settings"
|
||||
component={SettingsPage}
|
||||
options={{ drawerIcon: () => <IconButton icon="cog-outline" /> }}
|
||||
/>
|
||||
</Drawer.Navigator>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
debugBanner: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
transform: [{ rotate: '45deg' }],
|
||||
backgroundColor: 'red',
|
||||
zIndex: 1000,
|
||||
},
|
||||
debugText: {
|
||||
color: 'white',
|
||||
padding: 5,
|
||||
fontSize: 10,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -17,7 +17,6 @@ function AppInput(
|
|||
selectTextOnFocus
|
||||
ref={props.innerRef}
|
||||
blurOnSubmit={false}
|
||||
mode="outlined"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
|
65
AppStack.tsx
|
@ -13,7 +13,6 @@ import ViewGraph from "./ViewGraph";
|
|||
import ViewSetList from "./ViewSetList";
|
||||
import ViewWeightGraph from "./ViewWeightGraph";
|
||||
import Weight from "./weight";
|
||||
import { View, Text, StyleSheet } from "react-native";
|
||||
|
||||
export type StackParams = {
|
||||
Drawer: {};
|
||||
|
@ -52,50 +51,24 @@ const Stack = createStackNavigator<StackParams>();
|
|||
|
||||
export default function AppStack({ startup }: { startup: string }) {
|
||||
return (
|
||||
<>
|
||||
{__DEV__ && (
|
||||
<View style={styles.debugBanner}>
|
||||
<Text style={styles.debugText}>DEBUG</Text>
|
||||
</View>
|
||||
)}
|
||||
<Stack.Navigator
|
||||
screenOptions={{ headerShown: false, animationEnabled: false }}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="Drawer"
|
||||
component={AppDrawer}
|
||||
initialParams={{ startup }}
|
||||
/>
|
||||
<Stack.Screen name="EditSet" component={EditSet} />
|
||||
<Stack.Screen name="EditSets" component={EditSets} />
|
||||
<Stack.Screen name="EditPlan" component={EditPlan} />
|
||||
<Stack.Screen name="StartPlan" component={StartPlan} />
|
||||
<Stack.Screen name="ViewGraph" component={ViewGraph} />
|
||||
<Stack.Screen name="EditWeight" component={EditWeight} />
|
||||
<Stack.Screen name="ViewWeightGraph" component={ViewWeightGraph} />
|
||||
<Stack.Screen name="EditExercise" component={EditExercise} />
|
||||
<Stack.Screen name="EditExercises" component={EditExercises} />
|
||||
<Stack.Screen name="ViewSetList" component={ViewSetList} />
|
||||
</Stack.Navigator>
|
||||
</>
|
||||
<Stack.Navigator
|
||||
screenOptions={{ headerShown: false, animationEnabled: false }}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="Drawer"
|
||||
component={AppDrawer}
|
||||
initialParams={{ startup }}
|
||||
/>
|
||||
<Stack.Screen name="EditSet" component={EditSet} />
|
||||
<Stack.Screen name="EditSets" component={EditSets} />
|
||||
<Stack.Screen name="EditPlan" component={EditPlan} />
|
||||
<Stack.Screen name="StartPlan" component={StartPlan} />
|
||||
<Stack.Screen name="ViewGraph" component={ViewGraph} />
|
||||
<Stack.Screen name="EditWeight" component={EditWeight} />
|
||||
<Stack.Screen name="ViewWeightGraph" component={ViewWeightGraph} />
|
||||
<Stack.Screen name="EditExercise" component={EditExercise} />
|
||||
<Stack.Screen name="EditExercises" component={EditExercises} />
|
||||
<Stack.Screen name="ViewSetList" component={ViewSetList} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
debugBanner: {
|
||||
position: 'absolute',
|
||||
top: 20,
|
||||
right: 100,
|
||||
backgroundColor: 'red',
|
||||
zIndex: 1000,
|
||||
borderRadius: 5,
|
||||
},
|
||||
debugText: {
|
||||
color: 'white',
|
||||
padding: 5,
|
||||
fontSize: 10,
|
||||
},
|
||||
});
|
||||
|
|
109
Daily.tsx
|
@ -1,109 +0,0 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
import { FlatList, View } from "react-native";
|
||||
import { Button, IconButton, List } from "react-native-paper";
|
||||
import AppFab from "./AppFab";
|
||||
import DrawerHeader from "./DrawerHeader";
|
||||
import { LIMIT, PADDING } from "./constants";
|
||||
import GymSet, { defaultSet } from "./gym-set";
|
||||
import { getNow, setRepo, settingsRepo } from "./db";
|
||||
import { NavigationProp, useFocusEffect, useNavigation } from "@react-navigation/native";
|
||||
import { Like } from "typeorm";
|
||||
import Settings from "./settings";
|
||||
import { format } from "date-fns";
|
||||
import { DateTimePickerAndroid } from "@react-native-community/datetimepicker";
|
||||
import SetItem from "./SetItem";
|
||||
import { StackParams } from "./AppStack";
|
||||
|
||||
export default function Daily() {
|
||||
const [sets, setSets] = useState<GymSet[]>();
|
||||
const [day, setDay] = useState<Date>()
|
||||
const [settings, setSettings] = useState<Settings>();
|
||||
const navigation = useNavigation<NavigationProp<StackParams>>();
|
||||
|
||||
const mounted = async () => {
|
||||
const now = await getNow();
|
||||
let created = now.split('T')[0];
|
||||
setDay(new Date(created));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
mounted();
|
||||
}, [])
|
||||
|
||||
const refresh = async () => {
|
||||
if (!day) return;
|
||||
const created = day.toISOString().split('T')[0]
|
||||
const newSets = await setRepo.find({
|
||||
where: { hidden: 0 as any, created: Like(`${created}%`) },
|
||||
take: LIMIT,
|
||||
skip: 0,
|
||||
order: { created: "DESC" },
|
||||
});
|
||||
setSets(newSets);
|
||||
settingsRepo.findOne({ where: {} }).then(setSettings)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [day])
|
||||
|
||||
useFocusEffect(useCallback(() => {
|
||||
refresh();
|
||||
}, [day]))
|
||||
|
||||
const onAdd = async () => {
|
||||
const now = await getNow();
|
||||
let set: Partial<GymSet> = { ...sets[0] };
|
||||
if (!set) set = { ...defaultSet };
|
||||
set.created = now;
|
||||
delete set.id;
|
||||
navigation.navigate("EditSet", { set });
|
||||
|
||||
}
|
||||
|
||||
const onRight = () => {
|
||||
const newDay = new Date(day)
|
||||
newDay.setDate(newDay.getDate() + 1)
|
||||
setDay(newDay)
|
||||
}
|
||||
|
||||
const onLeft = () => {
|
||||
const newDay = new Date(day)
|
||||
newDay.setDate(newDay.getDate() - 1)
|
||||
setDay(newDay)
|
||||
}
|
||||
|
||||
const onDate = () => {
|
||||
DateTimePickerAndroid.open({
|
||||
value: new Date(day),
|
||||
onChange: (event, date) => {
|
||||
if (event.type === 'dismissed') return;
|
||||
setDay(date)
|
||||
},
|
||||
mode: 'date',
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DrawerHeader name="Daily" />
|
||||
|
||||
|
||||
<View style={{ padding: PADDING, flexGrow: 1 }}>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<IconButton style={{ marginRight: 'auto' }} icon="chevron-double-left" onPress={onLeft} />
|
||||
<Button onPress={onDate}>{format(day ? new Date(day) : new Date(), "PPPP")}</Button>
|
||||
<IconButton style={{ marginLeft: 'auto' }} icon="chevron-double-right" onPress={onRight} />
|
||||
</View>
|
||||
|
||||
{settings && (
|
||||
<FlatList ListEmptyComponent={<List.Item title="No sets yet" />} style={{ flex: 1 }} data={sets} renderItem={({ item }) => <SetItem ids={[]} setIds={() => { }} item={item} settings={settings} />} />
|
||||
)}
|
||||
|
||||
<AppFab onPress={onAdd} />
|
||||
</View>
|
||||
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -6,21 +6,15 @@ import { DrawerParams } from "./drawer-params";
|
|||
export default function DrawerHeader({
|
||||
name,
|
||||
children,
|
||||
ids,
|
||||
unSelect,
|
||||
}: {
|
||||
name: string;
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
ids?: unknown[],
|
||||
unSelect?: () => void,
|
||||
}) {
|
||||
const navigation = useNavigation<DrawerNavigationProp<DrawerParams>>();
|
||||
|
||||
return (
|
||||
<Appbar.Header>
|
||||
{ids && ids.length > 0 ? (<IconButton icon="arrow-left" onPress={unSelect} />) : (
|
||||
<IconButton icon="menu" onPress={navigation.openDrawer} />
|
||||
)}
|
||||
<IconButton icon="menu" onPress={navigation.openDrawer} />
|
||||
<Appbar.Content title={name} />
|
||||
{children}
|
||||
</Appbar.Header>
|
||||
|
|
|
@ -138,15 +138,17 @@ export default function EditExercise() {
|
|||
onChangeText={setName}
|
||||
onSubmitEditing={submitName}
|
||||
/>
|
||||
<AppInput
|
||||
innerRef={stepsRef}
|
||||
selectTextOnFocus={false}
|
||||
value={steps}
|
||||
onChangeText={setSteps}
|
||||
label="Steps"
|
||||
multiline
|
||||
onSubmitEditing={() => setsRef.current?.focus()}
|
||||
/>
|
||||
{settings?.steps && (
|
||||
<AppInput
|
||||
innerRef={stepsRef}
|
||||
selectTextOnFocus={false}
|
||||
value={steps}
|
||||
onChangeText={setSteps}
|
||||
label="Steps"
|
||||
multiline
|
||||
onSubmitEditing={() => setsRef.current?.focus()}
|
||||
/>
|
||||
)}
|
||||
<AppInput
|
||||
innerRef={setsRef}
|
||||
value={sets}
|
||||
|
|
|
@ -120,15 +120,17 @@ export default function EditExercises() {
|
|||
onChangeText={setName}
|
||||
onSubmitEditing={submitName}
|
||||
/>
|
||||
<AppInput
|
||||
innerRef={stepsRef}
|
||||
selectTextOnFocus={false}
|
||||
value={steps}
|
||||
onChangeText={setSteps}
|
||||
label={`Steps: ${oldSteps}`}
|
||||
multiline
|
||||
onSubmitEditing={() => setsRef.current?.focus()}
|
||||
/>
|
||||
{settings?.steps && (
|
||||
<AppInput
|
||||
innerRef={stepsRef}
|
||||
selectTextOnFocus={false}
|
||||
value={steps}
|
||||
onChangeText={setSteps}
|
||||
label={`Steps: ${oldSteps}`}
|
||||
multiline
|
||||
onSubmitEditing={() => setsRef.current?.focus()}
|
||||
/>
|
||||
)}
|
||||
<AppInput
|
||||
innerRef={setsRef}
|
||||
value={sets}
|
||||
|
|
|
@ -62,10 +62,7 @@ export default function EditSet() {
|
|||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
settingsRepo.findOne({ where: {} }).then(gotSettings => {
|
||||
setSettings(gotSettings);
|
||||
console.log(`${EditSet.name}.focus:`, { gotSettings })
|
||||
});
|
||||
settingsRepo.findOne({ where: {} }).then(setSettings);
|
||||
}, [])
|
||||
);
|
||||
|
||||
|
|
|
@ -125,8 +125,6 @@ export default function ExerciseList() {
|
|||
<>
|
||||
<DrawerHeader
|
||||
name={names.length > 0 ? `${names.length} selected` : "Exercises"}
|
||||
ids={names}
|
||||
unSelect={() => setNames([])}
|
||||
>
|
||||
<ListMenu
|
||||
onClear={clear}
|
||||
|
|
5
Gemfile
|
@ -3,7 +3,4 @@ source 'https://rubygems.org'
|
|||
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
|
||||
ruby ">= 2.6.10"
|
||||
|
||||
# Cocoapods 1.15 introduced a bug which break the build. We will remove the upper
|
||||
# bound in the template on Cocoapods with next React Native release.
|
||||
gem 'cocoapods', '>= 1.13', '< 1.15'
|
||||
gem 'activesupport', '>= 6.1.7.5', '< 7.1.0'
|
||||
gem 'cocoapods', '~> 1.12'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { ActivityIndicator, ScrollView, View } from "react-native";
|
||||
import { IconButton, Text } from "react-native-paper";
|
||||
import AppPieChart from "./AppPieChart";
|
||||
|
@ -66,6 +66,7 @@ export default function InsightsPage() {
|
|||
.then(() =>
|
||||
AppDataSource.manager.query(selectHours).then(setHourCounts)
|
||||
)
|
||||
.then(() => setLoadingHours(false))
|
||||
.finally(() => {
|
||||
setLoadingWeeks(false);
|
||||
setLoadingHours(false);
|
||||
|
@ -84,33 +85,6 @@ export default function InsightsPage() {
|
|||
return `${twelveHour} ${amPm}`;
|
||||
};
|
||||
|
||||
const hourCharts = useMemo(() => {
|
||||
if (loadingHours) return <ActivityIndicator />
|
||||
if (hourCounts?.length === 0) return (<Text style={{ marginBottom: MARGIN }}>
|
||||
No entries yet! Start recording sets to see your most active days of
|
||||
the week.
|
||||
</Text>)
|
||||
return <AppLineChart
|
||||
data={hourCounts.map((hc) => hc.count)}
|
||||
labels={hourCounts.map((hc) => hourLabel(hc.hour))}
|
||||
/>
|
||||
|
||||
}, [hourCounts, loadingHours])
|
||||
|
||||
const weekCharts = useMemo(() => {
|
||||
if (loadingWeeks) return <ActivityIndicator />
|
||||
if (weekCounts?.length === 0) return (<Text style={{ marginBottom: MARGIN }}>
|
||||
No entries yet! Start recording sets to see your most active days of
|
||||
the week.
|
||||
</Text>)
|
||||
return <AppPieChart
|
||||
options={weekCounts.map((weekCount) => ({
|
||||
label: DAYS[weekCount.week],
|
||||
value: weekCount.count,
|
||||
}))}
|
||||
/>
|
||||
}, [weekCounts, loadingWeeks])
|
||||
|
||||
return (
|
||||
<>
|
||||
<DrawerHeader name="Insights" />
|
||||
|
@ -162,7 +136,23 @@ export default function InsightsPage() {
|
|||
/>
|
||||
</View>
|
||||
|
||||
{weekCharts}
|
||||
{loadingWeeks ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<AppPieChart
|
||||
options={weekCounts.map((weekCount) => ({
|
||||
label: DAYS[weekCount.week],
|
||||
value: weekCount.count,
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
|
||||
{weekCounts?.length === 0 && (
|
||||
<Text style={{ marginBottom: MARGIN }}>
|
||||
No entries yet! Start recording sets to see your most active days of
|
||||
the week.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
|
@ -187,7 +177,21 @@ export default function InsightsPage() {
|
|||
/>
|
||||
</View>
|
||||
|
||||
{hourCharts}
|
||||
{loadingHours ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<AppLineChart
|
||||
data={hourCounts.map((hc) => hc.count)}
|
||||
labels={hourCounts.map((hc) => hourLabel(hc.hour))}
|
||||
/>
|
||||
)}
|
||||
|
||||
{hourCounts?.length === 0 && (
|
||||
<Text>
|
||||
No entries yet! Start recording sets to see your most active hours
|
||||
of the day.
|
||||
</Text>
|
||||
)}
|
||||
<View style={{ marginBottom: MARGIN }} />
|
||||
</ScrollView>
|
||||
|
||||
|
|
76
ListMenu.tsx
|
@ -42,50 +42,44 @@ export default function ListMenu({
|
|||
};
|
||||
|
||||
const select = () => {
|
||||
setShowMenu(false);
|
||||
onSelect();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{ids.length > 0 && (
|
||||
<IconButton icon="delete" onPress={() => setShowRemove(true)} />
|
||||
<Menu
|
||||
visible={showMenu}
|
||||
onDismiss={() => setShowMenu(false)}
|
||||
anchor={
|
||||
<IconButton onPress={() => setShowMenu(true)} icon="dots-vertical" />
|
||||
}
|
||||
>
|
||||
<Menu.Item leadingIcon="check-all" title="Select all" onPress={select} />
|
||||
<Menu.Item
|
||||
leadingIcon="close"
|
||||
title="Clear"
|
||||
onPress={clear}
|
||||
disabled={ids?.length === 0}
|
||||
/>
|
||||
<Menu.Item
|
||||
leadingIcon="pencil"
|
||||
title="Edit"
|
||||
onPress={edit}
|
||||
disabled={ids?.length === 0}
|
||||
/>
|
||||
{onCopy && (
|
||||
<Menu.Item
|
||||
leadingIcon="content-copy"
|
||||
title="Copy"
|
||||
onPress={copy}
|
||||
disabled={ids?.length === 0}
|
||||
/>
|
||||
)}
|
||||
<Menu
|
||||
visible={showMenu}
|
||||
onDismiss={() => setShowMenu(false)}
|
||||
anchor={
|
||||
<IconButton onPress={() => setShowMenu(true)} icon="dots-vertical" />
|
||||
}
|
||||
>
|
||||
<Menu.Item leadingIcon="check-all" title="Select all" onPress={select} />
|
||||
<Menu.Item
|
||||
leadingIcon="close"
|
||||
title="Clear"
|
||||
onPress={clear}
|
||||
disabled={ids?.length === 0}
|
||||
/>
|
||||
<Menu.Item
|
||||
leadingIcon="pencil"
|
||||
title="Edit"
|
||||
onPress={edit}
|
||||
disabled={ids?.length === 0}
|
||||
/>
|
||||
{onCopy && (
|
||||
<Menu.Item
|
||||
leadingIcon="content-copy"
|
||||
title="Copy"
|
||||
onPress={copy}
|
||||
disabled={ids?.length === 0}
|
||||
/>
|
||||
)}
|
||||
<Divider />
|
||||
<Menu.Item
|
||||
leadingIcon="delete"
|
||||
onPress={() => setShowRemove(true)}
|
||||
title="Delete"
|
||||
/>
|
||||
</Menu>
|
||||
<Divider />
|
||||
<Menu.Item
|
||||
leadingIcon="delete"
|
||||
onPress={() => setShowRemove(true)}
|
||||
title="Delete"
|
||||
/>
|
||||
<ConfirmDialog
|
||||
title={ids?.length === 0 ? "Delete all" : "Delete selected"}
|
||||
show={showRemove}
|
||||
|
@ -96,9 +90,9 @@ export default function ListMenu({
|
|||
{ids?.length === 0 ? (
|
||||
<>This irreversibly deletes records from the app. Are you sure?</>
|
||||
) : (
|
||||
<>This will delete {ids.length} {ids?.length > 1 ? "records" : "record"}. Are you sure?</>
|
||||
<>This will delete {ids?.length} record(s). Are you sure?</>
|
||||
)}
|
||||
</ConfirmDialog>
|
||||
</>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -94,10 +94,7 @@ export default function PlanList() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<DrawerHeader name={ids.length > 0 ? `${ids.length} selected` : "Plans"}
|
||||
ids={ids}
|
||||
unSelect={() => setIds([])}
|
||||
>
|
||||
<DrawerHeader name={ids.length > 0 ? `${ids.length} selected` : "Plans"}>
|
||||
<ListMenu
|
||||
onClear={clear}
|
||||
onCopy={copy}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -164,8 +164,6 @@ export default function SetList() {
|
|||
<>
|
||||
<DrawerHeader
|
||||
name={ids.length > 0 ? `${ids.length} selected` : "History"}
|
||||
ids={ids}
|
||||
unSelect={() => setIds([])}
|
||||
>
|
||||
<ListMenu
|
||||
onClear={clear}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { FlatList, NativeModules } from "react-native";
|
|||
import DocumentPicker from "react-native-document-picker";
|
||||
import { Dirs, FileSystem } from "react-native-file-access";
|
||||
import { Button } from "react-native-paper";
|
||||
import { check, PERMISSIONS, request, RESULTS } from "react-native-permissions";
|
||||
import AppInput from "./AppInput";
|
||||
import ConfirmDialog from "./ConfirmDialog";
|
||||
import { PADDING } from "./constants";
|
||||
|
@ -125,7 +126,6 @@ export default function SettingsPage() {
|
|||
await setRepo.update({}, { image: null });
|
||||
await settingsRepo.update({}, { sound: null, backup: false });
|
||||
reset({ index: 0, routes: [{ name: "Settings" }] });
|
||||
toast("Imported database successfully.")
|
||||
}, [reset]);
|
||||
|
||||
const today = new Date();
|
||||
|
@ -139,8 +139,7 @@ export default function SettingsPage() {
|
|||
items={[
|
||||
{ label: "History", value: "History", icon: 'history' },
|
||||
{ label: "Exercises", value: "Exercises", icon: 'dumbbell' },
|
||||
{ label: "Daily", value: "Daily", icon: 'calendar-outline' },
|
||||
{ label: "Plans", value: "Plans", icon: 'checkbox-multiple-marked-outline' },
|
||||
{ label: "Plans", value: "Plans", icon: 'calendar-outline' },
|
||||
{ label: "Graphs", value: "Graphs", icon: 'chart-bell-curve-cumulative' },
|
||||
{ label: "Timer", value: "Timer", icon: 'timer-outline' },
|
||||
{ label: "Weight", value: "Weight", icon: 'scale-bathroom' },
|
||||
|
@ -343,22 +342,38 @@ export default function SettingsPage() {
|
|||
onChange={async (value) => {
|
||||
setValue("vibrate", value);
|
||||
await settingsRepo.update({}, { vibrate: value });
|
||||
if (value) toast("Alarms will vibrate.");
|
||||
else toast("Stopped alarms from vibrating.");
|
||||
if (value) toast("Timers will now run after each set.");
|
||||
else toast("Stopped timers running after each set.");
|
||||
}}
|
||||
title={name}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Sound",
|
||||
name: "Disable sound",
|
||||
renderItem: (name: string) => (
|
||||
<Switch
|
||||
value={!settings.noSound}
|
||||
value={settings.noSound}
|
||||
onChange={async (value) => {
|
||||
setValue("noSound", !value);
|
||||
await settingsRepo.update({}, { noSound: !value });
|
||||
if (!value) toast("Alarms will no longer make a sound.");
|
||||
setValue("noSound", value);
|
||||
const silentPath = Dirs.DocumentDir + "/silent.mp3";
|
||||
|
||||
if (value) {
|
||||
await FileSystem.writeFile(silentPath, "");
|
||||
setValue("sound", silentPath);
|
||||
await settingsRepo.update(
|
||||
{},
|
||||
{
|
||||
sound: silentPath,
|
||||
noSound: value,
|
||||
}
|
||||
);
|
||||
} else if (!value && settings.sound === silentPath) {
|
||||
setValue("sound", null);
|
||||
await settingsRepo.update({}, { sound: null, noSound: value });
|
||||
}
|
||||
|
||||
if (value) toast("Alarms will no longer make a sound.");
|
||||
else toast("Enabled sound for alarms.");
|
||||
}}
|
||||
title={name}
|
||||
|
@ -410,6 +425,21 @@ export default function SettingsPage() {
|
|||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Show steps",
|
||||
renderItem: (name: string) => (
|
||||
<Switch
|
||||
value={settings.steps}
|
||||
onChange={async (value) => {
|
||||
setValue("steps", value);
|
||||
await settingsRepo.update({}, { steps: value });
|
||||
if (value) toast("Show steps for exercises.");
|
||||
else toast("Hid steps for exercises.");
|
||||
}}
|
||||
title={name}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Show date",
|
||||
renderItem: (name: string) => (
|
||||
|
@ -511,7 +541,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 +549,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) => (
|
||||
|
|
|
@ -31,7 +31,7 @@ export default function StartPlan() {
|
|||
const [reps, setReps] = useState(params.first?.reps.toString() || "0");
|
||||
const [weight, setWeight] = useState(params.first?.weight.toString() || "0");
|
||||
const [unit, setUnit] = useState<string>(params.first?.unit || "kg");
|
||||
const [selected, setSelected] = useState<number>(0);
|
||||
const [selected, setSelected] = useState<number | null>(null);
|
||||
const [settings, setSettings] = useState<Settings>();
|
||||
const [counts, setCounts] = useState<CountMany[]>();
|
||||
const weightRef = useRef<TextInput>(null);
|
||||
|
@ -88,6 +88,10 @@ export default function StartPlan() {
|
|||
useCallback(() => {
|
||||
settingsRepo.findOne({ where: {} }).then(setSettings);
|
||||
refresh();
|
||||
setRepo.find({ order: { created: "DESC" }, take: 1 }).then(sets => {
|
||||
const index = exercises.findIndex(exercise => exercise === sets[0].name);
|
||||
setSelected(index === -1 ? 0 : index);
|
||||
});
|
||||
// eslint-disable-next-line
|
||||
}, [])
|
||||
);
|
||||
|
@ -128,8 +132,6 @@ export default function StartPlan() {
|
|||
const canNotify = await check(PERMISSIONS.ANDROID.POST_NOTIFICATIONS);
|
||||
if (canNotify === RESULTS.DENIED)
|
||||
await request(PERMISSIONS.ANDROID.POST_NOTIFICATIONS);
|
||||
if (isNaN(exercise.total) ? 0 : exercise.total === best.sets - 1 && selected === exercises.length - 1)
|
||||
return
|
||||
NativeModules.AlarmModule.timer(milliseconds, `${exercise.name} (${exercise.total + 1}/${best.sets})`);
|
||||
};
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ export default function StartPlanItem(props: Props) {
|
|||
setShowMenu(false);
|
||||
if (!first) return toast("Nothing to undo.");
|
||||
await setRepo.delete(first.id);
|
||||
NativeModules.AlarmModule.stop();
|
||||
onUndo();
|
||||
}, [setShowMenu, onUndo, item.name]);
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { NativeModules, View } from "react-native";
|
||||
import { FAB, Text, useTheme } from "react-native-paper";
|
||||
import AppFab from "./AppFab";
|
||||
import DrawerHeader from "./DrawerHeader";
|
||||
import { settingsRepo } from "./db";
|
||||
import Settings from "./settings";
|
||||
import useTimer from "./use-timer";
|
||||
|
||||
export interface TickEvent {
|
||||
minutes: string;
|
||||
seconds: string;
|
||||
}
|
||||
|
||||
export default function TimerPage() {
|
||||
const { minutes, seconds, update } = useTimer();
|
||||
const [settings, setSettings] = useState<Settings>();
|
||||
const { colors } = useTheme();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
settingsRepo.findOne({ where: {} }).then(setSettings);
|
||||
}, [])
|
||||
);
|
||||
|
||||
const stop = () => {
|
||||
NativeModules.AlarmModule.stop();
|
||||
update();
|
||||
};
|
||||
|
||||
const add = async () => {
|
||||
console.log(`${TimerPage.name}.add:`, settings);
|
||||
NativeModules.AlarmModule.add();
|
||||
update();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DrawerHeader name="Timer" />
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexGrow: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 70, position: "absolute" }}>
|
||||
{minutes}:{seconds}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<FAB
|
||||
icon="plus"
|
||||
onPress={add}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 20,
|
||||
bottom: 20,
|
||||
backgroundColor: colors.primary,
|
||||
}}
|
||||
color={colors.background}
|
||||
/>
|
||||
<AppFab icon="stop" onPress={stop} />
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { ProgressBar } from "react-native-paper";
|
||||
import { TickEvent } from "./TimerPage";
|
||||
import { emitter } from "./emitter";
|
||||
|
||||
export default function TimerProgress() {
|
||||
const [progress, setProgress] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const description = emitter.addListener(
|
||||
"tick",
|
||||
({ minutes, seconds }: TickEvent) => {
|
||||
setProgress((Number(minutes) * 60 + Number(seconds)) / 210);
|
||||
}
|
||||
);
|
||||
return description.remove;
|
||||
}, []);
|
||||
|
||||
if (progress === 0) return null;
|
||||
|
||||
return (
|
||||
<ProgressBar
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
height: 5,
|
||||
}}
|
||||
progress={progress}
|
||||
/>
|
||||
);
|
||||
}
|
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 }}>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
apply plugin: "com.android.application"
|
||||
apply plugin: "com.facebook.react"
|
||||
apply plugin: "kotlin-android"
|
||||
apply plugin: "org.jetbrains.kotlin.android"
|
||||
|
||||
/**
|
||||
* This is the configuration block to customize your React Native Android app.
|
||||
|
@ -73,8 +72,7 @@ def jscFlavor = 'org.webkit:android-jsc:+'
|
|||
android {
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
compileSdk rootProject.ext.compileSdkVersion
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
namespace "com.massive"
|
||||
|
||||
|
@ -87,8 +85,8 @@ android {
|
|||
applicationId "com.massive"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 36250
|
||||
versionName "2.35"
|
||||
versionCode 36239
|
||||
versionName "2.24"
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
|
@ -130,8 +128,13 @@ dependencies {
|
|||
implementation 'com.opencsv:opencsv:5.5.2'
|
||||
implementation project(':react-native-sqlite-storage')
|
||||
implementation project(':react-native-vector-icons')
|
||||
implementation("com.facebook.react:flipper-integration")
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
|
||||
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.squareup.okhttp3', module:'okhttp'
|
||||
}
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
|
||||
if (hermesEnabled.toBoolean()) {
|
||||
implementation("com.facebook.react:hermes-android")
|
||||
} else {
|
||||
|
|
|
@ -7,5 +7,8 @@
|
|||
<application
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="28"
|
||||
tools:ignore="GoogleAppIndexingWarning"/>
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.massive;
|
||||
|
||||
import android.content.Context;
|
||||
import com.facebook.flipper.android.AndroidFlipperClient;
|
||||
import com.facebook.flipper.android.utils.FlipperUtils;
|
||||
import com.facebook.flipper.core.FlipperClient;
|
||||
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
|
||||
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
|
||||
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
|
||||
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
|
||||
import com.facebook.react.ReactInstanceEventListener;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.modules.network.NetworkingModule;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
/**
|
||||
* Class responsible of loading Flipper inside your React Native application. This is the debug
|
||||
* flavor of it. Here you can add your own plugins and customize the Flipper setup.
|
||||
*/
|
||||
public class ReactNativeFlipper {
|
||||
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
||||
if (FlipperUtils.shouldEnableFlipper(context)) {
|
||||
final FlipperClient client = AndroidFlipperClient.getInstance(context);
|
||||
|
||||
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
|
||||
client.addPlugin(new DatabasesFlipperPlugin(context));
|
||||
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
|
||||
client.addPlugin(CrashReporterPlugin.getInstance());
|
||||
|
||||
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
|
||||
NetworkingModule.setCustomClientBuilder(
|
||||
new NetworkingModule.CustomClientBuilder() {
|
||||
@Override
|
||||
public void apply(OkHttpClient.Builder builder) {
|
||||
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
|
||||
}
|
||||
});
|
||||
client.addPlugin(networkFlipperPlugin);
|
||||
client.start();
|
||||
|
||||
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
|
||||
// Hence we run if after all native modules have been initialized
|
||||
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
|
||||
if (reactContext == null) {
|
||||
reactInstanceManager.addReactInstanceEventListener(
|
||||
new ReactInstanceEventListener() {
|
||||
@Override
|
||||
public void onReactContextInitialized(ReactContext reactContext) {
|
||||
reactInstanceManager.removeReactInstanceEventListener(this);
|
||||
reactContext.runOnNativeModulesQueueThread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
client.addPlugin(new FrescoFlipperPlugin());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
client.addPlugin(new FrescoFlipperPlugin());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,55 +6,47 @@
|
|||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_NETWORK_STATE"
|
||||
tools:node="remove" />
|
||||
|
||||
<uses-permission
|
||||
android:name="com.google.android.gms.permission.AD_ID"
|
||||
tools:node="remove" />
|
||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID"
|
||||
tools:node="remove"/>
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
android:name=".MainApplication"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".TimerDone"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".TimerDone"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".StopAlarm"
|
||||
android:exported="true"
|
||||
android:process=":remote" />
|
||||
<activity
|
||||
android:name=".StopAlarm"
|
||||
android:exported="true"
|
||||
android:process=":remote" />
|
||||
|
||||
<service
|
||||
android:name=".TimerService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="specialUse">
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="App does not require SCHEDULE_EXACT_ALARM or USE_EXACT_ALARM, but needs foreground service for foreground timer."/>
|
||||
</service>
|
||||
<service
|
||||
android:name=".AlarmService"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
@ -1,25 +1,223 @@
|
|||
package com.massive
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.*
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.CountDownTimer
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.facebook.react.bridge.*
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule
|
||||
import kotlin.math.floor
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
class AlarmModule(context: ReactApplicationContext?) :
|
||||
ReactContextBaseJavaModule(context) {
|
||||
ReactContextBaseJavaModule(context), LifecycleEventListener {
|
||||
|
||||
private var countdownTimer: CountDownTimer? = null
|
||||
var currentMs: Long = 0
|
||||
private var running = false
|
||||
private var currentDescription = ""
|
||||
|
||||
override fun getName(): String {
|
||||
return "AlarmModule"
|
||||
}
|
||||
|
||||
private val stopReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
Log.d("AlarmModule", "Received stop broadcast intent")
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
private val addReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
add()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
reactApplicationContext.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST),
|
||||
Context.RECEIVER_NOT_EXPORTED)
|
||||
reactApplicationContext.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST),
|
||||
Context.RECEIVER_NOT_EXPORTED)
|
||||
}
|
||||
else {
|
||||
reactApplicationContext.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST))
|
||||
reactApplicationContext.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHostDestroy() {
|
||||
reactApplicationContext.unregisterReceiver(stopReceiver)
|
||||
reactApplicationContext.unregisterReceiver(addReceiver)
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun add() {
|
||||
Log.d("AlarmModule", "Add 1 min to alarm.")
|
||||
countdownTimer?.cancel()
|
||||
val newMs = if (running) currentMs.toInt().plus(60000) else 60000
|
||||
countdownTimer = getTimer(newMs)
|
||||
countdownTimer?.start()
|
||||
running = true
|
||||
val manager = getManager()
|
||||
manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
|
||||
val intent = Intent(reactApplicationContext, AlarmService::class.java)
|
||||
reactApplicationContext.stopService(intent)
|
||||
}
|
||||
|
||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||
fun getCurrent(): Int {
|
||||
Log.d("AlarmModule", "currentMs=$currentMs")
|
||||
if (running) return currentMs.toInt()
|
||||
return 0
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun stop() {
|
||||
Log.d("AlarmModule", "Stop alarm.")
|
||||
countdownTimer?.cancel()
|
||||
running = false
|
||||
val intent = Intent(reactApplicationContext, AlarmService::class.java)
|
||||
reactApplicationContext?.stopService(intent)
|
||||
val manager = getManager()
|
||||
manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
|
||||
manager.cancel(NOTIFICATION_ID_PENDING)
|
||||
val params =
|
||||
Arguments.createMap().apply {
|
||||
putString("minutes", "00")
|
||||
putString("seconds", "00")
|
||||
}
|
||||
reactApplicationContext
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
||||
.emit("tick", params)
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun timer(milliseconds: Int, description: String) {
|
||||
Log.d("AlarmModule", "Queue alarm for $milliseconds delay")
|
||||
val intent = Intent(reactApplicationContext, TimerService::class.java)
|
||||
intent.putExtra("milliseconds", milliseconds)
|
||||
intent.putExtra("description", description)
|
||||
reactApplicationContext.startForegroundService(intent)
|
||||
currentDescription = description
|
||||
val manager = getManager()
|
||||
manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
|
||||
val intent = Intent(reactApplicationContext, AlarmService::class.java)
|
||||
reactApplicationContext.stopService(intent)
|
||||
countdownTimer?.cancel()
|
||||
countdownTimer = getTimer(milliseconds)
|
||||
countdownTimer?.start()
|
||||
running = true
|
||||
}
|
||||
|
||||
private fun getTimer(
|
||||
endMs: Int,
|
||||
): CountDownTimer {
|
||||
val builder = getBuilder()
|
||||
return object : CountDownTimer(endMs.toLong(), 1000) {
|
||||
override fun onTick(current: Long) {
|
||||
currentMs = current
|
||||
val seconds =
|
||||
floor((current / 1000).toDouble() % 60).toInt().toString().padStart(2, '0')
|
||||
val minutes =
|
||||
floor((current / 1000).toDouble() / 60).toInt().toString().padStart(2, '0')
|
||||
builder.setContentText("$minutes:$seconds")
|
||||
.setAutoCancel(false)
|
||||
.setDefaults(0)
|
||||
.setProgress(endMs, current.toInt(), false)
|
||||
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
||||
.priority = NotificationCompat.PRIORITY_LOW
|
||||
val manager = getManager()
|
||||
manager.notify(NOTIFICATION_ID_PENDING, builder.build())
|
||||
val params =
|
||||
Arguments.createMap().apply {
|
||||
putString("minutes", minutes)
|
||||
putString("seconds", seconds)
|
||||
}
|
||||
reactApplicationContext
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
||||
.emit("tick", params)
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
val context = reactApplicationContext
|
||||
val intent = Intent(context, AlarmService::class.java)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent)
|
||||
}
|
||||
else {
|
||||
context.startService(intent)
|
||||
}
|
||||
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
||||
.emit(
|
||||
"tick",
|
||||
Arguments.createMap().apply {
|
||||
putString("minutes", "00")
|
||||
putString("seconds", "00")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnspecifiedImmutableFlag")
|
||||
private fun getBuilder(): NotificationCompat.Builder {
|
||||
val context = reactApplicationContext
|
||||
val contentIntent = Intent(context, MainActivity::class.java)
|
||||
val pendingContent =
|
||||
PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
val addBroadcast = Intent(ADD_BROADCAST).apply { setPackage(context.packageName) }
|
||||
val pendingAdd =
|
||||
PendingIntent.getBroadcast(context, 0, addBroadcast, PendingIntent.FLAG_MUTABLE)
|
||||
val stopBroadcast = Intent(STOP_BROADCAST)
|
||||
stopBroadcast.setPackage(context.packageName)
|
||||
val pendingStop =
|
||||
PendingIntent.getBroadcast(context, 0, stopBroadcast, PendingIntent.FLAG_IMMUTABLE)
|
||||
return NotificationCompat.Builder(context, CHANNEL_ID_PENDING)
|
||||
.setSmallIcon(R.drawable.ic_baseline_hourglass_bottom_24)
|
||||
.setContentTitle(currentDescription)
|
||||
.setContentIntent(pendingContent)
|
||||
.addAction(R.drawable.ic_baseline_stop_24, "Stop", pendingStop)
|
||||
.addAction(R.drawable.ic_baseline_stop_24, "Add 1 min", pendingAdd)
|
||||
.setDeleteIntent(pendingStop)
|
||||
}
|
||||
|
||||
private fun getManager(): NotificationManager {
|
||||
val notificationManager =
|
||||
reactApplicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val timersChannel =
|
||||
NotificationChannel(
|
||||
CHANNEL_ID_PENDING,
|
||||
CHANNEL_ID_PENDING,
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
timersChannel.setSound(null, null)
|
||||
timersChannel.description = "Progress on rest timers."
|
||||
notificationManager.createNotificationChannel(timersChannel)
|
||||
}
|
||||
|
||||
return notificationManager
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val STOP_BROADCAST = "stop-timer-event"
|
||||
const val ADD_BROADCAST = "add-timer-event"
|
||||
const val CHANNEL_ID_PENDING = "Timer"
|
||||
const val NOTIFICATION_ID_PENDING = 1
|
||||
}
|
||||
|
||||
override fun onHostResume() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onHostPause() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
package com.massive
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.*
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.AudioAttributes
|
||||
import android.media.MediaPlayer
|
||||
import android.media.MediaPlayer.OnPreparedListener
|
||||
import android.net.Uri
|
||||
import android.os.*
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
|
||||
class Settings(val sound: String?, val noSound: Boolean, val vibrate: Boolean, val duration: Long)
|
||||
|
||||
class AlarmService : Service(), OnPreparedListener {
|
||||
private var mediaPlayer: MediaPlayer? = null
|
||||
private var vibrator: Vibrator? = null
|
||||
|
||||
private fun getBuilder(): NotificationCompat.Builder {
|
||||
val context = applicationContext
|
||||
val contentIntent = Intent(context, MainActivity::class.java)
|
||||
val pendingContent =
|
||||
PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
val addBroadcast = Intent(AlarmModule.ADD_BROADCAST).apply {
|
||||
setPackage(context.packageName)
|
||||
}
|
||||
val pendingAdd =
|
||||
PendingIntent.getBroadcast(context, 0, addBroadcast, PendingIntent.FLAG_MUTABLE)
|
||||
val stopBroadcast = Intent(AlarmModule.STOP_BROADCAST)
|
||||
stopBroadcast.setPackage(context.packageName)
|
||||
val pendingStop =
|
||||
PendingIntent.getBroadcast(context, 0, stopBroadcast, PendingIntent.FLAG_IMMUTABLE)
|
||||
return NotificationCompat.Builder(context, AlarmModule.CHANNEL_ID_PENDING)
|
||||
.setSmallIcon(R.drawable.ic_baseline_hourglass_bottom_24).setContentTitle("Resting")
|
||||
.setSound(null)
|
||||
.setContentIntent(pendingContent)
|
||||
.addAction(R.drawable.ic_baseline_stop_24, "Stop", pendingStop)
|
||||
.addAction(R.drawable.ic_baseline_stop_24, "Add 1 min", pendingAdd)
|
||||
.setDeleteIntent(pendingStop)
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("Range")
|
||||
private fun getSettings(): Settings {
|
||||
val db = DatabaseHelper(applicationContext).readableDatabase
|
||||
val cursor = db.rawQuery("SELECT sound, noSound, vibrate, duration FROM settings", null)
|
||||
cursor.moveToFirst()
|
||||
val sound = cursor.getString(cursor.getColumnIndex("sound"))
|
||||
val noSound = cursor.getInt(cursor.getColumnIndex("noSound")) == 1
|
||||
val vibrate = cursor.getInt(cursor.getColumnIndex("vibrate")) == 1
|
||||
var duration = cursor.getLong(cursor.getColumnIndex("duration"))
|
||||
if (duration.toInt() == 0) duration = 300
|
||||
cursor.close()
|
||||
return Settings(sound, noSound, vibrate, duration)
|
||||
}
|
||||
|
||||
private fun playSound(settings: Settings) {
|
||||
if (settings.noSound) return
|
||||
if (settings.sound == null) {
|
||||
mediaPlayer = MediaPlayer.create(applicationContext, R.raw.argon)
|
||||
mediaPlayer?.start()
|
||||
mediaPlayer?.setOnCompletionListener { vibrator?.cancel() }
|
||||
} else {
|
||||
mediaPlayer = MediaPlayer().apply {
|
||||
setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build()
|
||||
)
|
||||
setDataSource(applicationContext, Uri.parse(settings.sound))
|
||||
prepare()
|
||||
start()
|
||||
setOnCompletionListener { vibrator?.cancel() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun doNotify(): Notification {
|
||||
val manager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val alarmsChannel = NotificationChannel(
|
||||
CHANNEL_ID_DONE,
|
||||
CHANNEL_ID_DONE,
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
)
|
||||
alarmsChannel.description = "Alarms for rest timers."
|
||||
alarmsChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||
alarmsChannel.setSound(null, null)
|
||||
manager.createNotificationChannel(alarmsChannel)
|
||||
}
|
||||
|
||||
val builder = getBuilder()
|
||||
val context = applicationContext
|
||||
val finishIntent = Intent(context, StopAlarm::class.java)
|
||||
val finishPending = PendingIntent.getActivity(
|
||||
context, 0, finishIntent, PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val fullIntent = Intent(context, TimerDone::class.java)
|
||||
val fullPending = PendingIntent.getActivity(
|
||||
context, 0, fullIntent, PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
builder.setContentText("Timer finished.").setProgress(0, 0, false)
|
||||
.setAutoCancel(true).setOngoing(true).setFullScreenIntent(fullPending, true)
|
||||
.setContentIntent(finishPending).setChannelId(CHANNEL_ID_DONE)
|
||||
.setCategory(NotificationCompat.CATEGORY_ALARM).priority =
|
||||
NotificationCompat.PRIORITY_HIGH
|
||||
val notification = builder.build()
|
||||
manager.notify(NOTIFICATION_ID_DONE, notification)
|
||||
manager.cancel(AlarmModule.NOTIFICATION_ID_PENDING)
|
||||
return notification
|
||||
}
|
||||
|
||||
@SuppressLint("Recycle")
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
val notification = doNotify()
|
||||
startForeground(NOTIFICATION_ID_DONE, notification)
|
||||
val settings = getSettings()
|
||||
playSound(settings)
|
||||
if (!settings.vibrate) return START_STICKY
|
||||
val pattern = longArrayOf(0, settings.duration, 1000, settings.duration, 1000, settings.duration)
|
||||
vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val vibratorManager = getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||
vibratorManager.defaultVibrator
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
getSystemService(VIBRATOR_SERVICE) as Vibrator
|
||||
}
|
||||
vibrator!!.vibrate(VibrationEffect.createWaveform(pattern, -1))
|
||||
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
handler.postDelayed({ vibrator!!.cancel() }, 10000)
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onPrepared(player: MediaPlayer) {
|
||||
player.start()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
mediaPlayer?.stop()
|
||||
mediaPlayer?.release()
|
||||
vibrator?.cancel()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID_DONE = "Alarm"
|
||||
const val NOTIFICATION_ID_DONE = 2
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -4,9 +4,12 @@ import android.content.Context
|
|||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.opencsv.CSVWriter
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
|
||||
class DatabaseHelper(context: Context) :
|
||||
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
|
||||
|
@ -15,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
|
||||
|
@ -24,50 +27,7 @@ class DatabaseHelper(context: Context) :
|
|||
context.contentResolver.openOutputStream(file.uri).use { outputStream ->
|
||||
val csvWrite = CSVWriter(outputStream?.writer())
|
||||
val db = this.readableDatabase
|
||||
|
||||
val setCursor = db.rawQuery("SELECT * FROM sets", null)
|
||||
csvWrite.writeNext(setCursor.columnNames)
|
||||
|
||||
var lastId = 0
|
||||
while(setCursor.moveToNext()) {
|
||||
val arrStr = arrayOfNulls<String>(setCursor.columnCount)
|
||||
for(i in 0 until setCursor.columnCount) {
|
||||
arrStr[i] = setCursor.getString(i)
|
||||
}
|
||||
val id = arrStr[0]?.toInt()
|
||||
if (id != null && id > lastId) lastId = id
|
||||
csvWrite.writeNext(arrStr)
|
||||
}
|
||||
|
||||
val weightCursor = db.rawQuery("SELECT * FROM weights", null)
|
||||
while (weightCursor.moveToNext()) {
|
||||
val arrStr = arrayOfNulls<String>(setCursor.columnCount)
|
||||
arrStr[0] = lastId++.toString()
|
||||
arrStr[1] = "Weight"
|
||||
arrStr[2] = "1"
|
||||
arrStr[3] = weightCursor.getString(1)
|
||||
arrStr[4] = weightCursor.getString(2)
|
||||
arrStr[5] = "kg"
|
||||
|
||||
csvWrite.writeNext(arrStr)
|
||||
}
|
||||
|
||||
csvWrite.close()
|
||||
setCursor.close()
|
||||
weightCursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
val cursor = db.rawQuery("SELECT * FROM sets", null)
|
||||
csvWrite.writeNext(cursor.columnNames)
|
||||
|
||||
while(cursor.moveToNext()) {
|
||||
|
|
|
@ -1,31 +1,5 @@
|
|||
package com.massive
|
||||
|
||||
import android.os.Bundle
|
||||
import com.facebook.react.ReactActivity
|
||||
import com.facebook.react.ReactActivityDelegate
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
||||
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
||||
import com.reactnativenavigation.NavigationActivity
|
||||
|
||||
class MainActivity : ReactActivity() {
|
||||
/**
|
||||
* Returns the name of the main component registered from JavaScript. This is used to schedule
|
||||
* rendering of the component.
|
||||
*/
|
||||
override fun getMainComponentName(): String = "massive"
|
||||
|
||||
/**
|
||||
* Returns the instance of the [ReactActivityDelegate]. Here we use a util class [ ] which allows you to easily enable Fabric and Concurrent React
|
||||
* (aka React 18) with two boolean flags.
|
||||
*/
|
||||
override fun createReactActivityDelegate(): ReactActivityDelegate {
|
||||
return DefaultReactActivityDelegate(
|
||||
this,
|
||||
mainComponentName, // If you opted-in for the New Architecture, we enable the Fabric Renderer.
|
||||
fabricEnabled
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(null)
|
||||
}
|
||||
}
|
||||
class MainActivity : NavigationActivity()
|
|
@ -3,39 +3,42 @@ package com.massive
|
|||
import android.app.Application
|
||||
import com.facebook.react.PackageList
|
||||
import com.facebook.react.ReactApplication
|
||||
import com.facebook.react.ReactHost
|
||||
import com.facebook.react.ReactNativeHost
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
|
||||
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
||||
import com.facebook.react.defaults.DefaultReactNativeHost
|
||||
import com.facebook.react.flipper.ReactNativeFlipper
|
||||
import com.facebook.soloader.SoLoader
|
||||
import com.reactnativenavigation.NavigationApplication
|
||||
import com.reactnativenavigation.react.NavigationReactNativeHost
|
||||
|
||||
class MainApplication : Application(), ReactApplication {
|
||||
override val reactNativeHost: ReactNativeHost =
|
||||
object : DefaultReactNativeHost(this) {
|
||||
override fun getPackages(): List<ReactPackage> =
|
||||
PackageList(this).packages.apply {
|
||||
add(MassivePackage())
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// add(MyReactNativePackage())
|
||||
}
|
||||
|
||||
override fun getJSMainModuleName(): String = "index"
|
||||
|
||||
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
|
||||
|
||||
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
||||
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
|
||||
class MainApplication : NavigationApplication() {
|
||||
private val mReactNativeHost = object : NavigationReactNativeHost(this) {
|
||||
override fun getUseDeveloperSupport(): Boolean {
|
||||
return BuildConfig.DEBUG
|
||||
}
|
||||
|
||||
override val reactHost: ReactHost
|
||||
get() = getDefaultReactHost(this.applicationContext, reactNativeHost)
|
||||
override fun getPackages(): List<ReactPackage> {
|
||||
val packages: MutableList<ReactPackage> = PackageList(this).packages
|
||||
packages.add(MassivePackage())
|
||||
return packages
|
||||
}
|
||||
|
||||
override fun getJSMainModuleName(): String {
|
||||
return "index"
|
||||
}
|
||||
|
||||
override val isNewArchEnabled: Boolean
|
||||
protected get() = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
||||
override val isHermesEnabled: Boolean
|
||||
protected get() = BuildConfig.IS_HERMES_ENABLED
|
||||
}
|
||||
|
||||
override fun getReactNativeHost(): ReactNativeHost {
|
||||
return mReactNativeHost
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
SoLoader.init(this, false)
|
||||
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
||||
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
||||
load()
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
package com.massive
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.massive.AlarmService
|
||||
import com.massive.MainActivity
|
||||
|
||||
class StopAlarm : Activity() {
|
||||
@RequiresApi(Build.VERSION_CODES.O_MR1)
|
||||
|
@ -13,7 +19,7 @@ class StopAlarm : Activity() {
|
|||
Log.d("AlarmActivity", "Call to AlarmActivity")
|
||||
super.onCreate(savedInstanceState)
|
||||
val context = applicationContext
|
||||
context.stopService(Intent(context, TimerService::class.java))
|
||||
context.stopService(Intent(context, AlarmService::class.java))
|
||||
savedInstanceState.apply { setShowWhenLocked(true) }
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
package com.massive
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP
|
||||
import android.app.PendingIntent
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.SystemClock
|
||||
import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
class Timer(private var msTimerDuration: Long) {
|
||||
|
||||
enum class State {
|
||||
Running,
|
||||
Paused,
|
||||
Expired
|
||||
}
|
||||
|
||||
fun start(context: Context) {
|
||||
if (state != State.Paused) return
|
||||
endTime = SystemClock.elapsedRealtime() + msTimerDuration
|
||||
registerPendingIntent(context)
|
||||
state = State.Running
|
||||
}
|
||||
|
||||
fun stop(context: Context) {
|
||||
if (state != State.Running) return
|
||||
msTimerDuration = endTime - SystemClock.elapsedRealtime()
|
||||
unregisterPendingIntent(context)
|
||||
state = State.Paused
|
||||
}
|
||||
|
||||
fun expire() {
|
||||
state = State.Expired
|
||||
msTimerDuration = 0
|
||||
totalTimerDuration = 0
|
||||
}
|
||||
|
||||
fun getRemainingSeconds(): Int {
|
||||
return (getRemainingMillis() / 1000).toInt()
|
||||
}
|
||||
|
||||
fun increaseDuration(context: Context, milli: Long) {
|
||||
val wasRunning = isRunning()
|
||||
if (wasRunning) stop(context)
|
||||
msTimerDuration += milli
|
||||
totalTimerDuration += milli
|
||||
if (wasRunning) start(context)
|
||||
}
|
||||
|
||||
fun isExpired(): Boolean {
|
||||
return state == State.Expired
|
||||
}
|
||||
|
||||
fun getDurationSeconds(): Int {
|
||||
return (totalTimerDuration / 1000).toInt()
|
||||
}
|
||||
|
||||
fun getRemainingMillis(): Long {
|
||||
return if (state == State.Running) endTime - SystemClock.elapsedRealtime()
|
||||
else
|
||||
msTimerDuration
|
||||
}
|
||||
|
||||
private fun isRunning(): Boolean {
|
||||
return state == State.Running
|
||||
}
|
||||
|
||||
private fun requestPermission(context: Context): Boolean {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return true
|
||||
val intent = Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
|
||||
intent.data = Uri.parse("package:" + context.packageName)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
return try {
|
||||
val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context2: Context?, intent: Intent?) {
|
||||
context.unregisterReceiver(this)
|
||||
registerPendingIntent(context)
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.registerReceiver(
|
||||
receiver,
|
||||
IntentFilter(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED),
|
||||
Context.RECEIVER_NOT_EXPORTED
|
||||
)
|
||||
} else {
|
||||
context.registerReceiver(
|
||||
receiver,
|
||||
IntentFilter(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED)
|
||||
)
|
||||
}
|
||||
|
||||
context.startActivity(intent)
|
||||
false
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Request for SCHEDULE_EXACT_ALARM rejected on your device",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun incorrectPermissions(context: Context, alarmManager: AlarmManager): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||
&& !alarmManager.canScheduleExactAlarms()
|
||||
&& !requestPermission(context)
|
||||
}
|
||||
|
||||
private fun getAlarmManager(context: Context): AlarmManager {
|
||||
return context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
}
|
||||
|
||||
private fun unregisterPendingIntent(context: Context) {
|
||||
val intent = Intent(context, TimerService::class.java)
|
||||
.setAction(TimerService.TIMER_EXPIRED)
|
||||
val pendingIntent = PendingIntent.getService(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val alarmManager = getAlarmManager(context)
|
||||
if (incorrectPermissions(context, alarmManager)) return
|
||||
|
||||
alarmManager.cancel(pendingIntent)
|
||||
pendingIntent.cancel()
|
||||
}
|
||||
|
||||
private fun registerPendingIntent(context: Context) {
|
||||
val intent = Intent(context, TimerService::class.java)
|
||||
.setAction(TimerService.TIMER_EXPIRED)
|
||||
val pendingIntent = PendingIntent.getService(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val alarmManager = getAlarmManager(context)
|
||||
if (incorrectPermissions(context, alarmManager)) return
|
||||
|
||||
alarmManager.setExactAndAllowWhileIdle(
|
||||
ELAPSED_REALTIME_WAKEUP,
|
||||
endTime,
|
||||
pendingIntent
|
||||
)
|
||||
}
|
||||
|
||||
private var endTime: Long = 0
|
||||
private var totalTimerDuration: Long = msTimerDuration
|
||||
private var state: State = State.Paused
|
||||
|
||||
companion object {
|
||||
fun emptyTimer(): Timer {
|
||||
return Timer(0)
|
||||
}
|
||||
|
||||
const val ONE_MINUTE_MILLI: Long = 60000
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
package com.massive
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
|
@ -7,24 +10,49 @@ import android.util.Log
|
|||
import android.view.View
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
|
||||
class TimerDone : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_timer_done)
|
||||
Log.d("TimerDone", "Rendered.")
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun stop(view: View) {
|
||||
Log.d("TimerDone", "Stopping...")
|
||||
applicationContext.stopService(Intent(applicationContext, TimerService::class.java))
|
||||
val manager = NotificationManagerCompat.from(this)
|
||||
manager.cancel(TimerService.ONGOING_ID)
|
||||
applicationContext.stopService(Intent(applicationContext, AlarmService::class.java))
|
||||
val manager = getManager()
|
||||
manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
|
||||
manager.cancel(AlarmModule.NOTIFICATION_ID_PENDING)
|
||||
val intent = Intent(applicationContext, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
applicationContext.startActivity(intent)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun getManager(): NotificationManager {
|
||||
val alarmsChannel = NotificationChannel(
|
||||
AlarmService.CHANNEL_ID_DONE,
|
||||
AlarmService.CHANNEL_ID_DONE,
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply {
|
||||
description = "Alarms for rest timers."
|
||||
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||
}
|
||||
val timersChannel = NotificationChannel(
|
||||
AlarmModule.CHANNEL_ID_PENDING,
|
||||
AlarmModule.CHANNEL_ID_PENDING,
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setSound(null, null)
|
||||
description = "Progress on rest timers."
|
||||
}
|
||||
val notificationManager = applicationContext.getSystemService(
|
||||
NotificationManager::class.java
|
||||
)
|
||||
notificationManager.createNotificationChannel(alarmsChannel)
|
||||
notificationManager.createNotificationChannel(timersChannel)
|
||||
return notificationManager
|
||||
}
|
||||
}
|
|
@ -1,358 +0,0 @@
|
|||
package com.massive
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.*
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
||||
import android.media.AudioAttributes
|
||||
import android.media.MediaPlayer
|
||||
import android.net.Uri
|
||||
import android.os.*
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
|
||||
class Settings(val sound: String?, val noSound: Boolean, val vibrate: Boolean, val duration: Long)
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
class TimerService : Service() {
|
||||
|
||||
private lateinit var timerHandler: Handler
|
||||
private var timerRunnable: Runnable? = null
|
||||
private var timer: Timer = Timer.emptyTimer()
|
||||
private var mediaPlayer: MediaPlayer? = null
|
||||
private var vibrator: Vibrator? = null
|
||||
private var currentDescription = ""
|
||||
|
||||
private val stopReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
Log.d("TimerService", "Received stop broadcast intent")
|
||||
timer.stop(applicationContext)
|
||||
timer.expire()
|
||||
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
private val addReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
timer.increaseDuration(applicationContext, Timer.ONE_MINUTE_MILLI)
|
||||
updateNotification(timer.getRemainingSeconds())
|
||||
mediaPlayer?.stop()
|
||||
vibrator?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
timerHandler = Handler(Looper.getMainLooper())
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
applicationContext.registerReceiver(
|
||||
stopReceiver, IntentFilter(STOP_BROADCAST),
|
||||
Context.RECEIVER_NOT_EXPORTED
|
||||
)
|
||||
applicationContext.registerReceiver(
|
||||
addReceiver, IntentFilter(ADD_BROADCAST),
|
||||
Context.RECEIVER_NOT_EXPORTED
|
||||
)
|
||||
} else {
|
||||
applicationContext.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST))
|
||||
applicationContext.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST))
|
||||
}
|
||||
}
|
||||
|
||||
private fun onTimerStart(intent: Intent?) {
|
||||
timerRunnable?.let { timerHandler.removeCallbacks(it) }
|
||||
currentDescription = intent?.getStringExtra("description").toString()
|
||||
|
||||
timer.stop(applicationContext)
|
||||
timer = Timer((intent?.getIntExtra("milliseconds", 0) ?: 0).toLong())
|
||||
timer.start(applicationContext)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(
|
||||
ONGOING_ID,
|
||||
getProgress(timer.getRemainingSeconds()).build(),
|
||||
FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
||||
)
|
||||
} else {
|
||||
startForeground(ONGOING_ID, getProgress(timer.getRemainingSeconds()).build())
|
||||
}
|
||||
|
||||
battery()
|
||||
Log.d("TimerService", "onTimerStart seconds=${timer.getDurationSeconds()}")
|
||||
|
||||
timerRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
val startTime = SystemClock.elapsedRealtime()
|
||||
if (timer.isExpired()) return
|
||||
updateNotification(timer.getRemainingSeconds())
|
||||
|
||||
val delay = timer.getRemainingMillis() % 1000
|
||||
timerHandler.postDelayed(this, if (SystemClock.elapsedRealtime() - startTime + delay > 980) 20 else delay)
|
||||
}
|
||||
}
|
||||
timerHandler.postDelayed(timerRunnable!!, 20)
|
||||
}
|
||||
|
||||
private fun onTimerExpired() {
|
||||
Log.d("TimerService", "onTimerExpired duration=${timer.getDurationSeconds()}")
|
||||
timer.expire()
|
||||
|
||||
val settings = getSettings()
|
||||
vibrate(settings)
|
||||
playSound(settings)
|
||||
notifyFinished()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent != null && intent.action == TIMER_EXPIRED) onTimerExpired()
|
||||
else onTimerStart(intent)
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
timerRunnable?.let { timerHandler.removeCallbacks(it) }
|
||||
applicationContext.unregisterReceiver(stopReceiver)
|
||||
applicationContext.unregisterReceiver(addReceiver)
|
||||
mediaPlayer?.stop()
|
||||
mediaPlayer?.release()
|
||||
vibrator?.cancel()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
@SuppressLint("BatteryLife")
|
||||
fun battery() {
|
||||
val powerManager =
|
||||
applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
val ignoring =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
powerManager.isIgnoringBatteryOptimizations(
|
||||
applicationContext.packageName
|
||||
)
|
||||
else true
|
||||
if (ignoring) return
|
||||
val intent = Intent(android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
|
||||
intent.data = Uri.parse("package:" + applicationContext.packageName)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
try {
|
||||
applicationContext.startActivity(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
"Requests to ignore battery optimizations are disabled on your device.",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("Range")
|
||||
private fun getSettings(): Settings {
|
||||
val db = DatabaseHelper(applicationContext).readableDatabase
|
||||
val cursor = db.rawQuery("SELECT sound, noSound, vibrate, duration FROM settings", null)
|
||||
cursor.moveToFirst()
|
||||
val sound = cursor.getString(cursor.getColumnIndex("sound"))
|
||||
val noSound = cursor.getInt(cursor.getColumnIndex("noSound")) == 1
|
||||
val vibrate = cursor.getInt(cursor.getColumnIndex("vibrate")) == 1
|
||||
var duration = cursor.getLong(cursor.getColumnIndex("duration"))
|
||||
if (duration.toInt() == 0) duration = 300
|
||||
cursor.close()
|
||||
return Settings(sound, noSound, vibrate, duration)
|
||||
}
|
||||
|
||||
private fun playSound(settings: Settings) {
|
||||
if (settings.noSound) return
|
||||
if (settings.sound == null) {
|
||||
mediaPlayer = MediaPlayer.create(applicationContext, R.raw.argon)
|
||||
mediaPlayer?.start()
|
||||
mediaPlayer?.setOnCompletionListener { vibrator?.cancel() }
|
||||
} else {
|
||||
mediaPlayer = MediaPlayer().apply {
|
||||
setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build()
|
||||
)
|
||||
setDataSource(applicationContext, Uri.parse(settings.sound))
|
||||
prepare()
|
||||
start()
|
||||
setOnCompletionListener { vibrator?.cancel() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getProgress(timeLeftInSeconds: Int): NotificationCompat.Builder {
|
||||
val notificationText = formatTime(timeLeftInSeconds)
|
||||
val notificationChannelId = "timer_channel"
|
||||
val notificationIntent = Intent(this, MainActivity::class.java)
|
||||
val contentPending = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
notificationIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val stopBroadcast = Intent(STOP_BROADCAST)
|
||||
stopBroadcast.setPackage(applicationContext.packageName)
|
||||
val stopPending =
|
||||
PendingIntent.getBroadcast(
|
||||
applicationContext,
|
||||
0,
|
||||
stopBroadcast,
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val addBroadcast =
|
||||
Intent(ADD_BROADCAST).apply { setPackage(applicationContext.packageName) }
|
||||
val addPending =
|
||||
PendingIntent.getBroadcast(
|
||||
applicationContext,
|
||||
0,
|
||||
addBroadcast,
|
||||
PendingIntent.FLAG_MUTABLE
|
||||
)
|
||||
|
||||
val notificationBuilder = NotificationCompat.Builder(this, notificationChannelId)
|
||||
.setContentTitle(currentDescription)
|
||||
.setContentText(notificationText)
|
||||
.setSmallIcon(R.drawable.ic_baseline_timer_24)
|
||||
.setProgress(timer.getDurationSeconds(), timeLeftInSeconds, false)
|
||||
.setContentIntent(contentPending)
|
||||
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
||||
.setAutoCancel(false)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setOngoing(true)
|
||||
.setDeleteIntent(stopPending)
|
||||
.addAction(R.drawable.ic_baseline_stop_24, "Stop", stopPending)
|
||||
.addAction(R.drawable.ic_baseline_stop_24, "Add 1 min", addPending)
|
||||
|
||||
val notificationManager = NotificationManagerCompat.from(this)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
notificationChannelId,
|
||||
"Timer Channel",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
return notificationBuilder
|
||||
}
|
||||
|
||||
private fun vibrate(settings: Settings) {
|
||||
if (!settings.vibrate) return
|
||||
val pattern =
|
||||
longArrayOf(0, settings.duration, 1000, settings.duration, 1000, settings.duration)
|
||||
vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val vibratorManager =
|
||||
getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||
vibratorManager.defaultVibrator
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
getSystemService(VIBRATOR_SERVICE) as Vibrator
|
||||
}
|
||||
vibrator!!.vibrate(VibrationEffect.createWaveform(pattern, 2))
|
||||
}
|
||||
|
||||
private fun notifyFinished() {
|
||||
val channelId = "finished_channel"
|
||||
val notificationManager = NotificationManagerCompat.from(this)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel =
|
||||
NotificationChannel(
|
||||
channelId,
|
||||
"Timer Finished Channel",
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
)
|
||||
channel.setSound(null, null)
|
||||
channel.setBypassDnd(true)
|
||||
channel.enableVibration(false)
|
||||
channel.description = "Plays an alarm when a rest timer completes."
|
||||
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
val fullIntent = Intent(applicationContext, TimerDone::class.java)
|
||||
val fullPending = PendingIntent.getActivity(
|
||||
applicationContext, 0, fullIntent, PendingIntent.FLAG_MUTABLE
|
||||
)
|
||||
val finishIntent = Intent(applicationContext, StopAlarm::class.java)
|
||||
val finishPending = PendingIntent.getActivity(
|
||||
applicationContext, 0, finishIntent, PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val stopBroadcast = Intent(STOP_BROADCAST)
|
||||
stopBroadcast.setPackage(applicationContext.packageName)
|
||||
val pendingStop =
|
||||
PendingIntent.getBroadcast(
|
||||
applicationContext,
|
||||
0,
|
||||
stopBroadcast,
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val builder = NotificationCompat.Builder(this, channelId)
|
||||
.setContentTitle("Timer finished")
|
||||
.setContentText(currentDescription)
|
||||
.setSmallIcon(R.drawable.ic_baseline_timer_24)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_ALARM)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentIntent(finishPending)
|
||||
.setFullScreenIntent(fullPending, true)
|
||||
.setAutoCancel(true)
|
||||
.setDeleteIntent(pendingStop)
|
||||
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return
|
||||
}
|
||||
notificationManager.notify(FINISHED_ID, builder.build())
|
||||
}
|
||||
|
||||
private fun updateNotification(seconds: Int) {
|
||||
val notificationManager = NotificationManagerCompat.from(this)
|
||||
val notification = getProgress(seconds)
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return
|
||||
}
|
||||
notificationManager.notify(ONGOING_ID, notification.build())
|
||||
}
|
||||
|
||||
private fun formatTime(timeInSeconds: Int): String {
|
||||
val minutes = timeInSeconds / 60
|
||||
val seconds = timeInSeconds % 60
|
||||
return String.format("%02d:%02d", minutes, seconds)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val STOP_BROADCAST = "stop-timer-event"
|
||||
const val ADD_BROADCAST = "add-timer-event"
|
||||
const val TIMER_EXPIRED = "timer-expired-event"
|
||||
const val ONGOING_ID = 1
|
||||
const val FINISHED_ID = 1
|
||||
}
|
||||
}
|
|
@ -5,6 +5,70 @@
|
|||
android:viewportHeight="108"
|
||||
android:viewportWidth="108"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#1d1f21"
|
||||
<path android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.massive;
|
||||
|
||||
import android.content.Context;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
|
||||
/**
|
||||
* Class responsible of loading Flipper inside your React Native application. This is the release
|
||||
* flavor of it so it's empty as we don't want to load Flipper.
|
||||
*/
|
||||
public class ReactNativeFlipper {
|
||||
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
||||
// Do nothing as we don't want to initialize Flipper on Release.
|
||||
}
|
||||
}
|
|
@ -2,24 +2,26 @@
|
|||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "34.0.0"
|
||||
buildToolsVersion = "33.0.0"
|
||||
minSdkVersion = 21
|
||||
compileSdkVersion = 34
|
||||
targetSdkVersion = 34
|
||||
compileSdkVersion = 33
|
||||
targetSdkVersion = 33
|
||||
|
||||
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
|
||||
ndkVersion = "25.1.8937393"
|
||||
kotlinVersion = "1.8.0"
|
||||
ndkVersion = "23.1.7779620"
|
||||
kotlinVersion = "1.8.22"
|
||||
RNNKotlinVersion = kotlinVersion
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle")
|
||||
classpath("com.facebook.react:react-native-gradle-plugin")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "com.facebook.react.rootproject"
|
||||
|
|
|
@ -24,6 +24,9 @@ android.useAndroidX=true
|
|||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
|
||||
# Version of flipper SDK to use with React Native
|
||||
FLIPPER_VERSION=0.182.0
|
||||
|
||||
# Use this property to specify which architecture you want to build.
|
||||
# You can also override it from the CLI using
|
||||
# ./gradlew <task> -PreactNativeArchitectures=x86_64
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
|
||||
validateDistributionUrl=true
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -83,8 +83,10 @@ done
|
|||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
@ -131,13 +133,10 @@ location of your Java installation."
|
|||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
|
@ -145,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
|
@ -153,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
|
@ -198,10 +197,6 @@ if "$cygwin" || "$msys" ; then
|
|||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
module.exports = {
|
||||
presets: ['module:@react-native/babel-preset'],
|
||||
presets: ['module:metro-react-native-babel-preset'],
|
||||
plugins: [
|
||||
'@babel/plugin-transform-flow-strip-types',
|
||||
['@babel/plugin-proposal-decorators', { legacy: true }],
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
['@babel/plugin-proposal-decorators', {legacy: true}],
|
||||
['@babel/plugin-proposal-class-properties', {loose: true}],
|
||||
'react-native-paper/babel',
|
||||
'react-native-reanimated/plugin',
|
||||
],
|
||||
|
|
|
@ -6,7 +6,7 @@ export const LIGHT_COLORS = [
|
|||
{ hex: "#FA8072", name: "Salmon" },
|
||||
{ hex: "#FFC0CB", name: "Pink" },
|
||||
{ hex: "#E9DCC9", name: "Linen" },
|
||||
{ hex: "#9ACD32", name: "Green" },
|
||||
{ hex: "#9ACD32", name: "Yellow Green" },
|
||||
{ hex: "#FFD700", name: "Gold" },
|
||||
{ hex: "#00CED1", name: "Turquoise" },
|
||||
];
|
||||
|
|
35
deploy.mjs
|
@ -1,37 +1,41 @@
|
|||
import { execSync } from 'child_process';
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import simpleGit from 'simple-git';
|
||||
import os from 'os';
|
||||
|
||||
execSync('npx tsc', { stdio: 'inherit' });
|
||||
process.chdir('android');
|
||||
|
||||
let build = readFileSync('android/app/build.gradle', 'utf8');
|
||||
const buildFilePath = join(process.cwd(), 'app', 'build.gradle');
|
||||
let buildFile = readFileSync(buildFilePath, 'utf8');
|
||||
|
||||
const codeMatch = build.match(/versionCode (\d+)/);
|
||||
if (!codeMatch) throw new Error('versionCode not found in build.gradle');
|
||||
const versionCode = parseInt(codeMatch[1], 10) + 1;
|
||||
build = build.replace(/versionCode \d+/, `versionCode ${versionCode}`);
|
||||
const versionCodeMatch = buildFile.match(/versionCode (\d+)/);
|
||||
if (!versionCodeMatch) throw new Error('versionCode not found in build.gradle');
|
||||
const versionCode = parseInt(versionCodeMatch[1], 10) + 1;
|
||||
buildFile = buildFile.replace(/versionCode \d+/, `versionCode ${versionCode}`);
|
||||
|
||||
const nameMatch = build.match(/versionName "(\d+\.\d+)"/);
|
||||
if (!nameMatch) throw new Error('versionName not found in build.gradle');
|
||||
const versionParts = nameMatch[1].split('.');
|
||||
const versionNameMatch = buildFile.match(/versionName "(\d+\.\d+)"/);
|
||||
if (!versionNameMatch) throw new Error('versionName not found in build.gradle');
|
||||
const versionParts = versionNameMatch[1].split('.');
|
||||
versionParts[1] = (parseInt(versionParts[1], 10) + 1).toString();
|
||||
const versionName = versionParts.join('.');
|
||||
build = build.replace(/versionName "\d+\.\d+"/, `versionName "${versionName}"`);
|
||||
buildFile = buildFile.replace(/versionName "\d+\.\d+"/, `versionName "${versionName}"`);
|
||||
|
||||
writeFileSync('android/app/build.gradle', build);
|
||||
writeFileSync(buildFilePath, buildFile);
|
||||
|
||||
let packageJson = JSON.parse(readFileSync('package.json', 'utf8'));
|
||||
const packageJsonPath = join(process.cwd(), '..', 'package.json');
|
||||
let packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
||||
packageJson.version = versionName;
|
||||
writeFileSync('package.json', JSON.stringify(packageJson, null, 2));
|
||||
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
||||
|
||||
const git = simpleGit();
|
||||
await git.add(['package.json', 'android/app/build.gradle']);
|
||||
await git.add([packageJsonPath, buildFilePath]);
|
||||
await git.log(['-1']).then(log => {
|
||||
const newTitle = `${log.latest.message} - ${versionName} 🚀`;
|
||||
console.log(newTitle);
|
||||
const message = [newTitle, log.latest.body].join('\n');
|
||||
return git.commit(message, [], ['--amend']);
|
||||
const newCommitMessage = [newTitle, log.latest.body].join('\n');
|
||||
return git.commit(newCommitMessage, [], ['--amend']);
|
||||
}).then(() => {
|
||||
return git.addTag(versionCode.toString());
|
||||
}).then(() => {
|
||||
|
@ -40,7 +44,6 @@ await git.log(['-1']).then(log => {
|
|||
console.error('Error amending commit:', err);
|
||||
});
|
||||
|
||||
process.chdir('android')
|
||||
const isWindows = os.platform() === 'win32';
|
||||
execSync(isWindows ? '.\\gradlew.bat bundleRelease -q' : './gradlew bundleRelease -q', { stdio: 'inherit' });
|
||||
execSync('bundle install --quiet', { stdio: 'inherit' });
|
||||
|
|
|
@ -7,5 +7,4 @@ export type DrawerParams = {
|
|||
Weight: {};
|
||||
Insights: {};
|
||||
Settings: {};
|
||||
Daily: {};
|
||||
};
|
||||
|
|
22
index.js
|
@ -2,8 +2,22 @@
|
|||
* @format
|
||||
*/
|
||||
|
||||
import {AppRegistry} from 'react-native';
|
||||
import App from './App';
|
||||
import {name as appName} from './app.json';
|
||||
import { Navigation } from "react-native-navigation";
|
||||
import App from './App'
|
||||
|
||||
AppRegistry.registerComponent(appName, () => App);
|
||||
Navigation.registerComponent('com.massive.WelcomeScreen', () => App)
|
||||
Navigation.events().registerAppLaunchedListener(() => {
|
||||
Navigation.setRoot({
|
||||
root: {
|
||||
stack: {
|
||||
children: [
|
||||
{
|
||||
component: {
|
||||
name: 'com.myApp.WelcomeScreen'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
13
ios/Podfile
|
@ -25,11 +25,17 @@ if linkage != nil
|
|||
use_frameworks! :linkage => linkage.to_sym
|
||||
end
|
||||
|
||||
target 'RnDiffApp' do
|
||||
target 'massive' do
|
||||
config = use_native_modules!
|
||||
|
||||
# Flags change depending on the env values.
|
||||
flags = get_default_flags()
|
||||
|
||||
use_react_native!(
|
||||
:path => config[:reactNativePath],
|
||||
# Hermes is now enabled by default. Disable by setting this flag to false.
|
||||
:hermes_enabled => flags[:hermes_enabled],
|
||||
:fabric_enabled => flags[:fabric_enabled],
|
||||
# Enables Flipper.
|
||||
#
|
||||
# Note that if you have use_frameworks! enabled, Flipper will not work and
|
||||
|
@ -39,7 +45,7 @@ target 'RnDiffApp' do
|
|||
:app_path => "#{Pod::Config.instance.installation_root}/.."
|
||||
)
|
||||
|
||||
target 'RnDiffAppTests' do
|
||||
target 'massiveTests' do
|
||||
inherit! :complete
|
||||
# Pods for testing
|
||||
end
|
||||
|
@ -51,5 +57,6 @@ target 'RnDiffApp' do
|
|||
config[:reactNativePath],
|
||||
:mac_catalyst_enabled => false
|
||||
)
|
||||
__apply_Xcode_12_5_M1_post_install_workaround(installer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
00E356F31AD99517003FC87E /* RnDiffAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* RnDiffAppTests.m */; };
|
||||
0C80B921A6F3F58F76C31292 /* libPods-RnDiffApp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-RnDiffApp.a */; };
|
||||
00E356F31AD99517003FC87E /* massiveTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* massiveTests.m */; };
|
||||
0C80B921A6F3F58F76C31292 /* libPods-massive.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-massive.a */; };
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||
7699B88040F8A987B510C191 /* libPods-RnDiffApp-RnDiffAppTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F6CBCC0A4E27FBF8BF4A61 /* libPods-RnDiffApp-RnDiffAppTests.a */; };
|
||||
7699B88040F8A987B510C191 /* libPods-massive-massiveTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F6CBCC0A4E27FBF8BF4A61 /* libPods-massive-massiveTests.a */; };
|
||||
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -22,27 +22,27 @@
|
|||
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
|
||||
remoteInfo = RnDiffApp;
|
||||
remoteInfo = massive;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
00E356EE1AD99517003FC87E /* RnDiffAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RnDiffAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
00E356EE1AD99517003FC87E /* massiveTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = massiveTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
00E356F21AD99517003FC87E /* RnDiffAppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RnDiffAppTests.m; sourceTree = "<group>"; };
|
||||
13B07F961A680F5B00A75B9A /* RnDiffApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RnDiffApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = RnDiffApp/AppDelegate.h; sourceTree = "<group>"; };
|
||||
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = RnDiffApp/AppDelegate.mm; sourceTree = "<group>"; };
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = RnDiffApp/Images.xcassets; sourceTree = "<group>"; };
|
||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RnDiffApp/Info.plist; sourceTree = "<group>"; };
|
||||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = RnDiffApp/main.m; sourceTree = "<group>"; };
|
||||
19F6CBCC0A4E27FBF8BF4A61 /* libPods-RnDiffApp-RnDiffAppTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RnDiffApp-RnDiffAppTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B4392A12AC88292D35C810B /* Pods-RnDiffApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RnDiffApp.debug.xcconfig"; path = "Target Support Files/Pods-RnDiffApp/Pods-RnDiffApp.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
5709B34CF0A7D63546082F79 /* Pods-RnDiffApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RnDiffApp.release.xcconfig"; path = "Target Support Files/Pods-RnDiffApp/Pods-RnDiffApp.release.xcconfig"; sourceTree = "<group>"; };
|
||||
5B7EB9410499542E8C5724F5 /* Pods-RnDiffApp-RnDiffAppTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RnDiffApp-RnDiffAppTests.debug.xcconfig"; path = "Target Support Files/Pods-RnDiffApp-RnDiffAppTests/Pods-RnDiffApp-RnDiffAppTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
5DCACB8F33CDC322A6C60F78 /* libPods-RnDiffApp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RnDiffApp.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = RnDiffApp/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
89C6BE57DB24E9ADA2F236DE /* Pods-RnDiffApp-RnDiffAppTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RnDiffApp-RnDiffAppTests.release.xcconfig"; path = "Target Support Files/Pods-RnDiffApp-RnDiffAppTests/Pods-RnDiffApp-RnDiffAppTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
00E356F21AD99517003FC87E /* massiveTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = massiveTests.m; sourceTree = "<group>"; };
|
||||
13B07F961A680F5B00A75B9A /* massive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = massive.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = massive/AppDelegate.h; sourceTree = "<group>"; };
|
||||
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = massive/AppDelegate.mm; sourceTree = "<group>"; };
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = massive/Images.xcassets; sourceTree = "<group>"; };
|
||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = massive/Info.plist; sourceTree = "<group>"; };
|
||||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = massive/main.m; sourceTree = "<group>"; };
|
||||
19F6CBCC0A4E27FBF8BF4A61 /* libPods-massive-massiveTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-massive-massiveTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B4392A12AC88292D35C810B /* Pods-massive.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-massive.debug.xcconfig"; path = "Target Support Files/Pods-massive/Pods-massive.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
5709B34CF0A7D63546082F79 /* Pods-massive.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-massive.release.xcconfig"; path = "Target Support Files/Pods-massive/Pods-massive.release.xcconfig"; sourceTree = "<group>"; };
|
||||
5B7EB9410499542E8C5724F5 /* Pods-massive-massiveTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-massive-massiveTests.debug.xcconfig"; path = "Target Support Files/Pods-massive-massiveTests/Pods-massive-massiveTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
5DCACB8F33CDC322A6C60F78 /* libPods-massive.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-massive.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = massive/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
89C6BE57DB24E9ADA2F236DE /* Pods-massive-massiveTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-massive-massiveTests.release.xcconfig"; path = "Target Support Files/Pods-massive-massiveTests/Pods-massive-massiveTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
@ -51,7 +51,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7699B88040F8A987B510C191 /* libPods-RnDiffApp-RnDiffAppTests.a in Frameworks */,
|
||||
7699B88040F8A987B510C191 /* libPods-massive-massiveTests.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -59,20 +59,20 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0C80B921A6F3F58F76C31292 /* libPods-RnDiffApp.a in Frameworks */,
|
||||
0C80B921A6F3F58F76C31292 /* libPods-massive.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
00E356EF1AD99517003FC87E /* RnDiffAppTests */ = {
|
||||
00E356EF1AD99517003FC87E /* massiveTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
00E356F21AD99517003FC87E /* RnDiffAppTests.m */,
|
||||
00E356F21AD99517003FC87E /* massiveTests.m */,
|
||||
00E356F01AD99517003FC87E /* Supporting Files */,
|
||||
);
|
||||
path = RnDiffAppTests;
|
||||
path = massiveTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
00E356F01AD99517003FC87E /* Supporting Files */ = {
|
||||
|
@ -83,7 +83,7 @@
|
|||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
13B07FAE1A68108700A75B9A /* RnDiffApp */ = {
|
||||
13B07FAE1A68108700A75B9A /* massive */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
||||
|
@ -93,15 +93,15 @@
|
|||
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
|
||||
13B07FB71A68108700A75B9A /* main.m */,
|
||||
);
|
||||
name = RnDiffApp;
|
||||
name = massive;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
||||
5DCACB8F33CDC322A6C60F78 /* libPods-RnDiffApp.a */,
|
||||
19F6CBCC0A4E27FBF8BF4A61 /* libPods-RnDiffApp-RnDiffAppTests.a */,
|
||||
5DCACB8F33CDC322A6C60F78 /* libPods-massive.a */,
|
||||
19F6CBCC0A4E27FBF8BF4A61 /* libPods-massive-massiveTests.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
@ -116,9 +116,9 @@
|
|||
83CBB9F61A601CBA00E9B192 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07FAE1A68108700A75B9A /* RnDiffApp */,
|
||||
13B07FAE1A68108700A75B9A /* massive */,
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */,
|
||||
00E356EF1AD99517003FC87E /* RnDiffAppTests */,
|
||||
00E356EF1AD99517003FC87E /* massiveTests */,
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||
BBD78D7AC51CEA395F1C20DB /* Pods */,
|
||||
|
@ -131,8 +131,8 @@
|
|||
83CBBA001A601CBA00E9B192 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07F961A680F5B00A75B9A /* RnDiffApp.app */,
|
||||
00E356EE1AD99517003FC87E /* RnDiffAppTests.xctest */,
|
||||
13B07F961A680F5B00A75B9A /* massive.app */,
|
||||
00E356EE1AD99517003FC87E /* massiveTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -140,10 +140,10 @@
|
|||
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B4392A12AC88292D35C810B /* Pods-RnDiffApp.debug.xcconfig */,
|
||||
5709B34CF0A7D63546082F79 /* Pods-RnDiffApp.release.xcconfig */,
|
||||
5B7EB9410499542E8C5724F5 /* Pods-RnDiffApp-RnDiffAppTests.debug.xcconfig */,
|
||||
89C6BE57DB24E9ADA2F236DE /* Pods-RnDiffApp-RnDiffAppTests.release.xcconfig */,
|
||||
3B4392A12AC88292D35C810B /* Pods-massive.debug.xcconfig */,
|
||||
5709B34CF0A7D63546082F79 /* Pods-massive.release.xcconfig */,
|
||||
5B7EB9410499542E8C5724F5 /* Pods-massive-massiveTests.debug.xcconfig */,
|
||||
89C6BE57DB24E9ADA2F236DE /* Pods-massive-massiveTests.release.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
|
@ -151,9 +151,9 @@
|
|||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
00E356ED1AD99517003FC87E /* RnDiffAppTests */ = {
|
||||
00E356ED1AD99517003FC87E /* massiveTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "RnDiffAppTests" */;
|
||||
buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "massiveTests" */;
|
||||
buildPhases = (
|
||||
A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */,
|
||||
00E356EA1AD99517003FC87E /* Sources */,
|
||||
|
@ -167,16 +167,17 @@
|
|||
dependencies = (
|
||||
00E356F51AD99517003FC87E /* PBXTargetDependency */,
|
||||
);
|
||||
name = RnDiffAppTests;
|
||||
productName = RnDiffAppTests;
|
||||
productReference = 00E356EE1AD99517003FC87E /* RnDiffAppTests.xctest */;
|
||||
name = massiveTests;
|
||||
productName = massiveTests;
|
||||
productReference = 00E356EE1AD99517003FC87E /* massiveTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
13B07F861A680F5B00A75B9A /* RnDiffApp */ = {
|
||||
13B07F861A680F5B00A75B9A /* massive */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RnDiffApp" */;
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "massive" */;
|
||||
buildPhases = (
|
||||
C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */,
|
||||
FD10A7F022414F080027D42C /* Start Packager */,
|
||||
13B07F871A680F5B00A75B9A /* Sources */,
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||
|
@ -188,9 +189,9 @@
|
|||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = RnDiffApp;
|
||||
productName = RnDiffApp;
|
||||
productReference = 13B07F961A680F5B00A75B9A /* RnDiffApp.app */;
|
||||
name = massive;
|
||||
productName = massive;
|
||||
productReference = 13B07F961A680F5B00A75B9A /* massive.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
@ -210,7 +211,7 @@
|
|||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "RnDiffApp" */;
|
||||
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "massive" */;
|
||||
compatibilityVersion = "Xcode 12.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
|
@ -223,8 +224,8 @@
|
|||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
13B07F861A680F5B00A75B9A /* RnDiffApp */,
|
||||
00E356ED1AD99517003FC87E /* RnDiffAppTests */,
|
||||
13B07F861A680F5B00A75B9A /* massive */,
|
||||
00E356ED1AD99517003FC87E /* massiveTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
@ -271,15 +272,15 @@
|
|||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-RnDiffApp/Pods-RnDiffApp-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-massive/Pods-massive-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-RnDiffApp/Pods-RnDiffApp-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-massive/Pods-massive-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RnDiffApp/Pods-RnDiffApp-frameworks.sh\"\n";
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-massive/Pods-massive-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */ = {
|
||||
|
@ -297,7 +298,7 @@
|
|||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-RnDiffApp-RnDiffAppTests-checkManifestLockResult.txt",
|
||||
"$(DERIVED_FILE_DIR)/Pods-massive-massiveTests-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
|
@ -319,7 +320,7 @@
|
|||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-RnDiffApp-checkManifestLockResult.txt",
|
||||
"$(DERIVED_FILE_DIR)/Pods-massive-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
|
@ -332,15 +333,15 @@
|
|||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-RnDiffApp-RnDiffAppTests/Pods-RnDiffApp-RnDiffAppTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-massive-massiveTests/Pods-massive-massiveTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-RnDiffApp-RnDiffAppTests/Pods-RnDiffApp-RnDiffAppTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-massive-massiveTests/Pods-massive-massiveTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RnDiffApp-RnDiffAppTests/Pods-RnDiffApp-RnDiffAppTests-frameworks.sh\"\n";
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-massive-massiveTests/Pods-massive-massiveTests-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = {
|
||||
|
@ -349,15 +350,15 @@
|
|||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-RnDiffApp/Pods-RnDiffApp-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-massive/Pods-massive-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-RnDiffApp/Pods-RnDiffApp-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-massive/Pods-massive-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RnDiffApp/Pods-RnDiffApp-resources.sh\"\n";
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-massive/Pods-massive-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */ = {
|
||||
|
@ -366,15 +367,34 @@
|
|||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-RnDiffApp-RnDiffAppTests/Pods-RnDiffApp-RnDiffAppTests-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-massive-massiveTests/Pods-massive-massiveTests-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-RnDiffApp-RnDiffAppTests/Pods-RnDiffApp-RnDiffAppTests-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-massive-massiveTests/Pods-massive-massiveTests-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RnDiffApp-RnDiffAppTests/Pods-RnDiffApp-RnDiffAppTests-resources.sh\"\n";
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-massive-massiveTests/Pods-massive-massiveTests-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
FD10A7F022414F080027D42C /* Start Packager */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Start Packager";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
@ -384,7 +404,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
00E356F31AD99517003FC87E /* RnDiffAppTests.m in Sources */,
|
||||
00E356F31AD99517003FC87E /* massiveTests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -402,7 +422,7 @@
|
|||
/* Begin PBXTargetDependency section */
|
||||
00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 13B07F861A680F5B00A75B9A /* RnDiffApp */;
|
||||
target = 13B07F861A680F5B00A75B9A /* massive */;
|
||||
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
@ -410,15 +430,15 @@
|
|||
/* Begin XCBuildConfiguration section */
|
||||
00E356F61AD99517003FC87E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 5B7EB9410499542E8C5724F5 /* Pods-RnDiffApp-RnDiffAppTests.debug.xcconfig */;
|
||||
baseConfigurationReference = 5B7EB9410499542E8C5724F5 /* Pods-massive-massiveTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = RnDiffAppTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
|
||||
INFOPLIST_FILE = massiveTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
@ -431,18 +451,18 @@
|
|||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RnDiffApp.app/RnDiffApp";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/massive.app/massive";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
00E356F71AD99517003FC87E /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 89C6BE57DB24E9ADA2F236DE /* Pods-RnDiffApp-RnDiffAppTests.release.xcconfig */;
|
||||
baseConfigurationReference = 89C6BE57DB24E9ADA2F236DE /* Pods-massive-massiveTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
INFOPLIST_FILE = RnDiffAppTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
|
||||
INFOPLIST_FILE = massiveTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
@ -455,19 +475,19 @@
|
|||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RnDiffApp.app/RnDiffApp";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/massive.app/massive";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
13B07F941A680F5B00A75B9A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-RnDiffApp.debug.xcconfig */;
|
||||
baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-massive.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = RnDiffApp/Info.plist;
|
||||
INFOPLIST_FILE = massive/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
@ -479,7 +499,7 @@
|
|||
"-lc++",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = RnDiffApp;
|
||||
PRODUCT_NAME = massive;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
@ -488,12 +508,12 @@
|
|||
};
|
||||
13B07F951A680F5B00A75B9A /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-RnDiffApp.release.xcconfig */;
|
||||
baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-massive.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
INFOPLIST_FILE = RnDiffApp/Info.plist;
|
||||
INFOPLIST_FILE = massive/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
@ -505,7 +525,7 @@
|
|||
"-lc++",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = RnDiffApp;
|
||||
PRODUCT_NAME = massive;
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
|
@ -516,7 +536,7 @@
|
|||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
|
@ -560,7 +580,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
/usr/lib/swift,
|
||||
"$(inherited)",
|
||||
|
@ -577,7 +597,6 @@
|
|||
"-DFOLLY_NO_CONFIG",
|
||||
"-DFOLLY_MOBILE=1",
|
||||
"-DFOLLY_USE_LIBCPP=1",
|
||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||
);
|
||||
SDKROOT = iphoneos;
|
||||
};
|
||||
|
@ -588,7 +607,7 @@
|
|||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
|
@ -625,7 +644,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
/usr/lib/swift,
|
||||
"$(inherited)",
|
||||
|
@ -641,7 +660,6 @@
|
|||
"-DFOLLY_NO_CONFIG",
|
||||
"-DFOLLY_MOBILE=1",
|
||||
"-DFOLLY_USE_LIBCPP=1",
|
||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||
);
|
||||
SDKROOT = iphoneos;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
|
@ -651,7 +669,7 @@
|
|||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "RnDiffAppTests" */ = {
|
||||
00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "massiveTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
00E356F61AD99517003FC87E /* Debug */,
|
||||
|
@ -660,7 +678,7 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RnDiffApp" */ = {
|
||||
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "massive" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
13B07F941A680F5B00A75B9A /* Debug */,
|
||||
|
@ -669,7 +687,7 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "RnDiffApp" */ = {
|
||||
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "massive" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
83CBBA201A601CBA00E9B192 /* Debug */,
|
||||
|
@ -681,4 +699,4 @@
|
|||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
self.moduleName = @"RnDiffApp";
|
||||
self.moduleName = @"massive";
|
||||
// You can add your custom initial props in the dictionary below.
|
||||
// They will be passed down to the ViewController used by React Native.
|
||||
self.initialProps = @{};
|
||||
|
@ -15,11 +15,6 @@
|
|||
}
|
||||
|
||||
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
|
||||
{
|
||||
return [self getBundleURL];
|
||||
}
|
||||
|
||||
- (NSURL *)getBundleURL
|
||||
{
|
||||
#if DEBUG
|
||||
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
|
||||
|
@ -28,4 +23,4 @@
|
|||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>RnDiffApp</string>
|
||||
<string>massive</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
@ -26,11 +26,14 @@
|
|||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<!-- Do not change NSAllowsArbitraryLoads to true, or you will risk app rejection! -->
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
|
@ -49,4 +52,4 @@
|
|||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
|
|
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 |
After Width: | Height: | Size: 104 KiB |
After Width: | Height: | Size: 184 KiB |
After Width: | Height: | Size: 123 KiB |
86
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "massive",
|
||||
"version": "2.35",
|
||||
"version": "2.24",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
|
@ -13,63 +13,63 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-proposal-decorators": "^7.23.9",
|
||||
"@babel/plugin-proposal-decorators": "^7.22.7",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.22.5",
|
||||
"@react-native-community/datetimepicker": "^7.6.2",
|
||||
"@react-native-masked-view/masked-view": "^0.3.0",
|
||||
"@react-navigation/drawer": "^6.6.7",
|
||||
"@react-navigation/native": "^6.1.10",
|
||||
"@react-navigation/stack": "^6.3.21",
|
||||
"@react-native-community/datetimepicker": "^7.4.0",
|
||||
"@react-native-masked-view/masked-view": "^0.2.9",
|
||||
"@react-navigation/drawer": "^6.6.3",
|
||||
"@react-navigation/native": "^6.1.7",
|
||||
"@react-navigation/stack": "^6.3.17",
|
||||
"@testing-library/jest-native": "^5.4.2",
|
||||
"@testing-library/react-native": "^12.1.2",
|
||||
"@types/d3-shape": "^3.1.6",
|
||||
"@types/react-native-sqlite-storage": "^6.0.5",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"@types/d3-shape": "^3.1.1",
|
||||
"@types/react-native-sqlite-storage": "^6.0.0",
|
||||
"@types/react-native-vector-icons": "^6.4.13",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"babel-preset-react-native": "^4.0.1",
|
||||
"date-fns": "^2.30.0",
|
||||
"eslint-plugin-flowtype": "^8.0.3",
|
||||
"react": "18.2.0",
|
||||
"react-hook-form": "^7.50.1",
|
||||
"react-native": "^0.73.0",
|
||||
"react": "^18.2.0",
|
||||
"react-hook-form": "^7.45.1",
|
||||
"react-native": "^0.72.3",
|
||||
"react-native-chart-kit": "^6.12.0",
|
||||
"react-native-document-picker": "^9.1.1",
|
||||
"react-native-file-access": "^3.0.5",
|
||||
"react-native-gesture-handler": "^2.14.0",
|
||||
"react-native-linear-gradient": "^2.8.3",
|
||||
"react-native-pager-view": "^6.2.3",
|
||||
"react-native-paper": "^5.12.3",
|
||||
"react-native-document-picker": "^9.0.1",
|
||||
"react-native-file-access": "^3.0.4",
|
||||
"react-native-gesture-handler": "^2.12.0",
|
||||
"react-native-linear-gradient": "^2.7.3",
|
||||
"react-native-navigation": "^7.38.1",
|
||||
"react-native-pager-view": "^6.2.0",
|
||||
"react-native-paper": "^5.9.1",
|
||||
"react-native-permissions": "^3.10.1",
|
||||
"react-native-reanimated": "^3.6.0",
|
||||
"react-native-safe-area-context": "^4.8.2",
|
||||
"react-native-screens": "^3.28.0",
|
||||
"react-native-reanimated": "^3.3.0",
|
||||
"react-native-safe-area-context": "^4.7.1",
|
||||
"react-native-screens": "^3.22.1",
|
||||
"react-native-share": "^9.2.3",
|
||||
"react-native-sqlite-storage": "^6.0.1",
|
||||
"react-native-svg": "^14.0.0",
|
||||
"react-native-svg": "^13.14.0",
|
||||
"react-native-vector-icons": "^9.2.0",
|
||||
"react-native-view-shot": "^3.8.0",
|
||||
"typeorm": "^0.3.20"
|
||||
"react-native-view-shot": "^3.7.0",
|
||||
"typeorm": "^0.3.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.9",
|
||||
"@babel/preset-env": "^7.23.9",
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@react-native/babel-preset": "0.73.21",
|
||||
"@react-native/eslint-config": "^0.73.2",
|
||||
"@react-native/metro-config": "^0.73.0",
|
||||
"@react-native/typescript-config": "^0.74.0",
|
||||
"@rnx-kit/align-deps": "^2.3.4",
|
||||
"@tsconfig/react-native": "^3.0.3",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "^18.2.55",
|
||||
"@types/react-test-renderer": "^18.0.7",
|
||||
"babel-jest": "^29.7.0",
|
||||
"eslint": "^8.56.0",
|
||||
"jest": "^29.7.0",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"@babel/core": "^7.22.9",
|
||||
"@babel/preset-env": "^7.22.9",
|
||||
"@babel/runtime": "^7.22.6",
|
||||
"@react-native/eslint-config": "^0.72.2",
|
||||
"@react-native/metro-config": "^0.72.9",
|
||||
"@tsconfig/react-native": "^3.0.2",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-test-renderer": "^18.0.0",
|
||||
"babel-jest": "^29.6.1",
|
||||
"eslint": "^8.45.0",
|
||||
"jest": "^29.6.1",
|
||||
"metro-react-native-babel-preset": "^0.77.0",
|
||||
"react-test-renderer": "^18.2.0",
|
||||
"simple-git": "^3.22.0",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
No user data is collected.
|
|
@ -1,17 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"lib": [
|
||||
"es2019"
|
||||
],
|
||||
"lib": ["es2019"],
|
||||
"jsx": "react-native",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"types": [
|
||||
"react-native",
|
||||
"jest",
|
||||
"typeorm"
|
||||
],
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"types": ["react-native", "jest", "typeorm"],
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": true,
|
||||
"noEmit": true,
|
||||
|
@ -29,6 +23,5 @@
|
|||
"babel.config.js",
|
||||
"metro.config.js",
|
||||
"jest.config.js"
|
||||
],
|
||||
"extends": "@react-native/typescript-config/tsconfig.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import { useCallback, useState } from "react";
|
||||
import { NativeModules } from "react-native";
|
||||
import { emitter } from "./emitter";
|
||||
import { TickEvent } from "./TimerPage";
|
||||
|
||||
export default function useTimer() {
|
||||
const [minutes, setMinutes] = useState("00");
|
||||
const [seconds, setSeconds] = useState("00");
|
||||
|
||||
const update = () => {
|
||||
const current: number = NativeModules.AlarmModule.getCurrent();
|
||||
setMinutes(
|
||||
Math.floor(current / 1000 / 60)
|
||||
.toString()
|
||||
.padStart(2, "0")
|
||||
);
|
||||
setSeconds(
|
||||
Math.floor((current / 1000) % 60)
|
||||
.toString()
|
||||
.padStart(2, "0")
|
||||
);
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
update();
|
||||
const listener = emitter.addListener("tick", (event: TickEvent) => {
|
||||
console.log(`${useTimer.name}.tick:`, { event });
|
||||
setMinutes(event.minutes);
|
||||
setSeconds(event.seconds);
|
||||
});
|
||||
return listener.remove;
|
||||
}, [])
|
||||
);
|
||||
|
||||
return { minutes, seconds, update };
|
||||
}
|