Comparing sensitive data, confidential files or internal emails?

Most legal and privacy policies prohibit uploading sensitive data online. Diffchecker Desktop ensures your confidential information never leaves your computer. Work offline and compare documents securely.

github issue

Created Diff never expires
149 removals
409 lines
27 additions
401 lines
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';
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);
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
*/
*/
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)
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(
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,


/*
/*
* 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)
*/
*/
child: _isCountriesDataFormed
child: _isCountriesDataFormed
? _getColumnBody()
? _getColumnBody()
: Center(child: CircularProgressIndicator()),
: Center(child: CircularProgressIndicator()),
),
),
);
);


Widget _getColumnBody() => Column(
Widget _getColumnBody() => Column(
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),
),


// 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)),


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'),
),
),


/*
/*
* 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
*/
*/
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),
),
),


// 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),
),
),


/*
/*
* Some informative text
* Some informative text
*/
*/
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)),
])),
])),
),
SizedBox(width: _fixedPadding),
],
),
),
SizedBox(width: _fixedPadding),
],
),


/*
/*
* 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
*/
*/
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)),
),
),
],
),
);
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(
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),


// 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());
}),
}),
)
)
],
],
),
),
),
),
);
),
),
);


/*
/*
* 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(
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");
// });
// });
}
}
}
}