forked from eatvio/eatvio_app
Merge pull request 'feat(profile): Issue-248' (#621) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#621
This commit is contained in:
@@ -42,9 +42,16 @@ jobs:
|
||||
env:
|
||||
KEYCHAIN_PASSWORD: ${{ vars.IOS_RUNNER_CI_PASSWORD }}
|
||||
|
||||
- name: Flutter build
|
||||
- name: Setup API key
|
||||
run: |
|
||||
echo "${API_APPSTORE_KEY}" | base64 --decode > ios/AuthKey.p8
|
||||
chmod 600 ios/AuthKey.p8
|
||||
ls -la ios/AuthKey.p8
|
||||
env:
|
||||
API_APPSTORE_KEY: ${{ vars.API_APPSTORE_KEY }}
|
||||
|
||||
- name: Flutter build
|
||||
run: |
|
||||
flutter clean
|
||||
flutter pub get
|
||||
flutter pub upgrade
|
||||
|
||||
@@ -18,8 +18,16 @@ default_platform(:ios)
|
||||
platform :ios do
|
||||
desc "Push a new beta build to TestFlight"
|
||||
lane :beta do
|
||||
UI.message "Checking API key file..."
|
||||
if File.exist?("./AuthKey.p8")
|
||||
UI.message "API key file exists"
|
||||
else
|
||||
UI.user_error!("API key file not found at ./AuthKey.p8")
|
||||
end
|
||||
|
||||
match(type: "appstore", readonly: true)
|
||||
increment_build_number(xcodeproj: "Runner.xcodeproj")
|
||||
|
||||
build_app(
|
||||
workspace: "Runner.xcworkspace",
|
||||
scheme: "Runner",
|
||||
@@ -27,7 +35,29 @@ 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: "./AuthKey.p8",
|
||||
duration: 1200,
|
||||
in_house: false
|
||||
)
|
||||
|
||||
begin
|
||||
upload_to_testflight(
|
||||
api_key_path: "./AuthKey.p8",
|
||||
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
|
||||
|
||||
@@ -760,76 +760,92 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
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<void>(
|
||||
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<ProfileAvatarCubit>().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<void>(
|
||||
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<ProfileAvatarCubit>().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<ProfileAvatarCubit>().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<ProfileAvatarCubit>().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<ProfileAvatarCubit>().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<ProfileAvatarCubit>().getMyProfile();
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
child: Hero(
|
||||
tag: state.profile.profilePhotoUrl,
|
||||
@@ -899,41 +915,68 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
),
|
||||
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<BlockedUsersCubit>().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<BlockedUsersCubit>().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<BlockedUsersCubit>().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<BlockedUsersCubit>().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<ProfileScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
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: [
|
||||
|
||||
Reference in New Issue
Block a user