new project stable version

This commit is contained in:
2026-05-10 19:11:31 +03:00
commit 3616f84556
391 changed files with 23857 additions and 0 deletions

View File

@@ -0,0 +1,170 @@
import 'dart:math';
import 'package:flutter/material.dart';
class BatteryIndicator extends StatelessWidget {
final double percent;
const BatteryIndicator({super.key, required this.percent});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 400,
child: Stack(
alignment: Alignment.center,
children: [
CustomPaint(
size: const Size(320, 320),
painter: _BatteryRingPainter(percent: percent),
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Заряд батареи',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
),
),
const SizedBox(height: 8),
Text(
'${(percent * 100).toInt()}%',
style: const TextStyle(
color: Colors.white,
fontSize: 48,
fontWeight: FontWeight.w300,
),
),
],
),
],
),
);
}
}
class _BatteryRingPainter extends CustomPainter {
final double percent;
_BatteryRingPainter({required this.percent});
// 🔹 Возвращает цвета и stops для текущего диапазона
(List<Color>, List<double>?) _getGradientForPercent() {
final p = percent * 100;
if (p >= 51) {
return (
const [
Color(0xFF86EFAC),
Color(0xFF67E8F9),
Color(0xFF86EFAC),
],
const [0.0, 0.5, 1.0],
);
} else if (p >= 16) {
return (
const [
Color(0xFFF1FF8B),
Color(0xFF8BFFAA),
Color(0xFFF1FF8B),
],
const [0.0, 0.5, 1.0],
);
} else {
return (
const [
Color(0xFFFF5757),
Color(0xFFF1FF8B),
Color(0xFFFF5757),
],
const [0.0, 0.5, 1.0],
);
}
}
@override
void paint(Canvas canvas, Size size) {
final center = size.center(Offset.zero);
final radius = size.width / 2 - 20;
// Фоновое кольцо
final backgroundPaint = Paint()
..color = Colors.white.withOpacity(0.08)
..style = PaintingStyle.stroke
..strokeWidth = 40;
canvas.drawCircle(center, radius, backgroundPaint);
// Деления
final tickPaint = Paint()
..color = Colors.white.withOpacity(0.15)
..strokeWidth = 2;
for (int i = 0; i < 100; i++) {
final angle = 2 * pi * i / 100 - pi / 2;
final isMajor = i % 10 == 0;
final innerRadius = radius - 40;
final outerRadius = isMajor ? radius - 28 : radius - 32;
final p1 = Offset(
center.dx + cos(angle) * innerRadius,
center.dy + sin(angle) * innerRadius,
);
final p2 = Offset(
center.dx + cos(angle) * outerRadius,
center.dy + sin(angle) * outerRadius,
);
canvas.drawLine(p1, p2, tickPaint);
}
// Получаем градиент по проценту
final (colors, stops) = _getGradientForPercent();
// Glow дуга
final glowPaint = Paint()
..shader = SweepGradient(
colors: colors,
stops: stops,
).createShader(Rect.fromCircle(center: center, radius: radius))
..style = PaintingStyle.stroke
..strokeWidth = 25
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 85)
..strokeCap = StrokeCap.round;
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
-pi / 2,
2 * pi * percent,
false,
glowPaint,
);
// Основная дуга
final progressPaint = Paint()
..shader = SweepGradient(
colors: colors,
stops: stops,
).createShader(Rect.fromCircle(center: center, radius: radius))
..style = PaintingStyle.stroke
..strokeWidth = 20
..strokeCap = StrokeCap.round;
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
-pi / 2,
2 * pi * percent,
false,
progressPaint,
);
// Внутренний круг
final innerCircle = Paint()
..color = const Color(0xFF16233F)
..style = PaintingStyle.fill;
canvas.drawCircle(center, radius - 60, innerCircle);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

View File

@@ -0,0 +1,95 @@
import 'dart:math';
import 'package:flutter/material.dart';
class MiniBatteryIndicator extends StatelessWidget {
final int percent;
const MiniBatteryIndicator({super.key, required this.percent});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 40,
height: 40,
child: CustomPaint(
painter: _MiniBatteryRingPainter(percent: percent),
),
);
}
}
class _MiniBatteryRingPainter extends CustomPainter {
final int percent;
_MiniBatteryRingPainter({required this.percent});
(List<Color>, List<double>?) _getGradientForPercent() {
final p = percent;
if (p >= 51) {
return (
const [
Color(0xFF86EFAC),
Color(0xFF67E8F9),
Color(0xFF86EFAC),
],
const [0.0, 0.5, 1.0],
);
} else if (p >= 16) {
return (
const [
Color(0xFFF1FF8B),
Color(0xFF8BFFAA),
Color(0xFFF1FF8B),
],
const [0.0, 0.5, 1.0],
);
} else {
return (
const [
Color(0xFFFF5757),
Color(0xFFF1FF8B),
Color(0xFFFF5757),
],
const [0.0, 0.5, 1.0],
);
}
}
@override
void paint(Canvas canvas, Size size) {
final center = size.center(Offset.zero);
final radius = size.width / 2 - 4; // поменьше
// Фоновое кольцо
final backgroundPaint = Paint()
..color = Colors.white.withOpacity(0.08)
..style = PaintingStyle.stroke
..strokeWidth = 4;
canvas.drawCircle(center, radius, backgroundPaint);
// Получаем цвета
final (colors, stops) = _getGradientForPercent();
// Основная дуга
final progressPaint = Paint()
..shader = SweepGradient(
colors: colors,
stops: stops,
).createShader(Rect.fromCircle(center: center, radius: radius))
..style = PaintingStyle.stroke
..strokeWidth = 4
..strokeCap = StrokeCap.round;
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
-pi / 2,
2 * pi * (percent / 100),
false,
progressPaint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

View File

@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import '../../../core/app_colors.dart';
class ScooterInfoItem extends StatelessWidget {
final String icon;
final String text;
const ScooterInfoItem({super.key, required this.icon, required this.text});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Image.asset(
icon,
height: 20,
width: 20,
),
const SizedBox(width: 6),
Text(
text,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
),
],
),
);
}
}

View File

@@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
import 'scooter_info_item.dart';
class ScooterInfoSection extends StatelessWidget {
const ScooterInfoSection({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: const [
ScooterInfoItem(icon: 'assets/icons/bolt.png', text: '47 км или 4 часа 17 минут'),
ScooterInfoItem(icon: 'assets/icons/speed.png', text: 'max = 25 км/ч'),
ScooterInfoItem(icon: 'assets/icons/location.png', text: 'пр. Московский, 33'),
Row(
children: [
ScooterInfoItem(icon: 'assets/icons/person.png', text: '120 м'),
SizedBox(width: 16),
ScooterInfoItem(icon: 'assets/icons/time.png', text: '1 минута'),
],
),
],
);
}
}

View File

@@ -0,0 +1,172 @@
import 'package:flutter/material.dart';
class SlideToReserveButton extends StatefulWidget {
final VoidCallback onSlideComplete;
const SlideToReserveButton({
super.key,
required this.onSlideComplete,
});
@override
State<SlideToReserveButton> createState() => _SlideToReserveButtonState();
}
class _SlideToReserveButtonState extends State<SlideToReserveButton>
with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _dragAnimation;
double _dragOffset = 0;
final double _maxDrag = 240; // ширина кнопки - ширина круга
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
_dragAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onDragUpdate(DragUpdateDetails details) {
setState(() {
_dragOffset += details.delta.dx;
if (_dragOffset < 0) _dragOffset = 0;
if (_dragOffset > _maxDrag) _dragOffset = _maxDrag;
});
}
void _onDragEnd(DragEndDetails details) {
if (_dragOffset >= _maxDrag * 0.8) {
_controller.forward();
Future.delayed(const Duration(milliseconds: 300), () {
widget.onSlideComplete();
});
} else {
_controller.reverse();
setState(() {
_dragOffset = 0;
});
}
}
@override
Widget build(BuildContext context) {
final isSlided = _dragOffset >= _maxDrag * 0.8;
final progress = _dragOffset / _maxDrag;
return SizedBox(
height: 64,
child: Stack(
children: [
// Фон кнопки: темный → градиент
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
color: isSlided
? Colors.transparent
: const Color(0xFF141530),
),
),
// Градиентный фон (появляется при свайпе)
if (isSlided)
ShaderMask(
shaderCallback: (Rect bounds) {
return const LinearGradient(
colors: [Color(0xFF67E8F9), Color(0xFF86EFAC)],
).createShader(bounds);
},
blendMode: BlendMode.srcIn,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
color: Colors.white,
),
),
),
// Текст «Забронировать» — виден только в начальном состоянии
if (!isSlided)
Center(
child: Text(
'Забронировать',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
// Три стрелки — справа от текста (только когда текст виден)
if (!isSlided)
Positioned(
right: 40,
child: Row(
children: [
Icon(Icons.arrow_forward_ios, size: 12, color: Colors.white),
Icon(Icons.arrow_forward_ios, size: 12, color: Colors.white.withOpacity(0.6)),
Icon(Icons.arrow_forward_ios, size: 12, color: Colors.white.withOpacity(0.3)),
],
),
),
// Круг с иконкой самоката — двигается слева направо
Transform.translate(
offset: Offset(_dragOffset, 0),
child: SizedBox(
width: 56,
height: 56,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: const Center(
child: Icon(
Icons.electric_scooter,
color: Color(0xFF141530),
size: 24,
),
),
),
),
),
// Иконка самоката справа — появляется при полном свайпе
if (isSlided)
Positioned(
right: 16,
child: SizedBox(
width: 32,
height: 32,
child: Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: const Center(
child: Icon(
Icons.electric_scooter,
color: Colors.white,
size: 16,
),
),
),
),
),
],
),
);
}
}