Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
402a5436bc | ||
|
|
80ca59d19f | ||
|
|
848fee6ff1 |
@@ -54,6 +54,8 @@ class MainActivity : FlutterActivity() {
|
||||
setPreferredRefreshRate(hz.toFloat())
|
||||
result.success(null)
|
||||
}
|
||||
"getSupportedAbis" -> result.success(Build.SUPPORTED_ABIS.toList())
|
||||
"getPrimaryAbi" -> result.success(Build.SUPPORTED_ABIS.firstOrNull() ?: "armeabi-v7a")
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
} catch (error: Throwable) {
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ class _AppOverlayState extends ConsumerState<_AppOverlay> {
|
||||
|
||||
Future<void> _downloadUpdate(AppUpdateInfo info) async {
|
||||
final updateService = ref.read(updateServiceProvider);
|
||||
final assetUrl = updateService.assetUrlForCurrentPlatform(info);
|
||||
final assetUrl = await updateService.assetUrlForCurrentPlatform(info);
|
||||
if (assetUrl == null) {
|
||||
final url = Uri.tryParse(info.htmlUrl);
|
||||
if (url != null) {
|
||||
|
||||
@@ -1,4 +1,32 @@
|
||||
const lunarisChangelog = [
|
||||
LunarisChange(
|
||||
version: '2.3.9',
|
||||
title: 'Исправление входа и синхронизации аккаунтов Pawchive',
|
||||
bullets: [
|
||||
'Исправлены API эндпоинты Pawchive: пути запросов избранных авторов и управления подписками приведены в соответствие со спецификацией API v1 (/api/v1/account/favorites, /api/v1/favorites/creator/...).',
|
||||
'Устранена ошибка 404: авторизация по логину и паролю, а также по session cookie теперь успешно завершается и сразу синхронизирует всех авторов.',
|
||||
'Информативные ошибки: понятные сообщения на русском при неверном пароле или сбое сети вместо сырых исключений Dio.',
|
||||
],
|
||||
),
|
||||
LunarisChange(
|
||||
version: '2.3.8',
|
||||
title: 'ABI-Aware Auto-Update — правильный APK для вашего процессора',
|
||||
bullets: [
|
||||
'Определение архитектуры Android: приложение читает Build.SUPPORTED_ABIS (arm64-v8a, armeabi-v7a, x86_64) через нативный канал.',
|
||||
'Точный APK из Gitea: при обновлении скачивается per-ABI APK (app-arm64-v8a-release.apk и т.д.), а не один общий файл.',
|
||||
'Fallback: нет per-ABI APK → универсальный APK → страница релиза в браузере.',
|
||||
'Кеш ABI: архитектура определяется один раз за сеанс.',
|
||||
],
|
||||
),
|
||||
LunarisChange(
|
||||
version: '2.3.7',
|
||||
title: 'Lazy Media Dimensions for Pawchive & Unknown-Size Providers',
|
||||
bullets: [
|
||||
'Auto-Resolve Image Dimensions: when a provider (such as Pawchive) does not supply width/height metadata, the app now lazily resolves the real pixel dimensions by decoding the image in the background and displays them as soon as they are ready.',
|
||||
'No More "0 × 0": the resolution badge now shows "… × …" while dimensions are being fetched, and the correct resolution (e.g. 1920 × 1080) once decoded. The badge is hidden entirely for video/link posts where dimensions cannot be resolved.',
|
||||
'Session Cache: resolved image dimensions are cached in memory for the lifetime of the app so repeated visits to the same post do not trigger re-downloads.',
|
||||
],
|
||||
),
|
||||
LunarisChange(
|
||||
version: '2.3.6',
|
||||
title: 'Pawchive Accounts Integration & Favorites Synchronization',
|
||||
|
||||
@@ -7,6 +7,9 @@ class AppUpdateInfo {
|
||||
required this.htmlUrl,
|
||||
required this.publishedAt,
|
||||
this.apkUrl,
|
||||
this.apkArm64Url,
|
||||
this.apkArmv7Url,
|
||||
this.apkX86_64Url,
|
||||
this.windowsInstallerUrl,
|
||||
this.portableZipUrl,
|
||||
this.linuxTarGzUrl,
|
||||
@@ -18,13 +21,36 @@ class AppUpdateInfo {
|
||||
final String body;
|
||||
final String htmlUrl;
|
||||
final DateTime? publishedAt;
|
||||
|
||||
/// Generic APK (fat / universal build). Used as a fallback.
|
||||
final String? apkUrl;
|
||||
|
||||
/// Per-ABI APKs produced by `flutter build apk --split-per-abi`.
|
||||
final String? apkArm64Url; // app-arm64-v8a-release.apk
|
||||
final String? apkArmv7Url; // app-armeabi-v7a-release.apk
|
||||
final String? apkX86_64Url; // app-x86_64-release.apk
|
||||
|
||||
final String? windowsInstallerUrl;
|
||||
final String? portableZipUrl;
|
||||
final String? linuxTarGzUrl;
|
||||
|
||||
/// Returns the best APK URL for [abi], falling back to the generic APK.
|
||||
String? apkUrlForAbi(String abi) {
|
||||
if (abi.contains('arm64') || abi == 'arm64-v8a') {
|
||||
return apkArm64Url ?? apkUrl;
|
||||
}
|
||||
if (abi.contains('x86_64')) {
|
||||
return apkX86_64Url ?? apkUrl;
|
||||
}
|
||||
// armeabi-v7a and everything else
|
||||
return apkArmv7Url ?? apkArm64Url ?? apkUrl;
|
||||
}
|
||||
|
||||
bool get hasAssets =>
|
||||
apkUrl != null ||
|
||||
apkArm64Url != null ||
|
||||
apkArmv7Url != null ||
|
||||
apkX86_64Url != null ||
|
||||
windowsInstallerUrl != null ||
|
||||
portableZipUrl != null ||
|
||||
linuxTarGzUrl != null;
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:dio/dio.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../core/http/dio_client.dart';
|
||||
import '../di/backend_providers.dart';
|
||||
import '../models/favorite_artist_item.dart';
|
||||
import '../models/pawchive_account.dart';
|
||||
import 'settings_service.dart';
|
||||
@@ -98,10 +97,25 @@ class PawchiveSyncService {
|
||||
}
|
||||
|
||||
// Check if redirect points back to login with flash error
|
||||
if (redirectLocation.contains('/account/login')) {
|
||||
if (redirectLocation.contains('/account/login') ||
|
||||
redirectLocation.contains('/login')) {
|
||||
throw StateError('Неверный логин или пароль');
|
||||
}
|
||||
|
||||
// Check for flash errors in session cookie if present
|
||||
try {
|
||||
final dotIdx = sessionValue.indexOf('.');
|
||||
final payloadBase64 =
|
||||
dotIdx != -1 ? sessionValue.substring(0, dotIdx) : sessionValue;
|
||||
final normalizedB64 = base64Url.normalize(payloadBase64);
|
||||
final decoded = utf8.decode(base64Url.decode(normalizedB64));
|
||||
if (decoded.contains('is incorrect') || decoded.contains('_flashes')) {
|
||||
throw StateError('Неверный логин или пароль');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is StateError) rethrow;
|
||||
}
|
||||
|
||||
// Verify session and fetch initial artists
|
||||
final remoteArtists = await fetchRemoteFavoriteArtists(
|
||||
baseUrl: normalizedBase,
|
||||
@@ -118,6 +132,15 @@ class PawchiveSyncService {
|
||||
isActive: true,
|
||||
syncedArtistsCount: remoteArtists.length,
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
final code = e.response?.statusCode;
|
||||
if (code == 401 || code == 403) {
|
||||
throw StateError('Неверный логин или пароль');
|
||||
} else if (code != null) {
|
||||
throw StateError('Ошибка сервера Pawchive (HTTP $code)');
|
||||
} else {
|
||||
throw StateError('Ошибка сети: ${e.message ?? e.type.name}');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is StateError || e is ArgumentError) rethrow;
|
||||
throw StateError('Ошибка входа в Pawchive: $e');
|
||||
@@ -156,7 +179,17 @@ class PawchiveSyncService {
|
||||
isActive: true,
|
||||
syncedArtistsCount: remoteArtists.length,
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
final code = e.response?.statusCode;
|
||||
if (code == 401 || code == 403) {
|
||||
throw StateError('Сессия недействительна или истекла');
|
||||
} else if (code != null) {
|
||||
throw StateError('Ошибка сервера Pawchive (HTTP $code)');
|
||||
} else {
|
||||
throw StateError('Ошибка сети: ${e.message ?? e.type.name}');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is StateError || e is ArgumentError) rethrow;
|
||||
throw StateError('Сессия недействительна или истекла: $e');
|
||||
}
|
||||
}
|
||||
@@ -172,7 +205,7 @@ class PawchiveSyncService {
|
||||
: baseUrl;
|
||||
|
||||
final response = await _dio.get<dynamic>(
|
||||
'$normalizedBase/account/favorites',
|
||||
'$normalizedBase/api/v1/account/favorites',
|
||||
queryParameters: {'type': 'artist'},
|
||||
options: Options(
|
||||
headers: {
|
||||
@@ -180,9 +213,17 @@ class PawchiveSyncService {
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'Lunaris/2.0.1 Flutter local booru browser',
|
||||
},
|
||||
validateStatus: (status) => status != null && status < 500,
|
||||
),
|
||||
);
|
||||
|
||||
if (response.statusCode == 401 || response.statusCode == 403) {
|
||||
throw StateError('Сессия истекла или недействительна');
|
||||
}
|
||||
if (response.statusCode != 200) {
|
||||
throw StateError('Ошибка сервера Pawchive (HTTP ${response.statusCode})');
|
||||
}
|
||||
|
||||
final data = response.data;
|
||||
if (data is! List) return const [];
|
||||
|
||||
@@ -344,7 +385,7 @@ class PawchiveSyncService {
|
||||
? account.baseUrl.substring(0, account.baseUrl.length - 1)
|
||||
: account.baseUrl;
|
||||
|
||||
final path = '$normalizedBase/favorites/creator/$service/$artistId';
|
||||
final path = '$normalizedBase/api/v1/favorites/creator/$service/$artistId';
|
||||
final options = Options(
|
||||
headers: {
|
||||
'Cookie': 'session=$cleanCookie',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../../app/app_version.dart';
|
||||
import '../../core/errors/failure.dart';
|
||||
import '../../core/utils/result.dart';
|
||||
@@ -15,9 +17,39 @@ class UpdateService {
|
||||
static const releasesUrl =
|
||||
'https://gitea.rardogsynapse.online/api/v1/repos/RarDog/Lunaris/releases';
|
||||
|
||||
static const _deviceChannel = MethodChannel('rulegel/device');
|
||||
|
||||
final Dio _dio;
|
||||
final SettingsService _settingsService;
|
||||
|
||||
// Cached primary ABI (e.g. "arm64-v8a").
|
||||
String? _cachedPrimaryAbi;
|
||||
|
||||
/// Returns the primary ABI of the running device.
|
||||
/// On non-Android platforms returns an empty string.
|
||||
Future<String> getPrimaryAbi() async {
|
||||
if (_cachedPrimaryAbi != null) return _cachedPrimaryAbi!;
|
||||
if (!Platform.isAndroid) return '';
|
||||
try {
|
||||
final abi = await _deviceChannel.invokeMethod<String>('getPrimaryAbi');
|
||||
_cachedPrimaryAbi = abi ?? 'armeabi-v7a';
|
||||
} catch (_) {
|
||||
_cachedPrimaryAbi = 'armeabi-v7a';
|
||||
}
|
||||
return _cachedPrimaryAbi!;
|
||||
}
|
||||
|
||||
/// Returns all supported ABIs of the running device.
|
||||
Future<List<String>> getSupportedAbis() async {
|
||||
if (!Platform.isAndroid) return const [];
|
||||
try {
|
||||
final abis = await _deviceChannel.invokeMethod<List<Object?>>('getSupportedAbis');
|
||||
return abis?.whereType<String>().toList() ?? const [];
|
||||
} catch (_) {
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<AppUpdateInfo?>> checkForUpdates({bool force = false}) async {
|
||||
final settingsResult = await _settingsService.getSettings();
|
||||
final settings = settingsResult is Success<AppSettings>
|
||||
@@ -97,21 +129,40 @@ class UpdateService {
|
||||
AppUpdateInfo _releaseFromJson(Map<String, dynamic> json) {
|
||||
final tag = (json['tag_name'] ?? '').toString();
|
||||
final assets = (json['assets'] as List?) ?? const [];
|
||||
|
||||
String? apkUrl;
|
||||
String? apkArm64Url;
|
||||
String? apkArmv7Url;
|
||||
String? apkX86_64Url;
|
||||
String? installerUrl;
|
||||
String? portableUrl;
|
||||
String? linuxTarGzUrl;
|
||||
|
||||
for (final item in assets.whereType<Map>()) {
|
||||
final name = (item['name'] ?? '').toString().toLowerCase();
|
||||
final url = (item['browser_download_url'] ?? '').toString();
|
||||
if (url.isEmpty) continue;
|
||||
if (name.endsWith('.apk')) apkUrl = url;
|
||||
|
||||
if (name.endsWith('.apk')) {
|
||||
// Detect per-ABI APKs by name pattern produced by --split-per-abi.
|
||||
if (name.contains('arm64-v8a') || name.contains('arm64_v8a')) {
|
||||
apkArm64Url = url;
|
||||
} else if (name.contains('armeabi-v7a') || name.contains('armeabi_v7a')) {
|
||||
apkArmv7Url = url;
|
||||
} else if (name.contains('x86_64')) {
|
||||
apkX86_64Url = url;
|
||||
} else {
|
||||
// Universal / fat APK
|
||||
apkUrl = url;
|
||||
}
|
||||
}
|
||||
if (name.endsWith('.exe')) installerUrl = url;
|
||||
if (name.endsWith('.zip')) portableUrl = url;
|
||||
if (name.endsWith('.tar.gz') || name.endsWith('.tgz')) {
|
||||
linuxTarGzUrl = url;
|
||||
}
|
||||
}
|
||||
|
||||
return AppUpdateInfo(
|
||||
version: _versionFromTag(tag),
|
||||
tagName: tag,
|
||||
@@ -120,15 +171,21 @@ class UpdateService {
|
||||
htmlUrl: (json['html_url'] ?? '').toString(),
|
||||
publishedAt: DateTime.tryParse((json['published_at'] ?? '').toString()),
|
||||
apkUrl: apkUrl,
|
||||
apkArm64Url: apkArm64Url,
|
||||
apkArmv7Url: apkArmv7Url,
|
||||
apkX86_64Url: apkX86_64Url,
|
||||
windowsInstallerUrl: installerUrl,
|
||||
portableZipUrl: portableUrl,
|
||||
linuxTarGzUrl: linuxTarGzUrl,
|
||||
);
|
||||
}
|
||||
|
||||
String? assetUrlForCurrentPlatform(AppUpdateInfo info) {
|
||||
/// Returns the download URL for the current platform/ABI.
|
||||
/// Pass [abi] (e.g. from [getPrimaryAbi]) on Android for per-ABI selection.
|
||||
Future<String?> assetUrlForCurrentPlatform(AppUpdateInfo info) async {
|
||||
if (Platform.isAndroid) {
|
||||
return info.apkUrl;
|
||||
final abi = await getPrimaryAbi();
|
||||
return info.apkUrlForAbi(abi);
|
||||
}
|
||||
if (Platform.isLinux) {
|
||||
return info.linuxTarGzUrl ?? info.portableZipUrl;
|
||||
|
||||
@@ -1185,7 +1185,7 @@ class _CommentsSectionState extends ConsumerState<_CommentsSection> {
|
||||
}
|
||||
}
|
||||
|
||||
class _PostInfoCard extends StatelessWidget {
|
||||
class _PostInfoCard extends StatefulWidget {
|
||||
const _PostInfoCard({
|
||||
required this.post,
|
||||
required this.strings,
|
||||
@@ -1198,8 +1198,90 @@ class _PostInfoCard extends StatelessWidget {
|
||||
final DownloadedMedia? localMedia;
|
||||
final int? fileSizeBytes;
|
||||
|
||||
@override
|
||||
State<_PostInfoCard> createState() => _PostInfoCardState();
|
||||
}
|
||||
|
||||
class _PostInfoCardState extends State<_PostInfoCard> {
|
||||
// Lazily resolved dimensions (used when post.width/height == 0).
|
||||
int? _resolvedWidth;
|
||||
int? _resolvedHeight;
|
||||
bool _resolvingDimensions = false;
|
||||
|
||||
static final _dimensionCache = <String, (int, int)>{};
|
||||
|
||||
static bool _isImageType(String fileType) {
|
||||
const imageTypes = {'image', 'jpeg', 'jpg', 'png', 'gif', 'webp', 'avif'};
|
||||
return imageTypes.contains(fileType.toLowerCase());
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_maybeResolveDimensions();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(_PostInfoCard old) {
|
||||
super.didUpdateWidget(old);
|
||||
if (old.post.fileUrl != widget.post.fileUrl) {
|
||||
_resolvedWidth = null;
|
||||
_resolvedHeight = null;
|
||||
_resolvingDimensions = false;
|
||||
_maybeResolveDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
void _maybeResolveDimensions() {
|
||||
final post = widget.post;
|
||||
// Only attempt lazy resolution when dimensions are unknown (0) and it's
|
||||
// an image (not video/link which we can't resolve this way).
|
||||
if (post.width != 0 || post.height != 0) return;
|
||||
if (!_isImageType(post.fileType)) return;
|
||||
final url = post.fileUrl.isNotEmpty ? post.fileUrl : post.sampleUrl;
|
||||
if (url.isEmpty) return;
|
||||
|
||||
// Check cache first.
|
||||
if (_dimensionCache.containsKey(url)) {
|
||||
final cached = _dimensionCache[url]!;
|
||||
_resolvedWidth = cached.$1;
|
||||
_resolvedHeight = cached.$2;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_resolvingDimensions) return;
|
||||
_resolvingDimensions = true;
|
||||
|
||||
final imageProvider = NetworkImage(url);
|
||||
final completer = imageProvider.resolve(ImageConfiguration.empty);
|
||||
completer.addListener(
|
||||
ImageStreamListener(
|
||||
(info, _) {
|
||||
final w = info.image.width;
|
||||
final h = info.image.height;
|
||||
_dimensionCache[url] = (w, h);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_resolvedWidth = w;
|
||||
_resolvedHeight = h;
|
||||
_resolvingDimensions = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: (_, __) {
|
||||
if (mounted) setState(() => _resolvingDimensions = false);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final post = widget.post;
|
||||
final strings = widget.strings;
|
||||
final localMedia = widget.localMedia;
|
||||
final fileSizeBytes = widget.fileSizeBytes;
|
||||
|
||||
final theme = Theme.of(context);
|
||||
final scheme = theme.colorScheme;
|
||||
final artists = post.tagGroups['artist'] ??
|
||||
@@ -1208,6 +1290,14 @@ class _PostInfoCard extends StatelessWidget {
|
||||
.map((t) => t.replaceFirst('artist:', ''))
|
||||
.toList();
|
||||
|
||||
// Determine which dimensions to display.
|
||||
final displayWidth = post.width != 0 ? post.width : _resolvedWidth;
|
||||
final displayHeight = post.height != 0 ? post.height : _resolvedHeight;
|
||||
final hasDimensions = displayWidth != null && displayHeight != null;
|
||||
final dimensionLabel = hasDimensions
|
||||
? '$displayWidth × $displayHeight'
|
||||
: (_resolvingDimensions ? '… × …' : null);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
@@ -1293,7 +1383,7 @@ class _PostInfoCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_formatDate(post.createdAt),
|
||||
_formatPostDate(post.createdAt),
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
@@ -1321,8 +1411,8 @@ class _PostInfoCard extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
fileSizeBytes != null && fileSizeBytes! > 0
|
||||
? DownloadedMediaService.formatBytes(fileSizeBytes!)
|
||||
fileSizeBytes != null && fileSizeBytes > 0
|
||||
? DownloadedMediaService.formatBytes(fileSizeBytes)
|
||||
: (strings.ru ? 'Офлайн' : 'Offline'),
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: scheme.onPrimaryContainer,
|
||||
@@ -1341,10 +1431,11 @@ class _PostInfoCard extends StatelessWidget {
|
||||
runSpacing: 8,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
_SpecBadge(
|
||||
icon: Icons.aspect_ratio_rounded,
|
||||
label: '${post.width} × ${post.height}',
|
||||
),
|
||||
if (dimensionLabel != null)
|
||||
_SpecBadge(
|
||||
icon: Icons.aspect_ratio_rounded,
|
||||
label: dimensionLabel,
|
||||
),
|
||||
if (post.fileType.isNotEmpty)
|
||||
_SpecBadge(
|
||||
icon: Icons.insert_drive_file_outlined,
|
||||
@@ -1373,10 +1464,10 @@ class _PostInfoCard extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static String _formatDate(DateTime date) {
|
||||
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
}
|
||||
String _formatPostDate(DateTime date) {
|
||||
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
class _SpecBadge extends StatelessWidget {
|
||||
|
||||
@@ -1030,7 +1030,7 @@ class _SettingsContentState extends ConsumerState<_SettingsContent> {
|
||||
|
||||
Future<void> _downloadUpdate(WidgetRef ref, AppUpdateInfo info) async {
|
||||
final updateService = ref.read(updateServiceProvider);
|
||||
final assetUrl = updateService.assetUrlForCurrentPlatform(info);
|
||||
final assetUrl = await updateService.assetUrlForCurrentPlatform(info);
|
||||
if (assetUrl == null) {
|
||||
final url = Uri.tryParse(info.htmlUrl);
|
||||
if (url != null) {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
name: gel_rule_app
|
||||
description: Local backend/core layer for a Flutter booru browser app.
|
||||
publish_to: "none"
|
||||
version: 2.3.6+46
|
||||
version: 2.3.9+49
|
||||
|
||||
environment:
|
||||
sdk: ">=3.3.0 <4.0.0"
|
||||
|
||||
@@ -253,7 +253,7 @@ void main() {
|
||||
);
|
||||
expect(addResult, isTrue);
|
||||
expect(lastMethod, equals('POST'));
|
||||
expect(lastPath, equals('https://pawchive.pw/favorites/creator/fanbox/12345'));
|
||||
expect(lastPath, equals('https://pawchive.pw/api/v1/favorites/creator/fanbox/12345'));
|
||||
|
||||
final removeResult = await service.toggleRemoteFavorite(
|
||||
account: account,
|
||||
@@ -263,7 +263,7 @@ void main() {
|
||||
);
|
||||
expect(removeResult, isTrue);
|
||||
expect(lastMethod, equals('DELETE'));
|
||||
expect(lastPath, equals('https://pawchive.pw/favorites/creator/fanbox/12345'));
|
||||
expect(lastPath, equals('https://pawchive.pw/api/v1/favorites/creator/fanbox/12345'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user