Programmatically changing TextSpans in the selected text of a SelectableText.rich - flutter

I have a SelectableText.rich widget in flutter where the usecase requires each word within the text to be its own TextSpan.
The app currently parses the text into words and sets up the list of TextSpans. I want the user to be able to select text and then trigger a modification to the styling of all TextSpans touched by the selection.
For example, let’s say the SelectableText.rich has the following text where each word is its own TextSpan:
The boy went to the store. He went to the store every day.
If the user selects “went to the store” in the first sentence and then taps on a ‘bold’ button, how can I identify which spans should be affected? Note that “went to the store” is in the source text twice but only the first instance should be modified (so I can’t simply search the source text).
Here's a stripped-down example (using only the first sentence):
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'RichText Selection Issue',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Selectable Text Problem')),
body: Padding(
padding: const EdgeInsets.all(30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SelectableText.rich(
TextSpan(text: 'The ', style: TextStyle(fontSize: 20), children: [
TextSpan(text: 'boy '),
TextSpan(text: 'went '),
TextSpan(text: 'to '),
TextSpan(text: 'the '),
TextSpan(text: 'store '),
TextSpan(text: '.'),
])),
const SizedBox(height: 20),
// Add button to trigger selection
ElevatedButton(
onPressed: () {},
child: const Text('Select'),
),
],
),
),
);
}
}
What would I do in the ElevatedButton's onPressed callback to trigger a rebuild that modified the style associated with the TextSpans for 'went to the store'? I can't take the example further than this because I don't know how to associate the selected text with the spans (which obviously would need 'style' properties and presumably some sort of state logic relating to whether or not they should be rendered as bold).
Any suggestions on how this could be handled (or pointers to any online articles covering this sort of topic)?
Thanks in advance!
Note : For the purposes of this question, please ignore the fact that the user could select partial words using the SelectableText widget. I haven’t thought through that issue yet (the objective would be that a partially selected word would be treated as the full word).
Edited to add an example.

Related

Flutter alignement and wrap of different texts

This is the model of what i want to do !
I'm contacting you because I'm trying to copy the Libération newspaper application but I'm stuck (already!) on the question of the title.
I have attached a visual and I will try to be clear. I would like the first part of the title to be in red and then the title to continue in black and line up again, aligned to the left, under the red title. I have tried many methods to no avail. Expanded, Wrap... either the text sticks out or, when I manage to make the line break of the black text, it is positioned under the black text, not under the red text, considering that the block starts on the right of the red block.
I tried Wrap, Row, Expanded.
Thank you :-)
this is just an example ,
If it's dynamic you might want to use: a separator; to split the title or the text or another symbol
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
final String title =
"Chez Pol; Et si on mesurait le bruit à l'Assemblée pour la santé des députés?";
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: SafeArea(
child: Scaffold(
body: RichText(
text: TextSpan(
text: title.split(';')[0].toString(),
style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
children: <TextSpan>[
TextSpan(
text: title.split(';')[1].toString(),
style: const TextStyle(color: Colors.black),
),
],
),
),
),
),
);
}
}

Gesture detection: SelectableText inside ElevatedButton

I have a problem in Flutter Web with components that are Clickable (ElevatedButton, GestureDetector, etc) and at the same time contains a SelectableText widget inside.
The main problem is that a single click on top of the SelectableText does not trigger the onPressed button callback. Instead, it looks like 'initiates' a text selection.
From my point of view, text selection should only be initialized if a double tap, drag start or long tap happens on the text, but not on single tap.
A very simplified version of my problem:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Center(
child: ElevatedButton(
onPressed: () {
print('onTap');
},
child: Container(
padding: const EdgeInsets.all(36),
child: const SelectableText(
'Select me, but I also want to be clickable!',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
),
),
),
),
),
);
}
}
Note the padding between the ElevatedButton and the SelectableText
Any tips? ideas?
Thanks in advance
I understand you are trying your best to mimic the "native web experience" where all texts are selectable, and like you said, "tap and drag start are different", but their differences might be as little as 1 pixel.
If you look at this web page (stack overflow), my user name is clickable, try to drag and select it, you can't. Now scroll up, below your question, the tags are clickable, try to drag and select on the word "flutter", you can't...
So honestly, for those texts on buttons, maybe just don't let them be selectable :D
But if you insist, there's onTap event on SelectableText widget as well, so you can fire the same callback as the button, so it doesn't matter where user clicked.

Flutter: Very basic stupid beginner question

I am trying to learn Flutter and so I am following a YouTube tutorial.
Now I have a question I can't really describe, it is working but i don't understand why.
My Program contains two .dart files, you can see them below.
Is the the Question Class in questions.dart executed by pressing Button 1 or 3 (Answer 1, Answer 3)?
If I understand that correctly the buttons will execute "onPressed: _answerQuestions,". But _answerQuestions only changes the State of "questionIndex" and is not calling something else, right? The UI gets rebuild and the Question Text changes.
But Questions Class does change the appearence of the text with the "style", this is its only task right now?
So why do we have a second dart File for that? We could simply change the style within the main.dart?
main.dart:
import 'package:flutter/material.dart';
import './question.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
var questionIndex = 0;
void _answerQuestions() {
setState(() {
questionIndex = questionIndex + 1;
});
print(questionIndex);
}
#override
Widget build(BuildContext context) {
var questions = [
'What\'s your favorite color?',
'What\'s your favorite animal?',
];
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('My First App'),
),
body: Column(
children: [
Question(questions[questionIndex]),
RaisedButton(
child: Text('Answer 1'),
onPressed: _answerQuestions,
),
RaisedButton(
child: Text('Answer 2'),
onPressed: () => print('Answer 2 chosen')),
RaisedButton(
child: Text('Answer 3'),
onPressed: _answerQuestions,
),
],
),
),
);
}
}
And question.dart:
import 'package:flutter/material.dart';
class Question extends StatelessWidget {
final String questionText;
Question(this.questionText);
#override
Widget build(BuildContext context) {
return Text(
questionText,
style: TextStyle(fontSize: 28),
);
}
}
Is the the Question Class in questions.dart executed by pressing
Button 1 or 3 (Answer 1, Answer 3)?
Both. Question is a StatlessWidget holding your question.
When you press any of the buttons(1 & 2) MyAppState gets rebuilt with a new question index and because you pass the question text to the Question Widget using that index, Question(questions[questionIndex]), Question will get a new value and show it, because you called setState, if you had not, behind the scenes it would still change the value but because the widget wouldn't be rebuilt, the visual side would stay the same.
The why you have two separate files? It makes it easier to see what your app is doing, especially for bigger projects. Usually, one file should have one purpose, do not mix functionalities in one file or you'll get a messy code really fast.
In this case, the equivalent for not having the question in a separate file would be:
body: Column(
children: [
// from
// Question(questions[questionIndex]),
// to
Text(
questions[questionIndex],
style: TextStyle(fontSize: 28),
),
RaisedButton(
child: Text('Answer 1'),
onPressed: _answerQuestions,
),
RaisedButton(
child: Text('Answer 2'),
onPressed: () => print('Answer 2 chosen')),
RaisedButton(
child: Text('Answer 3'),
onPressed: _answerQuestions,
),
],
),
But what if you need to use that same question in several places?
Text(
questions[questionIndex],
style: TextStyle(fontSize: 28),
),
Text(
questions[questionIndex],
style: TextStyle(fontSize: 28),
),
Text(
questions[questionIndex],
style: TextStyle(fontSize: 28),
),
Text(
questions[questionIndex],
style: TextStyle(fontSize: 28),
),
You'll have to write that same code every time you need it, and this is a really simple one, imagine you have a widget with much more code, do you really want to write it all over again every time you need it?
No, you just create a new widget thas has the functionality you need and call it whenever you need it.
Question(questions[questionIndex]),
Question(questions[questionIndex]),
Question(questions[questionIndex]),
Question(questions[questionIndex]),
When i code in flutter, i usually tend to start a file for every new "screen" in the app.
Also, i create one file for I/O (api connections, DB...), and a file for common functions.
It is good to have several files because the moment you want to build a big app you will realise how long it is. My last app was around 10k lines of code. Impossible to mantain with only 1 file.
Also, dividing by file is useful for code reutilisation.
GL
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
var questionIndex = 0;
void _answerQuestions() {
setState(() {
questionIndex = questionIndex + 1;
});
print(questionIndex);
}
#override
Widget build(BuildContext context) {
var questions = [
'What\'s your favorite color?',
'What\'s your favorite animal?',
];
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('My First App'),
),
body: Column(
children: [
Text(
questions[questionIndex],
style: TextStyle(fontSize: 28),
),
RaisedButton(
child: Text('Answer 1'),
onPressed: _answerQuestions,
),
RaisedButton(
child: Text('Answer 2'),
onPressed: () => print('Answer 2 chosen')),
RaisedButton(
child: Text('Answer 3'),
onPressed: _answerQuestions,
),
],
),
),
);
}
}
It can be done.. As #Michael Said you'll get a messy code really fast. You should follow BEST PRACTICES While Learning a Language. It is equally IMPORTANT. Will help you in long RUN.

How to update a custom Stateful Widget using Floating Action Button

I've recently started using Flutter just for fun, and I'm stuck on adding actual functionality to the code without having everything inside one class.
Essentially, I'm trying to use a FloatingActionButton to increment the value of a Text Widget which stores the value of the user's level as an integer, but I don't want to have the whole app as a StatefulWidget because only the level is going to be updated. When the button is pressed, the value should increment by 1 and then show the new value on the screen.
I have the Level Text Widget inside a StatefulWidget class along with a function to update the level by one and set the state; the MaterialApp inside a StatelessWidget class; and the main body code inside another StatelessWidget class.
If this isn't the best way to do it please do let me know so I can improve for future projects, thanks.
main.dart
import 'package:flutter/material.dart';
main() => runApp(Start());
/// The Material App
class Start extends StatelessWidget{
#override
Widget build(BuildContext context){
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.grey[800],
appBar: AppBar(
title: Text("Home Page"),
backgroundColor: Colors.cyan,
centerTitle: true,
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.orange,
child: Icon(Icons.add, color: Colors.black,),
),
body: HomePage(),
),
);
}
}
/// Main Content for the page (body)
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// removed other children so there's less code to scan through for you :)
Padding(
padding: EdgeInsets.fromLTRB(30, 0, 0, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Text that just says "Level"
Text(
"Level",
style: TextStyle(
color: Colors.orange,
fontWeight: FontWeight.bold,
fontSize: 32,
),
),
// space between text and actual level value
SizedBox(height: 10),
// Create new level widget
Level(),
],
),
),
],
),
);
}
}
/// Updating level using a Stateful Widget
class Level extends StatefulWidget{
#override
State<StatefulWidget> createState(){
return _LevelState();
}
}
class _LevelState extends State<Level>{
int level = 0;
void incrementLevel(){
setState(() {
level += 1;
});
}
#override
Widget build(BuildContext context){
return Text(
"$level",
style: TextStyle(
color: Colors.grey[900],
fontWeight: FontWeight.normal,
fontSize: 28,
),
);
}
}
It actually is a weird way of doing it. However, there is various ways of achieving this
To give an example:
You can use KEYs to remotely redraw the child state
If you want an advanced solution that can assist you in bigger projects. You can use state management tecniques. You can find a lot of tutorials in the internet but these are some of them. BLOC, Provider, InheritedWidget.
Basicaly all of them does the same thing. Lifts up the state data so the place of the redrawn widget on the widget tree will not be important.
I strongly encourage you to watch some tutorials starting with the Provider. I hope this helps

Is There A Way to Either Return 2 values for the property floatingActionButton or to call a setState Whenever a variable Changes?

I'm new to flutter,
So I'm working on a Flutter app & I Have My Functions & Widget File named: 'Function' Separated from my Main but I'm trying to set state in 'Main' from 'Function'
I have tried to set a global variable inside a MyText class Widget inside 'Function' and import 'Main'==> Function & Vice Versa at the same time, but I can't seem to manipulate the GlobalKey variable which would trigger setState again
(Class & MyText class Has since been Scrapped)
I also tried to set the function from my other file to the button like so
floatingActionButton: functions.random()
And somehow was able to set state (Sorry I Forgot How), but it kept running without being pressed
'Main.dart' Sample Code
String display = "";
class _MyHomePageState extends State<MyHomePage> {
var currentScreen = display;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AutoSizeText(
currentScreen,
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: functions.menu(),
);
} //Build
}// _MyHomePageState
'Function.dart' Sample Code
SpeedDial menu(){
return SpeedDial(
SpeedDialChild(
child: Icon(Icons.autorenew),
backgroundColor: Colors.lightBlueAccent,
label: 'New Activity',
labelStyle: TextStyle(fontSize: 18.0),
onTap: () => random(),
),
)
random(){
...
return someString;
}
Intended Result
When Child 'New Activity' is clicked, setState is called with the variable currentScreen's state being set to the result from the function 'random()'
Thank You In Advance!
I don't know if this can finish your problem, but the approach will be a little different from your approach because I'm not really familiar with the globalsetkey works...
so from my perspective I'm using the bloc pattern so hope this can give you some inspiration how to resolve this problems
in classBLoc.dart
BehaviorSubject<bool> _dialClicked = BehaviorSubject<bool>();
Observable<bool> get dialClicked => _dialClicked.stream;
Function(bool) get dialClickedListener => _dialClicked.sink.add;
in main.dart
section floatingActionButton,
floatingActionButton: functions.menu(currentScreen),
in Function.dart
Widget menu(){
final bloc = Provider.of<classBLoc>();
return StreamBuilder(stream: bloc.dialClicked, initialData: false,builder: (context, snapshot){
return SpeedDial(
SpeedDialChild(
child: Icon(Icons.autorenew),
backgroundColor: Colors.lightBlueAccent,
label: 'New Activity',
labelStyle: TextStyle(fontSize: 18.0),
onTap: () => snapshot.data ? currentScreen : random(), // this used for checking data if have already been clicked or not
);
}),
);
Hope this can you some inspiration how it's worked //sorry if there is wrong bracketed