feat: немного анимированных иконок

This commit is contained in:
torvalds
2026-07-04 13:48:12 +07:00
parent 15d0d1b663
commit ff3f76b5db
13 changed files with 236 additions and 16 deletions
+1
View File
@@ -29,6 +29,7 @@
<application
android:label="Komet"
android:name="${applicationName}"
android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
View File
+9
View File
@@ -0,0 +1,9 @@
class AppAnimations {
const AppAnimations._();
static const String _dir = 'assets/lottie';
static const String clock = '$_dir/ic_clock.json';
static const String search = '$_dir/ic_search.json';
static const String settings = '$_dir/ic_settings.json';
}
@@ -34,6 +34,7 @@ import '../../../backend/api.dart';
import '../../../core/protocol/opcode_map.dart';
import '../../../core/protocol/packet.dart';
import '../../../core/utils/haptics.dart';
import '../../../core/config/app_animations.dart';
import '../../../core/config/app_stories.dart';
import '../../../backend/models/chat_folder.dart';
import '../../../backend/modules/account.dart';
@@ -138,6 +139,7 @@ class _ChatListScreenState extends State<ChatListScreen>
icon: Symbols.settings,
label: 'Настройки',
longPressable: true,
animationAsset: AppAnimations.settings,
),
];
+33 -4
View File
@@ -25,6 +25,7 @@ import 'package:komet/frontend/screens/chats/chat_list_screen.dart';
import 'package:komet/frontend/screens/chats/poll_create_screen.dart';
import 'package:komet/frontend/widgets/custom_notification.dart';
import 'package:komet/frontend/widgets/chat_menu_overlay.dart';
import 'package:komet/frontend/widgets/animated_lottie_icon.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../../main.dart';
import '../../../backend/api.dart';
@@ -42,6 +43,7 @@ import '../../../core/storage/draft_store.dart';
import '../../../core/cache/info_cache.dart';
import '../../../core/cache/message_session_cache.dart';
import '../../../core/utils/haptics.dart';
import '../../../core/config/app_animations.dart';
import '../../../core/config/app_cache_extent.dart';
import '../../../core/config/app_message_actions_style.dart';
import '../../../core/config/app_swipe_back_desktop.dart';
@@ -2268,7 +2270,25 @@ class _ChatScreenState extends State<ChatScreen>
),
child: const SizedBox.expand(),
)
: null,
: (chrome == ChatChromeStyle.none && !glossy)
? IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
cs.surface,
cs.surface,
cs.surface.withValues(alpha: 0.0),
],
stops: const [0.0, 0.72, 1.0],
),
),
child: const SizedBox.expand(),
),
)
: null,
foregroundColor: cs.onSurface,
surfaceTintColor: Colors.transparent,
iconTheme: IconThemeData(color: cs.onSurface),
@@ -2368,8 +2388,13 @@ class _ChatScreenState extends State<ChatScreen>
onPressed: _closeSearch,
);
final searchBtn = IconButton(
icon: Icon(Symbols.search, weight: glossy ? 500 : 400,
color: cs.onSurface),
icon: AnimatedLottieIcon(
asset: AppAnimations.search,
color: cs.onSurface,
size: 24,
active: true,
animateOnMount: true,
),
onPressed: () {
_searchDebounce?.cancel();
_runSearch(_searchController.text);
@@ -4380,6 +4405,7 @@ class _ChatScreenState extends State<ChatScreen>
children: [
Expanded(
child: Stack(
fit: StackFit.expand,
children: [
if (_wallpaper != null)
Positioned.fill(
@@ -4509,7 +4535,10 @@ class _ChatScreenState extends State<ChatScreen>
double _floatingDateTop(BuildContext context) {
if (AppChatChrome.current.value == ChatChromeStyle.color) return 8;
return MediaQuery.paddingOf(context).top + 8;
final glossy = AppVisualStyle.current.value == VisualStyle.glossy;
return MediaQuery.paddingOf(context).top +
(glossy ? _glossyHeaderHeight : kToolbarHeight) +
8;
}
Widget _buildLoadMoreIndicator() {
@@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class AnimatedIconButton extends StatefulWidget {
final String asset;
final VoidCallback? onPressed;
final double size;
final String? tooltip;
const AnimatedIconButton({
super.key,
required this.asset,
this.onPressed,
this.size = 28,
this.tooltip,
});
@override
State<AnimatedIconButton> createState() => _AnimatedIconButtonState();
}
class _AnimatedIconButtonState extends State<AnimatedIconButton>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _enter() => _controller.forward();
void _exit() => _controller.reverse();
void _tapDown() => _controller.forward(from: 0);
@override
Widget build(BuildContext context) {
final Widget button = MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) => _enter(),
onExit: (_) => _exit(),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: widget.onPressed,
onTapDown: (_) => _tapDown(),
child: SizedBox.square(
dimension: widget.size,
child: Lottie.asset(
widget.asset,
controller: _controller,
fit: BoxFit.contain,
onLoaded: (composition) {
_controller.duration = composition.duration;
},
),
),
),
);
final tooltip = widget.tooltip;
if (tooltip != null) {
return Tooltip(message: tooltip, child: button);
}
return button;
}
}
@@ -0,0 +1,78 @@
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class AnimatedLottieIcon extends StatefulWidget {
final String asset;
final Color color;
final double size;
final bool active;
final bool animateOnMount;
const AnimatedLottieIcon({
super.key,
required this.asset,
required this.color,
required this.size,
this.active = false,
this.animateOnMount = false,
});
@override
State<AnimatedLottieIcon> createState() => _AnimatedLottieIconState();
}
class _AnimatedLottieIconState extends State<AnimatedLottieIcon>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
if (widget.active && widget.animateOnMount) {
_controller.forward(from: 0);
} else {
_controller.value = widget.active ? 1.0 : 0.0;
}
}
@override
void didUpdateWidget(AnimatedLottieIcon oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.active && !oldWidget.active) {
_controller.forward(from: 0);
} else if (!widget.active && oldWidget.active) {
_controller.value = 0;
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox.square(
dimension: widget.size,
child: Lottie.asset(
widget.asset,
controller: _controller,
fit: BoxFit.contain,
delegates: LottieDelegates(
values: [
ValueDelegate.color(const ['**'], value: widget.color),
ValueDelegate.strokeColor(const ['**'], value: widget.color),
],
),
onLoaded: (composition) {
_controller.duration = composition.duration;
},
),
);
}
}
+19 -12
View File
@@ -2,17 +2,20 @@ import 'package:flutter/material.dart';
import '../../core/config/app_pill_gradient.dart';
import '../../core/config/app_visual_style.dart';
import 'animated_lottie_icon.dart';
import 'glossy_pill.dart';
class PillNavItem {
final IconData icon;
final String label;
final bool longPressable;
final String? animationAsset;
const PillNavItem({
required this.icon,
required this.label,
this.longPressable = false,
this.animationAsset,
});
}
@@ -210,6 +213,20 @@ class _PillNavCell extends StatelessWidget {
required this.onLongPress,
});
Widget _buildIcon() {
final color = selected ? cs.onPrimary : cs.onSurface;
final asset = item.animationAsset;
if (asset != null) {
return AnimatedLottieIcon(
asset: asset,
color: color,
size: iconSize,
active: selected,
);
}
return Icon(item.icon, color: color, size: iconSize, fill: 1);
}
@override
Widget build(BuildContext context) {
final opacityDuration = animationDuration == Duration.zero
@@ -223,24 +240,14 @@ class _PillNavCell extends StatelessWidget {
behavior: HitTestBehavior.opaque,
child: Center(
child: iconsOnly
? Icon(
item.icon,
color: selected ? cs.onPrimary : cs.onSurface,
size: iconSize,
fill: 1,
)
? _buildIcon()
: FittedBox(
fit: BoxFit.scaleDown,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
item.icon,
color: selected ? cs.onPrimary : cs.onSurface,
size: iconSize,
fill: 1,
),
_buildIcon(),
AnimatedContainer(
duration: animationDuration,
curve: Curves.easeOutCubic,
+13
View File
@@ -3,6 +3,7 @@ import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/cupertino.dart' show CupertinoPageTransitionsBuilder;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:video_player_media_kit/video_player_media_kit.dart';
@@ -80,6 +81,16 @@ final RouteObserver<PageRoute<dynamic>> appRouteObserver =
bool isOnemeFlavor = false;
const PageTransitionsTheme _appPageTransitions = PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.macOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.windows: ZoomPageTransitionsBuilder(),
TargetPlatform.linux: ZoomPageTransitionsBuilder(),
},
);
Future<Locale> _loadInitialLocale() async {
final prefs = await SharedPreferences.getInstance();
final code = prefs.getString('app_locale');
@@ -711,6 +722,7 @@ class KometAppState extends State<KometApp>
ThemeData(
useMaterial3: true,
colorScheme: light,
pageTransitionsTheme: _appPageTransitions,
textTheme: AppFonts.textTheme(
_fontId,
ThemeData(brightness: Brightness.light).textTheme,
@@ -721,6 +733,7 @@ class KometAppState extends State<KometApp>
ThemeData(
useMaterial3: true,
colorScheme: dark,
pageTransitionsTheme: _appPageTransitions,
textTheme: AppFonts.textTheme(
_fontId,
ThemeData(brightness: Brightness.dark).textTheme,
+1
View File
@@ -125,6 +125,7 @@ flutter:
- assets/komet.png
- assets/komet_icon.png
- assets/meteor_icon.png
- assets/lottie/
fonts:
- family: Inter