Related
I use a FormBuilder with a global key, I have a showModalBottomSheet in which i have my filters rendered, before I click the apply button my filters are reset, but after I applied the filters and I want to reset my filters, my _formKey.currentState?.reset() stops working.
I think the problem is in _buildFilterField but I don't quite understand how to reset it.
final _formKey = GlobalKey<FormBuilderState>();
var filterData;
bool isFilterLoading = true;
Future showFilter(context) => showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) => StatefulBuilder(builder: (context, innerSetState) {
Future.delayed(const Duration(milliseconds: 700), () {
innerSetState(() {
isFilterLoading = false;
});
});
return SafeArea(
child: FormBuilder(
key: _formKey,
child: Container(
child: isFilterLoading != true
? Column(children: [
Padding(
padding: const EdgeInsets.only(),
child: Row(
children: [
TextButton(
onPressed: () {
_formKey.currentState?.reset();
},
child: const Text(
'Сброс',
style: TextStyle(
color: Colors.black, fontSize: 16),
)),
],
),
),
_myRangeSlider(),
_buildFilterField(filterData),
Padding(
padding:
const EdgeInsets.only(top: 10, bottom: 10),
child: SizedBox(
child: ElevatedButton(
onPressed: () {
_formKey.currentState!.save();
filterValues = _formKey.currentState!.value;
setState(() {
String encodedString =
filterValues.keys.map((key) {
return key +
'=' +
Uri.encodeQueryComponent(
filterValues[key].toString());
}).join('&');
filters = encodedString;
page = 1;
list.clear();
productFuture = getProducts(val, filters);
});
Navigator.pop(context);
},
child: const Text(
'Показать результаты',
),
),
),
),
])
: const Center(child: CircularProgressIndicator())),
),
);
}));
Widget _buildFilterField(Map<String, dynamic> field) {
List<Widget> children = [];
print(filterValues);
field.keys.forEach((key) {
if (field[key]["type"] == 'radio') {
children.add(FormBuilderRadioGroup(
initialValue: filterValues?[key] ?? field[key]["initial_value"],
name: field[key]["value"],
decoration: const InputDecoration(
border: InputBorder.none,
),
options: [
for (var option in field[key]["data"])
FormBuilderFieldOption(
value: option['value'],
child: Text(
option['text'],
)),
],
));
}
});
return children.isEmpty
? Container()
: Column(
children: children,
);
}
I build this code below that makes possible to show my Popupmenubuttons Items when I put the cursor over the button without clicking the button. But there is a problem. I can show my items with this method but I can not make possible to close my items in the moment I move my cursor from the button. Please look carefully my code and tell me if you have any idea how to solve this.
openPopUpItem2() {
GestureDetector? detector;
searchForGestureDetector(BuildContext element) {
element.visitChildElements((element) {
if (element.widget != null && element.widget is GestureDetector) {
detector = element.widget as GestureDetector;
} else {
searchForGestureDetector(element);
}
});
}
searchForGestureDetector(keyList[2].currentContext!);
detector!.onTap!();
}
The code below shows how I have used the function to show my popupItems.
InkWell(
onHover: (value) {
if (value) openPopUpItem2();
},
onTap: () {},
child: PopupMenuButton(
key: keyList[2],
tooltip: '',
color: Color(0xFF262533),
position: PopupMenuPosition.under,
child: MouseRegion(
onEnter: (details) =>
setState(() => ishovering2 = true),
onExit: (details) => setState(() {
ishovering2 = false;
}),
child: Text(
'Blog',
style: TextStyle(
color: ishovering2 ? Colors.pink : Colors.white,
fontSize: 24,
fontFamily: 'Poppins',
),
),
),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
PopupMenuItem(
child: OnHover(
builder: (isHovered) {
final color = isHovered
? Colors.pink
: const Color(0xFF262533);
return ListTile(
title: const Text(
'Archiv',
style: TextStyle(color: Colors.white),
),
enabled: true,
hoverColor: color,
onTap: () {},
);
},
),
),
]),
),
This is tricky indeed. What comes to my mind is if you would wrap your button on another InkWell and add some padding to it. And on onHover method you would dismiss your items.
Here is how I think it should look like:
InkWell(
onHiver: (){
dismissItems();
},
child:
Padding(
padding: //some padding
child: // your button
)
)
I integrated Agora in Flutter Application with the help of agora_rtc_engine 4.0.7, but upon making a video call for the very first time the local camera view is not working. When we disconnect the call and call back again then everything works fine, the local camera view started working.
Again when we kill the application from Phone memory and rerun it then also the same problem occurs for the first time.
VideoCallingScreen.dart file
class VideoCallingScreen extends StatefulWidget {
String doctorId;
String bookingId;
VideoCallingScreen(this.doctorId, this.bookingId);
#override
_VideoCallingScreenState createState() => _VideoCallingScreenState();
}
class _VideoCallingScreenState extends State<VideoCallingScreen> {
int? _remoteUid;
RtcEngine? _engine;
bool isJoined = false,
switchCamera = true,
switchRender = true,
muteAudio = false,
muteVideo = false;
bool remoteUserMicMute = false, remoteUserVideoMute = false;
bool resizeVideo = false;
ServerHandler _serverHandler = ServerHandler();
String? token;
String? channelName;
String? channelId;
late user.Details userDetails;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
getAgoraToken().then((value) {
if (value) {
initForAgora();
} else {
_showMyDialog();
}
});
// initForAgora();
}
Future<void> _showMyDialog() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text('No Channel Found'),
content: SingleChildScrollView(
child: ListBody(
children: const <Widget>[
Text('No video channel has been created by the Doctor'),
],
),
),
actions: <Widget>[
TextButton(
child: const Text('Ok'),
onPressed: () async {
// int count = 0;
Navigator.pop(context);
Navigator.pop(_scaffoldKey.currentContext!);
},
),
],
);
},
);
}
Future<bool> getAgoraToken() async {
await Screen.keepOn(true);
var tokenBody = await _serverHandler.joinAgoraChannel(
widget.doctorId, widget.bookingId);
print('token Body from videoPage' + tokenBody.toString());
if (tokenBody['success'] == 1) {
setState(() {
token = tokenBody['channel']['access_token_patient'];
channelName = tokenBody['channel']['channel_name'];
print('**********Token Set********' + token!);
channelId = tokenBody['channel']['id'].toString();
});
return true;
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
"Unable to get Video Call Token, Please check your internet Connection and try again."),
));
return false;
}
}
Future<void> initForAgora() async {
// retrieve permissions
await [Permission.microphone, Permission.camera].request();
userDetails = await _serverHandler.getUserProfile();
//create the engine
_engine = await RtcEngine.create(appId);
await _engine?.enableVideo();
_engine?.setEventHandler(
RtcEngineEventHandler(
joinChannelSuccess: (String channel, int uid, int elapsed) async {
print("local user $uid joined");
// await _engine!.enableVideo();
},
userJoined: (int uid, int elapsed) {
print("remote user $uid joined");
setState(() {
_remoteUid = uid;
});
},
tokenPrivilegeWillExpire: (token) async {
await getAgoraToken();
await _engine?.renewToken(token);
},
userOffline: (int uid, UserOfflineReason reason) {
print("remote user $uid left channel");
// _engine!.enableVideo();
setState(() {
_remoteUid = null;
});
_userLeftTheCall();
},
userMuteVideo: (int uid, bool isMute) {
print('Audio Mutted');
setState(() {
remoteUserVideoMute = isMute;
});
},
userMuteAudio: (int uid, bool isMute) {
print('Audio Mutted');
setState(() {
remoteUserMicMute = isMute;
});
},
),
);
await getAgoraToken();
await _engine?.joinChannel(token, channelName!, null, userDetails.id!);
}
// Create UI with local view and remote view
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
key: _scaffoldKey,
body: SafeArea(
child: Stack(
children: [
Center(
child:
resizeVideo ? _renderLocalPreview() : _renderRemoteVideo(),
),
if (remoteUserVideoMute)
BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 10,
sigmaY: 10,
),
child: Padding(
padding: const EdgeInsets.only(bottom: 32),
child: Center(
child: Text(
'Doctor video Paused',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
),
),
),
if (remoteUserMicMute)
Padding(
padding: const EdgeInsets.only(top: 32),
child: Center(
child: Text(
'Doctor Mic Muted',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
),
),
Positioned(
top: 8,
left: 8,
child: InkWell(
//Uncomment it to unable resize
// onTap: () {
// setState(() {
// resizeVideo = !resizeVideo;
// });
// },
child: Container(
width: 100,
height: 120,
child: Stack(
children: [
ImageFiltered(
imageFilter: muteVideo
? ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0)
: ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: resizeVideo
? _renderRemoteVideo()
: _renderLocalPreview(), // Widget that is blurred
),
if (muteVideo)
Positioned(
top: 4,
left: 4,
child: Icon(
Icons.videocam_off_outlined,
color: Colors.white,
size: 32,
),
),
if (muteAudio)
Positioned(
bottom: 4,
right: 4,
child: Icon(
Icons.mic_off_outlined,
color: Colors.white,
size: 32,
),
),
],
),
),
),
),
Positioned(
bottom: 32,
right: 8,
left: 8,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () async {
await Screen.keepOn(false);
await _engine?.leaveChannel();
await _serverHandler.leaveChannel(channelId!);
Navigator.pop(_scaffoldKey.currentContext!);
},
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: Colors.red),
child: Icon(
Icons.call_end,
color: Colors.white,
),
),
),
InkWell(
onTap: this._switchCamera,
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: Colors.white),
child: Icon(
Icons.flip_camera_android_rounded,
color: Colors.black87,
),
),
),
InkWell(
onTap: this._onToggleMuteVideo,
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: Colors.white),
child: Icon(
muteVideo
? Icons.videocam_off_outlined
: Icons.videocam_outlined,
color: Colors.black87,
),
),
),
InkWell(
onTap: this._onToggleMute,
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: Colors.white),
child: Icon(
muteAudio
? Icons.mic_off_outlined
: Icons.mic_none_outlined,
color: Colors.black87,
),
),
),
],
),
)
],
),
),
),
);
}
// current user video
Widget _renderLocalPreview() {
return RtcLocalView.SurfaceView();
}
// remote user video
Widget _renderRemoteVideo() {
// return RtcRemoteView.SurfaceView(uid: 70);
if (_remoteUid != null) {
return RtcRemoteView.SurfaceView(uid: _remoteUid!);
} else {
return Text(
'Please wait, doctor is joining shortly',
style: TextStyle(
color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
);
}
}
_switchCamera() {
_engine?.switchCamera().then((value) {
setState(() {
switchCamera = !switchCamera;
});
}).catchError((err) {
log('switchCamera $err');
});
}
void _onToggleMute() {
setState(() {
muteAudio = !muteAudio;
});
_engine!.muteLocalAudioStream(muteAudio);
}
void _onToggleMuteVideo() {
setState(() {
muteVideo = !muteVideo;
});
_engine!.muteLocalVideoStream(muteVideo);
}
Future<bool> _onWillPop() async {
return (await showDialog(
context: _scaffoldKey.currentContext!,
builder: (context) => new AlertDialog(
title: new Text('Are you sure?'),
content: new Text('Do you want to exit exit Video Call?'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(_scaffoldKey.currentContext!),
child: new Text('No'),
),
TextButton(
onPressed: () async {
await Screen.keepOn(false);
await _serverHandler.leaveChannel(channelId!);
await _engine?.leaveChannel();
Navigator.pop(_scaffoldKey.currentContext!);
Navigator.pop(_scaffoldKey.currentContext!);
},
child: new Text('Yes'),
),
],
),
)) ??
false;
}
_userLeftTheCall() async {
return (await showDialog(
context: _scaffoldKey.currentContext!,
builder: (context) => new AlertDialog(
title: new Text('Doctor Left'),
content: new Text('Doctor left this call please join Again'),
actions: <Widget>[
TextButton(
onPressed: () async {
// await _serverHandler.leaveChannel(channelId!);
await Screen.keepOn(false);
await _engine?.leaveChannel();
Navigator.pop(_scaffoldKey.currentContext!);
Navigator.pop(_scaffoldKey.currentContext!);
// Navigator.of(context).pop(true);
},
child: new Text('Okay'),
),
],
),
)) ?? false;
}
}
Please ignore things which are not relevent. Also the log is too long to share, the log keeps on running during the entire video call.
Mobile Screenshot
Thank You
I want to know how to move between pages in flutter introduction screen. To clarify the problem, I want to go back a page when skip button is pressed.
So far I have done this:
class _IntroPageState extends State<IntroPage> {
int currentIndex = 0;
void _onIntroEnd(context) {
getIt<IntroLocalDataSource>().setIntroSeenState(true);
Navigator.of(context).pushReplacementNamed(SignInPage.id);
}
#override
Widget build(BuildContext context) {
final strings = Languages.of(context);
final bodyStyle = Theme.of(context)
.textTheme
.subtitle2
?.copyWith(fontSize: k16TextFontSize);
final titleStyle = Theme.of(context)
.textTheme
.headline1
?.copyWith(fontSize: k20TextFontSize);
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Flexible(
child: Container(
child: IntroductionScreen(
onChange: (index) {
setState(() {
currentIndex = index;
});
},
rtl: true,
globalBackgroundColor: Colors.transparent,
showNextButton: false,
rawPages: strings.introScreenTitles.asMap().entries.map((entry) {
int idx = entry.key;
String val = entry.value;
return Center(
child: IntroCardWidget(
index: idx,
title: val,
bodyStyle: bodyStyle,
titleStyle: titleStyle,
image: Assets.introImages[idx],
description: strings.introScreenDescriptions[idx],
));
}).toList(),
showDoneButton: false,
curve: Curves.fastLinearToSlowEaseIn,
showSkipButton: currentIndex != 0,
skip: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 0,
primary: kLightTextColor,
shape: CircleBorder(),
),
onPressed: () {
setState(() {
currentIndex--; // <<--------- This does not work
});
},
child: Icon(
Icons.keyboard_arrow_left_rounded,
color: Theme.of(context).primaryColor,
)),
dotsDecorator: DotsDecorator(
color: kLightTextColor.withOpacity(.15),
activeSize: Size(20, 10),
activeShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25.0)),
activeColor: kLightTextColor),
controlsPadding: kIsWeb
? const EdgeInsets.all(12.0)
: const EdgeInsets.symmetric(
vertical: Spacings.paddingSm,
horizontal: Spacings.paddingXs),
),
),
),
Container(
constraints: BoxConstraints(
maxHeight: Spacings.margin5Xl, minHeight: Spacings.margin5Xl),
margin: EdgeInsets.only(bottom: Spacings.margin5Xl),
child: currentIndex != 3
? Container()
: ElevatedButton(
onPressed: () => _onIntroEnd(context),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: Spacings.padding5Xl,
vertical: Spacings.paddingSm),
child: Text(
strings.goToVerificationPageBtnText,
style: TextStyle(
fontSize: k16TextFontSize, color: kDarkTextColor),
),
),
style: ElevatedButton.styleFrom(
elevation: 1,
primary: kLightTextColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(Spacings.radiusLg),
),
),
),
),
],
);
}
But the page does not change. It seems to make sense, as the index is not passed to the IntroductionScreen widget. So I was wondering how to navigate in these pages as will.
I found a way to do what I wanted.
It could be done with using keys & calling IntroductionScreenState's methods.
After going through the source code of library, it seems it is changing pages using this public method:
Future<void> animateScroll(int page) async {
setState(() => _isScrolling = true);
await _pageController.animateToPage(
max(min(page, getPagesLength() - 1), 0),
duration: Duration(milliseconds: widget.animationDuration),
curve: widget.curve,
);
if (mounted) {
setState(() => _isScrolling = false);
}
}
and also has these methods for going forward & backward:
void next() => animateScroll(_currentPage.round() + 1);
void previous() => animateScroll(_currentPage.round() - 1);
From this point, it was just a matter of calling the methods of this state when needed. This could be done using keys:
GlobalKey<IntroductionScreenState> _introScreenKey =
GlobalKey<IntroductionScreenState>();
IntroductionScreen(
key: _introScreenKey,
skip: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 0,
primary: kLightTextColor,
shape: CircleBorder(),
),
onPressed: () {
_introScreenKey.currentState?.previous(); // <<----- This here
},
child: Icon(
Icons.keyboard_arrow_left_rounded,
color: Theme.of(context).primaryColor,
)),
)
And ofcourse it is pretty easy to navigate directly to any page using the animateScroll().
Since you're using navigator, you should give a try to Navigator.pop()
onPressed: () { Navigator.pop(context); }
here you can find some examples
I have a flutter project with a FloatingSearchBar from this library (pub.dev). I have four buttons, one of which is a GestureDetector (which is not really important but explains some behind scenes info).
This all looks like this:
or this:
or one other variation.
These buttons all work fine, although I've spent a lot of time getting them that way. They are toggleable, and their Icons are decided by a variable that can be updated using setState.
There's also, however, this layout:
which should be able to be toggled on and off by this switch/button inside the menu:
It too just updates the offlineMode variable inside a setState. But this setState doesn't update the search bar until I force the bar to update by clicking any of the buttons.
Why is setState not working in this particular circumstance?
Code for search bar:
Widget buildFloatingSearchBar(AsyncSnapshot snapshot) {
const bool isPortrait = true;
return DescribedFeatureOverlay( //Don't worry about these I don't think
featureId: 'search',
tapTarget: Icon(Icons.search),
title: Text('Search'),
description: Text(
'Tap the bar at the top to open the search interface, where you can search for GeoTags by name, approximate location or by author.'),
overflowMode: OverflowMode.extendBackground,
backgroundColor: Theme.of(context).primaryColor,
targetColor: Colors.white,
textColor: Colors.white,
child: FloatingSearchBar(
borderRadius: BorderRadius.all(
Radius.circular(50),
),
progress: loadingSearch,
title: offlineMode //Checking offlineMode variable Should update with setState
? Row(
children: [
Text(
'Geotagger',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
Text(
' - Offline Mode',
style: TextStyle(
//fontWeight: FontWeight.bold,
fontSize: 15,
),
),
],
)
: null,
backgroundColor: Theme.of(context).backgroundColor,
hint: 'Find a GeoTag...', //Shown if offlineMode is off (title is null)
transitionDuration: const Duration(milliseconds: 800),
transitionCurve: Curves.easeInOut,
physics: const BouncingScrollPhysics(),
axisAlignment: isPortrait ? 0.0 : -1.0,
openAxisAlignment: 0.0,
maxWidth: isPortrait ? 600 : 500,
height: compassExpanded ? 62 : 48,
debounceDelay: const Duration(milliseconds: 1500),
onQueryChanged: (inQuery) {
String query = inQuery.trim();
if (offlineMode || query == null || query == '' || query == '#') {
return;
}
if (query.startsWith('#')) {}
setState(() {
loadingSearch = true;
});
// Call your model, bloc, controller here.
Future.delayed(const Duration(seconds: 3), () {
setState(() {
loadingSearch = false;
});
});
},
// Specify a custom transition to be used for
// animating between opened and closed stated.
transition: ExpandingFloatingSearchBarTransition(),
accentColor: Theme.of(context).accentColor,
actions: [
FloatingSearchBarAction.searchToClear(
showIfClosed: false,
),
FloatingSearchBarAction( //This one is to check current GPS state
showIfOpened: false,
child: StreamBuilder<Object>(
stream: Stream.periodic(Duration(seconds: 5), (x) => x),
builder: (context, _) {
return FutureBuilder(
future: Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.medium,
timeLimit: Duration(seconds: 5),
),
builder: (context, compassDir) {
if (compassDir.hasError) {
//print('failure');
return Padding(
padding: const EdgeInsets.only(
right: 7,
bottom: 2,
),
child: Icon(
Icons.wrong_location,
color: Colors.red,
),
);
} else {
return Container(width: 0, height: 0);
}
});
}),
),
FloatingSearchBarAction(
showIfOpened: false,
child: DescribedFeatureOverlay(
featureId: 'toggleLocationSnapping',
tapTarget: Icon(Icons.control_camera),
title: Text('Location Snapping'),
description: Text(
'This button toggles the map mode between snapping (default) and free. When the icon shown is a circle with a line across it, tap it to switch to free mode, and you\'ll be able to move, zoom and rotate the map freely. In snapping mode, you will be snapped to your current location and orientation, and you\'ll also be unable to pan, rotate or zoom (usually).'),
//overflowMode: OverflowMode.extendBackground,
backgroundColor: Theme.of(context).primaryColor,
targetColor: Colors.white,
textColor: Colors.white,
child: CircularButton(
icon: Icon(moveMapToUser
? Icons.location_disabled
: Icons.my_location),
onPressed: () {
setState(() {
moveMapToUser = !moveMapToUser;
rotateMapToUser = false;
});
},
),
),
),
FloatingSearchBarAction(
showIfOpened: false,
child: Visibility(
visible: moveMapToUser,
child: DescribedFeatureOverlay(
featureId: 'rotationAndNorth',
tapTarget: Icon(Icons.screen_rotation),
title: Text('Rotation & Panning Mode'),
description: Text(
'This button has three states. When showing an upward facing arrow in free mode, tap it to orientate the map north, and remain in free mode. When showing a lock symbol in snapping mode, tap it to renable automatic rotation and prevent zooming. When showing an unlock symbol in snapping mode, tap it to allow rotation and zooming, but still prevent panning away from your current location.'),
//overflowMode: OverflowMode.extendBackground,
backgroundColor: Theme.of(context).primaryColor,
targetColor: Colors.white,
textColor: Colors.white,
child: CircularButton(
icon: Icon(rotateMapToUser ? Icons.lock_open : Icons.lock),
onPressed: () {
setState(() {
rotateMapToUser = !rotateMapToUser;
if (!rotateMapToUser) {
controller.rotate(0);
}
});
},
),
),
),
),
FloatingSearchBarAction(
showIfOpened: false,
child: Visibility(
visible: !moveMapToUser,
child: CircularButton(
icon: Icon(Icons.north),
onPressed: () {
setState(() {
controller.rotate(0);
});
},
),
),
),
FloatingSearchBarAction(
showIfOpened: false,
child: Visibility(
visible: !offlineMode,
child: DescribedFeatureOverlay(
featureId: 'refresh',
tapTarget: Icon(Icons.refresh),
title: Text('Refresh'),
description: Text(
'Tap this button to search the area visible on your device for GeoTags after panning the map.'),
//overflowMode: OverflowMode.extendBackground,
backgroundColor: Theme.of(context).primaryColor,
targetColor: Colors.white,
textColor: Colors.white,
child: CircularButton(
icon: const Icon(Icons.refresh),
onPressed: () {
//setState(() {
paintGeoTags(
true,
() => setState(() {
loadingSearch = true;
}),
() => setState(() {
loadingSearch = false;
}));
//});
}),
),
),
),
FloatingSearchBarAction( //Profile Pic icon
showIfOpened: !compassExpanded, //Don't worry about compassExpanded
child: Visibility(
visible: !offlineMode, //Should update on setState
child: DescribedFeatureOverlay(
featureId: 'profile',
tapTarget: Icon(Icons.account_circle),
title: Text('Public Profile'),
description: Text(
'Tap this button to view your public profile, and view information such as rank, total points and various other information. You can manage your profile by tapping the settings cog icon in that screen, or by choosing Manage My Profile from the menu. The colored border represents the color of your current rank, and the point will always face north. You can hold down on this icon to toggle the visibility of the compass point and to expand or reduce the height of the search bar. \n\nAfter you\'ve tapped the icon above to move to the next button introduction, tap the menu button in the top left, and your introduction will continue there!'),
overflowMode: OverflowMode.extendBackground,
backgroundColor: Theme.of(context).primaryColor,
targetColor: Colors.white,
textColor: Colors.white,
child: Hero(
tag: 'profileImage',
child: GestureDetector(
onTap: () => Navigator.pushNamed(
context,
ProfileScreen.routeName,
arguments: ScreenArguments(
globals.userData.displayName,
//'test user',
FirebaseFirestore.instance
.doc('users/' + globals.userData.uid),
true,
),
),
onLongPressStart: (e) {
setState(() {
compassExpanding = true;
});
},
onLongPressEnd: (e) => {
setState(() {
compassExpanded = !compassExpanded;
compassExpanding = false;
}),
//showLogoutAlert(context)
},
child: StreamBuilder<Object>(
stream: FlutterCompass.events,
builder: (context, compassDir) {
if (snapshot.hasError) {
return Center(
child: Text('Error: ${snapshot.error}'));
} else {
if (snapshot.data == null) {
return Container();
}
}
return Padding(
padding: const EdgeInsets.only(
left: 4,
),
child: Container(
width: 41,
height: 41,
transform: Matrix4Transform()
.rotateDegrees(
double.parse(((compassDir.data) ?? 0)
.toString()) *
-1 +
45,
origin: Offset(41 / 2, 41 / 2))
.matrix4,
decoration: ShapeDecoration(
//shape: BoxShape.circle,
shape: CustomRoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(21),
bottomRight: Radius.circular(21),
topRight: Radius.circular(21),
topLeft: Radius.circular(
compassExpanded ? 0 : 21),
),
leftSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
bottomLeftCornerSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
rightSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
bottomRightCornerSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
bottomSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
topSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
topRightCornerSide: BorderSide(
width: 2,
color: changeMedalColor(
snapshot.data.points),
),
),
color: //!compassExpanding
/*?*/ changeMedalColor(
snapshot.data.points)
//: Theme.of(context).accentColor,
/*border: Border.all(
width: 2,
color: changeMedalColor(snapshot.data.points),
),*/
),
//duration: Duration(milliseconds: 250),
child: SizedBox(
child: !compassExpanding
? Transform.rotate(
angle: vmath.radians(double.parse(
((compassDir.data) ?? 0)
.toString()) -
45),
child: CircleAvatar(
minRadius: 1000,
maxRadius: 5000,
backgroundImage: NetworkImage(
globals.userData.photoURL,
),
backgroundColor: changeMedalColor(
snapshot.data.points),
),
)
: Transform.rotate(
angle: vmath.radians(double.parse(
((compassDir.data) ?? 0)
.toString()) -
45),
child: Icon(Icons.expand,
color: Colors.white),
),
),
),
);
}),
),
),
),
),
),
],
builder: (context, transition) {
return ClipRRect(
//borderRadius: BorderRadius.circular(8),
child: Material(
color: Colors.white,
//elevation: 4.0,
child: !offlineMode
? Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Search for a GeoTag by name, approximate location. To search by author, use \'#\' in front of the search query.',
textAlign: TextAlign.center,
),
),
SizedBox(height: 15),
Column(
mainAxisSize: MainAxisSize.min,
children: Colors.accents.map((color) {
return Container(height: 112, color: color);
}).toList(),
),
],
)
: Column(
children: [
Center(
child: Icon(
Icons.cloud_off,
size: 40,
),
),
SizedBox(height: 10),
Text(
'You\'re in Offline Mode',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
'Whilst in Offline Mode, the search feature is disabled.'),
],
),
),
);
},
),
);
}
Code inside drawer to update variable:
DescribedFeatureOverlay(
featureId: 'offlineMode',
tapTarget: Icon(Icons.file_download),
title: Text('Offline Mode'),
description: Text(
'One of the most important and complex features of this app. Allows for fully offline functioning, including downloading large areas of maps. You can see more detail about it\'s features and drawbacks by tapping this menu item after the introduction is complete.'),
overflowMode: OverflowMode.extendBackground,
backgroundColor: Theme.of(widget.mainContext).primaryColor,
targetColor: Colors.white,
textColor: Colors.white,
child: ListTile(
title: Text(offlineMode
? 'Offline Manager'
: (internetConnected
? 'Enable Offline Mode'
: 'Offline Mode Disabled')),
onTap: offlineMode
? () {
Navigator.pushNamed(
widget.mainContext, OfflineManagerScreen.routeName);
}
: (internetConnected //Don't worry about internetConnected
? () {
showAlert(
context,
widget.mainContext,
'Offline Mode',
<Widget>[
//Some text widgets
],
'Agree & Enable Offline Mode',
'Cancel', () {
StorageManager.saveData('offlineMode', 'true');
setState(() {
offlineMode = true; //Should Update
});
if (!Directory(globals.saveDir.path + '/tiles')
.existsSync()) {
Directory(globals.saveDir.path + '/tiles')
.createSync(recursive: true);
}
});
}
: null),
leading: Icon(offlineMode
? Icons.offline_pin
: (internetConnected
? Icons.file_download
: Icons.cloud_off)),
trailing: Switch(
value: offlineMode,
onChanged: offlineMode && internetConnected
? (bool newVal) {
showAlert(
context,
widget.mainContext,
'Disable Offline Mode',
<Widget>[
const Text(
'Disabling Offline Mode will delete all saved tiles, and resync information with the server. Any GeoTags tagged will be uploaded, and the appropriate point value will be added to your account. Are you sure you want to disable Offline Mode?',
textAlign: TextAlign.justify,
),
],
'Disable',
'Stay Offline', () {
setState(() {
offlineMode = false; //Should update
});
StorageManager.saveData('offlineMode', 'false');
Directory(globals.saveDir.path + '/tiles')
.deleteSync(recursive: true);
setState(() {
totalSize = 0;
filesList = [];
sizeList = [];
});
});
}
: null,
activeColor: Theme.of(widget.mainContext).primaryColorLight,
),
),
),
If you haven't already realised, this is my first big Flutter project, so tips about other things are also appreciated!
Widget is updating on inner widget. but all state is not being changed.
you should pass state to all your inner widgets like this (in showalert in your case)
StatefulBuilder(builder: (context, newState) {
return
showAlert(
context,
widget.mainContext,
'Offline Mode',
<Widget>[
//Some text widgets
],
'Agree & Enable Offline Mode',
'Cancel', () {
StorageManager.saveData('offlineMode', 'true');
newState(() {
offlineMode = true; //Should Update
});
if (!Directory(globals.saveDir.path + '/tiles')
.existsSync()) {
Directory(globals.saveDir.path + '/tiles')
.createSync(recursive: true);
}
});
});
Please try this! I didnt test, but the logic is you should pass the state.
Maybe you should wrap listtile with new state..