Skip to content

Commit

Permalink
feat: abstinence (#508)
Browse files Browse the repository at this point in the history
  • Loading branch information
YoanRos authored Dec 21, 2023
1 parent 24e47db commit 84752db
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 30 deletions.
25 changes: 25 additions & 0 deletions api/src/controllers/appMilestone.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,31 @@ router.post(

// USER SURVEY:
if (req.headers.appversion < 205) return res.status(200).send({ ok: true });
if (req.headers.appversion >= 226) {
const newUserAbstinenceFeature = await prisma.appMilestone.findUnique({
where: { id: `${user.id}_@NewUserAbstinenceFeature` },
});
if (!newUserAbstinenceFeature) {
await prisma.appMilestone.create({
data: {
id: `${user.id}_@NewUserAbstinenceFeature`,
userId: user.id,
date: dayjs().format("YYYY-MM-DD"),
},
});
return res.status(200).send({
ok: true,
showInAppModal: {
id: "@NewUserAbstinenceFeature",
title: "Nouveau : le compteur d'abstinence est arrivé !",
content:
"Si vous avez choisi d'être abstinent, vous pouvez désormais voir votre nombre de jours consécutifs sans avoir consommé d'alcool. Rendez-vous dans l'onglet calendrier !",
CTATitle: "Découvrir",
CTANavigation: ["ABSTINENCE_SELECTION"],
},
});
}
}
if (req.headers.appversion >= 218) {
const newUserShareFeature = await prisma.appMilestone.findUnique({
where: { id: `${user.id}_@NewUserShareFeature` },
Expand Down
2 changes: 1 addition & 1 deletion app/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 225
versionCode 226
versionName "1.22.2"
}

Expand Down
2 changes: 1 addition & 1 deletion app/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "oz_ensemble",
"displayName": "Oz Ensemble",
"version": {
"buildNumber": 225,
"buildNumber": 226,
"buildName": "1.22.2"
}
}
4 changes: 2 additions & 2 deletions app/ios/oz_ensemble.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@
CODE_SIGN_ENTITLEMENTS = oz_ensemble/oz_ensemble.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 225;
CURRENT_PROJECT_VERSION = 226;
DEVELOPMENT_TEAM = 76GBKHVK25;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = oz_ensemble/Info.plist;
Expand Down Expand Up @@ -537,7 +537,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 225;
CURRENT_PROJECT_VERSION = 226;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 76GBKHVK25;
INFOPLIST_FILE = oz_ensemble/Info.plist;
Expand Down
10 changes: 10 additions & 0 deletions app/src/Router.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import BadgeModal from './scenes/Badges/BadgeModal';
import InAppModal from './components/InAppModal';
import Goal from './scenes/Gains/Goal';
import GainsReminder from './scenes/Gains/GainsReminder';
import AbstinenceSelection from './scenes/Gains/AbstinenceSelection';
import GainsPreviousConsumption from './scenes/Gains/GainsPreviousConsumption';
import Sevrage from './scenes/Gains/Sevrage';
import UserSurvey from './scenes/Quizzs/UserSurvey';
Expand Down Expand Up @@ -287,6 +288,15 @@ const Router = () => {
animation: 'fade',
}}
/>
<ModalsStack.Screen
name="ABSTINENCE_SELECTION"
component={AbstinenceSelection}
options={{
headerShown: false,
contentStyle: { backgroundColor: 'rgba(0,0,0,0.3)' },
animation: 'fade',
}}
/>
<ModalsStack.Screen
name="NPS_SCREEN"
component={NPSScreen}
Expand Down
22 changes: 18 additions & 4 deletions app/src/components/Calendar.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useFocusEffect, useNavigation } from '@react-navigation/native';
import dayjs from 'dayjs';
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Text, View, TouchableOpacity, Dimensions, PixelRatio } from 'react-native';
import { useRecoilState, useRecoilValue } from 'recoil';
import { derivedDataFromDrinksState } from '../recoil/consos';
Expand Down Expand Up @@ -75,14 +75,14 @@ const Calendar = ({ onScrollToDate, selectedMonth }) => {
const lastDayOfMonth = selectedMonth.endOf('month');
const firstDayOfCalendar = firstDayOfMonth.startOf('week').format('YYYY-MM-DD');
const today = dayjs().startOf('day');
const { dailyDoses, weeklyDoses, calendarDays, calendarGoalsStartOfWeek } =
const { dailyDoses, weeklyDoses, calendarDays, calendarGoalsStartOfWeek, abstinenceDays } =
useRecoilValue(derivedDataFromDrinksState);
const isOnboarded = useRecoilValue(isOnboardedSelector);
const [modalContent, setModalContent] = useState(null);
const nbDays = dayjs(firstDayOfCalendar).add(35, 'days').diff(lastDayOfMonth) > 0 ? 35 : 42;
const [goals, setGoals] = useRecoilState(goalsState);
const navigation = useNavigation();

const [isAbstinent, setIsAbstinent] = useState(false);
const [showOnboardingGainModal, setShowOnboardingGainModal] = useState(false);
const navigateToFirstStep = useCallback(() => {
logEvent({
Expand All @@ -93,6 +93,9 @@ const Calendar = ({ onScrollToDate, selectedMonth }) => {
setShowOnboardingGainModal(false);
}, [navigation]);

useFocusEffect(() => {
setIsAbstinent(storage.getBoolean('@isAbstinent'));
});
useFocusEffect(
useCallback(() => {
API.get({
Expand Down Expand Up @@ -164,7 +167,6 @@ const Calendar = ({ onScrollToDate, selectedMonth }) => {
}
return daysByWeek;
}, [firstDayOfCalendar, nbDays, calendarDays, calendarGoalsStartOfWeek, today, weeklyDoses, goals.length]);

const handleDayPress = useCallback(
(dateString) => {
if (!isOnboarded) return setShowOnboardingGainModal(true);
Expand Down Expand Up @@ -323,6 +325,18 @@ const Calendar = ({ onScrollToDate, selectedMonth }) => {
</View>
</View>
</View>
{isAbstinent && (
<View className="flex flex-row justify-start bg-[#FEF0D6] py-3 mx-8 rounded-lg">
<View className="ml-2">
<LegendStar size={fontSize * 1.5} />
</View>
<Text className="text-[#4030A5] font-semibold ml-2 mt-1">
{`Abstinent depuis ${abstinenceDays} jour${abstinenceDays > 1 ? 's' : ''} ${
abstinenceDays > 1 ? 'consécutifs' : ''
}`}
</Text>
</View>
)}
<CalendarLegend navigateToFirstStep={navigateToFirstStep} />
<ModalGoal
content={modalContent}
Expand Down
48 changes: 29 additions & 19 deletions app/src/components/InAppModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import AnnouncementCalendar2 from './illustrations/AnnouncementCalendar2';
import UserSurveyLogo from './illustrations/UserSurveyLogo';
import { logEvent } from '../services/logEventsWithMatomo';
import { BadgeShareNoStars } from '../scenes/Badges/Svgs/BadgeShareNoStars';

import StarAbstinenceFeature from './illustrations/icons/StarsAbstinenceFeature';
/* example
{
title: '1er jour complété',
Expand Down Expand Up @@ -108,25 +108,35 @@ const InAppModal = ({ navigation, route }) => {
return (
<SafeAreaView className="bg-white rounded-t-xl mt-auto">
<View className="p-4">
<TouchableOpacity
onPress={() => {
if (inAppModal?.id.includes('NewUserSurveyAnnouncement')) {
logEvent({ category: 'USER_SURVEY', action: 'USER_SURVEY_IN_APP_CLOSE_BUTTON' });
}
onClose();
}}
hitSlop={hitSlop(15)}>
<Svg fill="none" viewBox="0 0 24 24" className="absolute right-0 mb-8 h-5 w-5">
<Path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
stroke="black"
d="M6 18L18 6M6 6l12 12"
/>
</Svg>
</TouchableOpacity>
{!inAppModal?.id.includes('NewUserAbstinenceFeature') && (
<TouchableOpacity
onPress={() => {
if (inAppModal?.id.includes('NewUserSurveyAnnouncement')) {
logEvent({ category: 'USER_SURVEY', action: 'USER_SURVEY_IN_APP_CLOSE_BUTTON' });
}
onClose();
}}
hitSlop={hitSlop(15)}>
<Svg fill="none" viewBox="0 0 24 24" className="absolute right-0 mb-8 h-5 w-5">
<Path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
stroke="black"
d="M6 18L18 6M6 6l12 12"
/>
</Svg>
</TouchableOpacity>
)}
<View className="w-full mb-6 mt-6 flex flex-col items-center space-y-2">
{inAppModal?.id.includes('NewUserAbstinenceFeature') && (
<View className="mx-2 flex flex-col items-center">
<StarAbstinenceFeature />
<Text className="text-4xl text-[#4030A5] font-black">4</Text>
<Text className="font-extrabold text-[#4030A5] text-xl">jours d'affilée</Text>
<Text className="text-light text-[#4030A5]">sans consommation d'alcool</Text>
</View>
)}
{inAppModal?.id.includes('NewCalendarAnnouncement') && (
<View className="mx-2 flex flex-col items-center">
<AnnouncementCalendar1 size={screenWidth - 14} />
Expand Down
14 changes: 14 additions & 0 deletions app/src/components/illustrations/icons/AbstinenceIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as React from 'react';
import Svg, { Path, Rect } from 'react-native-svg';

const AbstinenceIcon = ({ size, ...props }) => (
<Svg width={size} height={size} viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<Rect width="25" height="25" rx="7" fill="#FEF0D6" />
<Path
fill="#FCBC49"
d="M12.258 5.823a1 1 0 0 1 1.484 0l2.593 2.873a1 1 0 0 0 .294.224l3.466 1.738a1 1 0 0 1 .44 1.353L18.682 15.6a1 1 0 0 0-.104.334l-.493 3.941a1 1 0 0 1-1.24.845l-3.598-.916a1 1 0 0 0-.494 0l-3.599.916a1 1 0 0 1-1.239-.845l-.493-3.94a1 1 0 0 0-.104-.335l-1.853-3.59a1 1 0 0 1 .44-1.352L9.37 8.92a1 1 0 0 0 .294-.224l2.593-2.873Z"
/>
</Svg>
);

export default AbstinenceIcon;
15 changes: 15 additions & 0 deletions app/src/components/illustrations/icons/StarsAbstinenceFeature.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as React from 'react';
import Svg, { Path } from 'react-native-svg';

function StarAbstinenceFeature() {
return (
<Svg width={86} height={53} fill="none" xmlns="http://www.w3.org/2000/svg">
<Path
fill="#FCBC49"
d="M42.258.823a1 1 0 0 1 1.484 0l6.486 7.188c.084.093.184.169.295.224l8.572 4.298a1 1 0 0 1 .44 1.353l-4.553 8.816a1 1 0 0 0-.104.335l-1.23 9.838a1 1 0 0 1-1.24.845l-9.161-2.333a1.001 1.001 0 0 0-.494 0l-9.161 2.333a1 1 0 0 1-1.24-.845l-1.23-9.838a1 1 0 0 0-.104-.335l-4.553-8.816a1 1 0 0 1 .44-1.353l8.572-4.298a1 1 0 0 0 .295-.224L42.258.823ZM74.258 31.823a1 1 0 0 1 1.484 0l3.89 4.312a1 1 0 0 0 .295.224l5.168 2.59a1 1 0 0 1 .44 1.354l-2.753 5.33a1 1 0 0 0-.104.336l-.739 5.906a1 1 0 0 1-1.239.845l-5.453-1.388a.998.998 0 0 0-.494 0L69.3 52.72a1 1 0 0 1-1.24-.845l-.738-5.906a1 1 0 0 0-.104-.335l-2.754-5.331a1 1 0 0 1 .44-1.353l5.169-2.592a1 1 0 0 0 .294-.223l3.89-4.312ZM10.258 31.823a1 1 0 0 1 1.484 0l3.89 4.312a1 1 0 0 0 .295.224l5.168 2.59a1 1 0 0 1 .44 1.354l-2.753 5.33a1 1 0 0 0-.104.336l-.739 5.906a1 1 0 0 1-1.239.845l-5.453-1.388a.998.998 0 0 0-.494 0L5.3 52.72a1 1 0 0 1-1.239-.845l-.739-5.906a1 1 0 0 0-.104-.335L.464 40.303a1 1 0 0 1 .44-1.353l5.169-2.592a1 1 0 0 0 .294-.223l3.89-4.312Z"
/>
</Svg>
);
}

export default StarAbstinenceFeature;
15 changes: 14 additions & 1 deletion app/src/recoil/consos.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,14 @@ export const derivedDataFromDrinksState = selector({
const calendarGoalsStartOfWeek = {};
const weeklyKcals = {};
const weeklyExpenses = {};
let abstinenceDays = 0;
let abstinenceSerieBroken = false;
let doneDay = null;

// temp variables
let startOfWeek = null;
let goalStartOfWeek = null;
let currentWeek = {};

for (const drink of drinks) {
const day = dayjs(drink.timestamp).format('YYYY-MM-DD');
// init startOfWeek
Expand Down Expand Up @@ -168,6 +170,16 @@ export const derivedDataFromDrinksState = selector({
dailyDoses[day] = 0;
}
dailyDoses[day] = dailyDoses[day] + dose;
// abstinence days
if (!abstinenceSerieBroken && doneDay !== day) {
if (dose === 0 && (!doneDay || doneDay === dayjs(day).add(1, 'day').format('YYYY-MM-DD'))) {
abstinenceDays++;
} else {
abstinenceSerieBroken = true;
}
}
doneDay = day;

// weekly doses
if (!weeklyDoses[startOfWeek]) {
weeklyDoses[startOfWeek] = 0;
Expand Down Expand Up @@ -295,6 +307,7 @@ export const derivedDataFromDrinksState = selector({
calendarGoalsStartOfWeek,
weeklyKcals,
weeklyExpenses,
abstinenceDays,
};
},
});
2 changes: 0 additions & 2 deletions app/src/scenes/ConsoFollowUp/Feed.js
Original file line number Diff line number Diff line change
Expand Up @@ -452,8 +452,6 @@ const FeedDayItem = ({ date, index, addDrinksRequest, deleteDrinkRequest, update
{drinksContexts[date].context.map((_drinksContext) => {
const contextName = contextsCatalogObject[_drinksContext].displayFeed;



return (
<View key={contextName} className="bg-[#4030A5] rounded-lg py-2 px-2 mr-3 mb-2">
<Text className="color-white font-bold">{contextName}</Text>
Expand Down
51 changes: 51 additions & 0 deletions app/src/scenes/Gains/AbstinenceSelection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import { View, Text, SafeAreaView, TouchableOpacity, Dimensions } from 'react-native';

import { storage } from '../../services/storage';

const AbstinenceSelection = ({ navigation }) => {
const onClose = () => {
navigation.goBack();
};
return (
<SafeAreaView
style={{
justifyContent: 'start',
marginTop: Dimensions.get('window').height * 0.3,
}}
className=" rounded-t-xl">
<View className="bg-white rounded-xl mx-5">
<View className=" flex w-full mb-2 px-2">
<Text className="text-center mb-3 text-xl mt-8 px-2 font-extrabold">Compteur d'abstinence</Text>
<Text className="line text-lg text-justify color-black mb-5 mx-6 mt-2">
Il sera affiché sous votre calendrier et vous permettra de suivre vos jours consécutifs sans consommation
d'alcool si vous avez fait le choix d'être abstinent.
</Text>
<Text className=" text-center mb-1 text-xl px-2 font-bold">
Voulez-vous activer le compteur d'abstinence ?
</Text>
<View className="flex flex-row justify-center p-2 mb-6 mt-2">
<TouchableOpacity
className="justify-center items-center rounded-3xl h-12 w-24 bg-[#DE285E] mr-6"
onPress={() => {
onClose();
storage.set('@isAbstinent', true);
}}>
<Text className="text-xl color-white font-extrabold">Oui</Text>
</TouchableOpacity>
<TouchableOpacity
className="justify-center items-center rounded-3xl h-12 w-24 bg-[#4030A5] mr-6"
onPress={() => {
onClose();
storage.set('@isAbstinent', false);
}}>
<Text className="text-xl color-white font-extrabold">Non</Text>
</TouchableOpacity>
</View>
</View>
</View>
</SafeAreaView>
);
};

export default AbstinenceSelection;
14 changes: 14 additions & 0 deletions app/src/scenes/Infos/Infos.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import ArrowRight from '../../components/ArrowRight';
import PreviousConsumption from '../../components/illustrations/icons/PreviousConsumption';
import GoalSetup from '../../components/illustrations/icons/GoalSetup';
import ReminderIcon from '../../components/illustrations/icons/ReminderIcon';
import AbstinenceIcon from '../../components/illustrations/icons/AbstinenceIcon';
import ExportDataIcon from '../../components/illustrations/icons/ExportDataIcon';
import ShareAppIcon from '../../components/illustrations/icons/ShareAppIcon';
import RateAppIcon from '../../components/illustrations/icons/RateAppIcon';
Expand Down Expand Up @@ -159,6 +160,19 @@ const InfosMenu = ({ navigation }) => {
}}
/>
<View className="w-full border border-[#E8E8EA] mt-4 mb-4" />
<MenuItem
caption={"Compteur d'abstinence"}
Icon={AbstinenceIcon}
onPress={() => {
logEvent({
category: 'ABSTINENCE',
action: 'ABSTINENCE_OPEN',
name: 'INFOS',
});
navigation.navigate('ABSTINENCE_SELECTION');
}}
/>
<View className="w-full border border-[#E8E8EA] mt-4 mb-4" />
<MenuItem
caption={'Exporter mes consommations'}
Icon={ExportDataIcon}
Expand Down

0 comments on commit 84752db

Please sign in to comment.