Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67e178c89d | ||
|
|
8481611372 | ||
|
|
b1e95b370c | ||
|
|
0ef0d46919 | ||
|
|
b4d38f8e10 |
No files matched your search
@@ -2,14 +2,37 @@
|
|||||||
|
|
||||||
import '../../l10n/app_localizations.dart';
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
const int _serverErrorCode = 500;
|
/// 按后端 `ErrorCode` 取当前语言文案。未收录的业务码回退服务端 message。
|
||||||
|
|
||||||
/// 用户端不展示内部故障文案。500 与空提示改成可理解的说明,业务提示原样保留。
|
|
||||||
String userFacingApiMessage(AppLocalizations l10n, int code, String message) {
|
String userFacingApiMessage(AppLocalizations l10n, int code, String message) {
|
||||||
if (code >= _serverErrorCode || message.isEmpty) return l10n.authServerError;
|
final localized = _apiMessage(l10n, code);
|
||||||
|
if (localized != null) return localized;
|
||||||
|
if (message.isEmpty) return l10n.authServerError;
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 已定义错误码。500 用服务繁忙,不展示「系统异常」。
|
||||||
|
String? _apiMessage(AppLocalizations l10n, int code) {
|
||||||
|
return switch (code) {
|
||||||
|
400 => l10n.apiErrorBadRequest,
|
||||||
|
401 => l10n.apiErrorUnauthorized,
|
||||||
|
403 => l10n.apiErrorForbidden,
|
||||||
|
404 => l10n.apiErrorNotFound,
|
||||||
|
429 => l10n.apiErrorTooManyRequests,
|
||||||
|
500 => l10n.authServerError,
|
||||||
|
100000 => l10n.apiErrorLoginFailed,
|
||||||
|
100001 => l10n.apiErrorAccountDisabled,
|
||||||
|
100002 => l10n.apiErrorCoinNotEnough,
|
||||||
|
100003 => l10n.apiErrorEpisodeUnavailable,
|
||||||
|
100004 => l10n.apiErrorEpisodeUnlocked,
|
||||||
|
100005 => l10n.apiErrorOrderInvalid,
|
||||||
|
100006 => l10n.apiErrorVipExpired,
|
||||||
|
200001 => l10n.apiErrorDataExists,
|
||||||
|
200002 => l10n.apiErrorDataNotFound,
|
||||||
|
200003 => l10n.apiErrorNotAllowed,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// logic 里的错误在两种情况下是"哨兵值":本地网络异常(读不到后端提示语)与
|
/// logic 里的错误在两种情况下是"哨兵值":本地网络异常(读不到后端提示语)与
|
||||||
/// 我们自己包装的失败。这里统一翻译成当前语言的文案;服务端给的中文提示原样展示。
|
/// 我们自己包装的失败。这里统一翻译成当前语言的文案;服务端给的中文提示原样展示。
|
||||||
String errorText(AppLocalizations l10n, String? code, String fallback) {
|
String errorText(AppLocalizations l10n, String? code, String fallback) {
|
||||||
|
|||||||
+15
-6
@@ -438,6 +438,21 @@
|
|||||||
"authPasswordWeak": "Password needs both letters and numbers",
|
"authPasswordWeak": "Password needs both letters and numbers",
|
||||||
"authNetworkError": "Network error, please try again",
|
"authNetworkError": "Network error, please try again",
|
||||||
"authServerError": "The service is busy. Please try again shortly.",
|
"authServerError": "The service is busy. Please try again shortly.",
|
||||||
|
"apiErrorBadRequest": "Invalid request",
|
||||||
|
"apiErrorUnauthorized": "Sign in to continue",
|
||||||
|
"apiErrorForbidden": "You don't have permission",
|
||||||
|
"apiErrorNotFound": "Not found",
|
||||||
|
"apiErrorTooManyRequests": "Too many requests. Please try again later.",
|
||||||
|
"apiErrorLoginFailed": "Incorrect account or password",
|
||||||
|
"apiErrorAccountDisabled": "This account has been disabled",
|
||||||
|
"apiErrorCoinNotEnough": "Not enough coins",
|
||||||
|
"apiErrorEpisodeUnavailable": "This episode is unavailable",
|
||||||
|
"apiErrorEpisodeUnlocked": "This episode is already unlocked",
|
||||||
|
"apiErrorOrderInvalid": "This order is already paid or closed",
|
||||||
|
"apiErrorVipExpired": "Membership has expired",
|
||||||
|
"apiErrorDataExists": "This record already exists",
|
||||||
|
"apiErrorDataNotFound": "Not found",
|
||||||
|
"apiErrorNotAllowed": "This action isn't available right now",
|
||||||
"authSwitchToSignUp": "New here? Create an account",
|
"authSwitchToSignUp": "New here? Create an account",
|
||||||
"authSwitchToSignIn": "Already have an account? Sign in",
|
"authSwitchToSignIn": "Already have an account? Sign in",
|
||||||
"authGateTitle": "Sign in to continue",
|
"authGateTitle": "Sign in to continue",
|
||||||
@@ -595,10 +610,6 @@
|
|||||||
"accountDeactivateWarning": "Requests enter a 15-day cooling-off period. All devices are signed out immediately.",
|
"accountDeactivateWarning": "Requests enter a 15-day cooling-off period. All devices are signed out immediately.",
|
||||||
"accountDeactivateReason": "Reason (optional)",
|
"accountDeactivateReason": "Reason (optional)",
|
||||||
"accountDeactivateReasonHint": "Add more detail here",
|
"accountDeactivateReasonHint": "Add more detail here",
|
||||||
"accountDeactivateReasonUnused": "Not using it anymore",
|
|
||||||
"accountDeactivateReasonContent": "Not interested in the content",
|
|
||||||
"accountDeactivateReasonPrivacy": "Privacy concerns",
|
|
||||||
"accountDeactivateReasonOther": "Other",
|
|
||||||
"accountRequestDeactivate": "Request account deletion",
|
"accountRequestDeactivate": "Request account deletion",
|
||||||
"accountConfirmDeactivateTitle": "Request account deletion?",
|
"accountConfirmDeactivateTitle": "Request account deletion?",
|
||||||
"accountConfirmDeactivateMessage": "All devices will be signed out immediately. You can cancel during the 15-day cooling-off period after signing in again.",
|
"accountConfirmDeactivateMessage": "All devices will be signed out immediately. You can cancel during the 15-day cooling-off period after signing in again.",
|
||||||
@@ -722,8 +733,6 @@
|
|||||||
"homeBadgeHot": "Hot",
|
"homeBadgeHot": "Hot",
|
||||||
"homeBadgeNew": "New",
|
"homeBadgeNew": "New",
|
||||||
"homeBadgeAnime": "Anime",
|
"homeBadgeAnime": "Anime",
|
||||||
"homeGridFilterEmpty": "No dramas match these filters",
|
|
||||||
"homeGridClearFilter": "Clear filters",
|
|
||||||
"emptyContent": "No content yet",
|
"emptyContent": "No content yet",
|
||||||
"recommendTopTab": "Recommend",
|
"recommendTopTab": "Recommend",
|
||||||
"playlistTopTab": "Playlists",
|
"playlistTopTab": "Playlists",
|
||||||
|
|||||||
+15
-6
@@ -412,6 +412,21 @@
|
|||||||
"authPasswordWeak": "Kata sandi perlu huruf dan angka",
|
"authPasswordWeak": "Kata sandi perlu huruf dan angka",
|
||||||
"authNetworkError": "Jaringan bermasalah, coba lagi",
|
"authNetworkError": "Jaringan bermasalah, coba lagi",
|
||||||
"authServerError": "Layanan sedang sibuk. Coba lagi sebentar.",
|
"authServerError": "Layanan sedang sibuk. Coba lagi sebentar.",
|
||||||
|
"apiErrorBadRequest": "Permintaan tidak valid",
|
||||||
|
"apiErrorUnauthorized": "Masuk untuk melanjutkan",
|
||||||
|
"apiErrorForbidden": "Anda tidak memiliki izin",
|
||||||
|
"apiErrorNotFound": "Tidak ditemukan",
|
||||||
|
"apiErrorTooManyRequests": "Terlalu banyak permintaan. Coba lagi nanti.",
|
||||||
|
"apiErrorLoginFailed": "Akun atau kata sandi salah",
|
||||||
|
"apiErrorAccountDisabled": "Akun ini telah dinonaktifkan",
|
||||||
|
"apiErrorCoinNotEnough": "Koin tidak cukup",
|
||||||
|
"apiErrorEpisodeUnavailable": "Episode ini tidak tersedia",
|
||||||
|
"apiErrorEpisodeUnlocked": "Episode ini sudah terbuka",
|
||||||
|
"apiErrorOrderInvalid": "Pesanan ini sudah dibayar atau ditutup",
|
||||||
|
"apiErrorVipExpired": "Keanggotaan telah berakhir",
|
||||||
|
"apiErrorDataExists": "Data sudah ada",
|
||||||
|
"apiErrorDataNotFound": "Data tidak ditemukan",
|
||||||
|
"apiErrorNotAllowed": "Tindakan ini tidak tersedia saat ini",
|
||||||
"authSwitchToSignUp": "Baru di sini? Buat akun",
|
"authSwitchToSignUp": "Baru di sini? Buat akun",
|
||||||
"authSwitchToSignIn": "Sudah punya akun? Masuk",
|
"authSwitchToSignIn": "Sudah punya akun? Masuk",
|
||||||
"authGateTitle": "Masuk untuk melanjutkan",
|
"authGateTitle": "Masuk untuk melanjutkan",
|
||||||
@@ -559,10 +574,6 @@
|
|||||||
"accountDeactivateWarning": "Permintaan masuk masa tenang 15 hari. Semua perangkat langsung keluar.",
|
"accountDeactivateWarning": "Permintaan masuk masa tenang 15 hari. Semua perangkat langsung keluar.",
|
||||||
"accountDeactivateReason": "Alasan (opsional)",
|
"accountDeactivateReason": "Alasan (opsional)",
|
||||||
"accountDeactivateReasonHint": "Tambahkan penjelasan di sini",
|
"accountDeactivateReasonHint": "Tambahkan penjelasan di sini",
|
||||||
"accountDeactivateReasonUnused": "Tidak lagi digunakan",
|
|
||||||
"accountDeactivateReasonContent": "Konten tidak menarik",
|
|
||||||
"accountDeactivateReasonPrivacy": "Masalah privasi",
|
|
||||||
"accountDeactivateReasonOther": "Lainnya",
|
|
||||||
"accountRequestDeactivate": "Ajukan penghapusan akun",
|
"accountRequestDeactivate": "Ajukan penghapusan akun",
|
||||||
"accountConfirmDeactivateTitle": "Ajukan penghapusan akun?",
|
"accountConfirmDeactivateTitle": "Ajukan penghapusan akun?",
|
||||||
"accountConfirmDeactivateMessage": "Semua perangkat akan langsung keluar. Setelah masuk lagi, kamu dapat membatalkannya selama masa tenang 15 hari.",
|
"accountConfirmDeactivateMessage": "Semua perangkat akan langsung keluar. Setelah masuk lagi, kamu dapat membatalkannya selama masa tenang 15 hari.",
|
||||||
@@ -679,8 +690,6 @@
|
|||||||
"homeBadgeHot": "Populer",
|
"homeBadgeHot": "Populer",
|
||||||
"homeBadgeNew": "Baru",
|
"homeBadgeNew": "Baru",
|
||||||
"homeBadgeAnime": "Anime",
|
"homeBadgeAnime": "Anime",
|
||||||
"homeGridFilterEmpty": "Tidak ada drama yang cocok dengan filter",
|
|
||||||
"homeGridClearFilter": "Hapus filter",
|
|
||||||
"emptyContent": "Belum ada konten",
|
"emptyContent": "Belum ada konten",
|
||||||
"recommendTopTab": "Rekomendasi",
|
"recommendTopTab": "Rekomendasi",
|
||||||
"playlistTopTab": "Daftar Putar",
|
"playlistTopTab": "Daftar Putar",
|
||||||
|
|||||||
@@ -1906,6 +1906,96 @@ abstract class AppLocalizations {
|
|||||||
/// **'The service is busy. Please try again shortly.'**
|
/// **'The service is busy. Please try again shortly.'**
|
||||||
String get authServerError;
|
String get authServerError;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorBadRequest.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Invalid request'**
|
||||||
|
String get apiErrorBadRequest;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorUnauthorized.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Sign in to continue'**
|
||||||
|
String get apiErrorUnauthorized;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorForbidden.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'You don\'t have permission'**
|
||||||
|
String get apiErrorForbidden;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorNotFound.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Not found'**
|
||||||
|
String get apiErrorNotFound;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorTooManyRequests.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Too many requests. Please try again later.'**
|
||||||
|
String get apiErrorTooManyRequests;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorLoginFailed.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Incorrect account or password'**
|
||||||
|
String get apiErrorLoginFailed;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorAccountDisabled.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'This account has been disabled'**
|
||||||
|
String get apiErrorAccountDisabled;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorCoinNotEnough.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Not enough coins'**
|
||||||
|
String get apiErrorCoinNotEnough;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorEpisodeUnavailable.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'This episode is unavailable'**
|
||||||
|
String get apiErrorEpisodeUnavailable;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorEpisodeUnlocked.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'This episode is already unlocked'**
|
||||||
|
String get apiErrorEpisodeUnlocked;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorOrderInvalid.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'This order is already paid or closed'**
|
||||||
|
String get apiErrorOrderInvalid;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorVipExpired.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Membership has expired'**
|
||||||
|
String get apiErrorVipExpired;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorDataExists.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'This record already exists'**
|
||||||
|
String get apiErrorDataExists;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorDataNotFound.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Not found'**
|
||||||
|
String get apiErrorDataNotFound;
|
||||||
|
|
||||||
|
/// No description provided for @apiErrorNotAllowed.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'This action isn\'t available right now'**
|
||||||
|
String get apiErrorNotAllowed;
|
||||||
|
|
||||||
/// No description provided for @authSwitchToSignUp.
|
/// No description provided for @authSwitchToSignUp.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -2488,30 +2578,6 @@ abstract class AppLocalizations {
|
|||||||
/// **'Add more detail here'**
|
/// **'Add more detail here'**
|
||||||
String get accountDeactivateReasonHint;
|
String get accountDeactivateReasonHint;
|
||||||
|
|
||||||
/// No description provided for @accountDeactivateReasonUnused.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Not using it anymore'**
|
|
||||||
String get accountDeactivateReasonUnused;
|
|
||||||
|
|
||||||
/// No description provided for @accountDeactivateReasonContent.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Not interested in the content'**
|
|
||||||
String get accountDeactivateReasonContent;
|
|
||||||
|
|
||||||
/// No description provided for @accountDeactivateReasonPrivacy.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Privacy concerns'**
|
|
||||||
String get accountDeactivateReasonPrivacy;
|
|
||||||
|
|
||||||
/// No description provided for @accountDeactivateReasonOther.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Other'**
|
|
||||||
String get accountDeactivateReasonOther;
|
|
||||||
|
|
||||||
/// No description provided for @accountRequestDeactivate.
|
/// No description provided for @accountRequestDeactivate.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -3136,18 +3202,6 @@ abstract class AppLocalizations {
|
|||||||
/// **'Anime'**
|
/// **'Anime'**
|
||||||
String get homeBadgeAnime;
|
String get homeBadgeAnime;
|
||||||
|
|
||||||
/// No description provided for @homeGridFilterEmpty.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'No dramas match these filters'**
|
|
||||||
String get homeGridFilterEmpty;
|
|
||||||
|
|
||||||
/// No description provided for @homeGridClearFilter.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Clear filters'**
|
|
||||||
String get homeGridClearFilter;
|
|
||||||
|
|
||||||
/// No description provided for @emptyContent.
|
/// No description provided for @emptyContent.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -968,6 +968,52 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get authServerError =>
|
String get authServerError =>
|
||||||
'The service is busy. Please try again shortly.';
|
'The service is busy. Please try again shortly.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorBadRequest => 'Invalid request';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorUnauthorized => 'Sign in to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorForbidden => 'You don\'t have permission';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorNotFound => 'Not found';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorTooManyRequests =>
|
||||||
|
'Too many requests. Please try again later.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorLoginFailed => 'Incorrect account or password';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorAccountDisabled => 'This account has been disabled';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorCoinNotEnough => 'Not enough coins';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorEpisodeUnavailable => 'This episode is unavailable';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorEpisodeUnlocked => 'This episode is already unlocked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorOrderInvalid => 'This order is already paid or closed';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorVipExpired => 'Membership has expired';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorDataExists => 'This record already exists';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorDataNotFound => 'Not found';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorNotAllowed => 'This action isn\'t available right now';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authSwitchToSignUp => 'New here? Create an account';
|
String get authSwitchToSignUp => 'New here? Create an account';
|
||||||
|
|
||||||
@@ -1289,18 +1335,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get accountDeactivateReasonHint => 'Add more detail here';
|
String get accountDeactivateReasonHint => 'Add more detail here';
|
||||||
|
|
||||||
@override
|
|
||||||
String get accountDeactivateReasonUnused => 'Not using it anymore';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get accountDeactivateReasonContent => 'Not interested in the content';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get accountDeactivateReasonPrivacy => 'Privacy concerns';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get accountDeactivateReasonOther => 'Other';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get accountRequestDeactivate => 'Request account deletion';
|
String get accountRequestDeactivate => 'Request account deletion';
|
||||||
|
|
||||||
@@ -1638,12 +1672,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get homeBadgeAnime => 'Anime';
|
String get homeBadgeAnime => 'Anime';
|
||||||
|
|
||||||
@override
|
|
||||||
String get homeGridFilterEmpty => 'No dramas match these filters';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get homeGridClearFilter => 'Clear filters';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get emptyContent => 'No content yet';
|
String get emptyContent => 'No content yet';
|
||||||
|
|
||||||
|
|||||||
@@ -971,6 +971,52 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get authServerError => 'Layanan sedang sibuk. Coba lagi sebentar.';
|
String get authServerError => 'Layanan sedang sibuk. Coba lagi sebentar.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorBadRequest => 'Permintaan tidak valid';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorUnauthorized => 'Masuk untuk melanjutkan';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorForbidden => 'Anda tidak memiliki izin';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorNotFound => 'Tidak ditemukan';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorTooManyRequests =>
|
||||||
|
'Terlalu banyak permintaan. Coba lagi nanti.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorLoginFailed => 'Akun atau kata sandi salah';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorAccountDisabled => 'Akun ini telah dinonaktifkan';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorCoinNotEnough => 'Koin tidak cukup';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorEpisodeUnavailable => 'Episode ini tidak tersedia';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorEpisodeUnlocked => 'Episode ini sudah terbuka';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorOrderInvalid => 'Pesanan ini sudah dibayar atau ditutup';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorVipExpired => 'Keanggotaan telah berakhir';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorDataExists => 'Data sudah ada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorDataNotFound => 'Data tidak ditemukan';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorNotAllowed => 'Tindakan ini tidak tersedia saat ini';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authSwitchToSignUp => 'Baru di sini? Buat akun';
|
String get authSwitchToSignUp => 'Baru di sini? Buat akun';
|
||||||
|
|
||||||
@@ -1288,18 +1334,6 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get accountDeactivateReasonHint => 'Tambahkan penjelasan di sini';
|
String get accountDeactivateReasonHint => 'Tambahkan penjelasan di sini';
|
||||||
|
|
||||||
@override
|
|
||||||
String get accountDeactivateReasonUnused => 'Tidak lagi digunakan';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get accountDeactivateReasonContent => 'Konten tidak menarik';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get accountDeactivateReasonPrivacy => 'Masalah privasi';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get accountDeactivateReasonOther => 'Lainnya';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get accountRequestDeactivate => 'Ajukan penghapusan akun';
|
String get accountRequestDeactivate => 'Ajukan penghapusan akun';
|
||||||
|
|
||||||
@@ -1638,12 +1672,6 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get homeBadgeAnime => 'Anime';
|
String get homeBadgeAnime => 'Anime';
|
||||||
|
|
||||||
@override
|
|
||||||
String get homeGridFilterEmpty => 'Tidak ada drama yang cocok dengan filter';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get homeGridClearFilter => 'Hapus filter';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get emptyContent => 'Belum ada konten';
|
String get emptyContent => 'Belum ada konten';
|
||||||
|
|
||||||
|
|||||||
@@ -955,6 +955,51 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get authServerError => '服务繁忙,请稍后再试';
|
String get authServerError => '服务繁忙,请稍后再试';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorBadRequest => '参数校验失败';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorUnauthorized => '未登录或登录已过期';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorForbidden => '无权限';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorNotFound => '资源不存在';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorTooManyRequests => '请求过于频繁,请稍后再试';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorLoginFailed => '账号或密码错误';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorAccountDisabled => '账号已停用';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorCoinNotEnough => '金币余额不足';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorEpisodeUnavailable => '剧集未发布或不存在';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorEpisodeUnlocked => '剧集已解锁';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorOrderInvalid => '订单已支付或已关闭';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorVipExpired => '会员已过期';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorDataExists => '数据已存在';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorDataNotFound => '数据不存在';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get apiErrorNotAllowed => '当前状态不允许该操作';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authSwitchToSignUp => '还没有账号?去注册';
|
String get authSwitchToSignUp => '还没有账号?去注册';
|
||||||
|
|
||||||
@@ -1266,18 +1311,6 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get accountDeactivateReasonHint => '也可以在这里补充说明';
|
String get accountDeactivateReasonHint => '也可以在这里补充说明';
|
||||||
|
|
||||||
@override
|
|
||||||
String get accountDeactivateReasonUnused => '不再使用';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get accountDeactivateReasonContent => '内容不感兴趣';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get accountDeactivateReasonPrivacy => '隐私顾虑';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get accountDeactivateReasonOther => '其他';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get accountRequestDeactivate => '申请注销账号';
|
String get accountRequestDeactivate => '申请注销账号';
|
||||||
|
|
||||||
@@ -1613,12 +1646,6 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get homeBadgeAnime => '漫剧';
|
String get homeBadgeAnime => '漫剧';
|
||||||
|
|
||||||
@override
|
|
||||||
String get homeGridFilterEmpty => '没有符合筛选条件的短剧';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get homeGridClearFilter => '清除筛选';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get emptyContent => '暂无内容';
|
String get emptyContent => '暂无内容';
|
||||||
|
|
||||||
|
|||||||
+15
-6
@@ -374,6 +374,21 @@
|
|||||||
"authPasswordWeak": "密码需同时包含字母和数字",
|
"authPasswordWeak": "密码需同时包含字母和数字",
|
||||||
"authNetworkError": "网络异常,请稍后重试",
|
"authNetworkError": "网络异常,请稍后重试",
|
||||||
"authServerError": "服务繁忙,请稍后再试",
|
"authServerError": "服务繁忙,请稍后再试",
|
||||||
|
"apiErrorBadRequest": "参数校验失败",
|
||||||
|
"apiErrorUnauthorized": "未登录或登录已过期",
|
||||||
|
"apiErrorForbidden": "无权限",
|
||||||
|
"apiErrorNotFound": "资源不存在",
|
||||||
|
"apiErrorTooManyRequests": "请求过于频繁,请稍后再试",
|
||||||
|
"apiErrorLoginFailed": "账号或密码错误",
|
||||||
|
"apiErrorAccountDisabled": "账号已停用",
|
||||||
|
"apiErrorCoinNotEnough": "金币余额不足",
|
||||||
|
"apiErrorEpisodeUnavailable": "剧集未发布或不存在",
|
||||||
|
"apiErrorEpisodeUnlocked": "剧集已解锁",
|
||||||
|
"apiErrorOrderInvalid": "订单已支付或已关闭",
|
||||||
|
"apiErrorVipExpired": "会员已过期",
|
||||||
|
"apiErrorDataExists": "数据已存在",
|
||||||
|
"apiErrorDataNotFound": "数据不存在",
|
||||||
|
"apiErrorNotAllowed": "当前状态不允许该操作",
|
||||||
"authSwitchToSignUp": "还没有账号?去注册",
|
"authSwitchToSignUp": "还没有账号?去注册",
|
||||||
"authSwitchToSignIn": "已有账号?去登录",
|
"authSwitchToSignIn": "已有账号?去登录",
|
||||||
"authGateTitle": "登录后可继续",
|
"authGateTitle": "登录后可继续",
|
||||||
@@ -521,10 +536,6 @@
|
|||||||
"accountDeactivateWarning": "提交后进入 15 天冷静期,所有设备会立即退出登录。",
|
"accountDeactivateWarning": "提交后进入 15 天冷静期,所有设备会立即退出登录。",
|
||||||
"accountDeactivateReason": "注销原因(选填)",
|
"accountDeactivateReason": "注销原因(选填)",
|
||||||
"accountDeactivateReasonHint": "也可以在这里补充说明",
|
"accountDeactivateReasonHint": "也可以在这里补充说明",
|
||||||
"accountDeactivateReasonUnused": "不再使用",
|
|
||||||
"accountDeactivateReasonContent": "内容不感兴趣",
|
|
||||||
"accountDeactivateReasonPrivacy": "隐私顾虑",
|
|
||||||
"accountDeactivateReasonOther": "其他",
|
|
||||||
"accountRequestDeactivate": "申请注销账号",
|
"accountRequestDeactivate": "申请注销账号",
|
||||||
"accountConfirmDeactivateTitle": "确认申请注销?",
|
"accountConfirmDeactivateTitle": "确认申请注销?",
|
||||||
"accountConfirmDeactivateMessage": "所有设备会立即退出登录。重新登录后,可在 15 天冷静期内撤销申请。",
|
"accountConfirmDeactivateMessage": "所有设备会立即退出登录。重新登录后,可在 15 天冷静期内撤销申请。",
|
||||||
@@ -641,8 +652,6 @@
|
|||||||
"homeBadgeHot": "热剧",
|
"homeBadgeHot": "热剧",
|
||||||
"homeBadgeNew": "新剧",
|
"homeBadgeNew": "新剧",
|
||||||
"homeBadgeAnime": "漫剧",
|
"homeBadgeAnime": "漫剧",
|
||||||
"homeGridFilterEmpty": "没有符合筛选条件的短剧",
|
|
||||||
"homeGridClearFilter": "清除筛选",
|
|
||||||
"emptyContent": "暂无内容",
|
"emptyContent": "暂无内容",
|
||||||
"recommendTopTab": "推荐",
|
"recommendTopTab": "推荐",
|
||||||
"playlistTopTab": "剧单",
|
"playlistTopTab": "剧单",
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
// Author: fruit.com.cn
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../core/theme/app_colors.dart';
|
|
||||||
import '../../core/theme/app_dimens.dart';
|
|
||||||
import '../../l10n/app_localizations.dart';
|
|
||||||
import 'account_deactivate_logic.dart';
|
|
||||||
|
|
||||||
/// 注销原因:固定选项在标题下换行排列,补充说明留在输入框内。
|
|
||||||
class DeactivateReasonField extends StatelessWidget {
|
|
||||||
const DeactivateReasonField({
|
|
||||||
super.key,
|
|
||||||
required this.controller,
|
|
||||||
required this.onChanged,
|
|
||||||
});
|
|
||||||
|
|
||||||
final TextEditingController controller;
|
|
||||||
final VoidCallback onChanged;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final l10n = AppLocalizations.of(context)!;
|
|
||||||
final options = _options(l10n);
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
l10n.accountDeactivateReason,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: AppColors.textPrimary,
|
|
||||||
fontSize: AppDimens.listTitle,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: AppDimens.spaceSm),
|
|
||||||
Wrap(
|
|
||||||
spacing: AppDimens.spaceSm,
|
|
||||||
runSpacing: AppDimens.spaceSm,
|
|
||||||
children: [
|
|
||||||
for (var index = 0; index < options.length; index++)
|
|
||||||
_chip(options[index], index, options),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: AppDimens.spaceMd),
|
|
||||||
TextField(
|
|
||||||
key: const Key('account-deactivate-reason'),
|
|
||||||
controller: controller,
|
|
||||||
maxLength: AccountDeactivateLogic.maxReasonLength,
|
|
||||||
minLines: 3,
|
|
||||||
maxLines: 4,
|
|
||||||
textAlignVertical: TextAlignVertical.top,
|
|
||||||
onChanged: (_) => onChanged(),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: l10n.accountDeactivateReasonHint,
|
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
|
||||||
alignLabelWithHint: true,
|
|
||||||
contentPadding: const EdgeInsets.all(AppDimens.cardPadding),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _chip(_ReasonOption option, int index, List<_ReasonOption> options) {
|
|
||||||
final selected = option.custom
|
|
||||||
? _customSelected(options)
|
|
||||||
: controller.text == option.label;
|
|
||||||
return ChoiceChip(
|
|
||||||
key: Key('account-deactivate-reason-$index'),
|
|
||||||
label: Text(option.label),
|
|
||||||
selected: selected,
|
|
||||||
onSelected: (_) => _select(option),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _customSelected(List<_ReasonOption> options) {
|
|
||||||
final text = controller.text.trim();
|
|
||||||
if (text.isEmpty) return false;
|
|
||||||
return options
|
|
||||||
.where((item) => !item.custom)
|
|
||||||
.every((item) => item.label != text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _select(_ReasonOption option) {
|
|
||||||
controller.text = option.custom ? '' : option.label;
|
|
||||||
controller.selection = TextSelection.collapsed(
|
|
||||||
offset: controller.text.length,
|
|
||||||
);
|
|
||||||
onChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<_ReasonOption> _options(AppLocalizations l10n) => [
|
|
||||||
_ReasonOption(l10n.accountDeactivateReasonUnused),
|
|
||||||
_ReasonOption(l10n.accountDeactivateReasonContent),
|
|
||||||
_ReasonOption(l10n.accountDeactivateReasonPrivacy),
|
|
||||||
_ReasonOption(l10n.accountDeactivateReasonOther, custom: true),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ReasonOption {
|
|
||||||
const _ReasonOption(this.label, {this.custom = false});
|
|
||||||
|
|
||||||
final String label;
|
|
||||||
final bool custom;
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import '../../core/data/remote/api_client.dart';
|
import '../../core/data/remote/api_client.dart';
|
||||||
import '../../core/data/services/auth_controller.dart';
|
import '../../core/data/services/auth_controller.dart';
|
||||||
|
import '../../core/formatting/error_text.dart';
|
||||||
import '../../core/platform/haptics.dart';
|
import '../../core/platform/haptics.dart';
|
||||||
import '../../core/platform/keyboard_inset.dart';
|
import '../../core/platform/keyboard_inset.dart';
|
||||||
import '../../core/theme/app_colors.dart';
|
import '../../core/theme/app_colors.dart';
|
||||||
@@ -180,7 +181,7 @@ class _PhoneAuthSheetState extends State<PhoneAuthSheet> {
|
|||||||
).showSnackBar(SnackBar(content: Text(l10n.authCodeSent)));
|
).showSnackBar(SnackBar(content: Text(l10n.authCodeSent)));
|
||||||
_codeFocus.requestFocus();
|
_codeFocus.requestFocus();
|
||||||
} on ApiException catch (error) {
|
} on ApiException catch (error) {
|
||||||
failureMessage = error.message;
|
failureMessage = userFacingApiMessage(l10n, error.code, error.message);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
failureMessage = l10n.authNetworkError;
|
failureMessage = l10n.authNetworkError;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -228,7 +229,7 @@ class _PhoneAuthSheetState extends State<PhoneAuthSheet> {
|
|||||||
}
|
}
|
||||||
if (mounted) Navigator.of(context).pop(true);
|
if (mounted) Navigator.of(context).pop(true);
|
||||||
} on ApiException catch (error) {
|
} on ApiException catch (error) {
|
||||||
failureMessage = error.message;
|
failureMessage = userFacingApiMessage(l10n, error.code, error.message);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
failureMessage = l10n.authNetworkError;
|
failureMessage = l10n.authNetworkError;
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -71,23 +71,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final l10n = await AppLocalizations.delegate.load(const Locale('en'));
|
final l10n = await AppLocalizations.delegate.load(const Locale('en'));
|
||||||
final titleTop = tester
|
expect(find.text(l10n.accountDeactivateReason), findsOneWidget);
|
||||||
.getTopLeft(find.text(l10n.accountDeactivateReason))
|
|
||||||
.dy;
|
|
||||||
final fieldTop = tester
|
|
||||||
.getTopLeft(find.byKey(const Key('account-deactivate-reason')))
|
|
||||||
.dy;
|
|
||||||
expect(fieldTop, greaterThan(titleTop));
|
|
||||||
expect(find.text(l10n.accountDeactivateReasonUnused), findsOneWidget);
|
|
||||||
await tester.tap(find.byKey(const Key('account-deactivate-reason-0')));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
expect(
|
|
||||||
tester
|
|
||||||
.widget<TextField>(find.byKey(const Key('account-deactivate-reason')))
|
|
||||||
.controller
|
|
||||||
?.text,
|
|
||||||
l10n.accountDeactivateReasonUnused,
|
|
||||||
);
|
|
||||||
await tester.enterText(
|
await tester.enterText(
|
||||||
find.byKey(const Key('account-deactivate-reason')),
|
find.byKey(const Key('account-deactivate-reason')),
|
||||||
'不再使用',
|
'不再使用',
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:fruitshorts/core/formatting/error_text.dart';
|
||||||
|
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('should_keep_login_failure_message_when_code_is_business', () async {
|
||||||
|
final l10n = await AppLocalizations.delegate.load(const Locale('zh'));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
userFacingApiMessage(l10n, 100000, '账号或密码错误'),
|
||||||
|
'账号或密码错误',
|
||||||
|
);
|
||||||
|
expect(userFacingApiMessage(l10n, 500, '系统异常'), l10n.authServerError);
|
||||||
|
|
||||||
|
final english = await AppLocalizations.delegate.load(const Locale('en'));
|
||||||
|
expect(
|
||||||
|
userFacingApiMessage(english, 100000, '账号或密码错误'),
|
||||||
|
'Incorrect account or password',
|
||||||
|
);
|
||||||
|
final indonesian = await AppLocalizations.delegate.load(const Locale('id'));
|
||||||
|
expect(
|
||||||
|
userFacingApiMessage(indonesian, 100000, '账号或密码错误'),
|
||||||
|
'Akun atau kata sandi salah',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in new issue
Block a user