techbusiness
techbusiness

Reputation: 33

How to make this widget in flutter?

Here is the image i am trying to build. Tried Stack with Alignment.topRight but i need to build a TextField after the IconButton that's the bottleneck. Little details: The grey box is the input field, which i will append some static text at start, as you can see in the pic below. Static Text = Happy Birthday, then the button with cross icon and after that a TextField as shown in the design. I tried different approaches but not coming-up with accurate results. enter image description here

  Row(
  children: [
    Container(
      margin: const EdgeInsets.only(top: 5, left: 5),
      padding: const EdgeInsets.only(top: 10, left: 10, bottom: 10),
      decoration:
          BoxDecoration(color: Constants.colorWhite.withOpacity(0.90)),
      child: Text(Constants.happyBirthday),
    ),
    Container(
        margin: const EdgeInsets.only(top: 5, right: 0),
        padding:
            const EdgeInsets.only(left: 5, top: 10, right: 0, bottom: 10),
        decoration:
            BoxDecoration(color: Constants.colorWhite.withOpacity(0.50)),
        child: Stack(
          children: [
            Text(widget.userName.toString()),
            const Positioned(top: 0, right: 0, child: Icon(Icons.close))
          ],
        )),
    Container(
      margin: const EdgeInsets.only(top: 5, left: 0),
      padding: const EdgeInsets.only(top: 10, left: 10, bottom: 10),
      decoration:
          BoxDecoration(color: Constants.colorWhite.withOpacity(0.90)),
      child: Text('${Constants.happyBirthday} '
          '${Constants.happyBirthday}'
          '${Constants.happyBirthday}'),
    ),
  ],
)

Upvotes: 0

Views: 145

Answers (1)

rickimaru
rickimaru

Reputation: 2490

You can create your own TextEditingController and override buildTextSpan.

enter image description here

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

/// @techbusiness
///
/// NOTE: Needs to be improved. Feel free to play with the styling 
///       to get your desired result. This is just to show you the
///       possibility of your design.
class TaggerTextEditingController extends TextEditingController {
  TaggerTextEditingController({
    this.onPressTag,
    this.onDeleteTag,
    this.tagger = '@',
    this.tagStyle,
    this.tagBackgroundColor,
    this.deleteIconSize = 15,
    super.text,
  });

  final ValueChanged<String>? onPressTag;
  final ValueChanged<String>? onDeleteTag;
  final String tagger;
  final TextStyle? tagStyle;
  final Color? tagBackgroundColor;
  final double deleteIconSize;

  void _onDeleteTag(String tag) {
    text = text.replaceFirst(tag, '');
    onDeleteTag?.call(tag.replaceFirst(tagger, ''));
  }

  WidgetSpan _buildTag(String tag, TextStyle? style) => WidgetSpan(
        alignment: PlaceholderAlignment.middle,
        child: Stack(
          clipBehavior: Clip.none,
          children: <Widget>[
            InkWell(
              onTap: () => onPressTag?.call(tag.replaceFirst(tagger, '')),
              borderRadius: const BorderRadius.all(Radius.circular(15)),
              child: Container(
                padding: const EdgeInsets.all(10),
                decoration: BoxDecoration(
                  borderRadius: const BorderRadius.all(Radius.circular(15)),
                  color: tagBackgroundColor ?? Colors.grey.shade200,
                ),
                child: Text(
                  tag.replaceFirst(tagger, ''),
                  style: style,
                ),
              ),
            ),
            Positioned(
              top: -deleteIconSize / 4,
              right: -deleteIconSize / 4,
              child: GestureDetector(
                onTap: () => _onDeleteTag(tag),
                child: Icon(
                  CupertinoIcons.xmark_circle_fill,
                  size: deleteIconSize,
                  color: Colors.red,
                ),
              ),
            ),
          ],
        ),
      );

  @override
  TextSpan buildTextSpan({
    required BuildContext context,
    TextStyle? style,
    required bool withComposing,
  }) {
    final List<TaggerText> texts = TaggerText.getTags(text, tagger);

    return TextSpan(
      children: texts
          .map(
            (TaggerText value) => value.isTag
                ? _buildTag(value.text, tagStyle ?? style)
                : TextSpan(
                    text: value.text,
                    style: style,
                  ),
          )
          .toList(),
    );
  }
}

//------------------------------------------------------------------------------
class TaggerText {
  const TaggerText(this.text, this.isTag);

  final String text;
  final bool isTag;

  static List<TaggerText> getTags(String text, String tagger) {
    final List<TaggerText> tags = <TaggerText>[];

    StringBuffer textPortion = StringBuffer();
    String prevChar = '';

    bool isTextPortionATag() => textPortion.toString().startsWith(tagger);

    void addTaggerText() => tags.add(
          TaggerText(textPortion.toString(), isTextPortionATag()),
        );

    for (final String char in text.characters) {
      if (char == tagger && prevChar == ' ') {
        addTaggerText();
        textPortion.clear();
      } else if (char == ' ' && isTextPortionATag()) {
        addTaggerText();
        textPortion.clear();
      }

      textPortion.write(char);
      prevChar = char;
    }

    if (textPortion.isNotEmpty) {
      addTaggerText();
    }

    return tags;
  }
}

How to use.

TextField(
  controller: TaggerTextEditingController(),
  maxLines: 5,
),

Upvotes: 1

Related Questions