Diff
checker
Text
Text
Bilder
Dokumente
Excel
Ordner
Legal
Enterprise
Desktop-App
Preise
Einloggen
Diffchecker Desktop herunterladen
Texte vergleichen
Finde den Unterschied zwischen zwei Textdateien
Werkzeuge
Verlauf
Live-Editor
Gleiches ausblenden
Zeilenumbruch aus
Ansicht
Zweispaltig
Einspaltig
Vergleichsgenauigkeit
Intelligent
Wort
Zeichen
Syntaxhervorhebung
Syntax auswählen
Ignorieren
Text umwandeln
Zur ersten Änderung
Eingabe bearbeiten
Diffchecker Desktop
Der sicherste Weg, Diffchecker zu nutzen. Hol dir die Desktop-App: Deine Diffs verlassen nie deinen Computer!
Desktop holen
github issue
Erstellt
vor 6 Jahren
Diff läuft nie ab
Löschen
Exportieren
Teilen
Erklären
142 Entfernungen
Zeilen
Gesamt
Entfernt
Zeichen
Gesamt
Entfernt
Um diese Funktion weiterhin zu nutzen, aktualisiere auf
Diff
checker
Pro
Preise anzeigen
409 Zeilen
Kopieren
14 Hinzufügungen
Zeilen
Gesamt
Hinzugefügt
Zeichen
Gesamt
Hinzugefügt
Um diese Funktion weiterhin zu nutzen, aktualisiere auf
Diff
checker
Pro
Preise anzeigen
401 Zeilen
Kopieren
import 'dart:async';
import 'dart:async';
import 'dart:convert';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/cupertino.dart';
Kopieren
Kopiert
Kopieren
Kopiert
import '
package:flutter_firebase/data_models/
countries.dart';
import '
countries.dart';
import '
package:flutter_firebase/firebase/auth/phone_auth/
code.dart';
import '
code.dart';
import '
package:flutter_firebase/firebase/auth/phone_auth/
verify.dart';
import '
verify.dart';
import 'package:flutter_firebase/utils/constants.dart';
import 'code.dart' show FirebasePhoneAuth
;
import 'code.dart' show FirebasePhoneAuth
, phoneAuthState
;
import '
widgets.dart';
import '
../../../utils/
widgets.dart';
/*
/*
* PhoneAuthUI - this file contains whole ui and controllers of ui
* PhoneAuthUI - this file contains whole ui and controllers of ui
* Background code will be in other class
* Background code will be in other class
* This code can be easily re-usable with any other service type, as UI part and background handling are completely from different sources
* This code can be easily re-usable with any other service type, as UI part and background handling are completely from different sources
* code.dart - Class to control background processes in phone auth verification using Firebase
* code.dart - Class to control background processes in phone auth verification using Firebase
*/
*/
// ignore: must_be_immutable
// ignore: must_be_immutable
class PhoneAuthGetPhone extends StatefulWidget {
class PhoneAuthGetPhone extends StatefulWidget {
/*
/*
* cardBackgroundColor & logo values will be passed to the constructor
* cardBackgroundColor & logo values will be passed to the constructor
* here we access these params in the _PhoneAuthState using "widget"
* here we access these params in the _PhoneAuthState using "widget"
*/
*/
Color cardBackgroundColor = Color(0xFF6874C2);
Color cardBackgroundColor = Color(0xFF6874C2);
Kopieren
Kopiert
Kopieren
Kopiert
String logo = Assets.firebase;
String appName = "Awesome app";
String appName = "Awesome app";
@override
@override
_PhoneAuthGetPhoneState createState() => _PhoneAuthGetPhoneState();
_PhoneAuthGetPhoneState createState() => _PhoneAuthGetPhoneState();
}
}
class _PhoneAuthGetPhoneState extends State<PhoneAuthGetPhone> {
class _PhoneAuthGetPhoneState extends State<PhoneAuthGetPhone> {
/*
/*
* _height & _width:
* _height & _width:
* will be calculated from the MediaQuery of widget's context
* will be calculated from the MediaQuery of widget's context
* countries:
* countries:
* will be a list of Country model, Country model contains name, dialCode, flag and code for various countries
* will be a list of Country model, Country model contains name, dialCode, flag and code for various countries
* and below params are all related to StreamBuilder
* and below params are all related to StreamBuilder
*/
*/
double _height, _width, _fixedPadding;
double _height, _width, _fixedPadding;
List<Country> countries = [];
List<Country> countries = [];
StreamController<List<Country>> _countriesStreamController;
StreamController<List<Country>> _countriesStreamController;
Stream<List<Country>> _countriesStream;
Stream<List<Country>> _countriesStream;
Sink<List<Country>> _countriesSink;
Sink<List<Country>> _countriesSink;
/*
/*
* _searchCountryController - This will be used as a controller for listening to the changes what the user is entering
* _searchCountryController - This will be used as a controller for listening to the changes what the user is entering
* and it's listener will take care of the rest
* and it's listener will take care of the rest
*/
*/
TextEditingController _searchCountryController = TextEditingController();
TextEditingController _searchCountryController = TextEditingController();
TextEditingController _phoneNumberController = TextEditingController();
TextEditingController _phoneNumberController = TextEditingController();
/*
/*
* This will be the index, we will modify each time the user selects a new country from the dropdown list(dialog),
* This will be the index, we will modify each time the user selects a new country from the dropdown list(dialog),
* As a default case, we are using India as default country, index = 31
* As a default case, we are using India as default country, index = 31
*/
*/
Kopieren
Kopiert
Kopieren
Kopiert
int _selectedCountryIndex =
100
;
int _selectedCountryIndex =
31
;
bool _isCountriesDataFormed = false;
bool _isCountriesDataFormed = false;
@override
@override
void initState() {
void initState() {
super.initState();
super.initState();
}
}
@override
@override
void dispose() {
void dispose() {
// While disposing the widget, we should close all the streams and controllers
// While disposing the widget, we should close all the streams and controllers
// Disposing Stream components
// Disposing Stream components
// _countriesSink.close();
// _countriesSink.close();
// _countriesStreamController.close();
// _countriesStreamController.close();
// Disposing _countriesSearchController
// Disposing _countriesSearchController
_searchCountryController.dispose();
_searchCountryController.dispose();
super.dispose();
super.dispose();
}
}
Future<List<Country>> loadCountriesJson() async {
Future<List<Country>> loadCountriesJson() async {
// Cleaning up the countries list before we put our data in it
// Cleaning up the countries list before we put our data in it
countries.clear();
countries.clear();
// Fetching the json file, decoding it and storing each object as Country in countries(list)
// Fetching the json file, decoding it and storing each object as Country in countries(list)
Kopieren
Kopiert
Kopieren
Kopiert
var value = await DefaultAssetBundle.of(context)
var value = await DefaultAssetBundle.of(context)
.loadString("
country_phone_codes.json");
.loadString("
data/
country_phone_codes.json");
var countriesJson = json.decode(value);
var countriesJson = json.decode(value);
for (var country in countriesJson) {
for (var country in countriesJson) {
countries.add(Country.fromJson(country));
countries.add(Country.fromJson(country));
}
}
//Finally adding the initial data to the _countriesSink
//Finally adding the initial data to the _countriesSink
// _countriesSink.add(countries);
// _countriesSink.add(countries);
return countries;
return countries;
}
}
@override
@override
Widget build(BuildContext context) {
Widget build(BuildContext context) {
// Fetching height & width parameters from the MediaQuery
// Fetching height & width parameters from the MediaQuery
// _logoPadding will be a constant, scaling it according to device's size
// _logoPadding will be a constant, scaling it according to device's size
_height = MediaQuery.of(context).size.height;
_height = MediaQuery.of(context).size.height;
_width = MediaQuery.of(context).size.width;
_width = MediaQuery.of(context).size.width;
_fixedPadding = _height * 0.025;
_fixedPadding = _height * 0.025;
WidgetsBinding.instance.addPostFrameCallback((Duration d) {
WidgetsBinding.instance.addPostFrameCallback((Duration d) {
if (countries.length < 240) {
if (countries.length < 240) {
loadCountriesJson().whenComplete(() {
loadCountriesJson().whenComplete(() {
setState(() => _isCountriesDataFormed = true);
setState(() => _isCountriesDataFormed = true);
});
});
}
}
});
});
/* Scaffold: Using a Scaffold widget as parent
/* Scaffold: Using a Scaffold widget as parent
* SafeArea: As a precaution - wrapping all child descendants in SafeArea, so that even notched phones won't loose data
* SafeArea: As a precaution - wrapping all child descendants in SafeArea, so that even notched phones won't loose data
* Center: As we are just having Card widget - making it to stay in Center would really look good
* Center: As we are just having Card widget - making it to stay in Center would really look good
* SingleChildScrollView: There can be chances arising where
* SingleChildScrollView: There can be chances arising where
*/
*/
return Scaffold(
return Scaffold(
backgroundColor: Colors.white.withOpacity(0.95),
backgroundColor: Colors.white.withOpacity(0.95),
body: SafeArea(
body: SafeArea(
child: Center(
child: Center(
child: SingleChildScrollView(
child: SingleChildScrollView(
child: _getBody(),
child: _getBody(),
),
),
),
),
),
),
);
);
}
}
/*
/*
* Widget hierarchy ->
* Widget hierarchy ->
* Scaffold -> SafeArea -> Center -> SingleChildScrollView -> Card()
* Scaffold -> SafeArea -> Center -> SingleChildScrollView -> Card()
* Card -> FutureBuilder -> Column()
* Card -> FutureBuilder -> Column()
*/
*/
Widget _getBody() => Card(
Widget _getBody() => Card(
Kopieren
Kopiert
Kopieren
Kopiert
color: widget.cardBackgroundColor,
color: widget.cardBackgroundColor,
elevation: 2.0,
elevation: 2.0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
child: SizedBox(
child: SizedBox(
height: _height * 8 / 10,
height: _height * 8 / 10,
width: _width * 8 / 10,
width: _width * 8 / 10,
Kopieren
Kopiert
Kopieren
Kopiert
/*
/*
* Fetching countries data from JSON file and storing them in a List of Country model:
* Fetching countries data from JSON file and storing them in a List of Country model:
* ref:- List<Country> countries
* ref:- List<Country> countries
* Until the data is fetched, there will be CircularProgressIndicator showing, describing something is on it's way
* Until the data is fetched, there will be CircularProgressIndicator showing, describing something is on it's way
* (Previously there was a FutureBuilder rather that the below thing, which created unexpected exceptions and had to be removed)
* (Previously there was a FutureBuilder rather that the below thing, which created unexpected exceptions and had to be removed)
*/
*/
Kopieren
Kopiert
Kopieren
Kopiert
child: _isCountriesDataFormed
child: _isCountriesDataFormed
? _getColumnBody()
? _getColumnBody()
: Center(child: CircularProgressIndicator()),
: Center(child: CircularProgressIndicator()),
),
),
);
);
Widget _getColumnBody() => Column(
Widget _getColumnBody() => Column(
Kopieren
Kopiert
Kopieren
Kopiert
mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
children: <Widget>[
// Logo: scaling to occupy 2 parts of 10 in the whole height of device
// Logo: scaling to occupy 2 parts of 10 in the whole height of device
Padding(
padding: EdgeInsets.all(_fixedPadding),
child: PhoneAuthWidgets.getLogo(
logoPath: widget.logo, height: _height * 0.2),
),
Kopieren
Kopiert
Kopieren
Kopiert
// AppName:
// AppName:
Text(widget.appName,
Text(widget.appName,
textAlign: TextAlign.center,
textAlign: TextAlign.center,
style: TextStyle(
style: TextStyle(
color: Colors.white,
color: Colors.white,
fontSize: 24.0,
fontSize: 24.0,
fontWeight: FontWeight.w700)),
fontWeight: FontWeight.w700)),
Kopieren
Kopiert
Kopieren
Kopiert
Padding(
Padding(
padding: EdgeInsets.only(top: _fixedPadding, left: _fixedPadding),
padding: EdgeInsets.only(top: _fixedPadding, left: _fixedPadding),
child: PhoneAuthWidgets.subTitle('Select your country'),
child: PhoneAuthWidgets.subTitle('Select your country'),
),
),
Kopieren
Kopiert
Kopieren
Kopiert
/*
/*
* Select your country, this will be a custom DropDown menu, rather than just as a dropDown
* Select your country, this will be a custom DropDown menu, rather than just as a dropDown
* onTap of this, will show a Dialog asking the user to select country they reside,
* onTap of this, will show a Dialog asking the user to select country they reside,
* according to their selection, prefix will change in the PhoneNumber TextFormField
* according to their selection, prefix will change in the PhoneNumber TextFormField
*/
*/
Kopieren
Kopiert
Kopieren
Kopiert
Padding(
Padding(
padding: EdgeInsets.only(left: _fixedPadding, right: _fixedPadding),
padding: EdgeInsets.only(left: _fixedPadding, right: _fixedPadding),
child: PhoneAuthWidgets.selectCountryDropDown(
child: PhoneAuthWidgets.selectCountryDropDown(
countries[_selectedCountryIndex], showCountries),
countries[_selectedCountryIndex], showCountries),
),
),
Kopieren
Kopiert
Kopieren
Kopiert
// Subtitle for Enter your phone
// Subtitle for Enter your phone
Padding(
Padding(
padding: EdgeInsets.only(top: 10.0, left: _fixedPadding),
padding: EdgeInsets.only(top: 10.0, left: _fixedPadding),
child: PhoneAuthWidgets.subTitle('Enter your phone'),
child: PhoneAuthWidgets.subTitle('Enter your phone'),
),
),
// PhoneNumber TextFormFields
// PhoneNumber TextFormFields
Padding(
Padding(
padding: EdgeInsets.only(
padding: EdgeInsets.only(
left: _fixedPadding,
left: _fixedPadding,
right: _fixedPadding,
right: _fixedPadding,
bottom: _fixedPadding),
bottom: _fixedPadding),
child: PhoneAuthWidgets.phoneNumberField(_phoneNumberController,
child: PhoneAuthWidgets.phoneNumberField(_phoneNumberController,
countries[_selectedCountryIndex].dialCode),
countries[_selectedCountryIndex].dialCode),
),
),
Kopieren
Kopiert
Kopieren
Kopiert
/*
/*
* Some informative text
* Some informative text
*/
*/
Kopieren
Kopiert
Kopieren
Kopiert
Row(
Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
children: <Widget>[
SizedBox(width: _fixedPadding),
SizedBox(width: _fixedPadding),
Icon(Icons.info, color: Colors.white, size: 20.0),
Icon(Icons.info, color: Colors.white, size: 20.0),
SizedBox(width: 10.0),
SizedBox(width: 10.0),
Expanded(
Expanded(
child: RichText(
child: RichText(
text: TextSpan(children: [
text: TextSpan(children: [
TextSpan(
TextSpan(
text: 'We will send ',
text: 'We will send ',
style: TextStyle(
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.w400)),
color: Colors.white, fontWeight: FontWeight.w400)),
TextSpan(
TextSpan(
text: 'One Time Password',
text: 'One Time Password',
style: TextStyle(
style: TextStyle(
color: Colors.white,
color: Colors.white,
fontSize: 16.0,
fontSize: 16.0,
fontWeight: FontWeight.w700)),
fontWeight: FontWeight.w700)),
TextSpan(
TextSpan(
text: ' to this mobile number',
text: ' to this mobile number',
style: TextStyle(
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.w400)),
color: Colors.white, fontWeight: FontWeight.w400)),
])),
])),
Kopieren
Kopiert
Kopieren
Kopiert
),
SizedBox(width: _fixedPadding),
],
),
),
Kopieren
Kopiert
Kopieren
Kopiert
SizedBox(width: _fixedPadding),
],
),
Kopieren
Kopiert
Kopieren
Kopiert
/*
/*
* Button: OnTap of this, it appends the dial code and the phone number entered by the user to send OTP,
* Button: OnTap of this, it appends the dial code and the phone number entered by the user to send OTP,
* knowing once the OTP has been sent to the user - the user will be navigated to a new Screen,
* knowing once the OTP has been sent to the user - the user will be navigated to a new Screen,
* where is asked to enter the OTP he has received on his mobile (or) wait for the system to automatically detect the OTP
* where is asked to enter the OTP he has received on his mobile (or) wait for the system to automatically detect the OTP
*/
*/
Kopieren
Kopiert
Kopieren
Kopiert
SizedBox(height: _fixedPadding * 1.5),
SizedBox(height: _fixedPadding * 1.5),
RaisedButton(
RaisedButton(
elevation: 16.0,
elevation: 16.0,
onPressed: startPhoneAuth,
onPressed: startPhoneAuth,
child: Padding(
child: Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(8.0),
child: Text(
child: Text(
'SEND OTP',
'SEND OTP',
style: TextStyle(
style: TextStyle(
color: widget.cardBackgroundColor, fontSize: 18.0),
color: widget.cardBackgroundColor, fontSize: 18.0),
),
),
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0)),
),
),
Kopieren
Kopiert
Kopieren
Kopiert
],
),
)
;
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0)),
)
,
],
)
;
/*
/*
* This will trigger a dialog, that will let the user to select their country, so the dialcode
* This will trigger a dialog, that will let the user to select their country, so the dialcode
* of their country will be automatically added at the end
* of their country will be automatically added at the end
*/
*/
showCountries() {
showCountries() {
/*
/*
* Initialising components required for StreamBuilder
* Initialising components required for StreamBuilder
* We will not be using _countriesStreamController anywhere, but just to initialize Stream & Sink from that
* We will not be using _countriesStreamController anywhere, but just to initialize Stream & Sink from that
* _countriesStream will give us the data what we need(output) - that will be used in StreamBuilder widget
* _countriesStream will give us the data what we need(output) - that will be used in StreamBuilder widget
* _countriesSink is the place where we send the data(input)
* _countriesSink is the place where we send the data(input)
*/
*/
_countriesStreamController = StreamController();
_countriesStreamController = StreamController();
_countriesStream = _countriesStreamController.stream;
_countriesStream = _countriesStreamController.stream;
_countriesSink = _countriesStreamController.sink;
_countriesSink = _countriesStreamController.sink;
_countriesSink.add(countries);
_countriesSink.add(countries);
_searchCountryController.addListener(searchCountries);
_searchCountryController.addListener(searchCountries);
showDialog(
showDialog(
context: context,
context: context,
builder: (BuildContext context) => searchAndPickYourCountryHere(),
builder: (BuildContext context) => searchAndPickYourCountryHere(),
barrierDismissible: false);
barrierDismissible: false);
}
}
/*
/*
* This will be the listener for searching the query entered by user for their country, (dialog pop-up),
* This will be the listener for searching the query entered by user for their country, (dialog pop-up),
* searches for the query and returns list of countries matching the query by adding the results to the sink of _countriesStream
* searches for the query and returns list of countries matching the query by adding the results to the sink of _countriesStream
*/
*/
searchCountries() {
searchCountries() {
String query = _searchCountryController.text;
String query = _searchCountryController.text;
if (query.length == 0 || query.length == 1) {
if (query.length == 0 || query.length == 1) {
if(!_countriesStreamController.isClosed)
if(!_countriesStreamController.isClosed)
_countriesSink.add(countries);
_countriesSink.add(countries);
// print('added all countries again');
// print('added all countries again');
} else if (query.length >= 2 && query.length <= 5) {
} else if (query.length >= 2 && query.length <= 5) {
List<Country> searchResults = [];
List<Country> searchResults = [];
searchResults.clear();
searchResults.clear();
countries.forEach((Country c) {
countries.forEach((Country c) {
if (c.toString().toLowerCase().contains(query.toLowerCase()))
if (c.toString().toLowerCase().contains(query.toLowerCase()))
searchResults.add(c);
searchResults.add(c);
});
});
_countriesSink.add(searchResults);
_countriesSink.add(searchResults);
// print('added few countries based on search ${searchResults.length}');
// print('added few countries based on search ${searchResults.length}');
} else {
} else {
//No results
//No results
List<Country> searchResults = [];
List<Country> searchResults = [];
_countriesSink.add(searchResults);
_countriesSink.add(searchResults);
// print('no countries added');
// print('no countries added');
}
}
}
}
/*
/*
* Child for Dialog
* Child for Dialog
* Contents:
* Contents:
* SearchCountryTextFormField
* SearchCountryTextFormField
* StreamBuilder
* StreamBuilder
* - Shows a list of countries
* - Shows a list of countries
*/
*/
Widget searchAndPickYourCountryHere() => WillPopScope(
Widget searchAndPickYourCountryHere() => WillPopScope(
Kopieren
Kopiert
Kopieren
Kopiert
onWillPop: () => Future.value(false),
onWillPop: () => Future.value(false),
child: Dialog(
child: Dialog(
key: Key('SearchCountryDialog'),
key: Key('SearchCountryDialog'),
elevation: 8.0,
elevation: 8.0,
shape:
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
child: Container(
child: Container(
margin: const EdgeInsets.all(5.0),
margin: const EdgeInsets.all(5.0),
child: Column(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
children: <Widget>[
// TextFormField for searching country
// TextFormField for searching country
PhoneAuthWidgets.searchCountry(_searchCountryController),
PhoneAuthWidgets.searchCountry(_searchCountryController),
Kopieren
Kopiert
Kopieren
Kopiert
// Returns a list of Countries that will change according to the search query
// Returns a list of Countries that will change according to the search query
SizedBox(
SizedBox(
height: 300.0,
height: 300.0,
child: StreamBuilder<List<Country>>(
child: StreamBuilder<List<Country>>(
//key: Key('Countries-StreamBuilder'),
//key: Key('Countries-StreamBuilder'),
stream: _countriesStream,
stream: _countriesStream,
builder: (context, snapshot) {
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.hasData) {
// print(snapshot.data.length);
// print(snapshot.data.length);
return snapshot.data.length == 0
return snapshot.data.length == 0
? Center(
? Center(
child: Text('Your search found no results',
child: Text('Your search found no results',
style: TextStyle(fontSize: 16.0)),
style: TextStyle(fontSize: 16.0)),
)
)
: ListView.builder(
: ListView.builder(
itemCount: snapshot.data.length,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int i) =>
itemBuilder: (BuildContext context, int i) =>
PhoneAuthWidgets.selectableWidget(
PhoneAuthWidgets.selectableWidget(
snapshot.data[i],
snapshot.data[i],
(Country c) => selectThisCountry(c)),
(Country c) => selectThisCountry(c)),
);
);
} else if (snapshot.hasError)
} else if (snapshot.hasError)
return Center(
return Center(
child: Text('Seems, there is an error',
child: Text('Seems, there is an error',
style: TextStyle(fontSize: 16.0)),
style: TextStyle(fontSize: 16.0)),
);
);
return Center(child: CircularProgressIndicator());
return Center(child: CircularProgressIndicator());
}),
}),
)
)
],
],
),
),
),
),
Kopieren
Kopiert
Kopieren
Kopiert
)
;
)
,
),
)
;
/*
/*
* This callback is triggered when the user taps(selects) on any country from the available list in dialog
* This callback is triggered when the user taps(selects) on any country from the available list in dialog
* Resets the search value
* Resets the search value
* Close the stream & sink
* Close the stream & sink
* Updates the selected Country and adds dialCode as prefix according to the user's selection
* Updates the selected Country and adds dialCode as prefix according to the user's selection
*/
*/
void selectThisCountry(Country country) {
void selectThisCountry(Country country) {
print(country);
print(country);
_searchCountryController.clear();
_searchCountryController.clear();
Navigator.of(context).pop();
Navigator.of(context).pop();
Future.delayed(Duration(milliseconds: 10)).whenComplete(() {
Future.delayed(Duration(milliseconds: 10)).whenComplete(() {
_countriesStreamController.close();
_countriesStreamController.close();
_countriesSink.close();
_countriesSink.close();
setState(() {
setState(() {
_selectedCountryIndex = countries.indexOf(country);
_selectedCountryIndex = countries.indexOf(country);
});
});
});
});
}
}
startPhoneAuth() {
startPhoneAuth() {
FirebasePhoneAuth.instantiate(
FirebasePhoneAuth.instantiate(
phoneNumber: countries[_selectedCountryIndex].dialCode +
phoneNumber: countries[_selectedCountryIndex].dialCode +
_phoneNumberController.text);
_phoneNumberController.text);
Navigator.of(context).pushReplacement(CupertinoPageRoute(
Navigator.of(context).pushReplacement(CupertinoPageRoute(
Kopieren
Kopiert
Kopieren
Kopiert
builder: (BuildContext context) => PhoneAuthVerify()));
builder: (BuildContext context) => PhoneAuthVerify()));
// FirebasePhoneAuth.stateStream.listen((state) {
// FirebasePhoneAuth.stateStream.listen((state) {
//
//
// print(state);
// print(state);
//
//
// if (state == PhoneAuthState.CodeSent) {
// if (state == PhoneAuthState.CodeSent) {
// Navigator.of(context).pushReplacement(CupertinoPageRoute(
// Navigator.of(context).pushReplacement(CupertinoPageRoute(
// builder: (BuildContext context) => PhoneAuthVerify()));
// builder: (BuildContext context) => PhoneAuthVerify()));
// }
// }
// if (state == PhoneAuthState.Failed)
// if (state == PhoneAuthState.Failed)
// debugPrint("Seems there is an issue with it");
// debugPrint("Seems there is an issue with it");
// });
// });
}
}
}
}
Gespeicherte Diffs
Originaltext
Datei öffnen
import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_firebase/data_models/countries.dart'; import 'package:flutter_firebase/firebase/auth/phone_auth/code.dart'; import 'package:flutter_firebase/firebase/auth/phone_auth/verify.dart'; import 'package:flutter_firebase/utils/constants.dart'; import 'code.dart' show FirebasePhoneAuth, phoneAuthState; import '../../../utils/widgets.dart'; /* * PhoneAuthUI - this file contains whole ui and controllers of ui * Background code will be in other class * This code can be easily re-usable with any other service type, as UI part and background handling are completely from different sources * code.dart - Class to control background processes in phone auth verification using Firebase */ // ignore: must_be_immutable class PhoneAuthGetPhone extends StatefulWidget { /* * cardBackgroundColor & logo values will be passed to the constructor * here we access these params in the _PhoneAuthState using "widget" */ Color cardBackgroundColor = Color(0xFF6874C2); String logo = Assets.firebase; String appName = "Awesome app"; @override _PhoneAuthGetPhoneState createState() => _PhoneAuthGetPhoneState(); } class _PhoneAuthGetPhoneState extends State<PhoneAuthGetPhone> { /* * _height & _width: * will be calculated from the MediaQuery of widget's context * countries: * will be a list of Country model, Country model contains name, dialCode, flag and code for various countries * and below params are all related to StreamBuilder */ double _height, _width, _fixedPadding; List<Country> countries = []; StreamController<List<Country>> _countriesStreamController; Stream<List<Country>> _countriesStream; Sink<List<Country>> _countriesSink; /* * _searchCountryController - This will be used as a controller for listening to the changes what the user is entering * and it's listener will take care of the rest */ TextEditingController _searchCountryController = TextEditingController(); TextEditingController _phoneNumberController = TextEditingController(); /* * This will be the index, we will modify each time the user selects a new country from the dropdown list(dialog), * As a default case, we are using India as default country, index = 31 */ int _selectedCountryIndex = 100; bool _isCountriesDataFormed = false; @override void initState() { super.initState(); } @override void dispose() { // While disposing the widget, we should close all the streams and controllers // Disposing Stream components // _countriesSink.close(); // _countriesStreamController.close(); // Disposing _countriesSearchController _searchCountryController.dispose(); super.dispose(); } Future<List<Country>> loadCountriesJson() async { // Cleaning up the countries list before we put our data in it countries.clear(); // Fetching the json file, decoding it and storing each object as Country in countries(list) var value = await DefaultAssetBundle.of(context) .loadString("data/country_phone_codes.json"); var countriesJson = json.decode(value); for (var country in countriesJson) { countries.add(Country.fromJson(country)); } //Finally adding the initial data to the _countriesSink // _countriesSink.add(countries); return countries; } @override Widget build(BuildContext context) { // Fetching height & width parameters from the MediaQuery // _logoPadding will be a constant, scaling it according to device's size _height = MediaQuery.of(context).size.height; _width = MediaQuery.of(context).size.width; _fixedPadding = _height * 0.025; WidgetsBinding.instance.addPostFrameCallback((Duration d) { if (countries.length < 240) { loadCountriesJson().whenComplete(() { setState(() => _isCountriesDataFormed = true); }); } }); /* Scaffold: Using a Scaffold widget as parent * SafeArea: As a precaution - wrapping all child descendants in SafeArea, so that even notched phones won't loose data * Center: As we are just having Card widget - making it to stay in Center would really look good * SingleChildScrollView: There can be chances arising where */ return Scaffold( backgroundColor: Colors.white.withOpacity(0.95), body: SafeArea( child: Center( child: SingleChildScrollView( child: _getBody(), ), ), ), ); } /* * Widget hierarchy -> * Scaffold -> SafeArea -> Center -> SingleChildScrollView -> Card() * Card -> FutureBuilder -> Column() */ Widget _getBody() => Card( color: widget.cardBackgroundColor, elevation: 2.0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), child: SizedBox( height: _height * 8 / 10, width: _width * 8 / 10, /* * Fetching countries data from JSON file and storing them in a List of Country model: * ref:- List<Country> countries * Until the data is fetched, there will be CircularProgressIndicator showing, describing something is on it's way * (Previously there was a FutureBuilder rather that the below thing, which created unexpected exceptions and had to be removed) */ child: _isCountriesDataFormed ? _getColumnBody() : Center(child: CircularProgressIndicator()), ), ); Widget _getColumnBody() => Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ // Logo: scaling to occupy 2 parts of 10 in the whole height of device Padding( padding: EdgeInsets.all(_fixedPadding), child: PhoneAuthWidgets.getLogo( logoPath: widget.logo, height: _height * 0.2), ), // AppName: Text(widget.appName, textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: 24.0, fontWeight: FontWeight.w700)), Padding( padding: EdgeInsets.only(top: _fixedPadding, left: _fixedPadding), child: PhoneAuthWidgets.subTitle('Select your country'), ), /* * Select your country, this will be a custom DropDown menu, rather than just as a dropDown * onTap of this, will show a Dialog asking the user to select country they reside, * according to their selection, prefix will change in the PhoneNumber TextFormField */ Padding( padding: EdgeInsets.only(left: _fixedPadding, right: _fixedPadding), child: PhoneAuthWidgets.selectCountryDropDown( countries[_selectedCountryIndex], showCountries), ), // Subtitle for Enter your phone Padding( padding: EdgeInsets.only(top: 10.0, left: _fixedPadding), child: PhoneAuthWidgets.subTitle('Enter your phone'), ), // PhoneNumber TextFormFields Padding( padding: EdgeInsets.only( left: _fixedPadding, right: _fixedPadding, bottom: _fixedPadding), child: PhoneAuthWidgets.phoneNumberField(_phoneNumberController, countries[_selectedCountryIndex].dialCode), ), /* * Some informative text */ Row( mainAxisSize: MainAxisSize.min, children: <Widget>[ SizedBox(width: _fixedPadding), Icon(Icons.info, color: Colors.white, size: 20.0), SizedBox(width: 10.0), Expanded( child: RichText( text: TextSpan(children: [ TextSpan( text: 'We will send ', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w400)), TextSpan( text: 'One Time Password', style: TextStyle( color: Colors.white, fontSize: 16.0, fontWeight: FontWeight.w700)), TextSpan( text: ' to this mobile number', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w400)), ])), ), SizedBox(width: _fixedPadding), ], ), /* * Button: OnTap of this, it appends the dial code and the phone number entered by the user to send OTP, * knowing once the OTP has been sent to the user - the user will be navigated to a new Screen, * where is asked to enter the OTP he has received on his mobile (or) wait for the system to automatically detect the OTP */ SizedBox(height: _fixedPadding * 1.5), RaisedButton( elevation: 16.0, onPressed: startPhoneAuth, child: Padding( padding: const EdgeInsets.all(8.0), child: Text( 'SEND OTP', style: TextStyle( color: widget.cardBackgroundColor, fontSize: 18.0), ), ), color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.0)), ), ], ); /* * This will trigger a dialog, that will let the user to select their country, so the dialcode * of their country will be automatically added at the end */ showCountries() { /* * Initialising components required for StreamBuilder * We will not be using _countriesStreamController anywhere, but just to initialize Stream & Sink from that * _countriesStream will give us the data what we need(output) - that will be used in StreamBuilder widget * _countriesSink is the place where we send the data(input) */ _countriesStreamController = StreamController(); _countriesStream = _countriesStreamController.stream; _countriesSink = _countriesStreamController.sink; _countriesSink.add(countries); _searchCountryController.addListener(searchCountries); showDialog( context: context, builder: (BuildContext context) => searchAndPickYourCountryHere(), barrierDismissible: false); } /* * This will be the listener for searching the query entered by user for their country, (dialog pop-up), * searches for the query and returns list of countries matching the query by adding the results to the sink of _countriesStream */ searchCountries() { String query = _searchCountryController.text; if (query.length == 0 || query.length == 1) { if(!_countriesStreamController.isClosed) _countriesSink.add(countries); // print('added all countries again'); } else if (query.length >= 2 && query.length <= 5) { List<Country> searchResults = []; searchResults.clear(); countries.forEach((Country c) { if (c.toString().toLowerCase().contains(query.toLowerCase())) searchResults.add(c); }); _countriesSink.add(searchResults); // print('added few countries based on search ${searchResults.length}'); } else { //No results List<Country> searchResults = []; _countriesSink.add(searchResults); // print('no countries added'); } } /* * Child for Dialog * Contents: * SearchCountryTextFormField * StreamBuilder * - Shows a list of countries */ Widget searchAndPickYourCountryHere() => WillPopScope( onWillPop: () => Future.value(false), child: Dialog( key: Key('SearchCountryDialog'), elevation: 8.0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), child: Container( margin: const EdgeInsets.all(5.0), child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ // TextFormField for searching country PhoneAuthWidgets.searchCountry(_searchCountryController), // Returns a list of Countries that will change according to the search query SizedBox( height: 300.0, child: StreamBuilder<List<Country>>( //key: Key('Countries-StreamBuilder'), stream: _countriesStream, builder: (context, snapshot) { if (snapshot.hasData) { // print(snapshot.data.length); return snapshot.data.length == 0 ? Center( child: Text('Your search found no results', style: TextStyle(fontSize: 16.0)), ) : ListView.builder( itemCount: snapshot.data.length, itemBuilder: (BuildContext context, int i) => PhoneAuthWidgets.selectableWidget( snapshot.data[i], (Country c) => selectThisCountry(c)), ); } else if (snapshot.hasError) return Center( child: Text('Seems, there is an error', style: TextStyle(fontSize: 16.0)), ); return Center(child: CircularProgressIndicator()); }), ) ], ), ), ), ); /* * This callback is triggered when the user taps(selects) on any country from the available list in dialog * Resets the search value * Close the stream & sink * Updates the selected Country and adds dialCode as prefix according to the user's selection */ void selectThisCountry(Country country) { print(country); _searchCountryController.clear(); Navigator.of(context).pop(); Future.delayed(Duration(milliseconds: 10)).whenComplete(() { _countriesStreamController.close(); _countriesSink.close(); setState(() { _selectedCountryIndex = countries.indexOf(country); }); }); } startPhoneAuth() { FirebasePhoneAuth.instantiate( phoneNumber: countries[_selectedCountryIndex].dialCode + _phoneNumberController.text); Navigator.of(context).pushReplacement(CupertinoPageRoute( builder: (BuildContext context) => PhoneAuthVerify())); // FirebasePhoneAuth.stateStream.listen((state) { // // print(state); // // if (state == PhoneAuthState.CodeSent) { // Navigator.of(context).pushReplacement(CupertinoPageRoute( // builder: (BuildContext context) => PhoneAuthVerify())); // } // if (state == PhoneAuthState.Failed) // debugPrint("Seems there is an issue with it"); // }); } }
Bearbeitung
Datei öffnen
import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'countries.dart'; import 'code.dart'; import 'verify.dart'; import 'code.dart' show FirebasePhoneAuth; import 'widgets.dart'; /* * PhoneAuthUI - this file contains whole ui and controllers of ui * Background code will be in other class * This code can be easily re-usable with any other service type, as UI part and background handling are completely from different sources * code.dart - Class to control background processes in phone auth verification using Firebase */ // ignore: must_be_immutable class PhoneAuthGetPhone extends StatefulWidget { /* * cardBackgroundColor & logo values will be passed to the constructor * here we access these params in the _PhoneAuthState using "widget" */ Color cardBackgroundColor = Color(0xFF6874C2); String appName = "Awesome app"; @override _PhoneAuthGetPhoneState createState() => _PhoneAuthGetPhoneState(); } class _PhoneAuthGetPhoneState extends State<PhoneAuthGetPhone> { /* * _height & _width: * will be calculated from the MediaQuery of widget's context * countries: * will be a list of Country model, Country model contains name, dialCode, flag and code for various countries * and below params are all related to StreamBuilder */ double _height, _width, _fixedPadding; List<Country> countries = []; StreamController<List<Country>> _countriesStreamController; Stream<List<Country>> _countriesStream; Sink<List<Country>> _countriesSink; /* * _searchCountryController - This will be used as a controller for listening to the changes what the user is entering * and it's listener will take care of the rest */ TextEditingController _searchCountryController = TextEditingController(); TextEditingController _phoneNumberController = TextEditingController(); /* * This will be the index, we will modify each time the user selects a new country from the dropdown list(dialog), * As a default case, we are using India as default country, index = 31 */ int _selectedCountryIndex = 31; bool _isCountriesDataFormed = false; @override void initState() { super.initState(); } @override void dispose() { // While disposing the widget, we should close all the streams and controllers // Disposing Stream components // _countriesSink.close(); // _countriesStreamController.close(); // Disposing _countriesSearchController _searchCountryController.dispose(); super.dispose(); } Future<List<Country>> loadCountriesJson() async { // Cleaning up the countries list before we put our data in it countries.clear(); // Fetching the json file, decoding it and storing each object as Country in countries(list) var value = await DefaultAssetBundle.of(context).loadString("country_phone_codes.json"); var countriesJson = json.decode(value); for (var country in countriesJson) { countries.add(Country.fromJson(country)); } //Finally adding the initial data to the _countriesSink // _countriesSink.add(countries); return countries; } @override Widget build(BuildContext context) { // Fetching height & width parameters from the MediaQuery // _logoPadding will be a constant, scaling it according to device's size _height = MediaQuery.of(context).size.height; _width = MediaQuery.of(context).size.width; _fixedPadding = _height * 0.025; WidgetsBinding.instance.addPostFrameCallback((Duration d) { if (countries.length < 240) { loadCountriesJson().whenComplete(() { setState(() => _isCountriesDataFormed = true); }); } }); /* Scaffold: Using a Scaffold widget as parent * SafeArea: As a precaution - wrapping all child descendants in SafeArea, so that even notched phones won't loose data * Center: As we are just having Card widget - making it to stay in Center would really look good * SingleChildScrollView: There can be chances arising where */ return Scaffold( backgroundColor: Colors.white.withOpacity(0.95), body: SafeArea( child: Center( child: SingleChildScrollView( child: _getBody(), ), ), ), ); } /* * Widget hierarchy -> * Scaffold -> SafeArea -> Center -> SingleChildScrollView -> Card() * Card -> FutureBuilder -> Column() */ Widget _getBody() => Card( color: widget.cardBackgroundColor, elevation: 2.0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), child: SizedBox( height: _height * 8 / 10, width: _width * 8 / 10, /* * Fetching countries data from JSON file and storing them in a List of Country model: * ref:- List<Country> countries * Until the data is fetched, there will be CircularProgressIndicator showing, describing something is on it's way * (Previously there was a FutureBuilder rather that the below thing, which created unexpected exceptions and had to be removed) */ child: _isCountriesDataFormed ? _getColumnBody() : Center(child: CircularProgressIndicator()), ), ); Widget _getColumnBody() => Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ // Logo: scaling to occupy 2 parts of 10 in the whole height of device // AppName: Text(widget.appName, textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: 24.0, fontWeight: FontWeight.w700)), Padding( padding: EdgeInsets.only(top: _fixedPadding, left: _fixedPadding), child: PhoneAuthWidgets.subTitle('Select your country'), ), /* * Select your country, this will be a custom DropDown menu, rather than just as a dropDown * onTap of this, will show a Dialog asking the user to select country they reside, * according to their selection, prefix will change in the PhoneNumber TextFormField */ Padding( padding: EdgeInsets.only(left: _fixedPadding, right: _fixedPadding), child: PhoneAuthWidgets.selectCountryDropDown( countries[_selectedCountryIndex], showCountries), ), // Subtitle for Enter your phone Padding( padding: EdgeInsets.only(top: 10.0, left: _fixedPadding), child: PhoneAuthWidgets.subTitle('Enter your phone'), ), // PhoneNumber TextFormFields Padding( padding: EdgeInsets.only( left: _fixedPadding, right: _fixedPadding, bottom: _fixedPadding), child: PhoneAuthWidgets.phoneNumberField(_phoneNumberController, countries[_selectedCountryIndex].dialCode), ), /* * Some informative text */ Row( mainAxisSize: MainAxisSize.min, children: <Widget>[ SizedBox(width: _fixedPadding), Icon(Icons.info, color: Colors.white, size: 20.0), SizedBox(width: 10.0), Expanded( child: RichText( text: TextSpan(children: [ TextSpan( text: 'We will send ', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w400)), TextSpan( text: 'One Time Password', style: TextStyle( color: Colors.white, fontSize: 16.0, fontWeight: FontWeight.w700)), TextSpan( text: ' to this mobile number', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w400)), ])), ), SizedBox(width: _fixedPadding), ], ), /* * Button: OnTap of this, it appends the dial code and the phone number entered by the user to send OTP, * knowing once the OTP has been sent to the user - the user will be navigated to a new Screen, * where is asked to enter the OTP he has received on his mobile (or) wait for the system to automatically detect the OTP */ SizedBox(height: _fixedPadding * 1.5), RaisedButton( elevation: 16.0, onPressed: startPhoneAuth, child: Padding( padding: const EdgeInsets.all(8.0), child: Text( 'SEND OTP', style: TextStyle( color: widget.cardBackgroundColor, fontSize: 18.0), ), ), color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.0)), ), ], ); /* * This will trigger a dialog, that will let the user to select their country, so the dialcode * of their country will be automatically added at the end */ showCountries() { /* * Initialising components required for StreamBuilder * We will not be using _countriesStreamController anywhere, but just to initialize Stream & Sink from that * _countriesStream will give us the data what we need(output) - that will be used in StreamBuilder widget * _countriesSink is the place where we send the data(input) */ _countriesStreamController = StreamController(); _countriesStream = _countriesStreamController.stream; _countriesSink = _countriesStreamController.sink; _countriesSink.add(countries); _searchCountryController.addListener(searchCountries); showDialog( context: context, builder: (BuildContext context) => searchAndPickYourCountryHere(), barrierDismissible: false); } /* * This will be the listener for searching the query entered by user for their country, (dialog pop-up), * searches for the query and returns list of countries matching the query by adding the results to the sink of _countriesStream */ searchCountries() { String query = _searchCountryController.text; if (query.length == 0 || query.length == 1) { if(!_countriesStreamController.isClosed) _countriesSink.add(countries); // print('added all countries again'); } else if (query.length >= 2 && query.length <= 5) { List<Country> searchResults = []; searchResults.clear(); countries.forEach((Country c) { if (c.toString().toLowerCase().contains(query.toLowerCase())) searchResults.add(c); }); _countriesSink.add(searchResults); // print('added few countries based on search ${searchResults.length}'); } else { //No results List<Country> searchResults = []; _countriesSink.add(searchResults); // print('no countries added'); } } /* * Child for Dialog * Contents: * SearchCountryTextFormField * StreamBuilder * - Shows a list of countries */ Widget searchAndPickYourCountryHere() => WillPopScope( onWillPop: () => Future.value(false), child: Dialog( key: Key('SearchCountryDialog'), elevation: 8.0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), child: Container( margin: const EdgeInsets.all(5.0), child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ // TextFormField for searching country PhoneAuthWidgets.searchCountry(_searchCountryController), // Returns a list of Countries that will change according to the search query SizedBox( height: 300.0, child: StreamBuilder<List<Country>>( //key: Key('Countries-StreamBuilder'), stream: _countriesStream, builder: (context, snapshot) { if (snapshot.hasData) { // print(snapshot.data.length); return snapshot.data.length == 0 ? Center( child: Text('Your search found no results', style: TextStyle(fontSize: 16.0)), ) : ListView.builder( itemCount: snapshot.data.length, itemBuilder: (BuildContext context, int i) => PhoneAuthWidgets.selectableWidget( snapshot.data[i], (Country c) => selectThisCountry(c)), ); } else if (snapshot.hasError) return Center( child: Text('Seems, there is an error', style: TextStyle(fontSize: 16.0)), ); return Center(child: CircularProgressIndicator()); }), ) ], ), ), ), ); /* * This callback is triggered when the user taps(selects) on any country from the available list in dialog * Resets the search value * Close the stream & sink * Updates the selected Country and adds dialCode as prefix according to the user's selection */ void selectThisCountry(Country country) { print(country); _searchCountryController.clear(); Navigator.of(context).pop(); Future.delayed(Duration(milliseconds: 10)).whenComplete(() { _countriesStreamController.close(); _countriesSink.close(); setState(() { _selectedCountryIndex = countries.indexOf(country); }); }); } startPhoneAuth() { FirebasePhoneAuth.instantiate( phoneNumber: countries[_selectedCountryIndex].dialCode + _phoneNumberController.text); Navigator.of(context).pushReplacement(CupertinoPageRoute( builder: (BuildContext context) => PhoneAuthVerify())); // FirebasePhoneAuth.stateStream.listen((state) { // // print(state); // // if (state == PhoneAuthState.CodeSent) { // Navigator.of(context).pushReplacement(CupertinoPageRoute( // builder: (BuildContext context) => PhoneAuthVerify())); // } // if (state == PhoneAuthState.Failed) // debugPrint("Seems there is an issue with it"); // }); } }
Unterschied finden