dreampowder / flutter_social_textfield Goto Github PK
View Code? Open in Web Editor NEWA TextEditingController for providing common social media text contents.
License: BSD 3-Clause "New" or "Revised" License
A TextEditingController for providing common social media text contents.
License: BSD 3-Clause "New" or "Revised" License
There is not way to scroll text for SocialTextEditingController like for normal ScrollController we call controller.jumpTo()
Hi, this lib is useful.
I want customized the link pattern, is there a way to do it?
am using this logic to buld spans:
class WidgetSpanTextEditingController extends TextEditingController {
WidgetSpanTextEditingController({String? text})
: super.fromValue(text == null
? TextEditingValue.empty
: TextEditingValue(text: text));
@override
TextSpan buildTextSpan(
{required BuildContext context,
TextStyle? style,
required bool withComposing}) {
TextRange? matchedRange;
TextRange? matchedRange2;
if (text.contains('$SPLIT_CMD_START') && text.contains('$SPLIT_CMD_END')) {
matchedRange = _findMatchedRange(text);
matchedRange2 = _findMatchedRange(text, trimTag: false);
}
if (matchedRange != null) {
return TextSpan(
children: [
TextSpan(text: matchedRange.textBefore(text)),
WidgetSpan(
child: GestureDetector(
child: Padding(
padding: const EdgeInsets.only(
right: 5.0, top: 2.0, bottom: 2.0),
child: ClipRRect(
borderRadius:
const BorderRadius.all(Radius.circular(5.0)),
child: Container(
padding: const EdgeInsets.all(5.0),
color: Colors.orange,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(matchedRange2!
.textInside(text)
.trim()
.split(SPLIT_CMD_MID)[0]
//style: textStyle?.copyWith(color: Colors.orange),
),
const SizedBox(
width: 5.0,
),
InkWell(
child: const Icon(
Icons.close,
size: 15.0,
),
onTap: () {
// controller!.value = controller!.value
// .copyWith(
// text: controller!.text.replaceRange(
// start!, start! + text.length, ''),
// selection: TextSelection.fromPosition(
// TextPosition(offset: start!)));
clearMatchedText(matchedRange, text);
},
)
],
),
)),
),
onTap: () {})),
TextSpan(text: matchedRange.textAfter(text)),
],
style: style,
);
}
return TextSpan(text: text, style: style);
}
String clearMatchedText(TextRange? range, String text) {
if (range != null) if (range.start >= 0 && range.end <= text.length) {
text = text.replaceRange(range.start, range.end, '');
return text;
}
return text;
}
// TextRange _findMatchedRange(String text) {
// final RegExp matchPattern = RegExp(RegExp.escape('\uffff'));
// late TextRange matchedRange;
// for (final Match match in matchPattern.allMatches(text)) {
// matchedRange = TextRange(start: match.start, end: match.end);
// }
// return matchedRange;
// }
TextRange _findMatchedRange(String text, {bool trimTag = true}) {
final RegExp matchPattern = RegExp(r'<cmd>(.*?)<\/cmd>');
late TextRange matchedRange;
for (final Match match in matchPattern.allMatches(text)) {
int start = match.start;
if (!trimTag) start += '$SPLIT_CMD_START'.length;
int end = match.end;
if (!trimTag) end -= '$SPLIT_CMD_END'.length;
matchedRange = TextRange(start: start, end: end);
}
return matchedRange;
}
}
but question is when deleting, it is not delete whole span, but character by character.
When texting emojis, and you introduce a skin colour emoji, the emoji after it will be the same as first, and sometimes breaks the TextField State.
Any thoughts on this?
the method .subscribeToDetection is not there here is some of my code-
void initState() { _subCommentController = SocialTextEditingController()..setTextStyle(DetectedType.mention, TextStyle(color: Colors.purple, backgroundColor: Colors.purple.withAlpha(50))); _streamSubscription = _subCommentController.subscribeToDetection(func); super.initState(); }
and also how do i add suggestions when someone types like it comes in the example gif
@dreampowder thanks, you fixed it, but when I using
textController.replaceRange(text, lastDetection.range);
replace text to mention or hashtag, the cursor goes back to start (only the first time)
Originally posted by @minh-dai in #7 (comment)
The cursor goes back to the beginning though when you type it works normal. just the cursor seems to be at the starting point.
[✓] Flutter (Channel stable, 2.10.1, on macOS 12.1 21C52 darwin-arm, locale
Video:
Hello!
Thank you for this awesome package.
I was wondering if the support for custom types would be added. Right now I'm using all 3 customizable DetectionTypes and may need more in the future. Is there a way to do that, please?
I see you fixed it, but it doesn't work
we are trying to show text into list view in which initially we have to show 2 lines only into cell later in detail screen we have to show entire text.
\u0600-\u06FF
Hey can you please remove this print call?
print("will add space");
Thanks
Here https://bhupesh-v.github.io/ is a sample URL that is not being detected
Seems like the regexContent is missing "-" as valid char
const urlRegexContent = "((http|https)://)(www.)?" +
"[a-zA-Z0-9@:%._\\+~#?&//=]" +
"{2,256}\\.[a-z]" +
"{2,6}\\b([-a-zA-Z0-9@:%" +
"._\\+~#?&//=]*)";
Here's a draft solution
void main() {
const oldurlRegexContent = "((http|https)://)(www.)?" +
"[a-zA-Z0-9@:%._\\+~#?&//=]" +
"{2,256}\\.[a-z]" +
"{2,6}\\b([-a-zA-Z0-9@:%" +
"._\\+~#?&//=]*)";
// notice the extra "-"
const newurlRegexContent = "((http|https)://)(www.)?" +
"[a-zA-Z0-9@:%._\\+~#?&//=-]" +
"{2,256}\\.[a-z]" +
"{2,6}\\b([-a-zA-Z0-9@:%" +
"._\\+~#?&//=]*)";
var f1 = RegExp(oldurlRegexContent);
var f2 = RegExp(newurlRegexContent);
print(f1.stringMatch("https://bhupesh-v.github.io/"));
// null
print(f2.stringMatch("https://bhupesh-v.github.io/"));
// https://bhupesh-v.github.io/
}
this is the reply textfield
replyField({required this.commentID, required this.docName, Key? key})
: super(key: key);
final String commentID;
final String docName;
@override
_replyFieldState createState() => _replyFieldState();
}
class _replyFieldState extends State<replyField> {
late final CollectionReference users =
FirebaseFirestore.instance.collection('Users');
late final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
late final FirebaseStorage storage = FirebaseStorage.instance;
late final CollectionReference posts =
FirebaseFirestore.instance.collection('Posts');
late StreamSubscription<SocialContentDetection> _streamSubscription;
SocialTextEditingController _subCommentController =
SocialTextEditingController()
..setTextStyle(
DetectedType.mention,
TextStyle(
color: Colors.purple,
backgroundColor: Colors.purple.withAlpha(50)));
bool isSuggestion = false;
FocusNode _focusNode = FocusNode();
ScrollController _scrollController = ScrollController();
late SocialContentDetection lastDetection;
@override
void initState() {
super.initState();
_streamSubscription =
_subCommentController.subscribeToDetection(testFunction);
}
void testFunction(SocialContentDetection detection) {
lastDetection = detection;
}
void dispose() {
_focusNode.dispose();
_streamSubscription.cancel();
_subCommentController.dispose();
_scrollController.dispose();
super.dispose();
}
PreferredSize mention(double height) {
return PreferredSize(
child: FutureBuilder<QuerySnapshot>(
future: users.get(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return SizedBox.shrink();
default:
if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
var suggestions = snapshot.data!.docs
.where((element) => element
.get('username')
.contains(lastDetection.text.replaceAll("@", "")))
.toList();
return Container(
child: ListView.separated(
itemCount: suggestions.length,
itemBuilder: (context, index) {
return ListTile(
title: suggestions[index].get("username"),
);
},
separatorBuilder: (context, index) {
return Divider(color: Colors.grey[600]);
},
),
);
}
}
}),
preferredSize: Size.fromHeight(height),
);
}
@override
Widget build(BuildContext context) {
var height = MediaQuery.of(context).size.height * 0.4;
return DefaultSocialTextFieldController(
focusNode: _focusNode,
scrollController: _scrollController,
textEditingController: _subCommentController,
child: Container(
padding: EdgeInsets.all(8),
child: Column(
children: <Widget>[
Expanded(
child: TextField(
focusNode: _focusNode,
expands: true,
scrollController: _scrollController,
scrollPhysics: AlwaysScrollableScrollPhysics(),
controller: _subCommentController,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(Icons.reply),
onPressed: () async {
doing things with firebase and etc. etc.
setState(() {});
}
},
),
filled: true,
hintText: "Reply",
prefixIcon: Icon(Icons.messenger, color: Colors.grey[600]),
border: OutlineInputBorder(
borderSide: BorderSide.none,
),
),
textInputAction: TextInputAction.done,
minLines: null,
maxLines: null,
),
),
],
),
),
detectionBuilders: {DetectedType.mention: (_) => mention(height)},
);
}
}`
the reply textfield is being used in a ListTile'schildren: which is inside an ExpansionTile which is inside a streambuilder here is the full code(its big and from the ExpansionTile() ):
return ExpansionTile(
textColor:
Colors.black54,
iconColor:
Colors.black54,
leading:
CircleAvatar(
backgroundColor:
Colors.black,
),
title:
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
commentData[commentIndex]['username'],
),
Text(commentData[commentIndex]['Time']),
],
),
subtitle:
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Text.rich(
TextSpan(
text: '',
children: commentData[commentIndex]['comment'].split(' ').map<InlineSpan>((w) {
return w.startsWith('@') && w.length > 1
? TextSpan(
text: ' ' + w,
style: TextStyle(color: Colors.blue),
recognizer: TapGestureRecognizer()..onTap = () {},
)
: TextSpan(text: ' ' + w, style: TextStyle(color: Colors.black));
}).toList()),
),
commentLikeButton(commentData[commentIndex]["Likes"], list[index]['document_name'], commentID[commentIndex], "")
],
),
trailing:
SizedBox.shrink(),
children: [
// reply list
subcommentData.isEmpty
? Text('No Comments Yet')
: ListView.separated(
itemCount: subcommentData.length,
scrollDirection: Axis.vertical,
shrinkWrap: true,
separatorBuilder: (context, index) {
return Divider(
color: Colors.grey[600],
);
},
itemBuilder: (context, replyIndex) {
return Container(
height: 50,
color: Colors.white,
child: ListTile(
dense: true,
leading: CircleAvatar(
backgroundColor: Colors.black,
),
title: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Text(subcommentData[replyIndex]['username']),
Text(subcommentData[replyIndex]['Time'])
]),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text.rich(
TextSpan(
text: '',
children: commentData[replyIndex]['comment'].split(' ').map<InlineSpan>((w) {
return w.startsWith('@') && w.length > 1
? TextSpan(
text: ' ' + w,
style: TextStyle(color: Colors.blue),
recognizer: TapGestureRecognizer()..onTap = () {},
)
: TextSpan(text: ' ' + w, style: TextStyle(color: Colors.black));
}).toList()),
),
commentLikeButton(subcommentData[replyIndex]['Likes'], list[index]['document_name'], commentID[commentIndex], subcommentID[replyIndex])
],
),
),
);
}),
// reply text field
SizedBox(
height: 10,
),
replyField(
commentID: commentID[commentIndex],
docName: list[index]['document_name']),
],
// trailing: Text(date),
);
Since we are covering URL and #hastags then adding email support could be a good idea
An issue occurred after upgrading flutter 2.2.0
** BUILD FAILED **
Xcode's output:
↳
4
Invalid depfile: /Users/claude/Documents/projects/Lat/Lat/.dart_tool/flutter_build/de53b0890724bc58734a7ab9fa32f191/kernel_snapshot.d
../../../../.pub-cache/hosted/pub.dartlang.org/flutter_social_textfield-0.0.3/lib/controller/social_text_editing_controller.dart:94:12: Error: The method 'SocialTextEditingController.buildTextSpan' has fewer named arguments than those of overridden method 'TextEditingController.buildTextSpan'.
TextSpan buildTextSpan({TextStyle? style, required bool withComposing}) {
^
../../../../fvm/versions/2.2.0/packages/flutter/lib/src/widgets/editable_text.dart:192:12: Context: This is the overridden method ('buildTextSpan').
TextSpan buildTextSpan({required BuildContext context, TextStyle? style , required bool withComposing}) {
^
../../../../.pub-cache/hosted/pub.dartlang.org/flutter_social_textfield-0.0.3/lib/controller/social_text_editing_controller.dart:94:12: Error: The method 'SocialTextEditingController.buildTextSpan' doesn't have the named parameter 'context' of overridden method 'TextEditingController.buildTextSpan'.
TextSpan buildTextSpan({TextStyle? style, required bool withComposing}) {
^
../../../../fvm/versions/2.2.0/packages/flutter/lib/src/widgets/editable_text.dart:192:12: Context: This is the overridden method ('buildTextSpan').
TextSpan buildTextSpan({required BuildContext context, TextStyle? style , required bool withComposing}) {
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.