first commit

This commit is contained in:
2025-03-03 20:59:42 +03:00
commit 273e68557a
1099 changed files with 17880 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
// Flutter imports:
import 'package:flutter/material.dart';
class BottomSafeSpace extends StatelessWidget {
///
/// Отступ от нижней границы экрана
///
/// Для iOS значение будет не нулевое если есть "полоска"
/// Для Android в основном будет 0
///
const BottomSafeSpace({
super.key,
});
@override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).padding.bottom,
);
}
}

View File

@@ -0,0 +1,24 @@
// Flutter imports:
import 'package:flutter/material.dart';
class LooseFocus extends StatelessWidget {
///
/// Теряет фокус если тапнули по пустой области
///
const LooseFocus({
required this.child,
super.key,
});
/// Потомок
final Widget child;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => FocusScope.of(context).requestFocus(FocusNode()),
child: child,
);
}
}

View File

@@ -0,0 +1,68 @@
// Flutter imports:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
///
/// Направления свайпов
///
enum SwipeDirection {
///
/// Свайп вниз
///
DOWN,
///
/// Свайп вверх
///
UP,
}
class SwipeGesture extends StatelessWidget {
///
/// Отслеживание свайпа по виджету
///
const SwipeGesture({
required this.swipe,
required this.onSwipe,
required this.child,
super.key,
});
/// Направление движения
final SwipeDirection swipe;
/// Обработка свайпа
final VoidCallback onSwipe;
/// Потомок
final Widget child;
void _onVerticalDragUpdate(DragUpdateDetails e) {
const int sensitivity = 3;
switch (swipe) {
case SwipeDirection.DOWN:
if (e.delta.dy > sensitivity) {
onSwipe();
}
break;
case SwipeDirection.UP:
if (e.delta.dy < sensitivity) {
onSwipe();
}
break;
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
dragStartBehavior: DragStartBehavior.start,
behavior: HitTestBehavior.opaque,
onVerticalDragUpdate: _onVerticalDragUpdate,
child: child,
);
}
}

View File

@@ -0,0 +1,256 @@
// Flutter imports:
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
// Package imports:
import 'package:google_fonts/google_fonts.dart';
export '../../common/typography.dart';
abstract class TypographyType {
///
/// Размер шрифта
///
double get size;
///
/// Получение [TextStyle] для типа шрифта
///
TextStyle get style;
}
abstract class TypographyTypeRegular extends TypographyType {
///
/// Высота линии
///
double get height => 1.21;
@override
TextStyle get style {
return GoogleFonts.inter(
fontWeight: FontWeight.w400,
fontSize: size.sp,
height: height,
);
}
}
abstract class TypographyTypeMedium extends TypographyType {
///
/// Высота линии
///
double get height => 1.15;
@override
TextStyle get style {
return GoogleFonts.inter(
fontWeight: FontWeight.w500,
fontSize: size.sp,
height: height,
);
}
}
abstract class TypographyTypeSemiBold extends TypographyType {
///
/// Высота линии
///
double get height => 1.21;
@override
TextStyle get style {
return GoogleFonts.inter(
fontWeight: FontWeight.w600,
fontSize: size.sp,
height: height,
);
}
}
abstract class TypographyTypeBold extends TypographyType {
///
/// Высота линии
///
double get height => 1.21;
@override
TextStyle get style {
return GoogleFonts.inter(
fontWeight: FontWeight.w700,
fontSize: size.sp,
height: height,
);
}
}
abstract class TypographyTypeHeadBold extends TypographyType {
///
/// Высота линии
///
double get height => 1.36;
@override
TextStyle get style {
return GoogleFonts.inter(
fontWeight: FontWeight.w700,
fontSize: size.sp,
height: height,
);
}
}
///
/// Дополнительные возможности [AppTypography]
///
enum TypographyFeature {
///
/// Использование [Text]
///
DEFAULT,
///
/// Использование [RichText]
///
RICH,
///
/// Использование [SelectableText]
///
SELECTABLE,
}
///
/// Виджет для отображения текста в приложении
///
/// Тесно использует [TypographyType]
/// На его основе выбирается стиль текста
///
/// Важно: планшетная верстка не учитывается в стилях - меняется сам стиль,
/// поэтому контролировать нужно извне
///
class AppTypography extends StatelessWidget {
const AppTypography(
this.text, {
this.type,
this.color,
this.maxLines = 1,
this.textAlign = TextAlign.left,
this.fontWeight,
this.overflow = TextOverflow.ellipsis,
this.height,
this.softWrap,
this.textDecoration = TextDecoration.none,
super.key,
}) : typographyFeature = TypographyFeature.DEFAULT,
children = null;
const AppTypography.rich(
this.text, {
this.type,
this.color,
this.maxLines,
this.textAlign = TextAlign.left,
this.fontWeight,
this.overflow,
this.height,
this.softWrap,
this.textDecoration = TextDecoration.none,
this.children,
super.key,
}) : typographyFeature = TypographyFeature.RICH;
const AppTypography.selectable(
this.text, {
this.type,
this.color,
this.maxLines,
this.textAlign = TextAlign.left,
this.fontWeight,
this.overflow,
this.height,
this.softWrap,
this.textDecoration = TextDecoration.none,
super.key,
}) : typographyFeature = TypographyFeature.SELECTABLE,
children = null;
/// Текст
final String text;
/// Стиль текста
final TypographyType? type;
/// Цвет текста
final Color? color;
/// Кол-во линий
final int? maxLines;
/// Направление текста
final TextAlign textAlign;
/// Жирность шрифта
final FontWeight? fontWeight;
/// Overflow
final TextOverflow? overflow;
/// Межстрочный интервал
final double? height;
/// Мягкий перенос
final bool? softWrap;
/// Декорация текста
final TextDecoration textDecoration;
/// Использование фич типографии
final TypographyFeature typographyFeature;
/// Список [TextSpan]
final List<InlineSpan>? children;
@override
Widget build(BuildContext context) {
final DefaultTextStyle textStyle = DefaultTextStyle.of(context);
final TextStyle computeTextStyle =
type == null
? textStyle.style
: type!.style.copyWith(
color: color ?? textStyle.style.color,
fontWeight: fontWeight,
height: height,
decoration: textDecoration,
);
switch (typographyFeature) {
case TypographyFeature.DEFAULT:
return Text(
text,
maxLines: maxLines,
textAlign: textAlign,
overflow: overflow,
softWrap: softWrap,
style: computeTextStyle,
);
case TypographyFeature.RICH:
return RichText(
text: TextSpan(
text: text,
children: children,
style: computeTextStyle,
),
);
case TypographyFeature.SELECTABLE:
return SelectableText(
text,
maxLines: maxLines,
textAlign: textAlign,
style: computeTextStyle,
);
}
}
}

View File

@@ -0,0 +1,15 @@
// Flutter imports:
import 'package:flutter/material.dart';
class TypographySpan extends TextSpan {
///
/// Упрощенный вариант [TextSpan]
///
const TypographySpan(
this.text, {
super.style,
});
// ignore: annotate_overrides, overridden_fields
final String text;
}

View File

@@ -0,0 +1,35 @@
// Flutter imports:
import 'package:flutter/material.dart';
///
/// Виджет условной отрисовки
///
class Wif extends StatelessWidget {
/// Условие по которому будет происходить отрисовка
final bool condition;
/// Построение содержимого
final WidgetBuilder builder;
/// Виджет если условие не удовлетворительно
final WidgetBuilder? fallback;
///
/// Виджет условной отрисовки
///
const Wif({
required this.condition,
required this.builder,
this.fallback,
super.key,
});
@override
Widget build(BuildContext context) {
return condition
? builder(context)
: fallback != null
? fallback!(context)
: const Offstage();
}
}

View File

@@ -0,0 +1,61 @@
// Flutter imports:
import 'package:flutter/widgets.dart';
import 'package:remever/components/extensions/context.dart';
///
/// Разделитель между виджетами
///
class HSpace extends StatelessWidget {
/// Высота разделителя
final double height;
/// Флаг что необходимо использовать [SliverToBoxAdapter]
final bool useSliver;
///
/// Разделитель между виджетами
///
const HSpace(this.height, {super.key}) : useSliver = false;
const HSpace.sliver(this.height, {super.key}) : useSliver = true;
@override
Widget build(BuildContext context) {
Widget child = SizedBox(height: height.h);
if (useSliver) {
child = SliverToBoxAdapter(child: child);
}
return child;
}
}
///
/// Разделитель между виджетами
///
class WSpace extends StatelessWidget {
/// Ширина разделителя
final double width;
/// Флаг что необходимо использовать [SliverToBoxAdapter]
final bool useSliver;
///
/// Разделитель между виджетами
///
const WSpace(this.width, {super.key}) : useSliver = false;
const WSpace.sliver(this.width, {super.key}) : useSliver = true;
@override
Widget build(BuildContext context) {
Widget child = SizedBox(width: width.w);
if (useSliver) {
child = SliverToBoxAdapter(child: child);
}
return child;
}
}