Diff
checker
テキスト
テキスト
画像
ドキュメント
Excel
フォルダ
Legal
Enterprise
デスクトップ
料金
ログイン
Diffchecker デスクトップのダウンロード
テキスト比較
2 つのテキスト ファイルの違いを見つける
ツール
履歴
ライブエディター
未変更行を折りたたむ
折り返しなし
レイアウト
分割
統合
比較精度
スマート
単語
文字
シンタックスハイライト
構文を選択
無視
テキスト変換
最初の差分へ移動
入力を編集
Diffchecker Desktop
Diffcheckerを実行する最も安全な方法。Diffchecker Desktopアプリを入手:あなたの差分はコンピューターから出ることはありません!
Desktopを入手
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"); // }); } }
違いを見つける