40 changed files with 1024 additions and 670 deletions
@ -0,0 +1,97 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8" /> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
|||
<title>Voitrans Membership Service Agreement</title> |
|||
<style> |
|||
:root { color-scheme: light dark; } |
|||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; line-height: 1.65; max-width: 720px; margin: 0 auto; padding: 24px 16px 48px; color: #1a1a1a; } |
|||
@media (prefers-color-scheme: dark) { body { background: #111; color: #eee; } a { color: #8ab4f8; } } |
|||
h1 { font-size: 1.35rem; margin: 0 0 0.5em; } |
|||
.meta { font-size: 0.85rem; color: #666; margin-bottom: 1.5em; } |
|||
h2 { font-size: 1.05rem; margin: 1.4em 0 0.5em; } |
|||
p, li { font-size: 0.95rem; margin: 0.5em 0; } |
|||
ol { padding-left: 1.25em; } |
|||
.note { font-size: 0.85rem; color: #666; margin-top: 2em; padding-top: 1em; border-top: 1px solid #ddd; } |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<h1>Voitrans Membership Service Agreement</h1> |
|||
<p class="meta">Last updated: April 22, 2026<br />Effective date: April 22, 2026</p> |
|||
<p>Welcome to the membership services provided by the Voitrans application (the "App"). Before purchasing, subscribing to, or using any membership service, please carefully read and fully understand this Agreement, in particular the clauses regarding fees, automatic renewal, refunds, limitations of liability, and dispute resolution. By tapping "Agree", completing payment, or otherwise accepting the membership service, you are deemed to have read and agreed to be bound by this Agreement.</p> |
|||
<p>If you do not agree to this Agreement, please do not purchase or use the membership service.</p> |
|||
|
|||
<h2>1. Definitions</h2> |
|||
<ol> |
|||
<li><strong>Operator</strong>: refers to Shenzhen Liangchuang Times Technology Co., Ltd. ("Liangchuang"), the developer and operator of the App and the membership service.</li> |
|||
<li><strong>Membership Service</strong>: refers to the paid member benefits provided by the Operator, including but not limited to higher quotas for voice transcription, voice translation and AI assistant usage, exclusive features, priority customer support, and so on. The actual benefits are subject to the purchase page, your order, and any in-app description.</li> |
|||
<li><strong>User</strong>: refers to the natural person or duly authorized entity who has registered to use the App and purchased the membership service.</li> |
|||
</ol> |
|||
|
|||
<h2>2. Service Content</h2> |
|||
<p>The specific content, term, and scope of the membership service are subject to the in-app display, the order confirmation, and any then-effective description. The Operator may, based on business planning, compliance requirements, or technical conditions, optimize, adjust, or discontinue certain member benefits, and will notify you via in-app announcements, pop-ups, or other reasonable means. If any adjustment materially adversely affects benefits you have already paid for, the Operator will, to the extent permitted by law, provide reasonable compensation or alternative arrangements.</p> |
|||
|
|||
<h2>3. Fees and Payment</h2> |
|||
<ol> |
|||
<li>The membership service is a paid service. Prices are displayed on the purchase page and may vary by promotion, region, or distribution channel.</li> |
|||
<li>When you pay through a third-party payment channel (such as the Apple App Store, Google Play, WeChat Pay, Alipay, etc.), you must also comply with the rules of the corresponding payment channel. Payments are completed by the partner third-party payment institutions, which may use biometric information for payment verification. Please review the user agreement and privacy policy of the relevant third party.</li> |
|||
<li>If your membership includes <strong>automatic renewal</strong>, the renewal amount and cycle will be presented to you before each renewal in accordance with platform rules or applicable law. You may cancel automatic renewal at any time via the relevant platform (such as Apple App Store subscription management, Google Play subscription management) or via in-app instructions. After cancellation, your benefits within the current membership term will not be affected; no further charges will be made after the term ends.</li> |
|||
</ol> |
|||
|
|||
<h2>4. Membership Account</h2> |
|||
<ol> |
|||
<li>The membership service is bound to your Voitrans account, which can be registered via mobile phone number, email, or WeChat authorization.</li> |
|||
<li>Member benefits are for the account holder's personal use only and may not be transferred, gifted, lent, rented, sold, or otherwise made available to any third party.</li> |
|||
<li>You are responsible for safeguarding your account and password. Any loss caused by leakage of your account credentials shall be borne by you.</li> |
|||
</ol> |
|||
|
|||
<h2>5. Acceptable Use</h2> |
|||
<p>You shall use the membership service lawfully and in good faith, and shall not use it for any of the following:</p> |
|||
<ol> |
|||
<li>Any act that violates laws or regulations or infringes the lawful rights of others;</li> |
|||
<li>Reverse engineering, decompiling, disassembling, or otherwise attempting to obtain the source code of the App;</li> |
|||
<li>Any act that endangers network security;</li> |
|||
<li>Bypassing the usage limits of the membership service or unlawfully obtaining member benefits through technical means;</li> |
|||
<li>Using the membership service to send bulk advertising or spam information;</li> |
|||
<li>Any other act that violates the App's User Agreement and related rules.</li> |
|||
</ol> |
|||
<p>The Operator may, in accordance with this Agreement and related rules, take measures such as restricting features, suspending services, or terminating membership in response to violations.</p> |
|||
|
|||
<h2>6. Refunds and Termination</h2> |
|||
<ol> |
|||
<li>Once a digital service has been activated, no-reason refunds are generally not supported, except as required by laws and regulations, by the relevant app store policies, or as separately committed by the Operator. If you experience issues such as system failure or duplicate charges that are not caused by you, you may contact customer support via [Settings] - [User Feedback] in the App for verification and handling.</li> |
|||
<li>If your account or service is restricted or terminated due to your breach of this Agreement or related rules, fees already paid will not be refunded.</li> |
|||
<li>After your membership expires or is terminated, you will no longer be entitled to member benefits. The retention period of membership-related data is subject to the App's Privacy Policy.</li> |
|||
<li>If you delete your Voitrans account, the membership service will be terminated at the same time. Unused member benefits cannot be restored, and fees already paid will not be refunded (unless otherwise required by law).</li> |
|||
</ol> |
|||
|
|||
<h2>7. Privacy</h2> |
|||
<p>We value the protection of your personal information. The manner and scope in which we collect and use your personal information during the membership service are subject to the Voitrans Privacy Policy. Voice data, transcripts, translation records, and other data generated while using the membership service will be protected and processed in accordance with that Privacy Policy.</p> |
|||
|
|||
<h2>8. Disclaimer and Limitation of Liability</h2> |
|||
<ol> |
|||
<li>The membership service depends on networks, devices, and third-party services (including but not limited to third-party AI model services and speech recognition services). The Operator will use commercially reasonable efforts to maintain stable service but is not liable for service interruption or data loss caused by force majeure, network failures, third parties, or other causes outside the Operator's reasonable control.</li> |
|||
<li>The output of features such as voice transcription, voice translation, and the AI assistant is provided for reference only. The Operator does not guarantee its accuracy, completeness, or timeliness, and is not responsible for any action taken by you based on such output.</li> |
|||
<li>To the maximum extent permitted by law, the Operator's total liability arising out of or in connection with the membership service shall not exceed the amount you actually paid for the relevant order.</li> |
|||
</ol> |
|||
|
|||
<h2>9. Changes to this Agreement</h2> |
|||
<p>The Operator may revise this Agreement to reflect changes in laws, regulations, or business needs. Revisions will be notified via in-app announcements or other appropriate means. If you continue to use the membership service after the revision, you are deemed to accept the revised Agreement; if you do not agree, please stop using the service and may, where permitted, cancel automatic renewal.</p> |
|||
|
|||
<h2>10. Governing Law and Dispute Resolution</h2> |
|||
<p>The conclusion, validity, interpretation, and dispute resolution of this Agreement shall be governed by the laws of the mainland of the People's Republic of China. Any dispute arising out of this Agreement shall first be resolved through friendly negotiation. If negotiation fails, either party may file a lawsuit with a court of competent jurisdiction at the Operator's location (Shenzhen).</p> |
|||
|
|||
<h2>11. Miscellaneous</h2> |
|||
<ol> |
|||
<li>This Agreement constitutes the complete agreement between you and the Operator regarding the membership service, and applies together with the App's User Agreement and Privacy Policy. In the event of a conflict between this Agreement and the User Agreement on matters concerning the membership service, this Agreement shall prevail.</li> |
|||
<li>If any provision of this Agreement is held invalid or unenforceable, the validity of the remaining provisions shall not be affected.</li> |
|||
<li>The headings in this Agreement are for convenience only and do not affect the meaning or interpretation of any clause.</li> |
|||
</ol> |
|||
|
|||
<h2>12. Contact Us</h2> |
|||
<p>If you have any questions about this Agreement or the membership service, you may contact us via:<br /> |
|||
In-app feedback: Voitrans APP [Settings] - [User Feedback]<br /> |
|||
Support email: tech@voitrans.com</p> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,97 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-CN"> |
|||
<head> |
|||
<meta charset="utf-8" /> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
|||
<title>Voitrans会员服务协议</title> |
|||
<style> |
|||
:root { color-scheme: light dark; } |
|||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Microsoft YaHei", sans-serif; line-height: 1.65; max-width: 720px; margin: 0 auto; padding: 24px 16px 48px; color: #1a1a1a; } |
|||
@media (prefers-color-scheme: dark) { body { background: #111; color: #eee; } a { color: #8ab4f8; } } |
|||
h1 { font-size: 1.35rem; margin: 0 0 0.5em; } |
|||
.meta { font-size: 0.85rem; color: #666; margin-bottom: 1.5em; } |
|||
h2 { font-size: 1.05rem; margin: 1.4em 0 0.5em; } |
|||
p, li { font-size: 0.95rem; margin: 0.5em 0; } |
|||
ol { padding-left: 1.25em; } |
|||
.note { font-size: 0.85rem; color: #666; margin-top: 2em; padding-top: 1em; border-top: 1px solid #ddd; } |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<h1>Voitrans会员服务协议</h1> |
|||
<p class="meta">更新日期:2026年4月22日<br />生效日期:2026年4月22日</p> |
|||
<p>欢迎您使用Voitrans应用程序(下称"本应用")提供的会员服务。请您在购买、订阅或使用会员服务前,仔细阅读并充分理解本协议全部条款,特别是涉及费用、自动续费、退款、责任限制及争议解决的条款。您点击同意、完成支付或以其他方式接受会员服务的,即视为您已阅读并同意受本协议约束。</p> |
|||
<p>若您不同意本协议,请勿购买或使用会员服务。</p> |
|||
|
|||
<h2>一、定义</h2> |
|||
<ol> |
|||
<li><strong>运营方</strong>:指深圳市良闯时代科技有限公司(下称"良闯"),是本应用及会员服务的开发者和运营者。</li> |
|||
<li><strong>会员服务</strong>:指运营方向用户提供的付费会员权益,包括但不限于更高的语音转写时长额度、语音翻译时长额度、AI智能助手使用额度、专属功能、优先客服等,具体以购买页面、订单及本应用内说明为准。</li> |
|||
<li><strong>用户</strong>:指注册并使用本应用,且购买会员服务的自然人或经合法授权的主体。</li> |
|||
</ol> |
|||
|
|||
<h2>二、服务内容</h2> |
|||
<p>会员服务的具体内容、有效期限、使用范围以本应用内展示、订单确认信息及届时有效的说明为准。运营方有权根据业务规划、合规要求、技术条件等对会员权益进行优化、调整或下线部分功能,并将通过应用内公告、弹窗或其他合理方式提示;若调整对您已付费权益产生重大不利影响的,运营方将在法律允许的范围内提供合理补偿或替代方案。</p> |
|||
|
|||
<h2>三、费用与支付</h2> |
|||
<ol> |
|||
<li>会员服务为收费服务,价格以购买页面展示为准,可能因营销活动、地区或渠道不同而存在差异。</li> |
|||
<li>您通过第三方支付渠道(如Apple App Store、Google Play、微信支付、支付宝等)完成支付的,应同时遵守相应支付渠道的规则。支付通过合作的第三方支付机构提供的支付服务完成,第三方支付服务有可能使用生物识别信息用于付款验证,请阅读该第三方机构的用户协议及隐私政策。</li> |
|||
<li>若会员包含<strong>自动续费</strong>,我们将在续费前按平台规则或法律规定向您展示续费金额与周期;您可在相应平台(如Apple App Store的订阅管理、Google Play的订阅管理)或本应用内按指引关闭自动续费。关闭自动续费后,当前会员有效期内的权益不受影响,到期后将不再自动扣费。</li> |
|||
</ol> |
|||
|
|||
<h2>四、会员账号</h2> |
|||
<ol> |
|||
<li>会员服务与您的Voitrans账号绑定,您可通过手机号、电子邮箱或微信授权方式注册的账号使用会员服务。</li> |
|||
<li>会员权益仅限账号持有人本人使用,不得转让、赠与、借用、租用、售卖或以其他方式提供给第三方使用。</li> |
|||
<li>您应妥善保管账号及密码信息,因账号信息泄露导致的损失由您自行承担。</li> |
|||
</ol> |
|||
|
|||
<h2>五、使用规范</h2> |
|||
<p>您应合法、善意地使用会员服务,不得利用会员服务从事以下行为:</p> |
|||
<ol> |
|||
<li>违反法律法规、侵犯他人合法权益的行为;</li> |
|||
<li>对本应用进行反向工程、反编译、反汇编或其他试图获取源代码的行为;</li> |
|||
<li>利用会员服务从事任何危害网络安全的行为;</li> |
|||
<li>通过技术手段绕过会员服务的使用限制或非法获取会员权益;</li> |
|||
<li>利用会员服务批量发布、传送广告信息及垃圾信息;</li> |
|||
<li>其他违反本应用《用户协议》及相关规则的行为。</li> |
|||
</ol> |
|||
<p>运营方有权依据本协议及相关规则对违规行为采取限制功能、中止服务、终止会员资格等措施。</p> |
|||
|
|||
<h2>六、退款与终止</h2> |
|||
<ol> |
|||
<li>数字服务一经开通,除法律法规规定、应用商店政策或运营方另行承诺的情形外,原则上不支持无理由退款。若因系统故障、重复扣费等非用户过错导致的问题,您可通过本应用【设置】-【用户反馈】联系客服核实处理。</li> |
|||
<li>因您违反本协议或相关规则导致账号或服务被限制、终止的,已支付费用不予退还。</li> |
|||
<li>会员到期或终止后,您将无法继续享有会员权益。账号下与会员相关的数据保存期限以本应用《隐私政策》的规定为准。</li> |
|||
<li>如您注销Voitrans账号,会员服务将同时终止,未使用的会员权益将无法恢复,已支付费用不予退还(法律法规另有规定的除外)。</li> |
|||
</ol> |
|||
|
|||
<h2>七、隐私保护</h2> |
|||
<p>我们重视您的个人信息保护。在会员服务过程中,我们收集和使用您个人信息的方式和范围,以本应用《Voitrans隐私政策》为准。您使用会员服务时所产生的语音信息、转写文稿、翻译记录等数据,我们将按照隐私政策的规定进行保护和处理。</p> |
|||
|
|||
<h2>八、免责声明与责任限制</h2> |
|||
<ol> |
|||
<li>会员服务依赖网络、设备、第三方服务(包括但不限于第三方AI模型服务、语音识别服务等)等因素,运营方将尽力保障服务稳定,但不对因不可抗力、网络故障、第三方原因或非运营方可控原因导致的服务中断、数据丢失承担责任。</li> |
|||
<li>语音转写、语音翻译、AI智能助手等功能的输出结果仅供参考,运营方不保证其准确性、完整性和及时性,不对用户基于上述结果做出的任何行为承担责任。</li> |
|||
<li>在法律允许的范围内,运营方对会员服务所承担的赔偿责任以您就该笔订单实际支付的金额为上限。</li> |
|||
</ol> |
|||
|
|||
<h2>九、协议变更</h2> |
|||
<p>运营方可根据法律法规变化及业务需要修订本协议。修订后将通过应用内公告或其他适当方式通知;若您继续使用会员服务,即视为接受修订后的协议;若您不同意,应停止使用并可在规则允许范围内申请关闭自动续费。</p> |
|||
|
|||
<h2>十、适用法律与争议解决</h2> |
|||
<p>本协议的订立、效力、解释及争议解决均适用中华人民共和国大陆地区法律。因本协议引起的争议,双方应友好协商;协商不成的,任何一方均可向运营方所在地(深圳市)有管辖权的人民法院提起诉讼。</p> |
|||
|
|||
<h2>十一、其他</h2> |
|||
<ol> |
|||
<li>本协议构成您与运营方之间关于会员服务的完整协议,与本应用《用户协议》《隐私政策》共同适用。如本协议与《用户协议》存在冲突,就会员服务相关事项以本协议为准。</li> |
|||
<li>本协议中任何条款被认定为无效或不可执行的,不影响其他条款的效力。</li> |
|||
<li>本协议的标题仅为方便阅读而设,不影响条款的含义或解释。</li> |
|||
</ol> |
|||
|
|||
<h2>十二、联系我们</h2> |
|||
<p>如您对本协议或会员服务有任何疑问,可通过以下方式联系:<br /> |
|||
应用内反馈:Voitrans APP【设置】-【用户反馈】<br /> |
|||
客服邮箱:tech@voitrans.com</p> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,170 @@ |
|||
import 'dart:convert'; |
|||
|
|||
import 'package:get/get.dart'; |
|||
import 'package:http/http.dart' as http; |
|||
|
|||
import '../../core/utils/logger.dart'; |
|||
import '../../data/models/appconfig.dart'; |
|||
import 'language_configs/azure_language_config.dart'; |
|||
import 'language_manager.dart'; |
|||
|
|||
/// 微软 Azure Translator REST 翻译服务 |
|||
/// 端点:https://api.cognitive.microsofttranslator.com |
|||
/// 文档:https://learn.microsoft.com/azure/ai-services/translator/reference/v3-0-translate |
|||
class MicrosoftTranslationService extends GetxService { |
|||
static const String _defaultEndpoint = |
|||
'https://api.cognitive.microsofttranslator.com'; |
|||
static const String _apiPath = '/translate'; |
|||
static const String _apiVersion = '3.0'; |
|||
|
|||
late final String _subscriptionKey; |
|||
late final String _region; |
|||
late final String _endpoint; |
|||
|
|||
bool _isInitialized = false; |
|||
bool get isInitialized => _isInitialized; |
|||
|
|||
final LanguageManager _languageManager = Get.find<LanguageManager>(); |
|||
|
|||
/// shortCode -> 微软 translationCode 缓存(由 getAzureTranslationLanguageSpecs 构建) |
|||
final Map<String, String> _shortCodeToTranslationCode = {}; |
|||
|
|||
@override |
|||
Future<void> onInit() async { |
|||
await initialize(); |
|||
super.onInit(); |
|||
} |
|||
|
|||
Future<bool> initialize() async { |
|||
if (_isInitialized) return true; |
|||
try { |
|||
_subscriptionKey = AppConfig.env('AZURE_TRANSLATION_KEY') ?? ''; |
|||
_region = AppConfig.env('AZURE_TRANSLATION_REGION') ?? ''; |
|||
_endpoint = |
|||
AppConfig.env('AZURE_TRANSLATION_ENDPOINT') ?? _defaultEndpoint; |
|||
|
|||
if (_subscriptionKey.isEmpty) { |
|||
print( |
|||
'🟢[MS-TRANS-DBG] AZURE_TRANSLATION_KEY 未配置,服务无法初始化'); |
|||
Logger.error('微软翻译服务初始化失败:AZURE_TRANSLATION_KEY 为空'); |
|||
return false; |
|||
} |
|||
|
|||
for (final spec in getAzureTranslationLanguageSpecs()) { |
|||
final short = spec['shortCode']; |
|||
final code = spec['translationCode']; |
|||
if (short != null && code != null) { |
|||
_shortCodeToTranslationCode[short] = code; |
|||
} |
|||
} |
|||
|
|||
_isInitialized = true; |
|||
print( |
|||
'🟢[MS-TRANS-DBG] 微软翻译服务初始化成功: endpoint=$_endpoint, region=$_region, keyLen=${_subscriptionKey.length}, specCount=${_shortCodeToTranslationCode.length}'); |
|||
Logger.info('微软翻译服务初始化成功 region=$_region'); |
|||
return true; |
|||
} catch (e) { |
|||
print('🟢[MS-TRANS-DBG] 微软翻译服务初始化异常: $e'); |
|||
Logger.error('微软翻译服务初始化异常: $e'); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/// 把 ASR 代码(如 `zh-CN`)映射为微软 Translator 语言码(如 `zh-Hans`) |
|||
/// 找不到返回 null |
|||
String? _toTranslationCode(String asrCode) { |
|||
if (asrCode.isEmpty) return null; |
|||
final short = _languageManager.getShortCodeByAsrCode(asrCode); |
|||
if (short == null) return null; |
|||
return _shortCodeToTranslationCode[short]; |
|||
} |
|||
|
|||
/// 文本翻译 |
|||
/// [text] 待翻译文本 |
|||
/// [sourceLanguageCode] 源语言 ASR 码(传空串则让微软自动检测) |
|||
/// [targetLanguageCode] 目标语言 ASR 码 |
|||
Future<String?> translateText({ |
|||
required String text, |
|||
required String sourceLanguageCode, |
|||
required String targetLanguageCode, |
|||
}) async { |
|||
if (!_isInitialized) { |
|||
print('🟢[MS-TRANS-DBG] 未初始化,返回 null'); |
|||
Logger.error('微软翻译服务未初始化'); |
|||
return null; |
|||
} |
|||
if (text.isEmpty) return null; |
|||
|
|||
try { |
|||
final String? targetCode = _toTranslationCode(targetLanguageCode); |
|||
if (targetCode == null) { |
|||
print( |
|||
'🟢[MS-TRANS-DBG] 目标语言码映射失败: targetAsr=$targetLanguageCode,返回 null'); |
|||
Logger.error('微软翻译失败:目标语言 $targetLanguageCode 不在映射表'); |
|||
return null; |
|||
} |
|||
final String? sourceCode = _toTranslationCode(sourceLanguageCode); |
|||
|
|||
final queryParams = <String, String>{ |
|||
'api-version': _apiVersion, |
|||
'to': targetCode, |
|||
}; |
|||
if (sourceCode != null && sourceCode.isNotEmpty) { |
|||
queryParams['from'] = sourceCode; |
|||
} |
|||
|
|||
final uri = Uri.parse('$_endpoint$_apiPath') |
|||
.replace(queryParameters: queryParams); |
|||
final body = json.encode([ |
|||
{'text': text} |
|||
]); |
|||
final headers = { |
|||
'Content-Type': 'application/json', |
|||
'Ocp-Apim-Subscription-Key': _subscriptionKey, |
|||
'Ocp-Apim-Subscription-Region': _region, |
|||
'X-ClientTraceId': _newTraceId(), |
|||
}; |
|||
|
|||
print( |
|||
'🟢[MS-TRANS-DBG] 发起翻译请求: uri=$uri, from=$sourceCode, to=$targetCode, text=$text'); |
|||
final response = |
|||
await http.post(uri, headers: headers, body: body).timeout( |
|||
const Duration(seconds: 10), |
|||
onTimeout: () => http.Response('{"error":"timeout"}', 408), |
|||
); |
|||
print( |
|||
'🟢[MS-TRANS-DBG] 翻译响应: status=${response.statusCode}, bodyLen=${response.body.length}, body=${response.body.length > 500 ? response.body.substring(0, 500) : response.body}'); |
|||
|
|||
if (response.statusCode != 200) { |
|||
Logger.error( |
|||
'微软翻译 HTTP 错误: status=${response.statusCode}, body=${response.body}'); |
|||
return null; |
|||
} |
|||
|
|||
final decoded = json.decode(response.body); |
|||
if (decoded is! List || decoded.isEmpty) { |
|||
print('🟢[MS-TRANS-DBG] 响应非 List 或为空,解析失败'); |
|||
return null; |
|||
} |
|||
final first = decoded.first; |
|||
if (first is! Map) return null; |
|||
final translations = first['translations']; |
|||
if (translations is! List || translations.isEmpty) return null; |
|||
final translated = translations.first; |
|||
if (translated is! Map) return null; |
|||
final result = translated['text']; |
|||
if (result is! String) return null; |
|||
print('🟢[MS-TRANS-DBG] 翻译成功: $result'); |
|||
return result; |
|||
} catch (e, st) { |
|||
print('🟢[MS-TRANS-DBG] 翻译请求异常: $e\n$st'); |
|||
Logger.error('微软翻译请求异常: $e'); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
String _newTraceId() { |
|||
final now = DateTime.now().microsecondsSinceEpoch; |
|||
return 'ms-trans-$now'; |
|||
} |
|||
} |
|||
@ -0,0 +1,155 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|||
import 'package:get/get.dart'; |
|||
import '../webview_page.dart'; |
|||
import '../../../core/utils/doc_url_helper.dart'; |
|||
|
|||
/// 会员服务协议确认弹窗 |
|||
/// 在用户进入支付(充值/购买会员)前必须显式同意 |
|||
class MemberServiceAgreementDialog extends StatelessWidget { |
|||
final VoidCallback? onAccept; // 同意回调 |
|||
final VoidCallback? onReject; // 拒绝回调 |
|||
final bool canDismiss; // 是否可通过返回键关闭 |
|||
|
|||
const MemberServiceAgreementDialog({ |
|||
super.key, |
|||
this.onAccept, |
|||
this.onReject, |
|||
this.canDismiss = true, |
|||
}); |
|||
|
|||
/// 显示会员服务协议弹窗,返回值为 true 表示同意,false / null 表示拒绝或取消 |
|||
static Future<bool> show({bool canDismiss = true}) async { |
|||
final result = await Get.dialog<bool>( |
|||
MemberServiceAgreementDialog( |
|||
canDismiss: canDismiss, |
|||
onAccept: () => Get.back(result: true), |
|||
onReject: () => Get.back(result: false), |
|||
), |
|||
barrierDismissible: canDismiss, |
|||
); |
|||
return result == true; |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
final bool isDarkMode = Get.theme.brightness == Brightness.dark; |
|||
|
|||
return PopScope( |
|||
canPop: canDismiss, |
|||
child: Dialog( |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.circular(16.r), |
|||
), |
|||
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.white, |
|||
child: Container( |
|||
padding: EdgeInsets.all(20.w), |
|||
child: Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
children: [ |
|||
Text( |
|||
'memberServiceAgreementTitle'.tr, // 会员服务协议 |
|||
style: TextStyle( |
|||
fontSize: 18.sp, |
|||
fontWeight: FontWeight.bold, |
|||
color: isDarkMode ? Colors.white : Colors.black, |
|||
), |
|||
), |
|||
SizedBox(height: 16.h), |
|||
Padding( |
|||
padding: EdgeInsets.symmetric(horizontal: 20.w), |
|||
child: Text.rich( |
|||
TextSpan( |
|||
children: [ |
|||
TextSpan( |
|||
text: 'memberServiceAgreementIntro'.tr, |
|||
style: TextStyle( |
|||
fontSize: 14.sp, |
|||
color: isDarkMode |
|||
? Colors.white70 |
|||
: Colors.black87, |
|||
), |
|||
), |
|||
WidgetSpan( |
|||
alignment: PlaceholderAlignment.middle, |
|||
child: GestureDetector( |
|||
onTap: () { |
|||
Get.to(() => WebViewPage( |
|||
url: memberServiceAgreementUrl(), |
|||
title: 'memberServiceAgreement'.tr, |
|||
)); |
|||
}, |
|||
child: Text( |
|||
'memberServiceAgreement'.tr, // 《会员服务协议》 |
|||
style: TextStyle( |
|||
fontSize: 14.sp, |
|||
color: const Color(0xFF3B82F6), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
TextSpan( |
|||
text: 'memberServiceAgreementOutro'.tr, |
|||
style: TextStyle( |
|||
fontSize: 14.sp, |
|||
color: isDarkMode ? Colors.white70 : Colors.black87, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
textAlign: TextAlign.center, |
|||
), |
|||
), |
|||
SizedBox(height: 20.h), |
|||
Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|||
children: [ |
|||
TextButton( |
|||
onPressed: () { |
|||
if (onReject != null) { |
|||
onReject!(); |
|||
} else { |
|||
Get.back(result: false); |
|||
} |
|||
}, |
|||
style: TextButton.styleFrom( |
|||
foregroundColor: isDarkMode |
|||
? Colors.grey[400] |
|||
: Colors.black54, |
|||
), |
|||
child: Text( |
|||
'disagree'.tr, // 不同意 |
|||
style: TextStyle( |
|||
color: isDarkMode ? Colors.grey[400] : Colors.black54, |
|||
), |
|||
), |
|||
), |
|||
ElevatedButton( |
|||
onPressed: () { |
|||
if (onAccept != null) { |
|||
onAccept!(); |
|||
} else { |
|||
Get.back(result: true); |
|||
} |
|||
}, |
|||
style: ElevatedButton.styleFrom( |
|||
backgroundColor: const Color(0xFF0066CC), |
|||
foregroundColor: Colors.white, |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.circular(8.r), |
|||
), |
|||
elevation: isDarkMode ? 4 : 2, |
|||
padding: EdgeInsets.symmetric( |
|||
horizontal: 16.w, vertical: 10.h), |
|||
), |
|||
child: Text('agreeAndContinue'.tr), // 同意并继续 |
|||
), |
|||
], |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue