diff --git a/.gitea/workflows/test-flight-cd.yaml b/.gitea/workflows/test-flight-cd.yaml
index 4320ecc5..ac54d483 100644
--- a/.gitea/workflows/test-flight-cd.yaml
+++ b/.gitea/workflows/test-flight-cd.yaml
@@ -11,6 +11,30 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
+ - name: Checkout env repository
+ uses: actions/checkout@v2
+ with:
+ repository: mso7nueh/eatvio_app_env
+ path: env_repo
+ token: ${{ vars.MSO7NUEH_TOKEN }}
+ ref: main
+
+ - name: Copy .env file
+ run: |
+ mkdir -p env
+ cp env_repo/.env env/.env
+
+ - name: Load environment variables from .env
+ run: |
+ set -a
+ source env/.env
+ set +a
+ # Export all variables to GITHUB_ENV
+ while IFS= read -r line; do
+ if [[ $line =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
+ echo "$line" >> $GITHUB_ENV
+ fi
+ done < env/.env
- name: Unlock login keychain
run: |
@@ -18,9 +42,19 @@ jobs:
env:
KEYCHAIN_PASSWORD: ${{ vars.IOS_RUNNER_CI_PASSWORD }}
+ - name: Setup API key
+ run: |
+ echo "${API_APPSTORE_KEY}" | tr -d '\n' | base64 --decode > ios/AuthKey.p8
+ chmod 600 ios/AuthKey.p8
+ ls -la ios/AuthKey.p8
+ echo "File size: $(wc -c < ios/AuthKey.p8) bytes"
+ echo "Contents of ios directory:"
+ ls -la ios/
+ env:
+ API_APPSTORE_KEY: ${{ vars.API_APPSTORE_KEY }}
+
- name: Flutter build
run: |
- echo "${API_APPSTORE_KEY}" | base64 --decode > ios/AuthKey.p8
flutter clean
flutter pub get
flutter pub upgrade
@@ -36,4 +70,4 @@ jobs:
MATCH_GIT_URL: http://gitea.office.local:3000/mso7nueh/fastlane
FASTLANE_USER: eatvio2023@icloud.com
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ vars.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
- API_APPSTORE_KEY: ${{ vars.API_APPSTORE_KEY }}
\ No newline at end of file
+ API_APPSTORE_KEY: ${{ vars.API_APPSTORE_KEY }}
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 6388d648..2e0d56e4 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -396,7 +396,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 65;
+ CURRENT_PROJECT_VERSION = 67;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -546,7 +546,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 65;
+ CURRENT_PROJECT_VERSION = 67;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -584,7 +584,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 65;
+ CURRENT_PROJECT_VERSION = 67;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 9a16914c..a1ded5ee 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -21,7 +21,7 @@
CFBundleSignature
????
CFBundleVersion
- 65
+ 67
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile
index d9653264..f7261196 100644
--- a/ios/fastlane/Fastfile
+++ b/ios/fastlane/Fastfile
@@ -18,8 +18,35 @@ default_platform(:ios)
platform :ios do
desc "Push a new beta build to TestFlight"
lane :beta do
+ UI.message "Current working directory: #{Dir.pwd}"
+ UI.message "Checking API key file..."
+
+ possible_paths = [
+ "../AuthKey.p8",
+ "./AuthKey.p8",
+ "../../AuthKey.p8",
+ File.expand_path("../AuthKey.p8"),
+ File.expand_path("./AuthKey.p8")
+ ]
+
+ auth_key_path = nil
+ possible_paths.each do |path|
+ if File.exist?(path)
+ UI.message "Found AuthKey.p8 at: #{path}"
+ auth_key_path = File.expand_path(path)
+ break
+ end
+ end
+
+ if auth_key_path.nil?
+ UI.user_error!("API key file not found. Tried paths: #{possible_paths.join(', ')}")
+ end
+
+ UI.message "Using auth_key_path: #{auth_key_path}"
+
match(type: "appstore", readonly: true)
increment_build_number(xcodeproj: "Runner.xcodeproj")
+
build_app(
workspace: "Runner.xcworkspace",
scheme: "Runner",
@@ -27,7 +54,30 @@ platform :ios do
codesigning_identity: "Apple Distribution",
xcargs: "--dart-define=HIDE_PLANS=true"
)
- app_store_connect_api_key(key_id: "325JS9JCZH", issuer_id: "dfa27d1d-2bb8-4d4d-847e-0b32f9b880b3", key_filepath: "./AuthKey.p8")
- upload_to_testflight
+
+
+
+ app_store_connect_api_key(
+ key_id: "325JS9JCZH",
+ issuer_id: "dfa27d1d-2bb8-4d4d-847e-0b32f9b880b3",
+ key_filepath: auth_key_path,
+ duration: 1200,
+ in_house: false
+ )
+
+ begin
+ upload_to_testflight(
+ skip_waiting_for_build_processing: true,
+ skip_submission: true,
+ distribute_external: false,
+ notify_external_testers: false,
+ changelog: "TestFlight build #{get_build_number(xcodeproj: 'Runner.xcodeproj')}",
+ verbose: true
+ )
+ rescue => ex
+ UI.error "Upload to TestFlight failed: #{ex.message}"
+ UI.error "Full error: #{ex.backtrace.join("\n")}"
+ raise ex
+ end
end
end
diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart
index 1bbfbbc7..6e6f8891 100644
--- a/lib/features/diary/presentation/bloc/diary_cubit.dart
+++ b/lib/features/diary/presentation/bloc/diary_cubit.dart
@@ -136,15 +136,7 @@ class DiaryCubit extends Cubit {
final fifthMeal = MealModel(id: 0, time: '0', typeNumber: 5, satiety: 0, mood: 0, portions: fifthMealPortions ?? [], photos: []);
emit(
DiaryState.loadedTomorrowWithDiet(
- diary: diaryDay.copyWith(
- meals: [
- firstMeal,
- secondMeal,
- thirdMeal,
- fourthMeal,
- fifthMeal,
- ],
- ),
+ diary: diaryDay.copyWith(meals: [firstMeal, secondMeal, thirdMeal, fourthMeal, fifthMeal]),
selectedDay: selectedDay,
selectedMeal: selectedMeal,
),
@@ -156,7 +148,7 @@ class DiaryCubit extends Cubit {
Future fetchDiary([bool needLoading = false, int? diaryId, int? currentDay]) async {
this.diaryId ??= diaryId;
-
+ final isConsultantView = this.diaryId != null;
if (needLoading) {
emit(const DiaryState.loading());
selectedMeal = 1;
@@ -180,7 +172,8 @@ class DiaryCubit extends Cubit {
String? planName;
if (diaryDay.chosenPlanId != null) {
final chosenPlanModel = await chatRepository.getChosenPlan(id: diaryDay.chosenPlanId!);
- if (diaryDay.typeOfDiary.diaryState == DiaryStateEnum.lastDayCommented || diaryDay.typeOfDiary.diaryState == DiaryStateEnum.commented) {
+ if (!isConsultantView &&
+ (diaryDay.typeOfDiary.diaryState == DiaryStateEnum.lastDayCommented || diaryDay.typeOfDiary.diaryState == DiaryStateEnum.commented)) {
await diaryRepository.viewCommentOfDiaryDayByConsultant(diaryId: diaryDay.id);
}
consultantReview = await diaryRepository.getDiaryComments(planId: diaryDay.chosenPlanId!, diaryId: diaryDay.id);
@@ -192,24 +185,26 @@ class DiaryCubit extends Cubit {
final isDiaryFinished = diaryDay.typeOfDiary.diaryState == DiaryStateEnum.finished || diaryDay.typeOfDiary.diaryState == DiaryStateEnum.lastDayFinished;
- final isDiaryCommented = diaryDay.typeOfDiary.diaryState == DiaryStateEnum.commented ||
+ final isDiaryCommented =
+ diaryDay.typeOfDiary.diaryState == DiaryStateEnum.commented ||
diaryDay.typeOfDiary.diaryState == DiaryStateEnum.lastDayCommented ||
diaryDay.typeOfDiary.diaryState == DiaryStateEnum.reviewedByUser;
emit(
DiaryState.loaded(
- diary: diaryDay,
- nutrition: nutrition,
- selectedDay: selectedDay,
- selectedMeal: selectedMeal,
- isConsultantView: this.diaryId != null,
- consultantReview: consultantReview,
- isDiaryFinished: isDiaryFinished,
- isDiaryCommented: isDiaryCommented,
- chosenPlanCurrentDay: currentPlanDay,
- chosenPlanDuration: planDuration,
- chosenPlanName: planName,
- isMoodEnabled: isMoodEnabled,
- isWakeUpAndRetireEnabled: isWakeUpAndRetireTimeEnabled),
+ diary: diaryDay,
+ nutrition: nutrition,
+ selectedDay: selectedDay,
+ selectedMeal: selectedMeal,
+ isConsultantView: isConsultantView,
+ consultantReview: consultantReview,
+ isDiaryFinished: isDiaryFinished,
+ isDiaryCommented: isDiaryCommented,
+ chosenPlanCurrentDay: currentPlanDay,
+ chosenPlanDuration: planDuration,
+ chosenPlanName: planName,
+ isMoodEnabled: isMoodEnabled,
+ isWakeUpAndRetireEnabled: isWakeUpAndRetireTimeEnabled,
+ ),
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
@@ -217,17 +212,8 @@ class DiaryCubit extends Cubit {
}
}
- Future finishDiaryDay({
- required int diaryId,
- required String wakeUp,
- required String retire,
- }) async {
- await diaryRepository.finishDiaryDay(
- diaryId: diaryId,
- wakeUp: wakeUp,
- retire: retire,
- comment: comment.isEmpty ? null : comment,
- );
+ Future finishDiaryDay({required int diaryId, required String wakeUp, required String retire}) async {
+ await diaryRepository.finishDiaryDay(diaryId: diaryId, wakeUp: wakeUp, retire: retire, comment: comment.isEmpty ? null : comment);
await fetchDiary();
}
diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart
index 211543a9..f3329074 100644
--- a/lib/features/profile/presentation/pages/profile_screen.dart
+++ b/lib/features/profile/presentation/pages/profile_screen.dart
@@ -760,76 +760,92 @@ class _ProfileScreenState extends State {
child: Row(
children: [
PopupMenuButton(
- onSelected: (value) async {
- if (value == 'view') {
- context.pushTransparentRoute(rootNavigator: true, PhotoViewScreen(imageUrls: [baseUrl + state.profile.profilePhotoUrl], index: 0));
- }
- if (value == 'replace') {
- showCupertinoModalPopup(
- context: context,
- builder: (context) {
- final s = S.of(context);
- return CupertinoActionSheet(
- actions: [
- CupertinoActionSheetAction(
- onPressed: () async {
- PermissionHandler.checkPermission(
- permission: Permission.camera,
- title: s.allowAccessToCamera,
- description: s.allowAccessToCameraDescription,
- onGranted: () async {
- AppRouter.navigatorKey.currentContext?.pop();
- final picker = ImagePicker();
- XFile? image = await picker.pickImage(source: ImageSource.camera);
- if (image != null) {
- await profileCubit.uploadPhoto(File(image.path));
- await sl().getMyProfile();
- }
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
+ color: Colors.white,
+ constraints: const BoxConstraints(minWidth: 260),
+ surfaceTintColor: Colors.transparent,
+ elevation: 2,
+ itemBuilder: (context) => [
+ _buildCompactItem(
+ icon: 'assets/icons/eye.svg',
+ text: 'Посмотреть',
+ onTap: () async {
+ context.pushTransparentRoute(rootNavigator: true, PhotoViewScreen(imageUrls: [baseUrl + state.profile.profilePhotoUrl], index: 0));
+ },
+ ),
+ if (widget.author == null || widget.author!.authorIsMe) ...[
+ _buildDivider(),
+ _buildCompactItem(
+ icon: 'assets/icons/edit_paint.svg',
+ text: 'Заменить',
+ onTap: () async {
+ showCupertinoModalPopup(
+ context: context,
+ builder: (context) {
+ final s = S.of(context);
+ return CupertinoActionSheet(
+ actions: [
+ CupertinoActionSheetAction(
+ onPressed: () async {
+ PermissionHandler.checkPermission(
+ permission: Permission.camera,
+ title: s.allowAccessToCamera,
+ description: s.allowAccessToCameraDescription,
+ onGranted: () async {
+ AppRouter.navigatorKey.currentContext?.pop();
+ final picker = ImagePicker();
+ XFile? image = await picker.pickImage(source: ImageSource.camera);
+ if (image != null) {
+ await profileCubit.uploadPhoto(File(image.path));
+ await sl().getMyProfile();
+ }
+ },
+ context: context,
+ );
},
- context: context,
- );
- },
- child: const Row(children: [Icon(Icons.photo_camera), SizedBox(width: 8), Text('Фото')]),
- ),
- CupertinoActionSheetAction(
- onPressed: () async {
- PermissionHandler.checkPermission(
- permission: Permission.photos,
- title: s.allowAcessToGalery,
- description: s.allowAcessToGaleryDescription,
- onGranted: () async {
- AppRouter.navigatorKey.currentContext?.pop();
- final picker = ImagePicker();
- XFile? image = await picker.pickImage(source: ImageSource.gallery);
- if (image != null) {
- await profileCubit.uploadPhoto(File(image.path));
- await sl().getMyProfile();
- }
+ child: const Row(children: [Icon(Icons.photo_camera), SizedBox(width: 8), Text('Фото')]),
+ ),
+ CupertinoActionSheetAction(
+ onPressed: () async {
+ PermissionHandler.checkPermission(
+ permission: Permission.photos,
+ title: s.allowAcessToGalery,
+ description: s.allowAcessToGaleryDescription,
+ onGranted: () async {
+ AppRouter.navigatorKey.currentContext?.pop();
+ final picker = ImagePicker();
+ XFile? image = await picker.pickImage(source: ImageSource.gallery);
+ if (image != null) {
+ await profileCubit.uploadPhoto(File(image.path));
+ await sl().getMyProfile();
+ }
+ },
+ context: context,
+ );
},
- context: context,
- );
- },
- child: const Row(children: [Icon(Icons.photo), SizedBox(width: 8), Text('Галерея')]),
- ),
- ],
- cancelButton: CupertinoActionSheetAction(
- onPressed: () => Navigator.of(context).pop(),
- isDestructiveAction: true,
- child: const Text('Отмена'),
- ),
+ child: const Row(children: [Icon(Icons.photo), SizedBox(width: 8), Text('Галерея')]),
+ ),
+ ],
+ cancelButton: CupertinoActionSheetAction(
+ onPressed: () => Navigator.of(context).pop(),
+ isDestructiveAction: true,
+ child: const Text('Отмена'),
+ ),
+ );
+ },
);
},
- );
- }
- if (value == 'delete') {
- await profileCubit.deletePhoto();
- await sl().getMyProfile();
- }
- },
- itemBuilder: (context) => [
- const PopupMenuItem(value: 'view', child: Text('Посмотреть')),
- if (widget.author == null || widget.author!.authorIsMe) const PopupMenuItem(value: 'replace', child: Text('Заменить')),
- if (widget.author == null || widget.author!.authorIsMe) const PopupMenuItem(value: 'delete', child: Text('Удалить')),
+ ),
+ _buildDivider(),
+ _buildCompactItem(
+ icon: 'assets/icons/red_trash.svg',
+ text: 'Удалить',
+ onTap: () async {
+ await profileCubit.deletePhoto();
+ await sl().getMyProfile();
+ },
+ ),
+ ],
],
child: Hero(
tag: state.profile.profilePhotoUrl,
@@ -899,41 +915,68 @@ class _ProfileScreenState extends State {
),
const Spacer(),
PopupMenuButton(
- onSelected: (value) {
- if (value == 'share') {
- final box = context.findRenderObject() as RenderBox?;
- SharePlus.instance.share(
- ShareParams(uri: Uri.parse('$baseUrl/ru/profile/${state.profile.slug}'), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size),
- );
- } else if (value == 'block') {
- isFirstFetch = true;
- sl().blockUser(state.profile.slug);
- widget.author == null || widget.author!.authorIsMe ? profileCubit.getMyProfile() : profileCubit.getProfileBySlug(widget.author!.authorSlug);
- if (state.profile.roles.contains('consultant')) {
- plansCubit.getPlans(state.profile.slug);
- }
- isFirstFetch = false;
- } else if (value == 'unblock') {
- isFirstFetch = true;
- sl().unblockUser(state.profile.slug);
- widget.author == null || widget.author!.authorIsMe ? profileCubit.getMyProfile() : profileCubit.getProfileBySlug(widget.author!.authorSlug);
- if (state.profile.roles.contains('consultant')) {
- plansCubit.getPlans(state.profile.slug);
- }
- isFirstFetch = false;
- } else {
- showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- builder: (context) => ComplainingBottomSheet(complainedContentType: ComplainedContent.profile, slug: widget.author?.authorSlug),
- );
- }
- },
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
+ color: Colors.white,
+ constraints: const BoxConstraints(minWidth: 260),
+ surfaceTintColor: Colors.transparent,
+ elevation: 2,
itemBuilder: (context) => [
- const PopupMenuItem(value: 'share', child: Text('Поделиться')),
- if (widget.author != null) const PopupMenuItem(value: 'complain', child: Text('Пожаловаться')),
- if (widget.author != null && !state.profile.isBlockedByMe) const PopupMenuItem(value: 'block', child: Text('Заблокировать')),
- if (widget.author != null && state.profile.isBlockedByMe) const PopupMenuItem(value: 'unblock', child: Text('Разблокировать')),
+ _buildCompactItem(
+ icon: 'assets/icons/share2.svg',
+ text: 'Поделиться',
+ onTap: () {
+ final box = context.findRenderObject() as RenderBox?;
+ SharePlus.instance.share(
+ ShareParams(uri: Uri.parse('$baseUrl/ru/profile/${state.profile.slug}'), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size),
+ );
+ },
+ ),
+ if (widget.author != null) ...[
+ _buildDivider(),
+ _buildCompactItem(
+ icon: 'assets/icons/complain.svg',
+ text: 'Пожаловаться',
+ onTap: () {
+ showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ builder: (context) => ComplainingBottomSheet(complainedContentType: ComplainedContent.profile, slug: widget.author?.authorSlug),
+ );
+ },
+ ),
+ ],
+ if (widget.author != null && !state.profile.isBlockedByMe) ...[
+ _buildDivider(),
+ _buildCompactItem(
+ icon: 'assets/icons/complain.svg',
+ text: 'Заблокировать',
+ onTap: () {
+ isFirstFetch = true;
+ sl().blockUser(state.profile.slug);
+ widget.author == null || widget.author!.authorIsMe ? profileCubit.getMyProfile() : profileCubit.getProfileBySlug(widget.author!.authorSlug);
+ if (state.profile.roles.contains('consultant')) {
+ plansCubit.getPlans(state.profile.slug);
+ }
+ isFirstFetch = false;
+ },
+ ),
+ ],
+ if (widget.author != null && state.profile.isBlockedByMe) ...[
+ _buildDivider(),
+ _buildCompactItem(
+ icon: 'assets/icons/complain.svg',
+ text: 'Разблокировать',
+ onTap: () {
+ isFirstFetch = true;
+ sl().unblockUser(state.profile.slug);
+ widget.author == null || widget.author!.authorIsMe ? profileCubit.getMyProfile() : profileCubit.getProfileBySlug(widget.author!.authorSlug);
+ if (state.profile.roles.contains('consultant')) {
+ plansCubit.getPlans(state.profile.slug);
+ }
+ isFirstFetch = false;
+ },
+ ),
+ ],
],
child: const Icon(Icons.more_horiz_rounded),
),
@@ -942,6 +985,33 @@ class _ProfileScreenState extends State {
);
}
+ PopupMenuItem _buildCompactItem({required String icon, required String text, required Function() onTap}) {
+ return PopupMenuItem(
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
+ height: 36,
+ onTap: onTap,
+ child: SizedBox(
+ width: double.infinity,
+ child: Row(
+ children: [
+ Text(text, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
+ const Spacer(),
+ SvgPicture.asset(icon),
+ ],
+ ),
+ ),
+ );
+ }
+
+ PopupMenuItem _buildDivider() {
+ return PopupMenuItem(
+ enabled: false,
+ height: 1,
+ padding: EdgeInsets.zero,
+ child: Container(margin: const EdgeInsets.symmetric(horizontal: 12), height: 1, color: appTheme.dividerColor),
+ );
+ }
+
Row _additional(String title, String value) {
return Row(
children: [
diff --git a/pubspec.yaml b/pubspec.yaml
index fefe422b..3895a06b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
-version: 1.2.7+66
+version: 1.2.8+68
publish_to: 'none'
environment:
@@ -13,7 +13,11 @@ dependencies:
flutter_localizations:
sdk: flutter
- flutter_secure_storage: ^9.2.4
+ flutter_secure_storage:
+ git:
+ url: https://github.com/sherlockvn/flutter_secure_storage.git
+ ref: remove-old-encrypt-algorithm
+ path: flutter_secure_storage
flutter_bloc: ^9.1.1
cached_network_image: ^3.4.1
flutter_svg: ^2.2.0