feat: Material 3 Expressive анимации загрузки и нажатий
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import '../../core/utils/format.dart';
|
||||
import '../widgets/small_spinner.dart';
|
||||
|
||||
class DebugCacheSection extends StatelessWidget {
|
||||
final int cacheSize;
|
||||
@@ -129,14 +130,7 @@ class DebugCacheSection extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
if (clearingCache)
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
SmallSpinner(size: 20, color: cs.onSurfaceVariant),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import '../widgets/custom_notification.dart';
|
||||
import '../widgets/glossy_pill.dart';
|
||||
import '../widgets/small_spinner.dart';
|
||||
|
||||
class DebugIdSearchSection extends StatelessWidget {
|
||||
final TextEditingController idController;
|
||||
@@ -72,11 +73,7 @@ class DebugIdSearchSection extends StatelessWidget {
|
||||
FilledButton(
|
||||
onPressed: isSearching ? null : onSearch,
|
||||
child: isSearching
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
? const SmallSpinner(size: 20)
|
||||
: const Icon(Symbols.search, size: 20),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -6,6 +6,7 @@ import '../../core/protocol/opcode_map.dart';
|
||||
import '../../core/protocol/packet.dart';
|
||||
import '../../main.dart';
|
||||
import '../widgets/glossy_pill.dart';
|
||||
import '../widgets/small_spinner.dart';
|
||||
|
||||
class DebugSyncProbeSection extends StatefulWidget {
|
||||
const DebugSyncProbeSection({super.key});
|
||||
@@ -147,11 +148,7 @@ class _DebugSyncProbeSectionState extends State<DebugSyncProbeSection> {
|
||||
),
|
||||
),
|
||||
child: _loading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
? const SmallSpinner(size: 20)
|
||||
: const Text('Отправить'),
|
||||
),
|
||||
if (_result != null) ...[
|
||||
|
||||
@@ -11,6 +11,7 @@ import '../../../core/utils/sms_code_listener.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/login_success_screen.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
|
||||
class CodeConfirmationScreen extends StatefulWidget {
|
||||
final String phoneNumber;
|
||||
@@ -525,14 +526,7 @@ class _CodeConfirmationScreenState extends State<CodeConfirmationScreen>
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: recovering
|
||||
? SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: cs.onPrimaryContainer,
|
||||
),
|
||||
)
|
||||
? SmallSpinner(size: 24, color: cs.onPrimaryContainer)
|
||||
: Icon(
|
||||
Icons.arrow_forward,
|
||||
color: _codeController.text.length == 6
|
||||
|
||||
@@ -18,6 +18,7 @@ import '../digital_id/digital_id_web_screen.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/adaptive_shell.dart';
|
||||
import '../../widgets/sheet_helpers.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import '../../../backend/api.dart';
|
||||
import '../../../core/protocol/packet.dart';
|
||||
import '../../../main.dart';
|
||||
@@ -998,13 +999,9 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: _isPhoneValid && !_isOnline
|
||||
? SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: cs.onPrimaryContainer,
|
||||
),
|
||||
? SmallSpinner(
|
||||
size: 24,
|
||||
color: cs.onPrimaryContainer,
|
||||
)
|
||||
: Icon(
|
||||
Icons.arrow_forward,
|
||||
|
||||
@@ -3,6 +3,7 @@ import '../../../core/protocol/packet.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/login_success_screen.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import 'session_stale_recovery.dart';
|
||||
|
||||
class Password2FAScreen extends StatefulWidget {
|
||||
@@ -199,14 +200,7 @@ class _Password2FAScreenState extends State<Password2FAScreen>
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: _isLoading
|
||||
? SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: cs.onPrimaryContainer,
|
||||
),
|
||||
)
|
||||
? SmallSpinner(size: 24, color: cs.onPrimaryContainer)
|
||||
: Icon(
|
||||
Icons.arrow_forward,
|
||||
color: _passwordController.text.isNotEmpty
|
||||
|
||||
@@ -6,6 +6,7 @@ import '../../../backend/modules/account.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/login_success_screen.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
|
||||
class RegistrationScreen extends StatefulWidget {
|
||||
final String phoneNumber;
|
||||
@@ -110,14 +111,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(50)),
|
||||
child: _isSubmitting
|
||||
? SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.5,
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
? SmallSpinner(size: 22, color: cs.onSurfaceVariant)
|
||||
: Icon(
|
||||
Icons.arrow_forward,
|
||||
color: _canSubmit ? cs.onPrimaryContainer : cs.onSurfaceVariant,
|
||||
|
||||
@@ -8,6 +8,7 @@ import '../../../models/spoof_profile.dart';
|
||||
import '../../widgets/adaptive_shell.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/section_header.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
|
||||
class TokenLoginScreen extends StatefulWidget {
|
||||
final int? returnToAccountId;
|
||||
@@ -143,11 +144,7 @@ class _TokenLoginScreenState extends State<TokenLoginScreen> {
|
||||
shape: const StadiumBorder(),
|
||||
),
|
||||
child: _isLoading
|
||||
? const SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
? const SmallSpinner(size: 22)
|
||||
: Text(l10n.tokenLoginButton),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -24,6 +24,7 @@ import '../../../l10n/app_localizations.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/glossy_pill.dart';
|
||||
import '../../widgets/sheet_helpers.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import 'komet_hub.dart';
|
||||
|
||||
const Color _kEndRed = Color(0xFFE5484D);
|
||||
@@ -1261,14 +1262,7 @@ class _CallButton extends StatelessWidget {
|
||||
depth: 9,
|
||||
child: Center(
|
||||
child: busy
|
||||
? SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.5,
|
||||
valueColor: AlwaysStoppedAnimation(foreground),
|
||||
),
|
||||
)
|
||||
? SmallSpinner(size: 22, color: foreground)
|
||||
: Icon(icon, color: foreground, size: 26, fill: 1),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -12,6 +12,7 @@ import '../../widgets/komet_avatar.dart';
|
||||
import '../../widgets/connection_status.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/chat_menu_overlay.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import 'call_screen.dart';
|
||||
|
||||
class CallsTab extends StatefulWidget {
|
||||
@@ -421,7 +422,7 @@ class _CallsTabState extends State<CallsTab> {
|
||||
),
|
||||
Expanded(
|
||||
child: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
? const Center(child: SmallSpinner(size: 36))
|
||||
: filteredCalls.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:komet/core/utils/format.dart';
|
||||
import 'package:komet/frontend/widgets/animated_lottie_icon.dart';
|
||||
import 'package:komet/frontend/widgets/glossy_pill.dart';
|
||||
import 'package:komet/frontend/widgets/komet_avatar.dart';
|
||||
import 'package:komet/frontend/widgets/small_spinner.dart';
|
||||
import 'package:komet/frontend/screens/chats/chat/chat_search_controller.dart';
|
||||
import 'package:komet/frontend/screens/chats/chat/message_search_result.dart';
|
||||
|
||||
@@ -168,14 +169,7 @@ class SearchOverlay extends StatelessWidget {
|
||||
}
|
||||
if (loading) {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 26,
|
||||
height: 26,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.4,
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
child: SmallSpinner(size: 26, color: cs.onSurfaceVariant),
|
||||
);
|
||||
}
|
||||
return ValueListenableBuilder<bool>(
|
||||
|
||||
@@ -16,6 +16,7 @@ import '../../widgets/glossy_pill.dart';
|
||||
import '../../widgets/sheet_helpers.dart';
|
||||
import '../../widgets/swipe_route.dart';
|
||||
import '../../widgets/sliding_pill_nav.dart';
|
||||
import '../../widgets/springy_tap.dart';
|
||||
import '../../widgets/formatted_message_text.dart';
|
||||
import '../../../core/utils/format.dart';
|
||||
import '../../../core/utils/text_format.dart';
|
||||
@@ -2550,219 +2551,226 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
)
|
||||
: null,
|
||||
);
|
||||
return InkWell(
|
||||
return SpringyTap(
|
||||
key: ValueKey('chat_$id'),
|
||||
onTap: () {
|
||||
if (widget.forwardMode) {
|
||||
Navigator.of(context).pop(
|
||||
ForwardTarget(
|
||||
chatId: int.parse(id),
|
||||
name: name,
|
||||
imageUrl: imageUrl,
|
||||
chatType: chatType,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (_isSelectionMode) {
|
||||
_toggleSelection(id);
|
||||
return;
|
||||
}
|
||||
if (imageUrl.isNotEmpty) {
|
||||
unawaited(
|
||||
precacheImage(
|
||||
CachedNetworkImageProvider(
|
||||
imageUrl,
|
||||
maxWidth: kAvatarThumbSize,
|
||||
maxHeight: kAvatarThumbSize,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (widget.forwardMode) {
|
||||
Navigator.of(context).pop(
|
||||
ForwardTarget(
|
||||
chatId: int.parse(id),
|
||||
name: name,
|
||||
imageUrl: imageUrl,
|
||||
chatType: chatType,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (_isSelectionMode) {
|
||||
_toggleSelection(id);
|
||||
return;
|
||||
}
|
||||
if (imageUrl.isNotEmpty) {
|
||||
unawaited(
|
||||
precacheImage(
|
||||
CachedNetworkImageProvider(
|
||||
imageUrl,
|
||||
maxWidth: kAvatarThumbSize,
|
||||
maxHeight: kAvatarThumbSize,
|
||||
),
|
||||
context,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (widget.onChatSelected != null) {
|
||||
widget.onChatSelected!(
|
||||
DesktopChatSelection(
|
||||
chatId: int.parse(id),
|
||||
name: name,
|
||||
imageUrl: imageUrl,
|
||||
chatType: chatType,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
pushSwipeable(
|
||||
context,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (widget.onChatSelected != null) {
|
||||
widget.onChatSelected!(
|
||||
DesktopChatSelection(
|
||||
chatId: int.parse(id),
|
||||
name: name,
|
||||
imageUrl: imageUrl,
|
||||
chatType: chatType,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
pushSwipeable(
|
||||
context,
|
||||
(context) => ChatScreen(
|
||||
chatId: int.parse(id),
|
||||
name: name,
|
||||
imageUrl: imageUrl,
|
||||
chatType: chatType,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
onLongPress: widget.forwardMode ? null : () => _toggleSelection(id),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
color: isSelected
|
||||
? cs.primary.withValues(alpha: 0.08)
|
||||
: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 6),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
avatarCircle,
|
||||
if (isSelected)
|
||||
Positioned(
|
||||
right: -2,
|
||||
bottom: -2,
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: cs.primary,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: cs.surface, width: 2),
|
||||
),
|
||||
child: Icon(
|
||||
Symbols.check,
|
||||
color: cs.onPrimary,
|
||||
size: 14,
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (presenceUserId != 0)
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: OnlineDot(
|
||||
userId: presenceUserId,
|
||||
borderColor: cs.surface,
|
||||
),
|
||||
),
|
||||
],
|
||||
(context) => ChatScreen(
|
||||
chatId: int.parse(id),
|
||||
name: name,
|
||||
imageUrl: imageUrl,
|
||||
chatType: chatType,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
name,
|
||||
style: TextStyle(
|
||||
color: cs.onSurface,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.1,
|
||||
);
|
||||
}
|
||||
},
|
||||
onLongPress: widget.forwardMode ? null : () => _toggleSelection(id),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
color: isSelected
|
||||
? cs.primary.withValues(alpha: 0.08)
|
||||
: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 6),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
avatarCircle,
|
||||
if (isSelected)
|
||||
Positioned(
|
||||
right: -2,
|
||||
bottom: -2,
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: cs.primary,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: cs.surface, width: 2),
|
||||
),
|
||||
child: Icon(
|
||||
Symbols.check,
|
||||
color: cs.onPrimary,
|
||||
size: 14,
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (presenceUserId != 0)
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: OnlineDot(
|
||||
userId: presenceUserId,
|
||||
borderColor: cs.surface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
name,
|
||||
style: TextStyle(
|
||||
color: cs.onSurface,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.1,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (isVerified) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Symbols.verified,
|
||||
color: cs.primary,
|
||||
size: 16,
|
||||
weight: 600,
|
||||
fill: 1,
|
||||
),
|
||||
if (isVerified) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Symbols.verified,
|
||||
color: cs.primary,
|
||||
size: 16,
|
||||
weight: 600,
|
||||
fill: 1,
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isMuted) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Symbols.notifications_off,
|
||||
color: cs.outlineVariant,
|
||||
size: 14,
|
||||
weight: 400,
|
||||
if (isMuted) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Symbols.notifications_off,
|
||||
color: cs.outlineVariant,
|
||||
size: 14,
|
||||
weight: 400,
|
||||
),
|
||||
],
|
||||
if (isPinned) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Symbols.keep,
|
||||
color: cs.outlineVariant,
|
||||
size: 14,
|
||||
weight: 400,
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
time,
|
||||
style: TextStyle(
|
||||
color: cs.outline,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (isPinned) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Symbols.keep,
|
||||
color: cs.outlineVariant,
|
||||
size: 14,
|
||||
weight: 400,
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
time,
|
||||
style: TextStyle(color: cs.outline, fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ActivitySubtitle(
|
||||
chatId: int.tryParse(id) ?? 0,
|
||||
child: messageLine,
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ActivitySubtitle(
|
||||
chatId: int.tryParse(id) ?? 0,
|
||||
child: messageLine,
|
||||
),
|
||||
),
|
||||
),
|
||||
?statusIcon,
|
||||
const SizedBox(width: 8),
|
||||
if (unreadCount > 0)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isMuted
|
||||
? cs.surfaceContainerHighest
|
||||
: cs.primary,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
unreadCount.toString(),
|
||||
style: TextStyle(
|
||||
color: isMuted ? cs.outline : cs.onPrimary,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.1,
|
||||
?statusIcon,
|
||||
const SizedBox(width: 8),
|
||||
if (unreadCount > 0)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isMuted
|
||||
? cs.surfaceContainerHighest
|
||||
: cs.primary,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
unreadCount.toString(),
|
||||
style: TextStyle(
|
||||
color: isMuted
|
||||
? cs.outline
|
||||
: cs.onPrimary,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.1,
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (isRead)
|
||||
Icon(
|
||||
Symbols.done_all,
|
||||
color: cs.primary,
|
||||
size: 16,
|
||||
weight: 400,
|
||||
),
|
||||
)
|
||||
else if (isRead)
|
||||
Icon(
|
||||
Symbols.done_all,
|
||||
color: cs.primary,
|
||||
size: 16,
|
||||
weight: 400,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -87,6 +87,7 @@ import '../../widgets/lottie_image.dart';
|
||||
import '../../widgets/attachment_panel.dart';
|
||||
import '../../widgets/attachment/attachment_sheet.dart';
|
||||
import '../../widgets/sticker_pack_sheet.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import '../../widgets/swipe_to_pop.dart';
|
||||
import '../../widgets/swipe_route.dart';
|
||||
import '../../widgets/schedule_time_picker.dart';
|
||||
@@ -4610,14 +4611,7 @@ class _ChatScreenState extends State<ChatScreen>
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.2,
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
child: SmallSpinner(size: 22, color: cs.onSurfaceVariant),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import '../../../main.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/komet_avatar.dart';
|
||||
import '../../widgets/sheet_helpers.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import '../../widgets/swipe_route.dart';
|
||||
import 'chat_screen.dart';
|
||||
|
||||
@@ -295,7 +296,7 @@ class _CreateGroupFlowState extends State<_CreateGroupFlow> {
|
||||
child: _loading
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
child: Center(child: SmallSpinner(size: 36)),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:m3e_collection/m3e_collection.dart'
|
||||
show ExpressiveRefreshIndicator;
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import '../../../backend/modules/messages.dart';
|
||||
@@ -15,6 +17,7 @@ import '../../widgets/confirm_dialog.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/schedule_time_picker.dart';
|
||||
import '../../widgets/sheet_helpers.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
|
||||
class ScheduledMessagesScreen extends StatefulWidget {
|
||||
final int chatId;
|
||||
@@ -258,10 +261,10 @@ class _ScheduledMessagesScreenState extends State<ScheduledMessagesScreen> {
|
||||
),
|
||||
),
|
||||
body: _loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
? const Center(child: SmallSpinner(size: 36))
|
||||
: _messages.isEmpty
|
||||
? _empty(cs)
|
||||
: RefreshIndicator(
|
||||
: ExpressiveRefreshIndicator(
|
||||
onRefresh: _load,
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.all(16),
|
||||
|
||||
@@ -10,6 +10,7 @@ import '../../../core/storage/app_database.dart';
|
||||
import '../../../core/utils/debouncer.dart';
|
||||
import '../../../core/utils/names.dart';
|
||||
import '../../widgets/komet_avatar.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import '../../widgets/swipe_route.dart';
|
||||
import '../contacts/open_contact_profile.dart';
|
||||
import 'chat_screen.dart';
|
||||
@@ -232,7 +233,7 @@ class _SearchScreenState extends State<SearchScreen> {
|
||||
}
|
||||
if (!hasResults) {
|
||||
if (_loading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return const Center(child: SmallSpinner(size: 36));
|
||||
}
|
||||
return _buildHint(cs, Symbols.search_off, 'Ничего не найдено');
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import '../../../models/contact_info.dart';
|
||||
import '../../widgets/komet_avatar.dart';
|
||||
import '../../widgets/connection_status.dart';
|
||||
import '../../widgets/sheet_helpers.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import '../../widgets/springy_tap.dart';
|
||||
import '../chats/chat_info_screen.dart';
|
||||
import 'nfc_exchange_sheet.dart';
|
||||
import 'open_contact_profile.dart';
|
||||
@@ -115,83 +117,85 @@ class _ContactsTabState extends State<ContactsTab> {
|
||||
.trim();
|
||||
final nameToDisplay = fullName.isEmpty ? '+${contact.phone}' : fullName;
|
||||
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => openContactDialogProfile(
|
||||
context,
|
||||
contactId: contact.id,
|
||||
name: nameToDisplay,
|
||||
avatarUrl: contact.baseUrl,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: cs.primary.withValues(alpha: 0.1),
|
||||
width: 1,
|
||||
return SpringyTap(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => openContactDialogProfile(
|
||||
context,
|
||||
contactId: contact.id,
|
||||
name: nameToDisplay,
|
||||
avatarUrl: contact.baseUrl,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: cs.primary.withValues(alpha: 0.1),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: KometAvatar(
|
||||
name: nameToDisplay,
|
||||
imageUrl: contact.baseUrl,
|
||||
size: 48,
|
||||
),
|
||||
),
|
||||
child: KometAvatar(
|
||||
name: nameToDisplay,
|
||||
imageUrl: contact.baseUrl,
|
||||
size: 48,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
nameToDisplay,
|
||||
style: TextStyle(
|
||||
color: cs.onSurface,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
nameToDisplay,
|
||||
style: TextStyle(
|
||||
color: cs.onSurface,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (contact.isVerified) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Symbols.verified,
|
||||
color: cs.primary,
|
||||
size: 16,
|
||||
weight: 600,
|
||||
fill: 1,
|
||||
),
|
||||
if (contact.isVerified) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Symbols.verified,
|
||||
color: cs.primary,
|
||||
size: 16,
|
||||
weight: 600,
|
||||
fill: 1,
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
contact.updateTime > 0
|
||||
? 'Был(а) недавно'
|
||||
: '+${contact.phone}',
|
||||
style: TextStyle(
|
||||
color: cs.onSurfaceVariant,
|
||||
fontSize: 14,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
contact.updateTime > 0
|
||||
? 'Был(а) недавно'
|
||||
: '+${contact.phone}',
|
||||
style: TextStyle(
|
||||
color: cs.onSurfaceVariant,
|
||||
fontSize: 14,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -243,7 +247,7 @@ class _ContactsTabState extends State<ContactsTab> {
|
||||
),
|
||||
Expanded(
|
||||
child: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
? const Center(child: SmallSpinner(size: 36))
|
||||
: _contacts.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
@@ -457,11 +461,7 @@ class _SearchContactSheetState extends State<_SearchContactSheet> {
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
),
|
||||
child: _loading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
? const SmallSpinner(size: 20)
|
||||
: const Text('Найти'),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -15,6 +15,7 @@ import '../../../main.dart';
|
||||
import '../../../models/contact_info.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/komet_avatar.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
|
||||
enum _Stage {
|
||||
checking,
|
||||
@@ -243,7 +244,7 @@ class _NfcExchangeSheetState extends State<NfcExchangeSheet>
|
||||
case _Stage.checking:
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 40),
|
||||
child: CircularProgressIndicator(),
|
||||
child: SmallSpinner(size: 36),
|
||||
);
|
||||
case _Stage.unsupported:
|
||||
return _message(cs, Symbols.nfc, l10n.nfcUnsupported);
|
||||
@@ -420,11 +421,7 @@ class _NfcExchangeSheetState extends State<NfcExchangeSheet>
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
),
|
||||
child: _stage == _Stage.adding
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
? const SmallSpinner(size: 20)
|
||||
: Text(
|
||||
_stage == _Stage.added
|
||||
? l10n.nfcAdded
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:m3e_collection/m3e_collection.dart'
|
||||
show ExpressiveRefreshIndicator;
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import '../../../backend/modules/digital_id.dart';
|
||||
@@ -10,6 +12,7 @@ import '../../../models/digital_id.dart';
|
||||
import '../../widgets/connection_status.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/error_view.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import '../webapp/web_app_screen.dart';
|
||||
|
||||
String _documentLabel(AppLocalizations l10n, String type) {
|
||||
@@ -198,7 +201,7 @@ class _DigitalIdScreenState extends State<DigitalIdScreen> {
|
||||
|
||||
Widget _buildBody(ColorScheme cs) {
|
||||
if (_loading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return const Center(child: SmallSpinner(size: 36));
|
||||
}
|
||||
if (_error != null) {
|
||||
return ErrorView(message: _error!, onRetry: _load);
|
||||
@@ -206,7 +209,7 @@ class _DigitalIdScreenState extends State<DigitalIdScreen> {
|
||||
if (_docs == null) {
|
||||
return _buildOnboarding(cs);
|
||||
}
|
||||
return RefreshIndicator(
|
||||
return ExpressiveRefreshIndicator(
|
||||
onRefresh: _load,
|
||||
child: ListView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
@@ -278,11 +281,7 @@ class _DigitalIdScreenState extends State<DigitalIdScreen> {
|
||||
child: FilledButton.icon(
|
||||
onPressed: _busy ? null : _linkGosuslugi,
|
||||
icon: _busy
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
? const SmallSpinner(size: 18)
|
||||
: const Icon(Symbols.link, size: 18),
|
||||
label: Text(
|
||||
l10n.digitalIdLinkGosuslugiButton,
|
||||
|
||||
@@ -18,6 +18,7 @@ import '../../widgets/connection_status.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/glossy_pill.dart';
|
||||
import '../../widgets/sheet_helpers.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
|
||||
enum _EnvState { loading, notConfigured, ready }
|
||||
|
||||
@@ -333,7 +334,7 @@ class _CloudStorageScreenState extends State<CloudStorageScreen>
|
||||
),
|
||||
),
|
||||
body: switch (_envState) {
|
||||
_EnvState.loading => const Center(child: CircularProgressIndicator()),
|
||||
_EnvState.loading => const Center(child: SmallSpinner(size: 36)),
|
||||
_EnvState.notConfigured => _buildNotConfigured(cs),
|
||||
_EnvState.ready => _buildReady(cs),
|
||||
},
|
||||
@@ -375,14 +376,7 @@ class _CloudStorageScreenState extends State<CloudStorageScreen>
|
||||
),
|
||||
),
|
||||
child: _isCreatingEnv
|
||||
? SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: cs.onPrimary,
|
||||
),
|
||||
)
|
||||
? SmallSpinner(size: 18, color: cs.onPrimary)
|
||||
: Text(
|
||||
l10n.cloudStorageStart,
|
||||
style: const TextStyle(
|
||||
@@ -1053,14 +1047,7 @@ class _FileDetailsSheetState extends State<_FileDetailsSheet> {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_loading
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: cs.primary,
|
||||
),
|
||||
)
|
||||
? SmallSpinner(size: 20, color: cs.primary)
|
||||
: IconButton(
|
||||
icon: Icon(
|
||||
isExpired ? Symbols.add_link : Symbols.content_copy,
|
||||
@@ -1221,14 +1208,7 @@ class _SendByIdSheetState extends State<_SendByIdSheet> {
|
||||
),
|
||||
),
|
||||
child: _sending
|
||||
? SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: cs.onPrimary,
|
||||
),
|
||||
)
|
||||
? SmallSpinner(size: 18, color: cs.onPrimary)
|
||||
: Text(
|
||||
l10n.cloudStorageSend,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
|
||||
@@ -14,6 +14,7 @@ import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/connection_status.dart';
|
||||
import '../../widgets/glossy_pill.dart';
|
||||
import '../../widgets/prompt_dialog.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import '../../widgets/web_qr_login.dart';
|
||||
import 'web_qr_scan_screen.dart';
|
||||
|
||||
@@ -533,14 +534,10 @@ class _DevicesScreenState extends State<DevicesScreen>
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: isLoading
|
||||
? SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: cs.onSurfaceVariant.withValues(
|
||||
alpha: 0.5,
|
||||
),
|
||||
? SmallSpinner(
|
||||
size: 14,
|
||||
color: cs.onSurfaceVariant.withValues(
|
||||
alpha: 0.5,
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
|
||||
@@ -8,6 +8,7 @@ import '../../../main.dart' show accountModule, fileUploader, KometApp;
|
||||
import '../../widgets/connection_status.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/komet_avatar.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
|
||||
class EditProfileScreen extends StatefulWidget {
|
||||
const EditProfileScreen({super.key});
|
||||
@@ -180,11 +181,7 @@ class _EditProfileScreenState extends State<EditProfileScreen> {
|
||||
TextButton(
|
||||
onPressed: _isLoading || _isSaving ? null : _saveName,
|
||||
child: _isSaving
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
? const SmallSpinner(size: 16)
|
||||
: Text(
|
||||
l10n?.editProfileSave ?? 'Save',
|
||||
style: TextStyle(
|
||||
@@ -196,7 +193,7 @@ class _EditProfileScreenState extends State<EditProfileScreen> {
|
||||
],
|
||||
),
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
? const Center(child: SmallSpinner(size: 36))
|
||||
: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
|
||||
@@ -9,6 +9,7 @@ import '../../widgets/connection_status.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/glossy_pill.dart';
|
||||
import '../../widgets/section_header.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
|
||||
class InfoScreen extends StatefulWidget {
|
||||
const InfoScreen({super.key});
|
||||
@@ -68,7 +69,7 @@ class _InfoScreenState extends State<InfoScreen> {
|
||||
),
|
||||
),
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
? const Center(child: SmallSpinner(size: 36))
|
||||
: _info == null
|
||||
? Center(
|
||||
child: Text(
|
||||
|
||||
@@ -8,6 +8,7 @@ import '../../widgets/connection_status.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/section_header.dart';
|
||||
import '../../widgets/settings_card.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
|
||||
class NotificationsScreen extends StatefulWidget {
|
||||
const NotificationsScreen({super.key});
|
||||
@@ -101,7 +102,7 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: _loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
? const Center(child: SmallSpinner(size: 36))
|
||||
: ListView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 120),
|
||||
|
||||
@@ -7,6 +7,7 @@ import '../../../l10n/app_localizations.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/glossy_pill.dart';
|
||||
import '../../widgets/primary_loading_button.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
|
||||
class PasswordEntryScreen extends StatefulWidget {
|
||||
const PasswordEntryScreen({super.key});
|
||||
@@ -142,7 +143,7 @@ class _PasswordEntryScreenState extends State<PasswordEntryScreen> {
|
||||
if (_isLoading) {
|
||||
return Scaffold(
|
||||
backgroundColor: cs.surface,
|
||||
body: Center(child: CircularProgressIndicator(color: cs.primary)),
|
||||
body: Center(child: SmallSpinner(size: 36, color: cs.primary)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/connection_status.dart';
|
||||
import '../../widgets/glossy_pill.dart';
|
||||
import '../../widgets/sheet_helpers.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import 'password_entry_screen.dart';
|
||||
|
||||
class SecurityScreen extends StatefulWidget {
|
||||
@@ -219,14 +220,7 @@ class _SecurityScreenState extends State<SecurityScreen>
|
||||
if (_isSaving)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: cs.primary,
|
||||
),
|
||||
),
|
||||
child: SmallSpinner(size: 20, color: cs.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -17,6 +17,7 @@ import '../../widgets/info_action_sheet.dart';
|
||||
import '../../widgets/komet_avatar.dart';
|
||||
import '../../widgets/settings_card.dart';
|
||||
import '../../widgets/sheet_helpers.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../auth/login_screen.dart';
|
||||
import '../auth/proxy_settings_sheet.dart';
|
||||
@@ -222,7 +223,7 @@ class _SettingsTabState extends State<SettingsTab> {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
|
||||
if (_profile == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return const Center(child: SmallSpinner(size: 36));
|
||||
}
|
||||
|
||||
final String fullName =
|
||||
|
||||
@@ -18,6 +18,7 @@ import '../../widgets/connection_status.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/info_action_sheet.dart';
|
||||
import '../../widgets/section_header.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import '../auth/login_screen.dart';
|
||||
|
||||
enum SpoofingMethod { partial, full }
|
||||
@@ -444,7 +445,7 @@ class _SpoofScreenState extends State<SpoofScreen> {
|
||||
centerTitle: true,
|
||||
),
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
? const Center(child: SmallSpinner(size: 36))
|
||||
: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 120),
|
||||
child: Column(
|
||||
|
||||
@@ -14,6 +14,7 @@ import '../../../models/story.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/komet_avatar.dart';
|
||||
import '../../widgets/liquid_glass.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import 'story_owner_info.dart';
|
||||
|
||||
const _quickReactions = ['❤️', '🔥', '😍', '👏', '😂', '😮'];
|
||||
@@ -490,14 +491,7 @@ class _StoryViewerScreenState extends State<StoryViewerScreen>
|
||||
const _TopScrim(),
|
||||
if (loading)
|
||||
const Center(
|
||||
child: SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.4,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
child: SmallSpinner(size: 28, color: Colors.white),
|
||||
),
|
||||
SafeArea(
|
||||
child: Column(
|
||||
@@ -1069,14 +1063,7 @@ class _OwnerCover extends StatelessWidget {
|
||||
imageUrl: info?.avatarUrl,
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white30,
|
||||
),
|
||||
),
|
||||
const SmallSpinner(size: 22, color: Colors.white30),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -9,6 +9,7 @@ import '../../../core/storage/spoofing_service.dart';
|
||||
import '../../../main.dart' show api;
|
||||
import '../../widgets/connection_status.dart';
|
||||
import '../../widgets/error_view.dart';
|
||||
import '../../widgets/small_spinner.dart';
|
||||
import '../../widgets/webview_permission_prompt.dart';
|
||||
|
||||
class WebAppScreen extends StatefulWidget {
|
||||
@@ -137,7 +138,7 @@ class _WebAppScreenState extends State<WebAppScreen> {
|
||||
}
|
||||
final launch = _launch;
|
||||
if (launch == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return const Center(child: SmallSpinner(size: 36));
|
||||
}
|
||||
return InAppWebView(
|
||||
initialUrlRequest: URLRequest(url: WebUri(launch.url)),
|
||||
|
||||
@@ -16,6 +16,8 @@ import 'package:komet/frontend/widgets/sheet_helpers.dart';
|
||||
import 'package:komet/frontend/widgets/sliding_pill_nav.dart';
|
||||
import 'package:komet/l10n/app_localizations.dart';
|
||||
|
||||
import '../small_spinner.dart';
|
||||
|
||||
const int _navItemCount = 5;
|
||||
|
||||
List<PillNavItem> _buildNavItems(AppLocalizations l10n) => [
|
||||
@@ -395,7 +397,7 @@ class _AttachmentSheetState extends State<AttachmentSheet> {
|
||||
double bottomReserve,
|
||||
) {
|
||||
if (_loading) {
|
||||
return Center(child: CircularProgressIndicator(color: cs.primary));
|
||||
return Center(child: SmallSpinner(size: 36, color: cs.primary));
|
||||
}
|
||||
if (_permission == GalleryPermission.denied) {
|
||||
return _buildDenied(scrollController, cs, bottomReserve);
|
||||
|
||||
@@ -10,6 +10,7 @@ import '../../../../core/utils/haptics.dart';
|
||||
import '../../../../core/utils/logger.dart';
|
||||
import '../../../../core/utils/media_cache.dart';
|
||||
import '../../../../models/attachment.dart';
|
||||
import '../../small_spinner.dart';
|
||||
|
||||
class VideoNoteBubble extends StatefulWidget {
|
||||
final VideoAttachment attachment;
|
||||
@@ -182,10 +183,7 @@ class _VideoNoteBubbleState extends State<VideoNoteBubble> {
|
||||
child: _loading
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(14),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
child: SmallSpinner(size: 36, color: Colors.white),
|
||||
)
|
||||
: Icon(
|
||||
_error ? Symbols.error : Symbols.play_arrow,
|
||||
|
||||
@@ -13,6 +13,7 @@ import '../../../../core/utils/format.dart';
|
||||
import '../../../../core/utils/logger.dart';
|
||||
import '../../../../core/utils/media_cache.dart';
|
||||
import '../../custom_notification.dart';
|
||||
import '../../small_spinner.dart';
|
||||
|
||||
class VoiceMessageBubble extends StatefulWidget {
|
||||
final int duration;
|
||||
@@ -231,8 +232,8 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
|
||||
child: _loadingAudio
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
child: SmallSpinner(
|
||||
size: 36,
|
||||
color: widget.isMe
|
||||
? widget.cs.onPrimaryContainer
|
||||
: widget.cs.primary,
|
||||
@@ -273,13 +274,9 @@ class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
|
||||
height: 32,
|
||||
child: Center(
|
||||
child: _transcriptionLoading
|
||||
? SizedBox(
|
||||
width: 12,
|
||||
height: 12,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 1.5,
|
||||
color: widget.textColor.withValues(alpha: 0.6),
|
||||
),
|
||||
? SmallSpinner(
|
||||
size: 12,
|
||||
color: widget.textColor.withValues(alpha: 0.6),
|
||||
)
|
||||
: Text(
|
||||
'Т',
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:komet/frontend/widgets/attachment/photo_editor.dart';
|
||||
import 'package:komet/frontend/widgets/custom_notification.dart';
|
||||
|
||||
import '../../../core/config/app_colors.dart';
|
||||
import '../small_spinner.dart';
|
||||
|
||||
const Color _kBar = Color(0xFF1E1E1E);
|
||||
|
||||
@@ -218,11 +219,7 @@ class _MediaPreviewScreenState extends State<MediaPreviewScreen> {
|
||||
Widget _buildImage() {
|
||||
final file = _workingFile;
|
||||
if (file == null) {
|
||||
return const SizedBox(
|
||||
width: 36,
|
||||
height: 36,
|
||||
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white24),
|
||||
);
|
||||
return const SmallSpinner(size: 36, color: Colors.white24);
|
||||
}
|
||||
return Image.file(file, fit: BoxFit.contain, gaplessPlayback: true);
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ class _PhotoCropEditorState extends State<PhotoCropEditor> {
|
||||
final img = _image;
|
||||
if (img == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(color: Colors.white),
|
||||
child: SmallSpinner(size: 36, color: Colors.white),
|
||||
);
|
||||
}
|
||||
return LayoutBuilder(
|
||||
@@ -2350,7 +2350,7 @@ class _PhotoAdjustEditorState extends State<PhotoAdjustEditor> {
|
||||
final img = _image;
|
||||
if (img == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(color: Colors.white),
|
||||
child: SmallSpinner(size: 36, color: Colors.white),
|
||||
);
|
||||
}
|
||||
return LayoutBuilder(
|
||||
|
||||
@@ -6,6 +6,7 @@ import '../../backend/modules/contacts.dart';
|
||||
import '../../core/utils/media_saver.dart';
|
||||
import '../../main.dart';
|
||||
import 'custom_notification.dart';
|
||||
import 'small_spinner.dart';
|
||||
|
||||
class AvatarHistoryScreen extends StatefulWidget {
|
||||
final int contactId;
|
||||
@@ -210,14 +211,7 @@ class _AvatarHistoryScreenState extends State<AvatarHistoryScreen> {
|
||||
Expanded(child: _buildCounter()),
|
||||
IconButton(
|
||||
icon: _saving
|
||||
? const SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
? const SmallSpinner(size: 22, color: Colors.white)
|
||||
: const Icon(Symbols.download, color: Colors.white),
|
||||
onPressed: _pages.isEmpty || _saving ? null : _save,
|
||||
),
|
||||
@@ -276,7 +270,7 @@ class _AvatarHistoryScreenState extends State<AvatarHistoryScreen> {
|
||||
if (_pages.isEmpty) {
|
||||
return Center(
|
||||
child: _loading
|
||||
? const CircularProgressIndicator(color: Colors.white)
|
||||
? const SmallSpinner(size: 36, color: Colors.white)
|
||||
: const Text(
|
||||
'Нет фотографий',
|
||||
style: TextStyle(color: Colors.white54, fontSize: 15),
|
||||
@@ -293,7 +287,7 @@ class _AvatarHistoryScreenState extends State<AvatarHistoryScreen> {
|
||||
fit: BoxFit.contain,
|
||||
fadeInDuration: const Duration(milliseconds: 120),
|
||||
placeholder: (_, _) => const Center(
|
||||
child: CircularProgressIndicator(color: Colors.white),
|
||||
child: SmallSpinner(size: 36, color: Colors.white),
|
||||
),
|
||||
errorWidget: (_, _, _) =>
|
||||
const Icon(Symbols.broken_image, color: Colors.white54, size: 64),
|
||||
|
||||
@@ -23,6 +23,7 @@ import '../../screens/chats/chat_screen.dart';
|
||||
import '../custom_notification.dart';
|
||||
import '../komet_avatar.dart';
|
||||
import '../photo_viewer.dart';
|
||||
import '../small_spinner.dart';
|
||||
import '../swipe_route.dart';
|
||||
import '../video_player_screen.dart';
|
||||
|
||||
@@ -103,11 +104,7 @@ Widget _loadingState(ColorScheme cs) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 56),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 26,
|
||||
height: 26,
|
||||
child: CircularProgressIndicator(strokeWidth: 2.5, color: cs.primary),
|
||||
),
|
||||
child: SmallSpinner(size: 26, color: cs.primary),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -611,14 +608,7 @@ class _SharedMediaTabState extends State<SharedMediaTab> {
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Center(
|
||||
child: _loadingMore
|
||||
? SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.2,
|
||||
color: cs.primary,
|
||||
),
|
||||
)
|
||||
? SmallSpinner(size: 22, color: cs.primary)
|
||||
: TextButton(
|
||||
onPressed: _loadMore,
|
||||
child: Text(l10n.sharedLoadMore),
|
||||
@@ -1174,10 +1164,7 @@ class _ProfileVoiceTileState extends State<_ProfileVoiceTile> {
|
||||
child: _loadingAudio
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(13),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
child: SmallSpinner(size: 36, color: Colors.white),
|
||||
)
|
||||
: ValueListenableBuilder<double>(
|
||||
valueListenable: _progress,
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:m3e_collection/m3e_collection.dart';
|
||||
|
||||
import '../../backend/api.dart';
|
||||
import '../../main.dart' show api;
|
||||
import 'small_spinner.dart';
|
||||
|
||||
final ValueNotifier<bool> debugForceOffline = ValueNotifier<bool>(false);
|
||||
|
||||
@@ -175,14 +176,7 @@ class ConnectionSpinner extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.4,
|
||||
color: cs.primary,
|
||||
),
|
||||
),
|
||||
child: SmallSpinner(size: 20, color: cs.primary),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -14,6 +14,7 @@ import '../../l10n/app_localizations.dart';
|
||||
import 'custom_notification.dart';
|
||||
import 'komet_avatar.dart';
|
||||
import 'lottie_image.dart';
|
||||
import 'small_spinner.dart';
|
||||
|
||||
class ReactionEmoji {
|
||||
final String emoji;
|
||||
@@ -1227,11 +1228,7 @@ class _MessageActionsLayerState extends State<_MessageActionsLayer>
|
||||
body = const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 28),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(strokeWidth: 2.4),
|
||||
),
|
||||
child: SmallSpinner(size: 24),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -1314,11 +1311,7 @@ class _MessageActionsLayerState extends State<_MessageActionsLayer>
|
||||
body = const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 28),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(strokeWidth: 2.4),
|
||||
),
|
||||
child: SmallSpinner(size: 24),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -1647,11 +1640,7 @@ class _ReactionEmojiPickerState extends State<_ReactionEmojiPicker> {
|
||||
Expanded(
|
||||
child: !_loaded
|
||||
? const Center(
|
||||
child: SizedBox(
|
||||
width: 26,
|
||||
height: 26,
|
||||
child: CircularProgressIndicator(strokeWidth: 2.4),
|
||||
),
|
||||
child: SmallSpinner(size: 26),
|
||||
)
|
||||
: _results.isEmpty
|
||||
? const SizedBox.shrink()
|
||||
|
||||
@@ -21,6 +21,7 @@ import '../../main.dart';
|
||||
import '../../models/attachment.dart';
|
||||
import 'chat_menu_overlay.dart';
|
||||
import 'custom_notification.dart';
|
||||
import 'small_spinner.dart';
|
||||
|
||||
class PhotoViewerActions {
|
||||
final void Function(String messageId, int time)? goToMessage;
|
||||
@@ -550,14 +551,7 @@ class _PhotoViewerScreenState extends State<PhotoViewerScreen> {
|
||||
Expanded(child: _buildInfo(l10n)),
|
||||
IconButton(
|
||||
icon: _saving
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
? const SmallSpinner(size: 20, color: Colors.white)
|
||||
: const Icon(Symbols.download, color: Colors.white),
|
||||
onPressed: _saving ? null : _save,
|
||||
tooltip: l10n.sharedDownload,
|
||||
@@ -666,7 +660,7 @@ class _PhotoViewerScreenState extends State<PhotoViewerScreen> {
|
||||
fit: BoxFit.contain,
|
||||
fadeInDuration: const Duration(milliseconds: 120),
|
||||
placeholder: (_, _) =>
|
||||
const Center(child: CircularProgressIndicator(color: Colors.white)),
|
||||
const Center(child: SmallSpinner(size: 36, color: Colors.white)),
|
||||
errorWidget: (_, _, _) => _broken(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import '../../core/utils/format.dart';
|
||||
import '../../core/utils/haptics.dart';
|
||||
import '../../models/poll.dart';
|
||||
import 'custom_notification.dart';
|
||||
import 'small_spinner.dart';
|
||||
|
||||
class PollView extends StatefulWidget {
|
||||
final int chatId;
|
||||
@@ -222,14 +223,7 @@ class _PollViewState extends State<PollView>
|
||||
),
|
||||
),
|
||||
if (_voting && !multiple)
|
||||
SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 1.5,
|
||||
color: widget.dimColor,
|
||||
),
|
||||
),
|
||||
SmallSpinner(size: 14, color: widget.dimColor),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -253,14 +247,7 @@ class _PollViewState extends State<PollView>
|
||||
),
|
||||
),
|
||||
child: _voting
|
||||
? SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: widget.accentColor,
|
||||
),
|
||||
)
|
||||
? SmallSpinner(size: 16, color: widget.accentColor)
|
||||
: const Text('Проголосовать'),
|
||||
),
|
||||
),
|
||||
@@ -355,12 +342,14 @@ class _PollViewState extends State<PollView>
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value: fillFactor,
|
||||
minHeight: 6,
|
||||
backgroundColor: widget.dimColor.withValues(alpha: 0.2),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
widget.accentColor,
|
||||
child: Container(
|
||||
height: 6,
|
||||
color: widget.dimColor.withValues(alpha: 0.2),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FractionallySizedBox(
|
||||
widthFactor: fillFactor.clamp(0.0, 1.0),
|
||||
heightFactor: 1,
|
||||
child: ColoredBox(color: widget.accentColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'small_spinner.dart';
|
||||
import 'springy_tap.dart';
|
||||
|
||||
class PrimaryLoadingButton extends StatelessWidget {
|
||||
final ValueListenable<bool> loading;
|
||||
final VoidCallback? onPressed;
|
||||
@@ -23,23 +26,22 @@ class PrimaryLoadingButton extends StatelessWidget {
|
||||
final fg = foreground ?? cs.onPrimary;
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: loading,
|
||||
builder: (context, isLoading, _) => FilledButton(
|
||||
onPressed: isLoading ? null : onPressed,
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: background ?? cs.primary,
|
||||
foregroundColor: fg,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
builder: (context, isLoading, _) => SpringyTap(
|
||||
enabled: !isLoading,
|
||||
child: FilledButton(
|
||||
onPressed: isLoading ? null : onPressed,
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: background ?? cs.primary,
|
||||
foregroundColor: fg,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: isLoading
|
||||
? SmallSpinner(size: 20, color: fg)
|
||||
: child,
|
||||
),
|
||||
child: isLoading
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2, color: fg),
|
||||
)
|
||||
: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:m3e_collection/m3e_collection.dart';
|
||||
|
||||
class SmallSpinner extends StatelessWidget {
|
||||
final double size;
|
||||
final double strokeWidth;
|
||||
final Color? color;
|
||||
|
||||
const SmallSpinner({
|
||||
super.key,
|
||||
this.size = 26,
|
||||
this.strokeWidth = 2.4,
|
||||
this.color,
|
||||
});
|
||||
|
||||
@@ -17,9 +16,12 @@ class SmallSpinner extends StatelessWidget {
|
||||
return SizedBox(
|
||||
width: size,
|
||||
height: size,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: strokeWidth,
|
||||
color: color ?? Theme.of(context).colorScheme.primary,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.contain,
|
||||
child: ExpressiveLoadingIndicator(
|
||||
color: color ?? Theme.of(context).colorScheme.primary,
|
||||
constraints: BoxConstraints.tight(const Size.square(48)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -33,7 +35,7 @@ class BusyOverlay extends StatelessWidget {
|
||||
return const Positioned.fill(
|
||||
child: ColoredBox(
|
||||
color: Colors.black54,
|
||||
child: Center(child: CircularProgressIndicator(color: Colors.white)),
|
||||
child: Center(child: SmallSpinner(size: 44, color: Colors.white)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/physics.dart';
|
||||
|
||||
class SpringyTap extends StatefulWidget {
|
||||
final Widget child;
|
||||
final double pressedScale;
|
||||
final bool enabled;
|
||||
|
||||
const SpringyTap({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.pressedScale = 0.98,
|
||||
this.enabled = true,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SpringyTap> createState() => _SpringyTapState();
|
||||
}
|
||||
|
||||
class _SpringyTapState extends State<SpringyTap>
|
||||
with SingleTickerProviderStateMixin {
|
||||
static const Duration _pressDelay = Duration(milliseconds: 60);
|
||||
static const Duration _pressDuration = Duration(milliseconds: 90);
|
||||
|
||||
static final SpringDescription _spring = SpringDescription.withDampingRatio(
|
||||
ratio: 0.8,
|
||||
stiffness: 350,
|
||||
mass: 1,
|
||||
);
|
||||
|
||||
late final AnimationController _controller = AnimationController.unbounded(
|
||||
vsync: this,
|
||||
value: 1.0,
|
||||
);
|
||||
|
||||
Timer? _pressTimer;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pressTimer?.cancel();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _press() {
|
||||
_controller.stop();
|
||||
_controller.animateTo(
|
||||
widget.pressedScale,
|
||||
duration: _pressDuration,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
|
||||
void _release() {
|
||||
if (_controller.value == 1.0 && !_controller.isAnimating) return;
|
||||
_controller.animateWith(
|
||||
SpringSimulation(_spring, _controller.value, 1.0, 1.5),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pulse() async {
|
||||
_controller.stop();
|
||||
await _controller.animateTo(
|
||||
widget.pressedScale,
|
||||
duration: _pressDuration,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
if (mounted) _release();
|
||||
}
|
||||
|
||||
void _onDown(PointerDownEvent _) {
|
||||
_pressTimer?.cancel();
|
||||
_pressTimer = Timer(_pressDelay, () {
|
||||
_pressTimer = null;
|
||||
_press();
|
||||
});
|
||||
}
|
||||
|
||||
void _onUp(PointerUpEvent _) {
|
||||
if (_pressTimer != null) {
|
||||
_pressTimer!.cancel();
|
||||
_pressTimer = null;
|
||||
unawaited(_pulse());
|
||||
} else {
|
||||
_release();
|
||||
}
|
||||
}
|
||||
|
||||
void _onCancel(PointerCancelEvent _) {
|
||||
_pressTimer?.cancel();
|
||||
_pressTimer = null;
|
||||
_release();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!widget.enabled) return widget.child;
|
||||
return Listener(
|
||||
behavior: HitTestBehavior.deferToChild,
|
||||
onPointerDown: _onDown,
|
||||
onPointerUp: _onUp,
|
||||
onPointerCancel: _onCancel,
|
||||
child: ScaleTransition(scale: _controller, child: widget.child),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -310,13 +310,9 @@ class _StickerPackSheetState extends State<_StickerPackSheet> {
|
||||
),
|
||||
onPressed: _busy ? null : _toggle,
|
||||
child: _busy
|
||||
? SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.4,
|
||||
color: _isFavorite ? cs.onSurface : cs.onPrimary,
|
||||
),
|
||||
? SmallSpinner(
|
||||
size: 22,
|
||||
color: _isFavorite ? cs.onSurface : cs.onPrimary,
|
||||
)
|
||||
: Text(
|
||||
_isFavorite ? 'Убрать' : 'Добавить',
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
import '../../core/utils/format.dart';
|
||||
import 'small_spinner.dart';
|
||||
|
||||
class VideoPlayerScreen extends StatefulWidget {
|
||||
final Map<String, String> sources;
|
||||
@@ -133,11 +134,11 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
aspectRatio: c.value.aspectRatio,
|
||||
child: VideoPlayer(c),
|
||||
)
|
||||
: const CircularProgressIndicator(color: Colors.white),
|
||||
: const SmallSpinner(size: 36, color: Colors.white),
|
||||
),
|
||||
if (buffering)
|
||||
const Center(
|
||||
child: CircularProgressIndicator(color: Colors.white),
|
||||
child: SmallSpinner(size: 36, color: Colors.white),
|
||||
),
|
||||
if (!_error)
|
||||
AnimatedOpacity(
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import '../../main.dart' show accountModule;
|
||||
import 'custom_notification.dart';
|
||||
import 'sheet_helpers.dart';
|
||||
import 'small_spinner.dart';
|
||||
|
||||
Future<bool> showWebQrLoginConfirmSheet(BuildContext context) async {
|
||||
final agreed = await showModalBottomSheet<bool>(
|
||||
@@ -88,7 +89,7 @@ Future<bool> confirmAndAuthorizeWebQrLogin(
|
||||
color: cs.surfaceContainerHigh,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(28),
|
||||
child: CircularProgressIndicator(),
|
||||
child: SmallSpinner(size: 36),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+7
-1
@@ -80,6 +80,7 @@ import 'frontend/screens/auth/login_screen.dart';
|
||||
import 'frontend/widgets/adaptive_shell.dart';
|
||||
import 'frontend/widgets/custom_notification.dart';
|
||||
import 'frontend/widgets/liquid_glass.dart';
|
||||
import 'frontend/widgets/small_spinner.dart';
|
||||
import 'frontend/widgets/theme_reveal.dart';
|
||||
|
||||
final api = Api();
|
||||
@@ -98,6 +99,9 @@ final RouteObserver<PageRoute<dynamic>> appRouteObserver =
|
||||
|
||||
bool isOnemeFlavor = false;
|
||||
|
||||
const ProgressIndicatorThemeData _expressiveProgressTheme =
|
||||
ProgressIndicatorThemeData(year2023: false);
|
||||
|
||||
const PageTransitionsTheme _appPageTransitions = PageTransitionsTheme(
|
||||
builders: <TargetPlatform, PageTransitionsBuilder>{
|
||||
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),
|
||||
@@ -835,6 +839,7 @@ class KometAppState extends State<KometApp>
|
||||
useMaterial3: true,
|
||||
colorScheme: light,
|
||||
pageTransitionsTheme: _appPageTransitions,
|
||||
progressIndicatorTheme: _expressiveProgressTheme,
|
||||
textTheme: AppFonts.textTheme(
|
||||
_fontId,
|
||||
ThemeData(brightness: Brightness.light).textTheme,
|
||||
@@ -846,6 +851,7 @@ class KometAppState extends State<KometApp>
|
||||
useMaterial3: true,
|
||||
colorScheme: dark,
|
||||
pageTransitionsTheme: _appPageTransitions,
|
||||
progressIndicatorTheme: _expressiveProgressTheme,
|
||||
textTheme: AppFonts.textTheme(
|
||||
_fontId,
|
||||
ThemeData(brightness: Brightness.dark).textTheme,
|
||||
@@ -1064,7 +1070,7 @@ class _StartupScreenState extends State<_StartupScreen> {
|
||||
return Scaffold(
|
||||
backgroundColor: cs.surface,
|
||||
body: Center(
|
||||
child: CircularProgressIndicator(color: cs.primary, strokeWidth: 2),
|
||||
child: SmallSpinner(size: 36, color: cs.primary),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -262,7 +262,7 @@ SPEC CHECKSUMS:
|
||||
photo_manager: 25fd77df14f4f0ba5ef99e2c61814dde77e2bceb
|
||||
PromisesObjC: 752c3227f599e3467650e47ea36f433eeb10c273
|
||||
record_macos: 5d55909f9650314be6424ffd6b123ac75a08c3c1
|
||||
rlottie: c2780aabf858ca3c81785f09256827ca83b0765d
|
||||
rlottie: 206daeeeb0f9dec6594ed248eb0c7e876a289e93
|
||||
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
|
||||
Reference in New Issue
Block a user