사용 프레임워크 및 라이브러리
flutter 구조
main.dart => pages / components
main은 라우터 부분을 컨트롤 한다.
main.dart에서 라우터를 관리하고 모든걸 관리한다.
그리고 전체적인 구성을 sidebar와 pages들로 UI를 나누어 구성한다.
페이지는 총 3페이지로 구성되어있으며, url-parser / url-encoder / base64로 구성되어있다.
그리고 계속 들어가는 부분인 sidebar도 컴포넌트로 구성되어있다.
main 위젯에서 전체적인 부분은 MaterialApp으로 감싸주고 routes / theme / home 3가지로 나눈 다음
Home에서 컴포넌트들을 import하여 페이지를 구성해준다.
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: '/',
routes: {
'/url/parser': (context) => UrlParser(),
'/url/encoder/decoder': (context) => UrlEncoder(),
'/base64/encoder/decoder': (context) => Base64Encoder()
},
theme: ThemeData(
fontFamily: 'Ubuntu',
textTheme: const TextTheme(
bodyText1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold),
button: TextStyle(fontSize: 12)
)
),
home: Container(
color: const Color(0xFFF6F6F6),
child: Row(
children: [
const SideBar(),
const Expanded(
child: MyIp(),
),
],
),
)
);
}
CSS
전체적인 전역 css를 관리해주는 파일을 하나 만들어서
font.dart 파일을 만들고
import 'package:flutter/material.dart';
// set color
const kPrimaryColor = Color(0xFF4F46E5);
const kDefaultFontColor = Color(0xFF6B7280);
// set font
var kDefaultFontStyle = const TextStyle(
fontFamily: 'Ubuntu',
fontSize: 16,
fontWeight: FontWeight.normal,
decoration: TextDecoration.none,
color: kDefaultFontColor
);
var kSubFontStyle = const TextStyle(
fontFamily: 'Ubuntu',
fontSize: 12,
fontWeight: FontWeight.normal,
decoration: TextDecoration.none,
color: kDefaultFontColor
);
var kDefaultButtonFontStyle = const TextStyle(
fontFamily: 'Noto Sans',
fontSize: 12,
fontWeight: FontWeight.w400,
decoration: TextDecoration.none,
color: kDefaultFontColor
);
이런식으로 font.dart에서 만들어준 css부분을 페이지 style부분에서 import하여 사용한다.
style: kDefaultTitleFontStyle,
style: kSubFontStyle.copyWith(fontWeight: FontWeight.w400, color: Color(0xFF4F46E5))
이런식으로 가져와서 사용해준다.
위젯마다 문법이 다르기 때문에 위젯 문법에 맞게 css를 인라인으로 사용한다.
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(width: 2, color: Color(0xFF4F46E5))),
),
이런식으로 위젯마다 문법에 맞게 css를 인라인으로 사용한다.
Method / Library
TextEditingController()
final _idTextEditController = TextEditingController();
controller: _idTextEditController,
_idTextEditController.text = value;
=> textfield에 컨트롤러를 추가하여 안에있던 속성들을 컨트롤 해준다.
clipboard.paste()
FlutterClipboard.paste().then((value) {
setState(() {
_idTextEditController.text = value;
});
=>컨트롤러에 해당하는 부분의 텍스트에 값을 붙여넣는 라이브러리 함수이다.
child: UrlResult(urlData:_idTextEditController.text)
class UrlResult extends StatefulWidget {
const UrlResult({
Key? key,
required this.urlData
}) : super(key: key);
final urlData;
페이지 안에서는 widget.urlData로 사용한다.
=> 부모컴포넌트에서 자식컴포넌트를 호출하여 활용한다.
Uri uri = Uri.parse(widget.urlData)
uri.scheme,
uri.userInfo,
uri.host,
uri.port.toString() = 혼자 int타입이기 때문에 string으로 바꿔준다.
uri.path
uri.port등등
=>유저들이 붙여넣는 url들을 라이브러리를 이용하여 데이터를 parsing해준다.
encoded = Uri.encodeComponent(_idTextEditController.text)
=> url을 encode해줄때 사용한다. encoded라는 변수에 컨트롤러의 텍스트값을 인코딩한다.
decoded = Uri.decodeComponent(_idTextEditController.text)
=> url을 decode해줄때 사용한다. decoded라는 변수에 컨트롤러의 텍스트값을 디코딩한다.
Clipboard.setData(ClipboardData(text: encoded))
=>인코딩 한 값을 담은 변수 encoded를 clipboard에 Copy한다.
Toast알림
import 'package:flutter_toastr/flutter_toastr.dart';
_showToast(String msg, {int? duration, int? position}) {
FlutterToastr.show(
msg, context,
duration: FlutterToastr.lengthShort,
position: FlutterToastr.bottom
);
}
_showToast("Copy Successful.",)
=>toast라이브러리를 사용하여 알림창을 띄운다.
ModalRoute.of(context)?.settings.name == '/' ? kPrimaryColor : kDefaultFontColor
style: ModalRoute.of(context)?.settings.name == '/url/encoder/decoder' ?
=>라우터로 세팅이된 이름을 구분하여 css를 주는 방식이다.
!)꿀팁으로 웬만한 css는 이걸로 처리해주면 좋다.
Navigator.pushNamed(context, "/")
Navigator.pushNamed(context, '/url/encoder/decoder')
=>onTap이나 onPressed함수에 활용되어 라우터가 context 이름에 따라 이동된다.
Widget
StatelessWidget / StatefulWidget
StatelessWidget : 상태가 없는 위젯. 변화가 거의 없는 위젯은 이것으로 선언한다
StatefulWidget : state라는 데이터 변화를 감지하고, state가 변할시 위젯을 rebuild 하는 위젯. setState라는 함수를 통해 state변화를 감지하여야 한다
class UrlResult extends StatefulWidget {
//필수
const UrlResult({
Key? key,
required this.urlData
}) : super(key: key);
final urlData;
//부모 컴포넌트로부터 data를 받아왔을 경우에 위에 처럼 선언해준다.
@override
UrlResultState createState() => UrlResultState();
//필수
}
class UrlResultState extends State<UrlResult> {
//필수
//이제 여기서 본문에서 사용할 변수와 함수들을 선언해준다.
bool protocolView = false;
_showToast(String msg, {int? duration, int? position}) {
FlutterToastr.show(
msg, context,
duration: duration,
position: FlutterToastr.bottom
);
}
@override
Widget build(BuildContext context) {
Uri uri = Uri.parse(widget.urlData);
return
Container(
);}
initState / dispose
initState: StatefulWidget 생성시 초기에 딱 한번 호출. 이니셜라이징 할 곳은 이곳에 모아두자
dispose : StatefulWidget에서 state object가 필요없을시 불리는 함수. uninit 할곳은 이곳에 모아두자.
@override
void initState() {
super.initState();
_findInternetConnection();
_getMyIp();
}
GestureDetector / MouseRegion
GestureDetector : 많은 위젯들은 GestureDetector를 통해 다른위젯에 콜백을 전달합니다. 예를들어 IconButton, RaisedButton, FloatingActionButton 위젯은
onPressed() 라는, 유저가 위젯을 탭 했을때 호출되는 콜백을 가지고 있습니다. tap 제스쳐 외에 drag, scale등의 제스처를 감지할 수 있습니다.
MouseRegion : 마우스에 대한 속성들을 가지고 있는 위젯이다. 호버와 탭 커서등 여러가지 마우스에 속성을 부여할수있다.
onHover: (s){
setState(() {
protocolView = true;
});
},
Material / Scaffold(appBar ,Body ,BottomNavigationBar)
Material : Navigator라는 위젯을 제공합니다. Navigator는 위젯의 스택을 관리하며 각 스택은 "routes"라는 string 변수로 구분됩니다.
Scaffold : 전체적인 시멘틱 태크와 같이 Scaffold위젯 안에서 사용된다.
3부분으로 헤드 바디 바텀으로 나뉘어 위젯의 위치를 분류하여 꾸밀수있다.
여러 다른 위젯을 named arguments로 받는것을 보실수 있다.
Stack / SingleChildScrollView / PageView
Stack : children에 나열한 여러 위젯을 순서대로 겹치게해준다. 사진 위에 글자를 표현하거나 화면 위로 로딩 표시를 하는 상황에 사용한다.
SingleChildScrollView: 하나의 자식을 포함하는 스크롤을 가능하게 하는 위젯이다. 자식으로는 Column보다는 ListBody위젯을 사용해주면 더 간편해진다.
PageView : 여러 페이지를 좌우로 슬라이드 하여 넘길수 있도록 하는 위젯이다.
children 프로퍼티에 각 화면을 표현할 위젯을 여러개 준비하여 지정하면 Tap과 연동되어 활용된다.
Container / SizedBox
Container: 아무것도 없는 기본 위젯으로 div와 같은 형태를 띈다.
다양한 프로퍼티를 가지고 있기때문에 사용하기에 따라서 다양한 응용이 가능하다.
또다른 위젯을 가질수 잇으며, margin,padding등 여러가지 CSS를 적용할수있다.
SizedBox ; 위젯을 특정한 크기로 만들고 싶은경우 사용한다. 하지만 Container가 더 많이 쓰이며, Container보단 가볍다.
Column / Row(MainAxisSize / MainAxisAlignment / CrossAxisAlignment)
전체적으로 가로 / 세로로 나열할수있게 만들어주는 기본 방향 위젯이다.
이 위젯 안에서는 중요한 부분을 정의할수있는데 MainAxisSize / MainAxisAlignment / CrossAxisAlignment등을 적용할수있다. 이것들을 flex와 같이 사용할수있기 때문에 편리하다.
Expanded / Card / Flexible
Expanded : 자식위젯의 크기를 최대한으로 확장시켜주는 위젯으로 여러 위젯에 동시에 적용하면 flex프로퍼티에 정수 값을 지정하여 비율을 정할수 있으며 기본 값은 1이다.
Card : 카드 형태의 모양을 제공하는 위젯으로 기본적으로 크기가 0이므로 자식 위젯의 크기에 따라 크기가 결정된다.
Card(
shape : RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
elevation: [실수값], // 그림자 깊이
child: [위젯],
),
Flexible: Expanded와 비슷하지만 최대로 확장하는것이 아닌 flexible한 유연하게 크기를 조절할수가있다. Text부분이 overflow가 되거나 하는 부분들을 flexible로 감싸주면 효과가 적용된다.
Flexible(
child: Container(
height: 25,
child: Text(uri.scheme,
overflow: TextOverflow.ellipsis,
style: kDefaultFontStyle.copyWith(fontWeight: FontWeight.w300),
),
),
),
Text / Icon / Image / Progress / CircleAvatar / Button
Text : 글자를 표시하는 위젯이다.
Text( ' hello',
style: TextStyle(
fontSize: 40,
fontStyle, FontStyle.italic,
color: Colors.red
),
),
Icon : 아이콘은 그냥 아이콘 단독으로 사용가능하다.그리고 이미지를 부여할수도있다.
icon: parseHover ? SvgPicture.asset(
"assets/icons/edit.svg",
color: kDefaultFontColor,
)
Image : Image.asset("asset/sample.jpg")
Progress : 로딩중이거나 오래걸리는 작업을 할때 진행중임을 보여주는 위젯이다.
CircularProgressIndicator()
LinearProgressIndicator()
CircleAvatar : 프로필 화면 등에 많이 사용하는 원형 위젯으로 child 프로퍼티에 정의한 위젯을 원형으로 만들어준다.
CircleAvatar(
child: Icon(Icon.person),
),
CircleAvatar(
backgroundImage: NetworkImage([이미지 URL]),
),
Button :
1)RaisedButton : 입체감을 가지는 일반적인 버튼으로 onPress에 실행될 코드를 적용시켜야 비활성화가 풀린다.
2)FlatButton : 평평한 형태의 버튼이다.
3)IconButton : 아이콘을 표시하는 버튼이다. 아이콘 크기 색상등을 지정할수있다.
4)FloatingActionButton : 입체감있는 둥근 버튼으로 해당부분에 action을 줄수가있다.
ElevatedButton.icon : 아이콘과 텍스트를 같이 사용할수있으며 웬만한 기능들이 다 들어있는 최고의 버튼이다.
child: ElevatedButton.icon(
icon: parseHover ? SvgPicture.asset(
"assets/icons/edit.svg",
color: kDefaultFontColor,
) :
SvgPicture.asset(
"assets/icons/edit-over.svg",
color: kDefaultFontColor,
),
onPressed: _show,
label: Text(
"Parse",
style: parseHover ?
kSubFontStyle.copyWith(fontWeight: FontWeight.w400, color: Color(0xFF4F46E5))
: kSubFontStyle.copyWith(fontWeight: FontWeight.w400),
),
style: ElevatedButton.styleFrom(
primary: Colors.white,
fixedSize: const Size(80, 25),
side: parseHover ? BorderSide(width: 1.0, color: Color(0xFF4F46E5))
: BorderSide(width: 1.0, color: Colors.transparent)
),
),
LifeCycle
Project Pages / Components
main>>MyIP>components(pages)
main은 app 과router를 실행시켜준다.
) MyIP
유저의 IP를 컨택해서 구글맵을 연동하여 위치를 잡아주는 컴포넌트이다.
import 'package:flutter_svg/flutter_svg.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
@override
Widget build(BuildContext context) {
return Column(
children: [
Visibility(
child:_myIpContent(_ip),
visible: _isOnlineState,
),
Visibility(
child: _offlineState(),
visible: !_isOnlineState,
)
],
);
}
메인 context필드에서 리턴해주고 child부분에서 myIpContent와 offlineState 위젯을 보여준다.
bool result = await InternetConnectionChecker().hasConnection;
if (result) _findMyIp();
var url = Uri.parse('https://geolocation-db.com/json');
//인터넷 연결을 체크한 후 url을 파싱한다.
var response = await http.get(url);
//파싱된 url을 받아서 200체크를 해준 후 json code를 받아서 IPv4를 뿌려준다.
if (response.statusCode == 200) {
var responseData = jsonDecode(response.body);
_setMyIp(responseData["IPv4"]);
setState(() {
_ip = responseData["IPv4"];
});
}
}
인터넷을 체크하고 IP를 불러와 UI에 뿌려준다.
child: FlutterMap(
options: MapOptions(
center: LatLng(37.3686272, 127.1136256),
zoom: 16.0,
),
layers: [
TileLayerOptions(
urlTemplate: "https://api.mapbox.com/styles/v1/yoosangjun/cl1n9mhvh000514
pjz6cgkf26/tiles/256/{z}/{x}/{y}@2x?access_token=pk.eyJ1IjoieW9vc2FuZ2p1biIsImEiOiJjbDFuN2Y0MjAwcnBrM2JzOW........",
),
],
)
구글맵은 이런식으로 맵을 연동하고 좌표를 찍어서 UI에 뿌려준다.
1) url_parser.dart
Paste
Toast알림
Parse
3가지 기능이있다.
일단 전반적인 UI는 Material로 구분하였고 그 밑으로 paste clear textfield parse버튼 그리고 파싱 데이터를 다루는 url_result 컴포넌트로 나눠져있다.
버튼 UI는 MouseRegion위젯을 사용하여 hover기능을 추가하였고, 전체적인 버튼 css는 elevatedButton.icon위젯을 사용하여 텍스트와 아이콘을 같이 사용할수있다.
MouseRegion(
onHover: (s) {
setState(() {
pasteHover = true;
});
},
onExit: (s) {
setState(() {
pasteHover = false;
});
},
child: Container(
child: ElevatedButton.icon(
icon: pasteHover ? SvgPicture.asset(
"assets/icons/paste-over.svg",
) :
SvgPicture.asset(
"assets/icons/paste.svg",
),
onPressed: _pasteData,
label: Text(
"Paste",
style: pasteHover ?
kSubFontStyle.copyWith(fontWeight: FontWeight.w400, color: Color(0xFF4F46E5)) : kSubFontStyle.copyWith(fontWeight: FontWeight.w400),
),
style: ElevatedButton.styleFrom(
primary: Colors.white,
fixedSize: const Size(80, 25),
side: pasteHover ? BorderSide(width: 1.0, color: Color(0xFF4F46E5)) : BorderSide(width: 1.0, color: Color(0xFFE5E5E5))
),
),
),
),
첫번째로, paste기능은 FlutterClipboard.paste().then((value) 라이브러리 함수를 사용해주었다.
두번쨰로, Toast기능은 이런식으로 custom하여 사용가능하다.
_showToast("Copy Successful.");
_showToast(String msg, {int? duration, int? position}) {
FlutterToastr.show(
msg, context,
duration: FlutterToastr.lengthShort,
position: FlutterToastr.bottom
);}
세번째로, parsing data 부분인데 이건 컴포넌트화 해주었다. child: UrlResult(urlData:_idTextEditController.text),
상세한 내용은 밑에서 첨부하겠다.
2) url_result.dart
: 위에서 url을 입력하면 그 url을 parsing하여 결과를 부분적으로 뿌려준다.
그리고 결과마다 복사를 할수있게 해주었다.
첫번째로, 부모 컴포넌트인 url_parser에서 textfield에 입력된 url을 받아온다.
똑같이 required this.urlData로 받아와서
final urlData;로 선언을 해준다.
그다음 UI부분에만 widget.urlData로 사용해주면된다.
그리고 받아온 url을 라이브러리 함수를 사용하여 파싱해준다.
Uri uri = Uri.parse(widget.urlData);
그리고 결과부분들을 이런식으로 뿌려준다. text의 overflow가 적용되려면 container부분을 felxible로 감싸줘야한다.
그리고 복사 아이콘을 클릭했을 경우 Toast함수가 작동하도록 해주었다.
파싱한 결과로는 uri.scheme / uri.host / uri.port.toString() / uri.userInfo / path / query / fragment등을 사용했다.
Flexible(
child: Container(
height: 25,
child: Text(uri.scheme,
overflow: TextOverflow.ellipsis,
style: kDefaultFontStyle.copyWith(fontWeight: FontWeight.w300),
),
),
),
protocolView ?
IconButton(
hoverColor: Colors.transparent,
padding: EdgeInsets.fromLTRB(0,0,0,3),
constraints: BoxConstraints(),
icon: SvgPicture.asset(
"assets/icons/copy.svg",
color: kDefaultFontColor,
),
iconSize: 24,
onPressed: ()=>{
Clipboard.setData(ClipboardData(text: uri.scheme)),
_showToast("Copy Successful.",
duration: FlutterToastr.lengthShort,
position: FlutterToastr.bottom,
),
},
)
3) url_encoder.dart
encode
decode
copy
:이 컴포넌트도 url_parser 컴포넌트와 마찬가지로 버튼의 UI는 똑같지만, 다른점은 url을 encoder / decoder해주는 부분이다.
textfield 2개를 사용하였고, 위에 하나는 url을 입력하는 부분이고 두번째 textfield는 encode / decode한 url을 보여주는 부분이다.
final _idTextEditController = TextEditingController();
//텍스트필드에 controll을 주어 id값 처럼 활용해준다.
Container(
margin: EdgeInsets.fromLTRB(0,5,0,0),
width: 500.0, height: 80.0,
child:
TextField(
autofocus: true,
controller: _idTextEditController,
maxLines: 10,
style: kDefaultFontStyle.copyWith(fontSize: 12.0),
decoration: InputDecoration(
hintText: "Enter a message",
fillColor: Colors.white,
filled: true,
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(width: 2, color: Color(0xFFE5E5E5)),
borderRadius: BorderRadius.circular(5),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(width: 2, color: Color(0xFF4F46E5)),
borderRadius: BorderRadius.circular(5),
)),
onChanged: (text){
setState(() {
});
},
),
),
첫번째로, encode는 라이브러리가 아닌 내부 encodeComponent()함수를 사용했으며, encoded한 후 클립으로 복사하고 텍스트필드에 뿌려주는 함수이다.
_urlEncode(){
setState(() {
encoded = Uri.encodeComponent(_idTextEditController.text);
});
Clipboard.setData(ClipboardData(text: encoded));
FlutterClipboard.paste().then((value) {
setState(() {
_twoTextEditController.text = value;
});
});
}
두번쨰로, decode도 마찬가지이며, 유저가 encoded된 url을 입력하지 않은 경우 오류를 잡기위해서 try/catch를 적용해주었다.
_urlDecode(){
try{
setState(() {
decoded = Uri.decodeComponent(_idTextEditController.text);
});
Clipboard.setData(ClipboardData(text: decoded));
FlutterClipboard.paste().then((value) {
setState(() {
_twoTextEditController.text = value;
});
});
} catch(e){
_showToast("Enter a encoded URL.",);
}
}
세번째로, Copy는 onpress부분에서 url을 작성했을경우, 클립보드에 복사하여 toast알림이 호출되게 처리를 해주었다.
onPressed: () => {
if(_twoTextEditController.text.length > 0){
Clipboard.setData(ClipboardData(text: _twoTextEditController.text)),
_showToast("Copy Successful.",
)
}},
4) base64_encoder.dart
base64 encode
base64 decode
:위에 url_encoder 컴포넌트와 거의 유사하다.
비록 다른점은 일반적인 url을 encode하는게 아니라 base64를 encode하는것이다.
첫번째로, base64 encode이다. encoded = base64Url.encode(utf8.encode(_idTextEditController.text));
두번째로, base64 decode이다. decoded = utf8.decode(base64Url.decode(_idTextEditController.text));
마찬가지로, 오류를 위해서 try/catch를 적용해준다.
5.) sidebar.dart
ModalRoute.of(context)?.settings.name
:메인 Pages들과 함께 계속 사용해주는 컴포넌트 위젯으로 다른 페이지 컴포넌트에는 라우터 네이게이터를 사용해준다.
첫번째로, Home부분은 GestureDetector위젯을 사용하여 묶어주었으며, onTap하였을때 Navigator.pushNamed(context, "/"); 네이게이터를 통하여 이동하게 해주었다.
그리고 css적인 부분들은 ModalRoute.of(context)?.settings.name를 통하여 그 context Tap에 맞게 적용시켜주었다.
GestureDetector(
onTap: (){
Navigator.pushNamed(context, "/");
},
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 14),
decoration: const BoxDecoration(
color: Color(0xFFE5E5E5),
borderRadius: BorderRadius.all(Radius.circular(8.0))
),
// homebox
child: Column(
children: [
Row(
children: [
SvgPicture.asset(
'assets/icons/home.svg',
color: ModalRoute.of(context)?.settings.name == '/' ? kPrimaryColor : kDefaultFontColor,
width: ModalRoute.of(context)?.settings.name == '/' ? 19 : 16,
),
const SizedBox(width: 5,),
Text(
'MyIP',
style: ModalRoute.of(context)?.settings.name == '/' ?
kDefaultFontStyle.copyWith(fontWeight: FontWeight.w400, letterSpacing: -0.5, color: kPrimaryColor) :
kDefaultFontStyle.copyWith(fontWeight: FontWeight.w400, letterSpacing: -0.5),
)
],
),
Container(
alignment: Alignment.centerLeft,
child: Text(
_ip,
style: ModalRoute.of(context)?.settings.name == '/' ?
kDefaultFontStyle.copyWith(fontWeight: FontWeight.w400, letterSpacing: -0.5, color: kPrimaryColor) :
kDefaultFontStyle.copyWith(fontWeight: FontWeight.w400, letterSpacing: -0.5),
),
)
],
),
),
),
두번째로, 나머지 pages들은 Expanded위젯을 사용하여 묶어주었으며, ListView로 묶어서 처리해주었다.
onPress부분은 Navigator를 사용해주었으며, css부분은 ModalRoute.of(context)?.settings.name == '/url/parser' ?를 적용시켜 변화를 주었다.
Expanded(
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 10),
alignment: Alignment.topCenter,
child: ListView(
children: [
Container(
child: TextButton(
style: TextButton.styleFrom(
textStyle: const TextStyle()
),
onPressed: () {
Navigator.pushNamed(context, '/url/parser');
},
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'URL Parser',
style: ModalRoute.of(context)?.settings.name == '/url/parser' ?
kDefaultFontStyle.copyWith(fontWeight: FontWeight.w400, letterSpacing: -0.5, color: kPrimaryColor) :
kDefaultFontStyle.copyWith(fontWeight: FontWeight.w400, letterSpacing: -0.5),
),
),
),
),
App Store
google Analytics