Diff
checker
文本
文本
图像
文档
Excel
文件夹
Legal
Enterprise
桌面版
定价
登录
下载 Diffchecker 桌面版
比较文本
查找两个文本文件之间的差异
工具
历史
实时编辑器
折叠未更改行
关闭换行
视图
拆分
统一
比对精度
智能
单词
字符
语法高亮
选择语法
忽略
文本转换
转到第一个差异
编辑输入
Diffchecker Desktop
运行Diffchecker最安全的方式。获取Diffchecker桌面应用:您的差异永远不会离开您的电脑!
获取桌面版
github issue
创建于
6年前
差异永不过期
清除
导出
分享
解释
142 删除
行
总计
删除
字符
总计
删除
要继续使用此功能,请升级到
Diff
checker
Pro
查看价格
409 行
全部复制
14 添加
行
总计
添加
字符
总计
添加
要继续使用此功能,请升级到
Diff
checker
Pro
查看价格
401 行
全部复制
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");
// });
// });
}
}
}
}
已保存差异
原始文本
打开文件
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"); // }); } }
更改后文本
打开文件
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"); // }); } }
查找差异