DEV: Spacing & Widgets
This commit is contained in:
73
lib/Infrastructure/transparent_image.dart
Normal file
73
lib/Infrastructure/transparent_image.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
library transparent_image;
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
final Uint8List kTransparentImage = Uint8List.fromList(<int>[
|
||||
0x89,
|
||||
0x50,
|
||||
0x4E,
|
||||
0x47,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x1A,
|
||||
0x0A,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0D,
|
||||
0x49,
|
||||
0x48,
|
||||
0x44,
|
||||
0x52,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x08,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1F,
|
||||
0x15,
|
||||
0xC4,
|
||||
0x89,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0A,
|
||||
0x49,
|
||||
0x44,
|
||||
0x41,
|
||||
0x54,
|
||||
0x78,
|
||||
0x9C,
|
||||
0x63,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x01,
|
||||
0x0D,
|
||||
0x0A,
|
||||
0x2D,
|
||||
0xB4,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x49,
|
||||
0x45,
|
||||
0x4E,
|
||||
0x44,
|
||||
0xAE,
|
||||
0x42,
|
||||
0x60,
|
||||
0x82,
|
||||
]);
|
||||
@@ -1,7 +1,12 @@
|
||||
library astromic_mobile_elements;
|
||||
|
||||
/// A Calculator.
|
||||
class Calculator {
|
||||
/// Returns [value] plus 1.
|
||||
int addOne(int value) => value + 1;
|
||||
}
|
||||
//
|
||||
export './src/Spacing/spacing.astromic.dart';
|
||||
// export './src/2.Buttons/buttons.astromic.dart';
|
||||
// export './src/3.Selectors/selectors.astromic.dart';
|
||||
// export './src/4.Toggles/toggles.astromic.dart';
|
||||
// export './src/5.Fields/fields.astromic.dart';
|
||||
// export './src/6.Widgets/Widgets.astromic.dart';
|
||||
// //
|
||||
// export './src/styles/styles.astromic.dart';
|
||||
// export './src/configurations/configs.astromic.dart';
|
||||
49
lib/src/Spacing/spacing.astromic.dart
Normal file
49
lib/src/Spacing/spacing.astromic.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AstromicSpacing {
|
||||
//S1 -- SizedBox
|
||||
static Widget vsb([double h = 0]) => SizedBox(height: h);
|
||||
static Widget hsb([double w = 0]) => SizedBox(width: w);
|
||||
|
||||
//s1 --- FLEXER
|
||||
static Widget flexer(int factor, [Widget? child]) => factor >= 0
|
||||
? Expanded(
|
||||
flex: factor == 0 ? 1 : factor,
|
||||
child: child ?? Container(),
|
||||
)
|
||||
: factor == -1 && child != null
|
||||
? Flexible(child: child)
|
||||
: Spacer(flex: factor);
|
||||
|
||||
//S1 --- DIVIDER
|
||||
static divider(
|
||||
double thickness,
|
||||
Color color, {
|
||||
double length = 24.0,
|
||||
double margin = 8.0,
|
||||
Axis mode = Axis.horizontal,
|
||||
bool rounded = true,
|
||||
}) =>
|
||||
SizedBox(
|
||||
height: mode == Axis.horizontal
|
||||
? thickness + (2 * margin)
|
||||
: mode == Axis.vertical
|
||||
? length
|
||||
: 0,
|
||||
width: mode == Axis.vertical
|
||||
? thickness + (2 * margin)
|
||||
: mode == Axis.horizontal
|
||||
? length
|
||||
: 0,
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: mode == Axis.vertical ? margin : 0,
|
||||
vertical: mode == Axis.horizontal ? margin : 0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: rounded ? BorderRadius.circular(1000) : BorderRadius.zero,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
39
lib/src/Widgets/src/blur.widget.dart
Normal file
39
lib/src/Widgets/src/blur.widget.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget astromicBlurEffect(
|
||||
Widget child,
|
||||
double sigmaX,
|
||||
double sigmaY, {
|
||||
Widget? topChild,
|
||||
Color? overlayColor = const Color.fromRGBO(0, 0, 0, 0.1),
|
||||
TileMode? tileMode = TileMode.mirror,
|
||||
List<BoxShadow>? shadow,
|
||||
BorderRadius? radius,
|
||||
}) =>
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: shadow,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: radius ?? BorderRadius.zero,
|
||||
child: Stack(
|
||||
children: [
|
||||
child,
|
||||
BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 5.0,
|
||||
sigmaY: 5.0,
|
||||
tileMode: TileMode.mirror,
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: overlayColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
topChild ?? Container(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
217
lib/src/Widgets/src/image.widget.dart
Normal file
217
lib/src/Widgets/src/image.widget.dart
Normal file
@@ -0,0 +1,217 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:octo_image/octo_image.dart';
|
||||
|
||||
import '../../../Infrastructure/transparent_image.dart';
|
||||
|
||||
Widget astromicImage(
|
||||
BuildContext context, {
|
||||
//S1 -- Asset
|
||||
String? assetPath,
|
||||
String? assetURL,
|
||||
Uint8List? assetBytes,
|
||||
String? assetFallback,
|
||||
//
|
||||
//S1 -- Sizing | Width
|
||||
double? wFactor, // variable width ratio
|
||||
double? minW, // Min width allowed
|
||||
double? maxW, // Max width allowed
|
||||
double? fixedWidth, // Only used when uisng height variables
|
||||
//S1 -- Sizing | Width
|
||||
bool useHeight = false, // use height constrains
|
||||
double? hFactor, // Variable height ratio
|
||||
double? minH, // Min Height allowed
|
||||
double? maxH, // Max height allowed
|
||||
double? fixedHeight, // Only used when using width variables
|
||||
//S1 -- STYLING
|
||||
bool circular = false,
|
||||
double? border,
|
||||
Color? borderColor,
|
||||
EdgeInsetsGeometry? borderPadding,
|
||||
BorderRadiusGeometry radius = BorderRadius.zero,
|
||||
List<BoxShadow>? shadow,
|
||||
//S1 -- SVG FILTERS
|
||||
Color? color,
|
||||
BlendMode? blend,
|
||||
//S1 -- CONFIGS
|
||||
BoxFit fit = BoxFit.cover,
|
||||
Alignment alignment = Alignment.center,
|
||||
LinearGradient? linearGradient,
|
||||
//S1 -- STATE
|
||||
Widget? loadingWidget,
|
||||
Widget? errorWidget,
|
||||
}) {
|
||||
//
|
||||
assert(minW == null || maxW == null, "Please specify only one width constrain");
|
||||
assert(minH == null || maxH == null, "Please specify only one height constrain");
|
||||
assert(!useHeight || (hFactor != null), "Please specify The height factor and constrains");
|
||||
assert(
|
||||
(assetPath != null && assetBytes == null && assetURL == null) ||
|
||||
(assetPath == null && assetBytes != null && assetURL == null) ||
|
||||
(assetPath == null && assetBytes == null && assetURL != null) ||
|
||||
(assetFallback != null && assetFallback != ''),
|
||||
"Please specify only one Asset Source");
|
||||
//
|
||||
//
|
||||
bool fromPath = (assetPath != null && assetPath != '');
|
||||
bool fromBytes = (assetBytes != null && assetBytes.isNotEmpty);
|
||||
bool fromNetwork = (assetURL != null && assetURL != '' && Uri.tryParse(assetURL) != null);
|
||||
//
|
||||
String finalAssetKey = fromPath
|
||||
? assetPath
|
||||
: fromNetwork
|
||||
? assetURL
|
||||
: fromBytes
|
||||
? assetBytes.length.toString()
|
||||
: 'N/A';
|
||||
//
|
||||
bool isFallback = (assetBytes == null && assetURL == null && assetPath == null && assetFallback != null);
|
||||
bool isSVG = fromPath
|
||||
? assetPath.endsWith('.svg')
|
||||
: fromNetwork
|
||||
? assetURL.endsWith('.svg')
|
||||
: isFallback
|
||||
? assetFallback.endsWith('.svg')
|
||||
: false;
|
||||
//
|
||||
double h = MediaQuery.of(context).size.height;
|
||||
double? fh = hFactor != null ? hFactor * h : null;
|
||||
double w = MediaQuery.of(context).size.width;
|
||||
double? fw = wFactor != null ? wFactor * w : null;
|
||||
|
||||
//
|
||||
double? finalW = useHeight
|
||||
? fixedWidth
|
||||
: fw != null
|
||||
? fw > (maxW ?? double.infinity)
|
||||
? maxW
|
||||
: fw < (minW ?? 0)
|
||||
? minW
|
||||
: fw
|
||||
: null;
|
||||
double? finalH = !useHeight
|
||||
? fixedHeight
|
||||
: fh != null
|
||||
? fh > (maxH ?? double.infinity)
|
||||
? maxH
|
||||
: fh < (minH ?? 0)
|
||||
? minH
|
||||
: fh
|
||||
: null;
|
||||
//
|
||||
Widget finalError = errorWidget ??
|
||||
const Center(
|
||||
child: Text("Error has happened.."),
|
||||
);
|
||||
//
|
||||
Widget finalLoading = loadingWidget ?? Container();
|
||||
//
|
||||
ImageProvider finalImageProvider = isFallback
|
||||
? AssetImage(
|
||||
assetFallback,
|
||||
)
|
||||
: fromPath
|
||||
? AssetImage(
|
||||
assetPath,
|
||||
)
|
||||
: fromBytes
|
||||
? MemoryImage(assetBytes)
|
||||
: fromNetwork
|
||||
? CachedNetworkImageProvider(
|
||||
assetURL,
|
||||
cacheKey: assetURL,
|
||||
)
|
||||
: MemoryImage(kTransparentImage) as ImageProvider;
|
||||
//
|
||||
Widget? finalSVGWidget = fromPath
|
||||
? SvgPicture.asset(
|
||||
assetPath,
|
||||
key: ValueKey(assetPath),
|
||||
width: finalW,
|
||||
height: finalH,
|
||||
fit: fit,
|
||||
placeholderBuilder: (_) => SizedBox(
|
||||
width: finalW,
|
||||
height: finalH,
|
||||
child: finalLoading,
|
||||
),
|
||||
alignment: alignment,
|
||||
colorFilter: color != null ? ColorFilter.mode(color, blend ?? BlendMode.srcATop) : null,
|
||||
)
|
||||
: fromNetwork
|
||||
? SvgPicture.network(
|
||||
assetURL,
|
||||
key: ValueKey(assetURL),
|
||||
width: finalW,
|
||||
height: finalH,
|
||||
fit: fit,
|
||||
placeholderBuilder: (_) => SizedBox(
|
||||
width: finalW,
|
||||
height: finalH,
|
||||
child: finalLoading,
|
||||
),
|
||||
alignment: alignment,
|
||||
colorFilter: color != null ? ColorFilter.mode(color, blend ?? BlendMode.srcATop) : null,
|
||||
)
|
||||
: null;
|
||||
//
|
||||
return SizedBox(
|
||||
width: finalW,
|
||||
height: finalH,
|
||||
child: Stack(
|
||||
children: [
|
||||
OctoImage(
|
||||
key: ValueKey(finalAssetKey),
|
||||
//
|
||||
width: finalW,
|
||||
height: finalH,
|
||||
fit: fit,
|
||||
alignment: alignment,
|
||||
filterQuality: FilterQuality.none,
|
||||
color: color,
|
||||
colorBlendMode: blend ?? (isSVG ? BlendMode.srcATop : null),
|
||||
//
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return finalError;
|
||||
},
|
||||
//
|
||||
progressIndicatorBuilder: (a, b) => SizedBox(
|
||||
width: finalW,
|
||||
height: finalH,
|
||||
child: finalLoading,
|
||||
),
|
||||
|
||||
imageBuilder: (context, image) => Container(
|
||||
width: finalW,
|
||||
height: finalH,
|
||||
padding: borderPadding ?? EdgeInsets.zero,
|
||||
margin: EdgeInsets.zero,
|
||||
decoration: BoxDecoration(
|
||||
border: border != null
|
||||
? Border.all(
|
||||
strokeAlign: BorderSide.strokeAlignInside,
|
||||
width: border,
|
||||
color: borderColor ?? const Color(0xff000000),
|
||||
)
|
||||
: null,
|
||||
borderRadius: circular ? BorderRadius.circular(10000) : radius,
|
||||
boxShadow: shadow,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: circular ? BorderRadius.circular(10000) : radius,
|
||||
child: isSVG ? finalSVGWidget : image,
|
||||
),
|
||||
),
|
||||
image: isSVG ? MemoryImage(kTransparentImage) : finalImageProvider,
|
||||
),
|
||||
if (linearGradient != null)
|
||||
Container(
|
||||
decoration: BoxDecoration(gradient: linearGradient),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
76
lib/src/Widgets/src/scaffold.widget.dart
Normal file
76
lib/src/Widgets/src/scaffold.widget.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
//
|
||||
Widget astromicScaffold({
|
||||
//S1 -- AppBar
|
||||
PreferredSizeWidget? appBar,
|
||||
bool isAppbarStacked = false,
|
||||
//S1 -- FAB
|
||||
Widget? floatingActionButton,
|
||||
FloatingActionButtonLocation? floatingActionButtonLocation,
|
||||
//S1 -- Other Integral Components
|
||||
Widget? drawer,
|
||||
Widget? bottomNavigationBar,
|
||||
Widget? bottomSheet,
|
||||
//S1 -- Styling
|
||||
Color? backgroundColor,
|
||||
EdgeInsetsGeometry? padding,
|
||||
ScrollPhysics? scrollPhysics = const BouncingScrollPhysics(),
|
||||
//S1 -- Configs
|
||||
bool withSafeArea = false,
|
||||
bool extendBody = false,
|
||||
bool resizeToAvoidBottomInset = true,
|
||||
bool withScrollView = false,
|
||||
bool closeKeyboardOnTap = true,
|
||||
//
|
||||
Widget? body,
|
||||
//
|
||||
}) {
|
||||
assert(!withScrollView || (withScrollView && body is Column), 'Make sure you add a column in the body section!');
|
||||
//
|
||||
return Scaffold(
|
||||
backgroundColor: backgroundColor,
|
||||
//
|
||||
appBar: isAppbarStacked ? null : appBar,
|
||||
floatingActionButton: floatingActionButton,
|
||||
floatingActionButtonLocation: floatingActionButtonLocation,
|
||||
drawer: drawer,
|
||||
bottomNavigationBar: bottomNavigationBar,
|
||||
bottomSheet: bottomSheet,
|
||||
//
|
||||
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
|
||||
extendBody: extendBody,
|
||||
body: Stack(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: !withScrollView && closeKeyboardOnTap ? () => FocusManager.instance.primaryFocus?.unfocus() : null,
|
||||
child: withSafeArea
|
||||
? SafeArea(
|
||||
child: withScrollView
|
||||
? SingleChildScrollView(
|
||||
keyboardDismissBehavior: closeKeyboardOnTap ? ScrollViewKeyboardDismissBehavior.onDrag : ScrollViewKeyboardDismissBehavior.manual,
|
||||
physics: scrollPhysics,
|
||||
padding: padding ?? EdgeInsets.zero,
|
||||
child: body,
|
||||
)
|
||||
: Padding(
|
||||
padding: padding ?? EdgeInsets.zero,
|
||||
child: body,
|
||||
),
|
||||
)
|
||||
: withScrollView
|
||||
? SingleChildScrollView(
|
||||
keyboardDismissBehavior: closeKeyboardOnTap ? ScrollViewKeyboardDismissBehavior.onDrag : ScrollViewKeyboardDismissBehavior.manual,
|
||||
padding: padding ?? EdgeInsets.zero,
|
||||
child: body,
|
||||
)
|
||||
: Padding(
|
||||
padding: padding ?? EdgeInsets.zero,
|
||||
child: body,
|
||||
),
|
||||
),
|
||||
isAppbarStacked && appBar != null ? Wrap(children: [appBar]) : Container(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
157
lib/src/Widgets/widgets.astromic.dart
Normal file
157
lib/src/Widgets/widgets.astromic.dart
Normal file
@@ -0,0 +1,157 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
//s2 Core Packages Imports
|
||||
import 'package:astromic_mobile_elements/src/Widgets/src/blur.widget.dart';
|
||||
import 'package:astromic_mobile_elements/src/Widgets/src/scaffold.widget.dart';
|
||||
import 'src/image.widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AstromicWidgets {
|
||||
//S1 -- SCAFFOLD
|
||||
static Widget scaffold({
|
||||
//S1 -- AppBar
|
||||
PreferredSizeWidget? appBar,
|
||||
bool isAppbarStacked = false,
|
||||
//S1 -- FAB
|
||||
Widget? floatingActionButton,
|
||||
FloatingActionButtonLocation? floatingActionButtonLocation,
|
||||
//S1 -- Other Integral Components
|
||||
Widget? drawer,
|
||||
Widget? bottomNavigationBar,
|
||||
Widget? bottomSheet,
|
||||
//S1 -- Styling
|
||||
Color? backgroundColor,
|
||||
EdgeInsetsGeometry? padding,
|
||||
ScrollPhysics? scrollPhysics = const BouncingScrollPhysics(),
|
||||
//S1 -- Configs
|
||||
bool withSafeArea = false,
|
||||
bool extendBody = false,
|
||||
bool resizeToAvoidBottomInset = true,
|
||||
bool withScrollView = false,
|
||||
bool closeKeyboardOnTap = true,
|
||||
//
|
||||
Widget? body,
|
||||
//
|
||||
}) =>
|
||||
astromicScaffold(
|
||||
//
|
||||
appBar: appBar,
|
||||
isAppbarStacked: isAppbarStacked,
|
||||
//
|
||||
floatingActionButton: floatingActionButton,
|
||||
floatingActionButtonLocation: floatingActionButtonLocation,
|
||||
//
|
||||
drawer: drawer,
|
||||
bottomNavigationBar: bottomNavigationBar,
|
||||
bottomSheet: bottomSheet,
|
||||
//
|
||||
backgroundColor: backgroundColor,
|
||||
padding: padding,
|
||||
scrollPhysics: scrollPhysics,
|
||||
//
|
||||
withSafeArea: withSafeArea,
|
||||
extendBody: extendBody,
|
||||
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
|
||||
withScrollView: withScrollView,
|
||||
closeKeyboardOnTap: closeKeyboardOnTap,
|
||||
//
|
||||
body: body,
|
||||
);
|
||||
|
||||
//S1 -- IMAGE
|
||||
static Widget image(
|
||||
BuildContext context, {
|
||||
//S1 -- Asset
|
||||
String? assetPath,
|
||||
String? assetURL,
|
||||
Uint8List? assetBytes,
|
||||
String? assetFallback,
|
||||
//S1 -- Sizing | Width
|
||||
double? wFactor,
|
||||
double? minW,
|
||||
double? maxW,
|
||||
double? fixedWidth,
|
||||
//S1 -- Sizing | Width
|
||||
bool useHeight = false,
|
||||
double? hFactor,
|
||||
double? minH,
|
||||
double? maxH,
|
||||
double? fixedHeight,
|
||||
//S1 -- STYLING
|
||||
bool circular = false,
|
||||
double? border,
|
||||
Color? borderColor,
|
||||
EdgeInsetsGeometry? borderPadding,
|
||||
BorderRadiusGeometry radius = BorderRadius.zero,
|
||||
List<BoxShadow>? shadow,
|
||||
//S1 -- SVG FILTERS
|
||||
Color? color,
|
||||
BlendMode? blend,
|
||||
//S1 -- CONFIGS
|
||||
BoxFit fit = BoxFit.cover,
|
||||
Alignment alignment = Alignment.center,
|
||||
LinearGradient? linearGradient,
|
||||
//S1 -- STATE
|
||||
Widget? loadingWidget,
|
||||
Widget? errorWidget,
|
||||
}) =>
|
||||
astromicImage(
|
||||
context,
|
||||
//
|
||||
assetPath: assetPath,
|
||||
assetURL: assetURL,
|
||||
assetBytes: assetBytes,
|
||||
assetFallback: assetFallback,
|
||||
//
|
||||
wFactor: wFactor,
|
||||
minW: minW,
|
||||
maxW: maxW,
|
||||
fixedWidth: fixedWidth,
|
||||
//
|
||||
useHeight: useHeight,
|
||||
hFactor: hFactor,
|
||||
minH: minH,
|
||||
maxH: maxH,
|
||||
fixedHeight: fixedHeight,
|
||||
//
|
||||
circular: circular,
|
||||
border: border,
|
||||
borderColor: borderColor,
|
||||
borderPadding: borderPadding,
|
||||
radius: radius,
|
||||
shadow: shadow,
|
||||
//
|
||||
color: color,
|
||||
blend: blend,
|
||||
//
|
||||
fit: fit,
|
||||
alignment: alignment,
|
||||
linearGradient: linearGradient,
|
||||
//
|
||||
loadingWidget: loadingWidget,
|
||||
errorWidget: errorWidget,
|
||||
);
|
||||
|
||||
//S1 -- BLUR
|
||||
static Widget blur(
|
||||
Widget child,
|
||||
double sigmaX,
|
||||
double sigmaY, {
|
||||
Widget? topChild,
|
||||
Color? overlayColor = const Color.fromRGBO(0, 0, 0, 0.1),
|
||||
TileMode? tileMode = TileMode.mirror,
|
||||
List<BoxShadow>? shadow,
|
||||
BorderRadius? radius,
|
||||
}) =>
|
||||
astromicBlurEffect(
|
||||
child,
|
||||
sigmaX,
|
||||
sigmaY,
|
||||
topChild: topChild,
|
||||
overlayColor: overlayColor,
|
||||
tileMode: tileMode,
|
||||
shadow: shadow,
|
||||
radius: radius,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user