From c1d3c5445fe419270bc5ae2e5c5a7fce1b679826 Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Sun, 12 Nov 2023 22:22:20 +0330 Subject: [PATCH] Add sub link share --- assets/translations/strings_en.i18n.json | 2 ++ assets/translations/strings_fa.i18n.json | 2 ++ assets/translations/strings_ru.i18n.json | 4 +++- assets/translations/strings_zh.i18n.json | 2 ++ lib/features/common/profile_tile.dart | 15 +++++++++++++++ lib/utils/link_parsers.dart | 13 +++++++++++++ 6 files changed, 37 insertions(+), 1 deletion(-) diff --git a/assets/translations/strings_en.i18n.json b/assets/translations/strings_en.i18n.json index 54bc6b2a..ee72647a 100644 --- a/assets/translations/strings_en.i18n.json +++ b/assets/translations/strings_en.i18n.json @@ -69,6 +69,8 @@ }, "share": { "buttonText": "Share", + "exportToClipboardSuccess": "Exported to clipboard", + "exportSubLinkToClipboard": "Export subscription link to clipboard", "exportConfigToClipboard": "Export configuration to clipboard", "exportConfigToClipboardSuccess": "Configuration copied to clipboard" }, diff --git a/assets/translations/strings_fa.i18n.json b/assets/translations/strings_fa.i18n.json index 7595fbad..65930e3f 100644 --- a/assets/translations/strings_fa.i18n.json +++ b/assets/translations/strings_fa.i18n.json @@ -69,6 +69,8 @@ }, "share": { "buttonText": "اشتراک گذاری", + "exportToClipboardSuccess": "به کلیپ بورد اضافه شد", + "exportSubLinkToClipboard": "افزودن لینک اشتراک به کلیپ بورد", "exportConfigToClipboard": "افزودن پیکربندی به کلیپ بورد", "exportConfigToClipboardSuccess": "پیکربندی در کلیپ بورد کپی شد" }, diff --git a/assets/translations/strings_ru.i18n.json b/assets/translations/strings_ru.i18n.json index 21d6b34a..01f8f7be 100644 --- a/assets/translations/strings_ru.i18n.json +++ b/assets/translations/strings_ru.i18n.json @@ -69,6 +69,8 @@ }, "share": { "buttonText": "Делиться", + "exportToClipboardSuccess": "Экспортировано в буфер обмена", + "exportSubLinkToClipboard": "Экспортировать ссылку на подписку в буфер обмена", "exportConfigToClipboard": "Экспортировать конфигурацию в буфер обмена", "exportConfigToClipboardSuccess": "Конфигурация скопирована в буфер обмена." }, @@ -280,4 +282,4 @@ "short_description": "Автовыбор, SSH, VLESS, Vmess, Trojan, Reality, Sing-Box, Clash, Xray, Shadowsocks", "full_description": "Основная цель HiddifyNext — предоставить безопасный, удобный и эффективный клиент туннелирования. Он позволяет направлять весь трафик или трафик выбранного приложения на выбранный вами удалённый сервер, используя разрешение VPN–сервиса.\n\nПримечание: мы не предоставляем серверы, пользователи должны обеспечивать конфиденциальность своих действий в Интернете, используя собственный сервер или доверенные серверы.\n \nПоддерживаемые серверы:\n— Обычная ссылка на подписку V2ray/Xray\n— Ссылка на подписку Clash\n— Ссылка на подписку на Sing–Box\n\nВ чём уникальные особенности?\n — Удобство\n — Оптимизация и скорость\n — Автоматический выбор минимальной задержки\n — Отображение информации об использовании\n — Простой импорт ссылок одним щелчком мыши\n — Бесплатно и без рекламы\n — Простое переключение ссылок\n — …и много больше\n\nПоддержка:\n• Все протоколы, поддерживаемые Sing-Box\n• VLESS + xtls reality, vision\n• VMESS\n• Trojan\n• ShoadowSocks\n• Reality\n• V2ray\n• Hystria2\n• TUIC\n• SSH\n• ShadowTLS\n\n\nИсходный код доступен по адресу https://github.com/hiddify/Hiddify-Next.\nЯдро приложения основано на открытом исходном коде Sing–Box.\n\nОписание разрешений:\n— СЛУЖБА VPN: поскольку целью данного приложения является предоставление безопасного, удобного и эффективного клиента туннелирования, это разрешение необходимо, чтобы иметь возможность направлять трафик через туннель на удалённый сервер.\n— ЗАПРОС ВСЕХ ПАКЕТОВ: это разрешение позволяет включать или исключать определённые приложения для туннелирования.\n— ИНФОРМИРОВАНИЕ О ЗАВЕРШЕНИИ ЗАГРУЗКИ: это разрешение можно включить или отключить в настройках приложения, чтобы (де)активировать запуск приложения при загрузке устройства.\n— ПОСТОЯННОЕ УВЕДОМЛЕНИЕ: это разрешение необходимо, поскольку используется приоритетная служба для обеспечения непрерывной работы службы VPN.\n— Приложение не содержит рекламы. Сбор аналитики и данных о сбоях происходят только с явного согласия пользователя при первом использовании приложения." } -} +} \ No newline at end of file diff --git a/assets/translations/strings_zh.i18n.json b/assets/translations/strings_zh.i18n.json index 6e3ced28..152dacf2 100644 --- a/assets/translations/strings_zh.i18n.json +++ b/assets/translations/strings_zh.i18n.json @@ -69,6 +69,8 @@ }, "share": { "buttonText": "分享", + "exportToClipboardSuccess": "导出到剪贴板", + "exportSubLinkToClipboard": "将订阅链接导出到剪贴板", "exportConfigToClipboard": "将配置导出到剪贴板", "exportConfigToClipboardSuccess": "配置已复制到剪贴板" }, diff --git a/lib/features/common/profile_tile.dart b/lib/features/common/profile_tile.dart index 8d0ba2ab..a56374ed 100644 --- a/lib/features/common/profile_tile.dart +++ b/lib/features/common/profile_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hiddify/core/core_providers.dart'; @@ -288,6 +289,20 @@ class ProfileActionsMenu extends HookConsumerWidget { ), SubmenuButton( menuChildren: [ + if (profile case RemoteProfile(:final url, :final name)) + MenuItemButton( + child: Text(t.profile.share.exportSubLinkToClipboard), + onPressed: () async { + final link = LinkParser.generateSubShareLink(url, name); + if (link.isNotEmpty) { + await Clipboard.setData(ClipboardData(text: link)); + if (context.mounted) { + CustomToast(t.profile.share.exportToClipboardSuccess) + .show(context); + } + } + }, + ), MenuItemButton( child: Text(t.profile.share.exportConfigToClipboard), onPressed: () async { diff --git a/lib/utils/link_parsers.dart b/lib/utils/link_parsers.dart index 378ea4e3..d82b7aff 100644 --- a/lib/utils/link_parsers.dart +++ b/lib/utils/link_parsers.dart @@ -7,6 +7,19 @@ typedef ProfileLink = ({String url, String name}); // TODO: test and improve abstract class LinkParser { + static String generateSubShareLink(String url, [String? name]) { + final uri = Uri.tryParse(url); + if (uri == null) return ''; + return Uri( + scheme: 'hiddify', + host: 'install-sub', + queryParameters: { + "url": uri.toString(), + if (name != null) "name": name, + }, + ).toString(); + } + // protocols schemas static const protocols = {'clash', 'clashmeta', 'sing-box', 'hiddify'}; static const rawProtocols = {