How to use focus on RichText in flutter? - flutter

I have a list of text span like this
var spans = [
TextSpan(text: content, style: style),
TextSpan(text: content, style: style)
//and 100 more
]
I am loading this inside SingleChildScrollView like this.
SingleChildScrollView(
controller: scrollController,
child: Text.rich(
TextSpan(children: spans),
),
)
Now My question is how can I shift focus between the TextSpan list?. Say I want to load TextSpan at position 10 from spans list at top of screen without manually scrolling.

You could add a WidgetSpan next to each of your TextSpan, so you can assign it a key property which would be a GlobalKey so you can access its BuildContext to call the method Scrollable.ensureVisible().
Here's a code sample inspired by an answer to a similar question:
class ScrollToTextSpanPage extends StatefulWidget {
const ScrollToTextSpanPage({Key? key}) : super(key: key);
#override
State<ScrollToTextSpanPage> createState() => _ScrollToTextSpanPageState();
}
class _ScrollToTextSpanPageState extends State<ScrollToTextSpanPage> {
final _keys = List<GlobalKey>.generate(
_kTextBlocks.length,
(_) => GlobalKey(),
);
void _goToSpan(int spanIndex) {
Scrollable.ensureVisible(
_keys[spanIndex].currentContext!,
alignment: 0.2,
duration: const Duration(milliseconds: 300),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Scroll to TextSpan'),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => _goToSpan(8),
label: const Text('Go to span 8'),
icon: const Icon(Icons.navigate_next),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Text.rich(
TextSpan(
children: [
for (int i = 0; i < _kTextBlocks.length; i++) ...[
WidgetSpan(child: SizedBox(key: _keys[i])),
TextSpan(
text: 'Span $i\n',
style: const TextStyle(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: '${_kTextBlocks[i]}\n\n',
style: const TextStyle(fontWeight: FontWeight.normal),
),
],
),
],
],
),
),
),
);
}
}
Try the sample on DartPad

Related

How do i switch Appbar title and child content with state-management in flutter?

I have a problem i have been struggling to get done for a day now
I want to dynamically switch appbar from this :
to this :
when a button is pressed.
The button is situated in the scaffold bottomNavigationBar of the first appbar widget.
I will give the code snippet of this particular widget.
I tried creating an entirely different widget and set the button onTap function to route to the new widget created.
This is not a suitable solution for me as i wish to just change state of the appbar as to avoid the weird transition when changing pages.
Also please note that the second image has a leading button that would enable the user to go back to the previous appbar.
How do i achieve this?
THIS IS THE CODE SNIPPET
import 'package:flutter/material.dart';
class CustomersView extends StatefulWidget {
#override
State<CustomersView> createState() => _CustomersViewState();
}
class _CustomersViewState extends State<CustomersView> {
List<String> items = [
"All",
"Inactive",
"One time",
"Loyal",
"Active",
];
int current = 0;
List<DropdownMenuItem<String>> get dropdownItems {
List<DropdownMenuItem<String>> menuItems = [
DropdownMenuItem(
child: Text(
"Today",
),
value: "Today"),
];
return menuItems;
}
#override
Widget build(BuildContext context) {
//final controller = Get.put(EServicesController());
return Scaffold(
appBar: AppBar(
toolbarHeight: 60,
backgroundColor: Colors.white,
title: Text(
"Customers".tr,
style: GoogleFonts.poppins(
color: Color(0xff000000),
fontSize: 20,
fontWeight: FontWeight.w600),
),
actions: [
SearchButtonWidget(),
SettingsButtonWidget(),
],
centerTitle: false,
elevation: 0,
automaticallyImplyLeading: false,
leadingWidth: 15,
// leading: new IconButton(
// icon: new Icon(Icons.arrow_back_ios, color: Color(0xff3498DB)),
// onPressed: () => {Get.back()},
// ),
),
body: RefreshIndicator(
onRefresh: () async {
// Get.find<LaravelApiClient>().forceRefresh();
// await controller.refreshNotifications(showMessage: true);
// Get.find<LaravelApiClient>().unForceRefresh();
},
child: ListView(
primary: true,
children: <Widget>[
mainHeader(),
SizedBox(
height: 10,
),
CustomersCategoriesBuilder(current: current),
],
),
),
//floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
bottomNavigationBar: current == 0 ? SizedBox() : MessageCustomersButton(),
);
}
//Button that controls the appbar state
class MessageCustomersButton extends StatelessWidget {
const MessageCustomersButton({
Key key,
this.value = false,
}) : super(key: key);
final bool value;
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: FadeInDown(
child: MaterialButton(
onPressed: () {
//this is the new page route ( unsatisfied approach )
Get.toNamed(Routes.MESSAGE_CUSTOMERS);
},
color: Color(0xff34495E),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.18),
),
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 10),
minWidth: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.chat,
size: 18,
color: Colors.white,
),
SizedBox(
width: 10,
),
Text(
'Message Customers',
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600),
),
],
),
),
),
),
);
}
}
Try creating the widget for AppBar only and handle the different states of AppBar there only by passing a flag like isSecondStyleAppBar then in your CustomersView widget, handle the flag using setState
class CustomAppBar extends StatelessWidget {
final bool isSecondStyleAppBar;
const CustomAppBar(this.isSecondStyleAppBar, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const AppBar();
}
}

How do I fix my problem with routes in Flutter?

good evening. I am currently doing a To-do List in Flutter and I want to pass the Title of my List and the Description of my List when I click on a new screen but upon setting up Routes and and declaring the values on my next, it shows the "2 positional arguments expected, but 0 found" on the routes I've set up. Here are my codes:
Here is my 1st screen:
import 'package:flutter/material.dart';
import 'package:todo_list/details.dart';
import 'package:todo_list/note.dart';
class MyApp extends StatelessWidget {
final String text;
final int number;
final String listDescription;
const MyApp(
{super.key,
required this.text,
required this.number,
required this.listDescription});
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
DetailsPage.routeName: (ctx) => DetailsPage(),
},
home: CustomListTile(
text: text,
number: number,
listDescription: listDescription,
),
);
}
}
class CustomListTile extends StatelessWidget {
final String text;
final int number;
final String listDescription;
const CustomListTile(
{super.key,
required this.text,
required this.number,
required this.listDescription});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
Navigator.pushNamed(context, DetailsPage.routeName,
arguments: Note(title: text, description: listDescription));
},
/* onTap: () {
Widget okButton = TextButton(
child: const Text("CLOSE"),
onPressed: () {
Navigator.of(context).pop();
},
);
AlertDialog alert = AlertDialog(
title: Text(text),
content: Text('This item in the list contains $listDescription'),
actions: [
okButton,
]);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
});
}, */
child: Padding(
padding: const EdgeInsets.only(left: 20.0, right: 20.0, top: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("$number. $text",
style: const TextStyle(
fontSize: 20,
)),
const Icon(Icons.chevron_right)
],
),
Text(
listDescription,
style: const TextStyle(fontSize: 14, color: Colors.grey),
),
const Divider()
],
),
),
);
}
}
and here is my 2nd screen:
import 'package:flutter/material.dart';
import 'note.dart';
class DetailsPage extends StatefulWidget {
static const String routeName = "/details";
final String text;
final String listDescription;
const DetailsPage(this.text, this.listDescription, {super.key});
#override
State<DetailsPage> createState() => _DetailsPageState();
}
class _DetailsPageState extends State<DetailsPage> {
late Note params;
#override
void didChangeDependencies() {
params = ModalRoute.of(context)!.settings.arguments! as Note;
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
Widget titleSection = Container(
padding: const EdgeInsets.all(32),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.only(bottom: 0),
child: Text(
params.title,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 25,
),
),
),
],
),
),
],
),
);
Color color = Theme.of(context).primaryColor;
Widget buttonSection = Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildButtonColumn(
color,
Icons.edit,
'EDIT',
),
_buildButtonColumn(color, Icons.delete, 'DELETE'),
],
);
Widget textSection = Padding(
padding: const EdgeInsets.all(20),
child: Text(
params.description,
softWrap: true,
),
);
return MaterialApp(
title: 'Layout for a New Screen',
theme: ThemeData(
primarySwatch: Colors.brown,
),
home: Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
title: Text(params.title),
),
body: ListView(
children: [
Image.asset(
'lib/images/placeholder.jpg',
width: 600,
height: 240,
fit: BoxFit.cover,
),
titleSection,
buttonSection,
textSection,
],
),
),
);
}
Column _buildButtonColumn(
Color color,
IconData icon,
String label,
) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color),
Container(
margin: const EdgeInsets.only(top: 8),
child: Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: color,
),
),
),
],
);
}
}
/* return Scaffold(
appBar: AppBar(title: Text(text)),
body: Center(
child: Row(
children: [Text(description)],
),
));
}
} */
How do I make it so that the data I'll pass such as the Title and the Description will appear on the 2nd screen without the error "2 positional argument(s) expected, but 0 found.
Try adding the missing arguments." appearing.
I tried the Quick Fixes on VS Code such as adding a const modifier but I think the const modifier wouldn't do a fix since both data I'm trying to pass are dynamic and may change from time to time.
As you've define details page
class DetailsPage extends StatefulWidget {
static const String routeName = "/details";
final String text;
final String listDescription;
const DetailsPage(this.text, this.listDescription, {super.key});
You need to pass two string as positional argument.
So it can be
routes: {
DetailsPage.routeName: (ctx) => DetailsPage("text","description"),
},
also while you are using route arguments, you can remove theses from widget class and just accept from state class context with ModalRoute.
You can check this example and development/ui/navigation .

I want the text and icon together in a line under one widget in body

I want the text and icon together in a line under one widget in body. I tried and I got the following error
Too many positional arguments, 0 expected but 1 received
Can Any one help me with this? Thanks in advance!
void main() => runApp(const Myapp());
class Myapp extends StatefulWidget {
const myapp({Key? key}) : super(key: key);
_MyappState createState() => _MyappState();
}
class _MyappState extends State<Myapp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Padding(
padding: EdgeInsets.only(top: 30),
child: Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
/** Positioned WIdget **/
Positioned(
top: 0.0,
child: Image.asset(
'images/logo.png',
height: 150,
width: 150,
),
),
//Positioned
/** Positioned WIdget **/
//Positioned
], //<Widget>[]
), //Stack
),
Text.rich(
TextSpan(
children: [
TextSpan(text: 'Click'),
WidgetSpan(child: Icon(Icons.add)),
TextSpan(text: 'to add'),
],
),
)
),
);
}
}
First, the error "Too many positional arguments, 0 expected but 1 received", is due to the fact that the placement of RichText is outside the Stack Widget children. (The square brackets [].)
To achieve the desired output as you mentioned in the question, use a row specified in code comments with the attached code.
void main() => runApp(const Myapp());
class Myapp extends StatefulWidget {
const Myapp({Key? key}) : super(key: key);
#override
_MyappState createState() => _MyappState();
}
class _MyappState extends State<Myapp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Padding(
padding: const EdgeInsets.only(top: 30),
child: Stack(
alignment: AlignmentDirectional.center,
children: const <Widget>[
/** Positioned WIdget **/
// Positioned(
// top: 0.0,
// child: Image.asset(
// 'images/logo.png',
// height: 150,
// width: 150,
// ),
// ),
/** Positioned WIdget **/
/// This is where the RichTextWidget should be
Text.rich(
TextSpan(
children: [
TextSpan(text: 'Click'),
WidgetSpan(child: Icon(Icons.add)),
TextSpan(text: 'to add'),
],
),
),
/// Additionally, use a Row([Text,Icon,Text]) and use
/// crossAxisAlignment Property to align the children
], //<Widget>[]
), //Stack
),
/// You placed the below RickText outside the Stack, it should be above in the Widget[] of the stack.
// Text.rich(
// TextSpan(
// children: [
// TextSpan(text: 'Click'),
// WidgetSpan(child: Icon(Icons.add)),
// TextSpan(text: 'to add'),
// ],
// ),
// )
),
);
}
}
If you want to place a button with title and icon then you can use TextButton.icon instead of putting it inside a row or putting inside RichText.
Here's the button example:
class DemoWidget extends StatelessWidget {
const DemoWidget({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.mail),
label: const Text("Title"),
),
),
);
}
}
Then you can put it inside a stack and assign a background image in it.
You can use Positioned.fill and then try modifying the parameters until it matches your requirment.
caffold(
body: Center(
child: Container(color: Colors.lightGreen,
height: 200,
width: 200,
child: Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
Positioned(child: Image.asset("assets/test.png")),
const Text.rich(
TextSpan(
children: [
TextSpan(text: 'Click'),
WidgetSpan(child: Icon(Icons.add)),
TextSpan(text: 'to add'),
],
),
),
], //<Widget>[]
),
),
),
)

How to color the suggestions when the user is typing in flutter

I am using typeahead package in flutter to get suggestions to users as they type, what I want is to color the suggestions while the user is typing as its shouwn in the picture Colored suggestions while typing
Here is a simple example that am trying to implement
Main.dart
import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'CitiesService.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Colored suggestions Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Colored suggestions Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final myControllerCity = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Form(
key: this._formKey,
child: Padding(
padding: EdgeInsets.all(32.0),
child: Column(
children: <Widget>[
//SizedBox(height: 50,),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: TypeAheadFormField(
textFieldConfiguration: TextFieldConfiguration(
controller: myControllerCity,
decoration: InputDecoration(labelText: 'City')),
suggestionsCallback: (pattern) {
return CitiesService.getSuggestionsCities(pattern);
},
itemBuilder: (context, suggestion) {
//
List<String> splitted_names_of_cities = suggestion
.toString()
.toLowerCase()
.split(myControllerCity.text);
final children = <Widget>[];
for (var i = 1;
i < splitted_names_of_cities.length;
i++) {
children.add(new ListTile(
title: Text.rich(
TextSpan(
children: [
TextSpan(
text: myControllerCity.text,
style: TextStyle(color: Colors.red),
),
TextSpan(text: splitted_names_of_cities[i]),
],
),
)));
}
print("this is the list $splitted_names_of_cities");
return new ListView(
children: children,
);
},
transitionBuilder: (context, suggestionsBox, controller) {
return suggestionsBox;
},
onSuggestionSelected: (suggestion) {
myControllerCity.text = suggestion;
},
),
),
],
),
],
),
),
)),
);
}
}
And here is the function of the suggestions
static List<String> getSuggestionsCities(String query) {
List<String> wilayas = [];
algeria_cites.forEach((element) => wilayas.contains(element['wilaya_name_ascii']) ?
null : wilayas.add(element['wilaya_name_ascii']) );
wilayas.retainWhere((s) => s.toLowerCase().contains(query.toLowerCase()));
return wilayas;
}
In the previous code, for each item of the suggested cities name I was creating a full ListTile but I had to create TextSpan instead of that.
But each item has its specific length, for that reason I used List<InlineSpan>.
So here is the itemBuilder after fixing the error.
itemBuilder: (context, suggestion) {
List<InlineSpan> temp = [];
String suggestion_in_lower_case = suggestion.toString().toLowerCase();
List<String> splitted_names_of_cities = suggestion_in_lower_case.split(myControllerCity.text.toLowerCase());
for (var i = 0; i < splitted_names_of_cities.length-1; i++) {
if(splitted_names_of_cities.contains(myControllerCity.text.toLowerCase()));{
temp.add(
TextSpan(
text: splitted_names_of_cities[i],
style: TextStyle(
height: 1.0,
color: Colors.black,
),
),
);
temp.add(
TextSpan(
text: myControllerCity.text.toLowerCase(),
style: TextStyle(
color: Colors.red,
),
),
);
}
}
temp.add(
TextSpan(
text: splitted_names_of_cities.last,
style: TextStyle(
color: Colors.black,
),
),
);
return ListTile(
title: Text.rich(
TextSpan(
children: temp,
),
)
);
},
So it is working as expected, here is the demo.
https://i.stack.imgur.com/Tfs95.gif

Add a link to navigator in a text widget

I want to show a link to seperate view inside a text widget
I tried adding Two Text Widgets and a flat button in between but it doesnt show as expected
How I want it to look like
<p>This is a sample text</p>
This is what i tried
Column(
children: [
Text('This is a '),
FlatButton(
onPress: (){
Navigator.push(context, MaterialPageRoute(builder: (context)=>View()))
}
child: Text('sample')
),
Text(' text')
]
)
TapGestureRecognizer _recognizer;
#override
void initState() {
super.initState();
_recognizer = TapGestureRecognizer()..onTap = (){
print("tapped");
};
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(text: 'This is a '),
TextSpan(text: 'sample', style: TextStyle(fontWeight: FontWeight.bold), recognizer: _recognizer),
TextSpan(text: ' text'),
],
),
),
),
);
}