👽 Support the new Dyson Token

This commit is contained in:
LittleSheep 2025-05-28 23:21:13 +08:00
parent 5d8c73e468
commit bdc13978c3
20 changed files with 157 additions and 309 deletions

View File

@ -186,7 +186,7 @@ class MessageRepository {
} }
Future<LocalChatMessage> sendMessage( Future<LocalChatMessage> sendMessage(
String atk, String token,
String baseUrl, String baseUrl,
String roomId, String roomId,
String content, String content,
@ -232,7 +232,7 @@ class MessageRepository {
final cloudFile = final cloudFile =
await putMediaToCloud( await putMediaToCloud(
fileData: attachments[idx].data, fileData: attachments[idx].data,
atk: atk, atk: token,
baseUrl: baseUrl, baseUrl: baseUrl,
filename: attachments[idx].data.name ?? 'Post media', filename: attachments[idx].data.name ?? 'Post media',
mimetype: mimetype:

View File

@ -4,14 +4,11 @@ part 'auth.freezed.dart';
part 'auth.g.dart'; part 'auth.g.dart';
@freezed @freezed
sealed class AppTokenPair with _$AppTokenPair { sealed class AppToken with _$AppToken {
const factory AppTokenPair({ const factory AppToken({required String token}) = _AppToken;
required String accessToken,
required String refreshToken,
}) = _AppTokenPair;
factory AppTokenPair.fromJson(Map<String, dynamic> json) => factory AppToken.fromJson(Map<String, dynamic> json) =>
_$AppTokenPairFromJson(json); _$AppTokenFromJson(json);
} }
@freezed @freezed

View File

@ -14,42 +14,42 @@ part of 'auth.dart';
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
/// @nodoc /// @nodoc
mixin _$AppTokenPair { mixin _$AppToken {
String get accessToken; String get refreshToken; String get token;
/// Create a copy of AppTokenPair /// Create a copy of AppToken
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
$AppTokenPairCopyWith<AppTokenPair> get copyWith => _$AppTokenPairCopyWithImpl<AppTokenPair>(this as AppTokenPair, _$identity); $AppTokenCopyWith<AppToken> get copyWith => _$AppTokenCopyWithImpl<AppToken>(this as AppToken, _$identity);
/// Serializes this AppTokenPair to a JSON map. /// Serializes this AppToken to a JSON map.
Map<String, dynamic> toJson(); Map<String, dynamic> toJson();
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppTokenPair&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)); return identical(this, other) || (other.runtimeType == runtimeType&&other is AppToken&&(identical(other.token, token) || other.token == token));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,accessToken,refreshToken); int get hashCode => Object.hash(runtimeType,token);
@override @override
String toString() { String toString() {
return 'AppTokenPair(accessToken: $accessToken, refreshToken: $refreshToken)'; return 'AppToken(token: $token)';
} }
} }
/// @nodoc /// @nodoc
abstract mixin class $AppTokenPairCopyWith<$Res> { abstract mixin class $AppTokenCopyWith<$Res> {
factory $AppTokenPairCopyWith(AppTokenPair value, $Res Function(AppTokenPair) _then) = _$AppTokenPairCopyWithImpl; factory $AppTokenCopyWith(AppToken value, $Res Function(AppToken) _then) = _$AppTokenCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String accessToken, String refreshToken String token
}); });
@ -57,19 +57,18 @@ $Res call({
} }
/// @nodoc /// @nodoc
class _$AppTokenPairCopyWithImpl<$Res> class _$AppTokenCopyWithImpl<$Res>
implements $AppTokenPairCopyWith<$Res> { implements $AppTokenCopyWith<$Res> {
_$AppTokenPairCopyWithImpl(this._self, this._then); _$AppTokenCopyWithImpl(this._self, this._then);
final AppTokenPair _self; final AppToken _self;
final $Res Function(AppTokenPair) _then; final $Res Function(AppToken) _then;
/// Create a copy of AppTokenPair /// Create a copy of AppToken
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? accessToken = null,Object? refreshToken = null,}) { @pragma('vm:prefer-inline') @override $Res call({Object? token = null,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable
as String,refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable
as String, as String,
)); ));
} }
@ -80,47 +79,46 @@ as String,
/// @nodoc /// @nodoc
@JsonSerializable() @JsonSerializable()
class _AppTokenPair implements AppTokenPair { class _AppToken implements AppToken {
const _AppTokenPair({required this.accessToken, required this.refreshToken}); const _AppToken({required this.token});
factory _AppTokenPair.fromJson(Map<String, dynamic> json) => _$AppTokenPairFromJson(json); factory _AppToken.fromJson(Map<String, dynamic> json) => _$AppTokenFromJson(json);
@override final String accessToken; @override final String token;
@override final String refreshToken;
/// Create a copy of AppTokenPair /// Create a copy of AppToken
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false) @override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$AppTokenPairCopyWith<_AppTokenPair> get copyWith => __$AppTokenPairCopyWithImpl<_AppTokenPair>(this, _$identity); _$AppTokenCopyWith<_AppToken> get copyWith => __$AppTokenCopyWithImpl<_AppToken>(this, _$identity);
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return _$AppTokenPairToJson(this, ); return _$AppTokenToJson(this, );
} }
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppTokenPair&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppToken&&(identical(other.token, token) || other.token == token));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,accessToken,refreshToken); int get hashCode => Object.hash(runtimeType,token);
@override @override
String toString() { String toString() {
return 'AppTokenPair(accessToken: $accessToken, refreshToken: $refreshToken)'; return 'AppToken(token: $token)';
} }
} }
/// @nodoc /// @nodoc
abstract mixin class _$AppTokenPairCopyWith<$Res> implements $AppTokenPairCopyWith<$Res> { abstract mixin class _$AppTokenCopyWith<$Res> implements $AppTokenCopyWith<$Res> {
factory _$AppTokenPairCopyWith(_AppTokenPair value, $Res Function(_AppTokenPair) _then) = __$AppTokenPairCopyWithImpl; factory _$AppTokenCopyWith(_AppToken value, $Res Function(_AppToken) _then) = __$AppTokenCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String accessToken, String refreshToken String token
}); });
@ -128,19 +126,18 @@ $Res call({
} }
/// @nodoc /// @nodoc
class __$AppTokenPairCopyWithImpl<$Res> class __$AppTokenCopyWithImpl<$Res>
implements _$AppTokenPairCopyWith<$Res> { implements _$AppTokenCopyWith<$Res> {
__$AppTokenPairCopyWithImpl(this._self, this._then); __$AppTokenCopyWithImpl(this._self, this._then);
final _AppTokenPair _self; final _AppToken _self;
final $Res Function(_AppTokenPair) _then; final $Res Function(_AppToken) _then;
/// Create a copy of AppTokenPair /// Create a copy of AppToken
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? accessToken = null,Object? refreshToken = null,}) { @override @pragma('vm:prefer-inline') $Res call({Object? token = null,}) {
return _then(_AppTokenPair( return _then(_AppToken(
accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable
as String,refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable
as String, as String,
)); ));
} }

View File

@ -6,17 +6,12 @@ part of 'auth.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_AppTokenPair _$AppTokenPairFromJson(Map<String, dynamic> json) => _AppToken _$AppTokenFromJson(Map<String, dynamic> json) =>
_AppTokenPair( _AppToken(token: json['token'] as String);
accessToken: json['access_token'] as String,
refreshToken: json['refresh_token'] as String,
);
Map<String, dynamic> _$AppTokenPairToJson(_AppTokenPair instance) => Map<String, dynamic> _$AppTokenToJson(_AppToken instance) => <String, dynamic>{
<String, dynamic>{ 'token': instance.token,
'access_token': instance.accessToken, };
'refresh_token': instance.refreshToken,
};
_SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) => _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) =>
_SnAuthChallenge( _SnAuthChallenge(

View File

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -68,16 +67,9 @@ final apiClientProvider = Provider<Dio>((ref) {
RequestInterceptorHandler handler, RequestInterceptorHandler handler,
) async { ) async {
try { try {
final atk = await getFreshAtk( final token = await getToken(ref.watch(tokenProvider));
ref.watch(tokenPairProvider), if (token != null) {
ref.watch(serverUrlProvider), options.headers['Authorization'] = 'AtField $token';
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk != null) {
options.headers['Authorization'] = 'Bearer $atk';
} }
} catch (err) { } catch (err) {
// ignore // ignore
@ -95,105 +87,21 @@ final apiClientProvider = Provider<Dio>((ref) {
return dio; return dio;
}); });
final tokenPairProvider = Provider<AppTokenPair?>((ref) { final tokenProvider = Provider<AppToken?>((ref) {
final prefs = ref.watch(sharedPreferencesProvider); final prefs = ref.watch(sharedPreferencesProvider);
final tkPairString = prefs.getString(kTokenPairStoreKey); final tokenString = prefs.getString(kTokenPairStoreKey);
if (tkPairString == null) return null; if (tokenString == null) return null;
return AppTokenPair.fromJson(jsonDecode(tkPairString)); return AppToken.fromJson(jsonDecode(tokenString));
}); });
Future<(String, String)?> refreshToken(String baseUrl, String? rtk) async { // Token refresh functionality removed as per backend changes
if (rtk == null) return null;
final dio = Dio(); Future<String?> getToken(AppToken? token) async {
dio.options.baseUrl = baseUrl; return token?.token;
final resp = await dio.post(
'/auth/token',
data: {'grant_type': 'refresh_token', 'refresh_token': rtk},
);
final String atk = resp.data['access_token'];
final String nRtk = resp.data['refresh_token'];
return (atk, nRtk);
} }
Completer<String?>? _refreshCompleter; Future<void> setToken(SharedPreferences prefs, String token) async {
final appToken = AppToken(token: token);
Future<String?> getFreshAtk( final tokenString = jsonEncode(appToken);
AppTokenPair? tkPair, prefs.setString(kTokenPairStoreKey, tokenString);
String baseUrl, {
Function(String, String)? onRefreshed,
}) async {
var atk = tkPair?.accessToken;
var rtk = tkPair?.refreshToken;
if (_refreshCompleter != null) {
return await _refreshCompleter!.future;
} else {
_refreshCompleter = Completer<String?>();
}
try {
if (atk != null) {
final atkParts = atk.split('.');
if (atkParts.length != 3) {
throw Exception('invalid format of access token');
}
var rawPayload = atkParts[1].replaceAll('-', '+').replaceAll('_', '/');
switch (rawPayload.length % 4) {
case 0:
break;
case 2:
rawPayload += '==';
break;
case 3:
rawPayload += '=';
break;
default:
throw Exception('illegal format of access token payload');
}
final b64 = utf8.fuse(base64Url);
final payload = b64.decode(rawPayload);
final exp = jsonDecode(payload)['exp'];
if (exp <= DateTime.now().millisecondsSinceEpoch ~/ 1000) {
log('[Auth] Access token need refresh, doing it at ${DateTime.now()}');
final result = await refreshToken(baseUrl, rtk);
if (result == null) {
atk = null;
} else {
onRefreshed?.call(result.$1, result.$2);
atk = result.$1;
}
}
if (atk != null) {
_refreshCompleter!.complete(atk);
return atk;
} else {
log('[Auth] Access token refresh failed...');
_refreshCompleter!.complete(null);
}
}
} catch (err) {
log('[Auth] Failed to authenticate user... $err');
_refreshCompleter!.completeError(err);
} finally {
_refreshCompleter = null;
}
return null;
}
Future<void> setTokenPair(
SharedPreferences prefs,
String atk,
String rtk,
) async {
final tkPair = AppTokenPair(accessToken: atk, refreshToken: rtk);
final tkPairString = jsonEncode(tkPair);
prefs.setString(kTokenPairStoreKey, tkPairString);
} }

View File

@ -13,7 +13,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
Future<String?> getAccessToken() async { Future<String?> getAccessToken() async {
final prefs = _ref.read(sharedPreferencesProvider); final prefs = _ref.read(sharedPreferencesProvider);
return prefs.getString('dyn_user_atk'); return prefs.getString(kTokenPairStoreKey);
} }
Future<void> fetchUser() async { Future<void> fetchUser() async {

View File

@ -54,25 +54,18 @@ class WebSocketService {
_statusStreamController.sink.add(WebSocketState.connecting()); _statusStreamController.sink.add(WebSocketState.connecting());
final baseUrl = ref.watch(serverUrlProvider); final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk( final token = await getToken(ref.watch(tokenProvider));
ref.watch(tokenPairProvider),
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
final url = '$baseUrl/ws'.replaceFirst('http', 'ws'); final url = '$baseUrl/ws'.replaceFirst('http', 'ws');
log('[WebSocket] Trying connecting to $url'); log('[WebSocket] Trying connecting to $url');
try { try {
if (kIsWeb) { if (kIsWeb) {
_channel = WebSocketChannel.connect(Uri.parse('$url?tk=$atk')); _channel = WebSocketChannel.connect(Uri.parse('$url?tk=$token'));
} else { } else {
_channel = IOWebSocketChannel.connect( _channel = IOWebSocketChannel.connect(
Uri.parse(url), Uri.parse(url),
headers: {'Authorization': 'Bearer $atk'}, headers: {'Authorization': 'Bearer $token'},
); );
} }
await _channel!.ready; await _channel!.ready;

View File

@ -254,8 +254,8 @@ class AccountScreen extends HookConsumerWidget {
contentPadding: EdgeInsets.symmetric(horizontal: 24), contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('Copy access token'), title: Text('Copy access token'),
onTap: () async { onTap: () async {
final tk = ref.watch(tokenPairProvider); final tk = ref.watch(tokenProvider);
Clipboard.setData(ClipboardData(text: tk!.accessToken)); Clipboard.setData(ClipboardData(text: tk!.token));
}, },
), ),
if (kDebugMode) if (kDebugMode)

View File

@ -59,19 +59,12 @@ class UpdateProfileScreen extends HookConsumerWidget {
submitting.value = true; submitting.value = true;
try { try {
final baseUrl = ref.watch(serverUrlProvider); final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk( final token = await getToken(ref.watch(tokenProvider));
ref.watch(tokenPairProvider), if (token == null) throw ArgumentError('Token is null');
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw ArgumentError('Access token is null');
final cloudFile = final cloudFile =
await putMediaToCloud( await putMediaToCloud(
fileData: result, fileData: result,
atk: atk, atk: token,
baseUrl: baseUrl, baseUrl: baseUrl,
filename: result.name, filename: result.name,
mimetype: result.mimeType ?? 'image/jpeg', mimetype: result.mimeType ?? 'image/jpeg',

View File

@ -142,10 +142,9 @@ class _LoginCheckScreen extends HookConsumerWidget {
'/auth/token', '/auth/token',
data: {'grant_type': 'authorization_code', 'code': result.id}, data: {'grant_type': 'authorization_code', 'code': result.id},
); );
final atk = tokenResp.data['access_token']; final token = tokenResp.data['token'];
final rtk = tokenResp.data['refresh_token']; setToken(ref.watch(sharedPreferencesProvider), token);
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk); ref.invalidate(tokenProvider);
ref.invalidate(tokenPairProvider);
if (!context.mounted) return; if (!context.mounted) return;
final userNotifier = ref.read(userInfoProvider.notifier); final userNotifier = ref.read(userInfoProvider.notifier);
userNotifier.fetchUser().then((_) { userNotifier.fetchUser().then((_) {

View File

@ -522,19 +522,12 @@ class EditChatScreen extends HookConsumerWidget {
submitting.value = true; submitting.value = true;
try { try {
final baseUrl = ref.watch(serverUrlProvider); final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk( final token = await getToken(ref.watch(tokenProvider));
ref.watch(tokenPairProvider), if (token == null) throw ArgumentError('Token is null');
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw ArgumentError('Access token is null');
final cloudFile = final cloudFile =
await putMediaToCloud( await putMediaToCloud(
fileData: result, fileData: result,
atk: atk, atk: token,
baseUrl: baseUrl, baseUrl: baseUrl,
filename: result.name, filename: result.name,
mimetype: result.mimeType ?? 'image/jpeg', mimetype: result.mimeType ?? 'image/jpeg',

View File

@ -117,19 +117,12 @@ class MessagesNotifier extends _$MessagesNotifier {
messageRepositoryProvider(_roomId).future, messageRepositoryProvider(_roomId).future,
); );
final baseUrl = ref.read(serverUrlProvider); final baseUrl = ref.read(serverUrlProvider);
final atk = await getFreshAtk( final token = await getToken(ref.watch(tokenProvider));
ref.watch(tokenPairProvider), if (token == null) throw ArgumentError('Access token is null');
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw ArgumentError('Access token is null');
final currentMessages = state.value ?? []; final currentMessages = state.value ?? [];
await repository.sendMessage( await repository.sendMessage(
atk, token,
baseUrl, baseUrl,
_roomId, _roomId,
content, content,

View File

@ -6,7 +6,7 @@ part of 'room.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$messagesNotifierHash() => r'71a9fc1c6d024f6203f06225384c19335b9b6f2c'; String _$messagesNotifierHash() => r'afc4d43f4948ec571118cef0321838a6cefc89c0';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {

View File

@ -95,19 +95,12 @@ class EditPublisherScreen extends HookConsumerWidget {
submitting.value = true; submitting.value = true;
try { try {
final baseUrl = ref.watch(serverUrlProvider); final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk( final token = await getToken(ref.watch(tokenProvider));
ref.watch(tokenPairProvider), if (token == null) throw ArgumentError('Token is null');
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw ArgumentError('Access token is null');
final cloudFile = final cloudFile =
await putMediaToCloud( await putMediaToCloud(
fileData: result, fileData: result,
atk: atk, atk: token,
baseUrl: baseUrl, baseUrl: baseUrl,
filename: result.name, filename: result.name,
mimetype: result.mimeType ?? 'image/jpeg', mimetype: result.mimeType ?? 'image/jpeg',

View File

@ -7,7 +7,7 @@ part of 'notification.dart';
// ************************************************************************** // **************************************************************************
String _$notificationUnreadCountNotifierHash() => String _$notificationUnreadCountNotifierHash() =>
r'074143cf208a3afe1495be405198532a23ef77c8'; r'372a2cc259d7d838cd4f33a9129f7396ef31dbb9';
/// See also [NotificationUnreadCountNotifier]. /// See also [NotificationUnreadCountNotifier].
@ProviderFor(NotificationUnreadCountNotifier) @ProviderFor(NotificationUnreadCountNotifier)

View File

@ -125,21 +125,14 @@ class PostComposeScreen extends HookConsumerWidget {
final attachment = attachments.value[index]; final attachment = attachments.value[index];
if (attachment is SnCloudFile) return; if (attachment is SnCloudFile) return;
final baseUrl = ref.watch(serverUrlProvider); final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk( final token = await getToken(ref.watch(tokenProvider));
ref.watch(tokenPairProvider), if (token == null) throw ArgumentError('Token is null');
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw ArgumentError('Access token is null');
try { try {
attachmentProgress.value = {...attachmentProgress.value, index: 0}; attachmentProgress.value = {...attachmentProgress.value, index: 0};
final cloudFile = final cloudFile =
await putMediaToCloud( await putMediaToCloud(
fileData: attachment.data, fileData: attachment.data,
atk: atk, atk: token,
baseUrl: baseUrl, baseUrl: baseUrl,
filename: attachment.data.name ?? 'Post media', filename: attachment.data.name ?? 'Post media',
mimetype: mimetype:
@ -394,49 +387,32 @@ class PostComposeScreen extends HookConsumerWidget {
final isWide = isWideScreen(context); final isWide = isWideScreen(context);
return isWide return isWide
? Wrap( ? Wrap(
spacing: 8, spacing: 8,
runSpacing: 8, runSpacing: 8,
children: [ children: [
for (var idx = 0; idx < attachments.value.length; idx++) for (
SizedBox( var idx = 0;
width: constraints.maxWidth / 2 - 4, idx < attachments.value.length;
child: AttachmentPreview( idx++
item: attachments.value[idx], )
progress: attachmentProgress.value[idx], SizedBox(
onRequestUpload: () => uploadAttachment(idx), width: constraints.maxWidth / 2 - 4,
onDelete: () => deleteAttachment(idx), child: AttachmentPreview(
onMove: (delta) {
if (idx + delta < 0 ||
idx + delta >= attachments.value.length) {
return;
}
final clone = List.of(attachments.value);
clone.insert(
idx + delta,
clone.removeAt(idx),
);
attachments.value = clone;
},
),
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
for (var idx = 0; idx < attachments.value.length; idx++)
AttachmentPreview(
item: attachments.value[idx], item: attachments.value[idx],
progress: attachmentProgress.value[idx], progress:
onRequestUpload: () => uploadAttachment(idx), attachmentProgress.value[idx],
onRequestUpload:
() => uploadAttachment(idx),
onDelete: () => deleteAttachment(idx), onDelete: () => deleteAttachment(idx),
onMove: (delta) { onMove: (delta) {
if (idx + delta < 0 || if (idx + delta < 0 ||
idx + delta >= attachments.value.length) { idx + delta >=
attachments.value.length) {
return; return;
} }
final clone = List.of(attachments.value); final clone = List.of(
attachments.value,
);
clone.insert( clone.insert(
idx + delta, idx + delta,
clone.removeAt(idx), clone.removeAt(idx),
@ -444,8 +420,42 @@ class PostComposeScreen extends HookConsumerWidget {
attachments.value = clone; attachments.value = clone;
}, },
), ),
], ),
); ],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
for (
var idx = 0;
idx < attachments.value.length;
idx++
)
AttachmentPreview(
item: attachments.value[idx],
progress: attachmentProgress.value[idx],
onRequestUpload:
() => uploadAttachment(idx),
onDelete: () => deleteAttachment(idx),
onMove: (delta) {
if (idx + delta < 0 ||
idx + delta >=
attachments.value.length) {
return;
}
final clone = List.of(
attachments.value,
);
clone.insert(
idx + delta,
clone.removeAt(idx),
);
attachments.value = clone;
},
),
],
);
}, },
), ),
], ],

View File

@ -145,7 +145,7 @@ class _PublisherProviderElement
String get uname => (origin as PublisherProvider).uname; String get uname => (origin as PublisherProvider).uname;
} }
String _$publisherBadgesHash() => r'b26d8804ddc9734c453bdf76af0a9336f166542c'; String _$publisherBadgesHash() => r'a5781deded7e682a781ccd7854418f050438e3f4';
/// See also [publisherBadges]. /// See also [publisherBadges].
@ProviderFor(publisherBadges) @ProviderFor(publisherBadges)

View File

@ -211,19 +211,12 @@ class EditRealmScreen extends HookConsumerWidget {
submitting.value = true; submitting.value = true;
try { try {
final baseUrl = ref.watch(serverUrlProvider); final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk( final token = await getToken(ref.watch(tokenProvider));
ref.watch(tokenPairProvider), if (token == null) throw ArgumentError('Access token is null');
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw ArgumentError('Access token is null');
final cloudFile = final cloudFile =
await putMediaToCloud( await putMediaToCloud(
fileData: result, fileData: result,
atk: atk, atk: token,
baseUrl: baseUrl, baseUrl: baseUrl,
filename: result.name, filename: result.name,
mimetype: result.mimeType ?? 'image/jpeg', mimetype: result.mimeType ?? 'image/jpeg',

View File

@ -43,15 +43,8 @@ class CloudFilePicker extends HookConsumerWidget {
if (files.value.isEmpty) return; if (files.value.isEmpty) return;
final baseUrl = ref.read(serverUrlProvider); final baseUrl = ref.read(serverUrlProvider);
final atk = await getFreshAtk( final token = await getToken(ref.watch(tokenProvider));
ref.watch(tokenPairProvider), if (token == null) throw Exception("Unauthorized");
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
if (atk == null) throw Exception("Unauthorized");
List<SnCloudFile> result = List.empty(growable: true); List<SnCloudFile> result = List.empty(growable: true);
@ -64,7 +57,7 @@ class CloudFilePicker extends HookConsumerWidget {
final cloudFile = final cloudFile =
await putMediaToCloud( await putMediaToCloud(
fileData: file.data, fileData: file.data,
atk: atk, atk: token,
baseUrl: baseUrl, baseUrl: baseUrl,
filename: file.data.name ?? 'Post media', filename: file.data.name ?? 'Post media',
mimetype: mimetype:

View File

@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:island/pods/config.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:media_kit/media_kit.dart'; import 'package:media_kit/media_kit.dart';
@ -38,18 +37,10 @@ class _UniversalVideoState extends ConsumerState<UniversalVideo> {
final inCacheInfo = await DefaultCacheManager().getFileFromCache(url); final inCacheInfo = await DefaultCacheManager().getFileFromCache(url);
if (inCacheInfo == null) { if (inCacheInfo == null) {
log('[MediaPlayer] Miss cache: $url'); log('[MediaPlayer] Miss cache: $url');
final baseUrl = ref.watch(serverUrlProvider); final token = await getToken(ref.watch(tokenProvider));
final atk = await getFreshAtk(
ref.watch(tokenPairProvider),
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
final fileStream = DefaultCacheManager().getFileStream( final fileStream = DefaultCacheManager().getFileStream(
url, url,
headers: {'Authorization': 'Bearer $atk'}, headers: {'Authorization': 'Bearer $token'},
withProgress: true, withProgress: true,
); );
await for (var fileInfo in fileStream) { await for (var fileInfo in fileStream) {