diff --git a/android/app/build.gradle b/android/app/build.gradle
index f71e32826..d550836d4 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -100,8 +100,8 @@ android {
applicationId "lab.childmindinstitute.data"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 1601
- versionName "2.6.1"
+ versionCode 1604
+ versionName "2.6.2"
resValue "string", "app_name", "Mindlogger"
resValue "string", "build_config_package", "lab.childmindinstitute.data"
manifestPlaceholders = [
diff --git a/assets/translations/el.json b/assets/translations/el.json
index d5375db64..12f1dd150 100644
--- a/assets/translations/el.json
+++ b/assets/translations/el.json
@@ -131,7 +131,7 @@
"common_refresh_error": "Η λίστα βοηθητικών εφαρμογών δεν ανανεώθηκε. Προσπαθήστε ξανά."
},
"activity_list_component": {
- "no_activities_yet": "Δεν έχουν καθοριστεί ακόμη δραστηριότητες",
+ "no_activities": "Δεν υπάρχουν διαθέσιμες δραστηριότητες για να ολοκληρώσετε αυτήν τη στιγμή",
"insufficient_data_error": "Αυτή η βοηθητική εφαρμογή δεν ανανεώθηκε. Προσπαθήστε να ανανεώσετε ξανά.",
"other_error": "Προέκυψε απροσδιόριστο σφάλμα"
},
diff --git a/assets/translations/en.json b/assets/translations/en.json
index 0dae4556c..25d3a8aef 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -131,7 +131,7 @@
"common_refresh_error": "The applets list was not refreshed. Please try again."
},
"activity_list_component": {
- "no_activities_yet": "No activities specified yet",
+ "no_activities": "No activities are available for you to complete right now",
"insufficient_data_error": "This applet was not refreshed. Please try to refresh again.",
"other_error": "Undefined error occurred"
},
diff --git a/assets/translations/fr.json b/assets/translations/fr.json
index 51a702162..2fcaf8828 100644
--- a/assets/translations/fr.json
+++ b/assets/translations/fr.json
@@ -131,7 +131,7 @@
"common_refresh_error": "La liste des applets n'a pas été actualisée. Veuillez réessayer."
},
"activity_list_component": {
- "no_activities_yet": "Aucune activité spécifiée pour le moment",
+ "no_activities": "Aucune activité n'est disponible pour le moment",
"insufficient_data_error": "Cette applet n'a pas été actualisée. Veuillez réessayer d'actualiser.",
"other_error": "Une erreur non définie s'est produite"
},
diff --git a/ios/FlankerNativeComponents/GameManager.swift b/ios/FlankerNativeComponents/GameManager.swift
index 2ec23b7d4..ed88d0687 100644
--- a/ios/FlankerNativeComponents/GameManager.swift
+++ b/ios/FlankerNativeComponents/GameManager.swift
@@ -36,12 +36,16 @@ enum TypeTimeStamps {
}
protocol GameManagerProtocol: AnyObject {
- func updateText(text: String, color: UIColor, font: UIFont, isStart: Bool, typeTime: TypeTimeStamps)
+ func updateText(
+ text: String, color: UIColor, font: UIFont, isStart: Bool, typeTime: TypeTimeStamps)
func updateFixations(image: URL?, isStart: Bool, typeTime: TypeTimeStamps)
func updateTime(time: String)
func setEnableButton(isEnable: Bool)
- func updateTitleButton(left: String?, right: String?, leftImage: URL?, rightImage: URL?, countButton: Int)
- func resultTest(avrgTime: Int?, procentCorrect: Int?, data: FlankerModel?, dataArray: [FlankerModel]?, isShowResults: Bool, minAccuracy: Int)
+ func updateTitleButton(
+ left: String?, right: String?, leftImage: URL?, rightImage: URL?, countButton: Int)
+ func resultTest(
+ avrgTime: Int?, procentCorrect: Int?, data: FlankerModel?, dataArray: [FlankerModel]?,
+ isShowResults: Bool, minAccuracy: Int)
}
class GameManager {
@@ -78,11 +82,11 @@ class GameManager {
weak var delegate: GameManagerProtocol?
private let bootTime: Double = {
- let uptime = CACurrentMediaTime()
- let nowTime = Date().timeIntervalSince1970
- return nowTime - uptime
- }()
-
+ let uptime = CACurrentMediaTime()
+ let nowTime = Date().timeIntervalSince1970
+ return nowTime - uptime
+ }()
+
func startGame(timeSpeed: Float, isShowAnswers: Bool, countGame: Int) {
countAllGame = countGame
timeSpeedGame = TimeInterval(timeSpeed)
@@ -115,7 +119,7 @@ class GameManager {
case .fixations:
startFixationsTimestamp = time
case .trial:
- break
+ break
case .feedback:
startFeedbackTimestamp = time
case .response:
@@ -137,6 +141,7 @@ class GameManager {
func checkedAnswer(button: SelectedButton) {
guard !hasRespondedInCurrentTrial else { return }
+ guard let startTrialTimestamp = startTrialTimestamp else { return }
hasRespondedInCurrentTrial = true
respondTouchButton = bootTime + CACurrentMediaTime()
invalidateTimers()
@@ -144,7 +149,7 @@ class GameManager {
delegate?.setEnableButton(isEnable: false)
guard let gameParameters = gameParameters else { return }
- guard let startTrialTimestamp = startTrialTimestamp else { return }
+ guard countTest >= 0 && countTest < gameParameters.trials.count else { return }
var resultTime = (respondTouchButton! - startTrialTimestamp) * 1000
arrayTimes.append(Int(resultTime))
@@ -155,31 +160,39 @@ class GameManager {
startFeedbackTimestamp = bootTime + CACurrentMediaTime()
let correctChoice = gameParameters.trials[countTest].correctChoice
- let isCorrect = (button == .left && correctChoice == 0) || (button == .right && correctChoice == 1)
+ let isCorrect =
+ (button == .left && correctChoice == 0) || (button == .right && correctChoice == 1)
if isCorrect {
correctAnswers += 1
}
let buttonPressed = (button == .left) ? "0" : "1"
- let model = FlankerModel(rt: resultTime,
- stimulus: text,
- button_pressed: buttonPressed,
- image_time: endTrialTimestamp! * 1000,
- correct: isCorrect,
- start_timestamp: 0,
- tag: Constants.tag,
- trial_index: countTest + 1,
- start_time: startTrialTimestamp * 1000,
- response_touch_timestamp: respondTouchButton! * 1000)
+ let model = FlankerModel(
+ rt: resultTime,
+ stimulus: text,
+ button_pressed: buttonPressed,
+ image_time: endTrialTimestamp! * 1000,
+ correct: isCorrect,
+ start_timestamp: 0,
+ tag: Constants.tag,
+ trial_index: countTest + 1,
+ start_time: startTrialTimestamp * 1000,
+ response_touch_timestamp: respondTouchButton! * 1000)
resultManager.addStepData(data: model)
- delegate?.resultTest(avrgTime: nil, procentCorrect: nil, data: model, dataArray: nil, isShowResults: false, minAccuracy: gameParameters.minimumAccuracy)
+ delegate?.resultTest(
+ avrgTime: nil, procentCorrect: nil, data: model, dataArray: nil, isShowResults: false,
+ minAccuracy: gameParameters.minimumAccuracy)
if gameParameters.showFeedback {
let feedbackText = isCorrect ? Constants.correctText : Constants.inCorrectText
let feedbackColor = isCorrect ? Constants.greenColor : Constants.redColor
- delegate?.updateText(text: feedbackText, color: feedbackColor, font: Constants.smallFont, isStart: false, typeTime: .feedback)
- let timer = Timer(timeInterval: Constants.lowTimeInterval, target: self, selector: #selector(setDefaultText), userInfo: nil, repeats: false)
+ delegate?.updateText(
+ text: feedbackText, color: feedbackColor, font: Constants.smallFont, isStart: false,
+ typeTime: .feedback)
+ let timer = Timer(
+ timeInterval: Constants.lowTimeInterval, target: self, selector: #selector(setDefaultText),
+ userInfo: nil, repeats: false)
RunLoop.main.add(timer, forMode: .common)
} else {
setDefaultText(isFirst: false)
@@ -189,9 +202,10 @@ class GameManager {
@objc func setDefaultText(isFirst: Bool) {
guard let gameParameters = gameParameters else { return }
- hasRespondedInCurrentTrial = false
delegate?.setEnableButton(isEnable: false)
+ hasRespondedInCurrentTrial = false
+
if !isFirst {
endFeedbackTimestamp = bootTime + CACurrentMediaTime()
countTest += 1
@@ -208,12 +222,17 @@ class GameManager {
if gameParameters.showFixation {
startFixationsTimestamp = bootTime + CACurrentMediaTime()
- if let image = URL(string: gameParameters.fixation), gameParameters.fixation.contains("https") {
+ if let image = URL(string: gameParameters.fixation), gameParameters.fixation.contains("https")
+ {
delegate?.updateFixations(image: image, isStart: true, typeTime: .fixations)
} else {
- delegate?.updateText(text: gameParameters.fixation, color: .black, font: Constants.bigFont, isStart: true, typeTime: .fixations)
+ delegate?.updateText(
+ text: gameParameters.fixation, color: .black, font: Constants.bigFont, isStart: true,
+ typeTime: .fixations)
}
- timerSetText = Timer(timeInterval: gameParameters.fixationDuration / 1000, target: self, selector: #selector(setText), userInfo: nil, repeats: false)
+ timerSetText = Timer(
+ timeInterval: gameParameters.fixationDuration / 1000, target: self,
+ selector: #selector(setText), userInfo: nil, repeats: false)
RunLoop.main.add(timerSetText!, forMode: .common)
} else {
setText()
@@ -223,27 +242,33 @@ class GameManager {
@objc func setText() {
guard let gameParameters = gameParameters else { return }
guard countTest < gameParameters.trials.count else {
- handleEndOfGame()
- return
+ handleEndOfGame()
+ return
}
endFixationsTimestamp = bootTime + CACurrentMediaTime()
-
startTrialTimestamp = bootTime + CACurrentMediaTime()
+ hasRespondedInCurrentTrial = false
+
text = gameParameters.trials[countTest].stimulus.en
if let image = URL(string: text), text.contains("https") {
delegate?.updateFixations(image: image, isStart: true, typeTime: .trial)
} else {
- delegate?.updateText(text: text, color: .black, font: Constants.bigFont, isStart: true, typeTime: .trial)
+ delegate?.updateText(
+ text: text, color: .black, font: Constants.bigFont, isStart: true, typeTime: .trial)
}
- DispatchQueue.main.asyncAfter(deadline: .now()) {
- self.delegate?.setEnableButton(isEnable: true)
- self.timeResponse = Timer(timeInterval: gameParameters.trialDuration / 1000, target: self, selector: #selector(self.timeResponseFailed), userInfo: nil, repeats: false)
- RunLoop.main.add(self.timeResponse!, forMode: .common)
- }
+ delegate?.setEnableButton(isEnable: true)
+
+ timeResponse = Timer(
+ timeInterval: gameParameters.trialDuration / 1000,
+ target: self,
+ selector: #selector(timeResponseFailed),
+ userInfo: nil,
+ repeats: false)
+ RunLoop.main.add(timeResponse!, forMode: .common)
}
@objc func timeResponseFailed() {
@@ -256,27 +281,34 @@ class GameManager {
startFeedbackTimestamp = bootTime + CACurrentMediaTime()
if gameParameters.showFeedback {
- delegate?.updateText(text: Constants.timeRespondText, color: .black, font: Constants.smallFont, isStart: false, typeTime: .feedback)
+ delegate?.updateText(
+ text: Constants.timeRespondText, color: .black, font: Constants.smallFont, isStart: false,
+ typeTime: .feedback)
}
guard let startTrialTimestamp = startTrialTimestamp else { return }
- let model = FlankerModel(rt: 0.0,
- stimulus: text,
- button_pressed: nil,
- image_time: endTrialTimestamp! * 1000, // має намалювати
- correct: false,
- start_timestamp: 0, // вже намальовано
- tag: Constants.tag,
- trial_index: countTest + 1,
- start_time: startTrialTimestamp * 1000,
- response_touch_timestamp: 0)
+ let model = FlankerModel(
+ rt: 0.0,
+ stimulus: text,
+ button_pressed: nil,
+ image_time: endTrialTimestamp! * 1000, // має намалювати
+ correct: false,
+ start_timestamp: 0, // вже намальовано
+ tag: Constants.tag,
+ trial_index: countTest + 1,
+ start_time: startTrialTimestamp * 1000,
+ response_touch_timestamp: 0)
resultManager.addStepData(data: model)
- delegate?.resultTest(avrgTime: nil, procentCorrect: nil, data: model, dataArray: nil,isShowResults: gameParameters.showResults, minAccuracy: gameParameters.minimumAccuracy)
+ delegate?.resultTest(
+ avrgTime: nil, procentCorrect: nil, data: model, dataArray: nil,
+ isShowResults: gameParameters.showResults, minAccuracy: gameParameters.minimumAccuracy)
if gameParameters.showFeedback {
- let timer = Timer(timeInterval: Constants.lowTimeInterval, target: self, selector: #selector(setDefaultText), userInfo: nil, repeats: false)
+ let timer = Timer(
+ timeInterval: Constants.lowTimeInterval, target: self, selector: #selector(setDefaultText),
+ userInfo: nil, repeats: false)
RunLoop.main.add(timer, forMode: .common)
} else {
setDefaultText(isFirst: false)
@@ -314,6 +346,7 @@ class GameManager {
countTest = -1
correctAnswers = 0
arrayTimes = []
+ hasRespondedInCurrentTrial = false
invalidateTimers()
}
@@ -323,8 +356,8 @@ class GameManager {
}
}
-private extension GameManager {
- func updateButtonTitle() {
+extension GameManager {
+ fileprivate func updateButtonTitle() {
guard let gameParameters = gameParameters else { return }
guard countTest < gameParameters.trials.count else { return }
diff --git a/ios/MindloggerMobile.xcodeproj/project.pbxproj b/ios/MindloggerMobile.xcodeproj/project.pbxproj
index 52aeb23ba..9ad9452e2 100644
--- a/ios/MindloggerMobile.xcodeproj/project.pbxproj
+++ b/ios/MindloggerMobile.xcodeproj/project.pbxproj
@@ -1667,7 +1667,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
- CURRENT_PROJECT_VERSION = 1601;
+ CURRENT_PROJECT_VERSION = 1604;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
@@ -1679,7 +1679,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
- MARKETING_VERSION = 2.6.1;
+ MARKETING_VERSION = 2.6.2;
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
@@ -1698,7 +1698,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1601;
+ CURRENT_PROJECT_VERSION = 1604;
INFOPLIST_FILE = MindloggerMobileTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
LD_RUNPATH_SEARCH_PATHS = (
@@ -1706,7 +1706,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
- MARKETING_VERSION = 2.6.1;
+ MARKETING_VERSION = 2.6.2;
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
@@ -1728,7 +1728,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 1601;
+ CURRENT_PROJECT_VERSION = 1604;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8RHKE85KB6;
ENABLE_BITCODE = NO;
@@ -1739,7 +1739,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 2.6.1;
+ MARKETING_VERSION = 2.6.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1769,7 +1769,7 @@
CODE_SIGN_ENTITLEMENTS = MindloggerMobile/MindloggerMobileRelease.entitlements;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 1601;
+ CURRENT_PROJECT_VERSION = 1604;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8RHKE85KB6;
INFOPLIST_FILE = MindloggerMobile/Info.plist;
@@ -1779,7 +1779,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 2.6.1;
+ MARKETING_VERSION = 2.6.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1979,7 +1979,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 1601;
+ CURRENT_PROJECT_VERSION = 1604;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8RHKE85KB6;
ENABLE_BITCODE = NO;
@@ -1990,7 +1990,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 2.6.1;
+ MARKETING_VERSION = 2.6.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -2019,7 +2019,7 @@
CODE_SIGN_ENTITLEMENTS = MindloggerMobileDevRelease.entitlements;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 1601;
+ CURRENT_PROJECT_VERSION = 1604;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8RHKE85KB6;
INFOPLIST_FILE = "MindloggerMobile dev-Info.plist";
@@ -2029,7 +2029,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 2.6.1;
+ MARKETING_VERSION = 2.6.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -2058,7 +2058,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 1601;
+ CURRENT_PROJECT_VERSION = 1604;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8RHKE85KB6;
ENABLE_BITCODE = NO;
@@ -2069,7 +2069,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 2.6.1;
+ MARKETING_VERSION = 2.6.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -2099,7 +2099,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 1601;
+ CURRENT_PROJECT_VERSION = 1604;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8RHKE85KB6;
INFOPLIST_FILE = "MindloggerMobile qa-Info.plist";
@@ -2109,7 +2109,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 2.6.1;
+ MARKETING_VERSION = 2.6.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -2136,7 +2136,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MindloggerMobileStagingDebug.entitlements;
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 1601;
+ CURRENT_PROJECT_VERSION = 1604;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8RHKE85KB6;
ENABLE_BITCODE = NO;
@@ -2147,7 +2147,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 2.6.1;
+ MARKETING_VERSION = 2.6.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -2176,7 +2176,7 @@
CODE_SIGN_ENTITLEMENTS = MindloggerMobileStagingRelease.entitlements;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 1601;
+ CURRENT_PROJECT_VERSION = 1604;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8RHKE85KB6;
INFOPLIST_FILE = "MindloggerMobile staging-Info.plist";
@@ -2186,7 +2186,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 2.6.1;
+ MARKETING_VERSION = 2.6.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -2215,7 +2215,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 1601;
+ CURRENT_PROJECT_VERSION = 1604;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8RHKE85KB6;
ENABLE_BITCODE = NO;
@@ -2226,7 +2226,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 2.6.1;
+ MARKETING_VERSION = 2.6.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -2256,7 +2256,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 1601;
+ CURRENT_PROJECT_VERSION = 1604;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8RHKE85KB6;
INFOPLIST_FILE = "MindloggerMobile uat-Info.plist";
@@ -2266,7 +2266,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 2.6.1;
+ MARKETING_VERSION = 2.6.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
diff --git a/src/features/pass-survey/model/flankerNextStepEvaluator.ts b/src/features/pass-survey/model/flankerNextStepEvaluator.ts
index 8576959d1..682e82d27 100644
--- a/src/features/pass-survey/model/flankerNextStepEvaluator.ts
+++ b/src/features/pass-survey/model/flankerNextStepEvaluator.ts
@@ -47,11 +47,7 @@ export const evaluateFlankerNextStep = (
}
}
- const minimumAccuracy = itemConfiguration.minimumAccuracy;
-
- if (!minimumAccuracy) {
- return currentIndex + 1;
- }
+ const minimumAccuracy = itemConfiguration.minimumAccuracy ?? 0;
if (correctCount * 100 >= totalCount * minimumAccuracy) {
const lastPracticeIndex = items.findIndex(
diff --git a/src/screens/ui/RootNavigator.tsx b/src/screens/ui/RootNavigator.tsx
index eea70cea8..da7959974 100644
--- a/src/screens/ui/RootNavigator.tsx
+++ b/src/screens/ui/RootNavigator.tsx
@@ -242,7 +242,7 @@ export const RootNavigator = () => {
- Back
+ {t('applet_invite_flow:back')}
diff --git a/src/shared/ui/icons/ChecklistIcon.tsx b/src/shared/ui/icons/ChecklistIcon.tsx
new file mode 100644
index 000000000..f453cb2b4
--- /dev/null
+++ b/src/shared/ui/icons/ChecklistIcon.tsx
@@ -0,0 +1,10 @@
+import Svg, { Path, SvgProps } from 'react-native-svg';
+
+export const ChecklistIcon = (props: SvgProps) => (
+
+);
diff --git a/src/widgets/activity-group/ui/ActivityGroups.tsx b/src/widgets/activity-group/ui/ActivityGroups.tsx
index d04511d8e..425d52c10 100644
--- a/src/widgets/activity-group/ui/ActivityGroups.tsx
+++ b/src/widgets/activity-group/ui/ActivityGroups.tsx
@@ -1,6 +1,7 @@
-import { FC } from 'react';
+import { FC, useMemo } from 'react';
import { useIsFetching } from '@tanstack/react-query';
+import { useTranslation } from 'react-i18next';
import {
CheckAvailability,
@@ -9,10 +10,12 @@ import {
import { getAppletCompletedEntitiesKey } from '@app/shared/lib/utils/reactQueryHelpers';
import { ActivityIndicator } from '@app/shared/ui/ActivityIndicator';
import { Box, BoxProps, XStack, YStack } from '@app/shared/ui/base';
+import { ChecklistIcon } from '@app/shared/ui/icons/ChecklistIcon';
import { LoadListError } from '@app/shared/ui/LoadListError';
-import { NoListItemsYet } from '@app/shared/ui/NoListItemsYet';
import { ActivitySectionList } from './ActivitySectionList';
+import { EmptyState } from './EmptyState';
+import { ActivityGroupType } from '../lib/types/activityGroup';
import { useActivityGroups } from '../model/hooks/useActivityGroups';
import { useBaseInfo } from '../model/hooks/useBaseInfo';
@@ -23,6 +26,8 @@ type Props = {
} & BoxProps;
export const ActivityGroups: FC = props => {
+ const { t } = useTranslation();
+
const isLoadingCompletedEntities =
useIsFetching({
exact: true,
@@ -34,6 +39,26 @@ export const ActivityGroups: FC = props => {
const { responseTypes } = data || {};
const hasError = !isSuccess || !!baseInfoError;
+ const renderedGroups = useMemo(() => {
+ const hasActivities = groups.some(g => g.activities.length);
+
+ if (hasActivities) {
+ // Only show empty available group if there are no in-progress activities
+ const showAvailableGroup = !groups.some(
+ g => g.type === ActivityGroupType.InProgress && g.activities.length,
+ );
+
+ // Filter out empty groups, but show the available group based on above logic
+ return groups.filter(
+ g =>
+ g.activities.length ||
+ (g.type === ActivityGroupType.Available && showAvailableGroup),
+ );
+ } else {
+ return null;
+ }
+ }, [groups]);
+
if (isLoadingCompletedEntities || isLoading) {
return (
= props => {
);
}
- if (isSuccess && !groups?.length) {
- return (
-
-
-
- );
- }
-
return (
-
+ {renderedGroups ? (
+
+ ) : (
+ }
+ description={t('activity_list_component:no_activities')}
+ />
+ )}
);
diff --git a/src/widgets/activity-group/ui/ActivitySectionList.tsx b/src/widgets/activity-group/ui/ActivitySectionList.tsx
index b01d49c65..9a98e838c 100644
--- a/src/widgets/activity-group/ui/ActivitySectionList.tsx
+++ b/src/widgets/activity-group/ui/ActivitySectionList.tsx
@@ -28,8 +28,10 @@ import {
getSupportsMobile,
} from '@app/shared/lib/utils/responseTypes';
import { Box, YStack } from '@app/shared/ui/base';
+import { ChecklistIcon } from '@app/shared/ui/icons/ChecklistIcon';
import { Text } from '@app/shared/ui/Text';
+import { EmptyState } from './EmptyState';
import { ActivityListGroup } from '../lib/types/activityGroup';
import { useAvailabilityEvaluator } from '../model/hooks/useAvailabilityEvaluator';
@@ -54,16 +56,10 @@ export function ActivitySectionList({
const { isUploading } = useUploadObservable();
- const sections = useMemo(() => {
- return groups
- .filter(g => g.activities.length)
- .map(group => {
- return {
- data: group.activities,
- key: t(group.name),
- };
- });
- }, [t, groups]);
+ const sections = useMemo(
+ () => groups.map(group => ({ data: group.activities, key: t(group.name) })),
+ [t, groups],
+ );
const { startFlow, startActivity } = useStartEntity({
hasMediaReferences: getDefaultMediaLookupService().hasMediaReferences,
@@ -200,6 +196,16 @@ export function ActivitySectionList({
/>
);
}}
+ // SectionList doesn't provide a prop for section empty components, so we use
+ // renderSectionFooter to conditionally render any empty sections.
+ renderSectionFooter={({ section }) =>
+ section.data.length ? null : (
+ }
+ description={t('activity_list_component:no_activities')}
+ />
+ )
+ }
ItemSeparatorComponent={ItemSeparator}
stickySectionHeadersEnabled={false}
contentContainerStyle={styles.sectionList}
diff --git a/src/widgets/activity-group/ui/EmptyState.tsx b/src/widgets/activity-group/ui/EmptyState.tsx
new file mode 100644
index 000000000..080b1f3c6
--- /dev/null
+++ b/src/widgets/activity-group/ui/EmptyState.tsx
@@ -0,0 +1,26 @@
+import { ReactNode } from 'react';
+
+import { BoxProps, YStack } from '@app/shared/ui/base';
+import { Text } from '@app/shared/ui/Text';
+
+type Props = BoxProps & {
+ icon: ReactNode;
+ description: string;
+};
+
+export const EmptyState = ({ icon, description, ...rest }: Props) => {
+ return (
+
+ {icon}
+
+ {description}
+
+
+ );
+};