From cd8f22959e62c0b7b191e64f4ecfec53cba9f364 Mon Sep 17 00:00:00 2001 From: Iván Ávalos Date: Sat, 20 May 2023 16:43:21 -0600 Subject: Se añade autenticación y onboarding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/heart.json | 1 + assets/loading.gif | Bin 0 -> 126404 bytes assets/logo.png | Bin 0 -> 27033 bytes assets/logo.svg | 114 +++++++++++++ assets/share.json | 1 + assets/star.json | 1 + lib/firebase/auth.dart | 83 ++++++++++ lib/firebase/storage.dart | 16 ++ lib/main.dart | 67 +++++--- lib/providers/theme_provider.dart | 25 +++ lib/routes.dart | 15 ++ lib/screens/dashboard_screen.dart | 100 ++++++++++++ lib/screens/login_screen.dart | 171 +++++++++++++++++++ lib/screens/onboarding_screen.dart | 103 ++++++++++++ lib/screens/register_screen.dart | 178 ++++++++++++++++++++ lib/settings/preferences.dart | 35 ++++ lib/settings/themes.dart | 16 ++ lib/widgets/avatar_picker.dart | 61 +++++++ lib/widgets/loading_modal_widget.dart | 22 +++ lib/widgets/responsive.dart | 34 ++++ pubspec.lock | 298 +++++++++++++++++++++++++++++++++- pubspec.yaml | 12 ++ 22 files changed, 1325 insertions(+), 28 deletions(-) create mode 100644 assets/heart.json create mode 100644 assets/loading.gif create mode 100644 assets/logo.png create mode 100644 assets/logo.svg create mode 100644 assets/share.json create mode 100644 assets/star.json create mode 100644 lib/firebase/auth.dart create mode 100644 lib/firebase/storage.dart create mode 100644 lib/providers/theme_provider.dart create mode 100644 lib/routes.dart create mode 100644 lib/screens/dashboard_screen.dart create mode 100644 lib/screens/login_screen.dart create mode 100644 lib/screens/onboarding_screen.dart create mode 100644 lib/screens/register_screen.dart create mode 100644 lib/settings/preferences.dart create mode 100644 lib/settings/themes.dart create mode 100644 lib/widgets/avatar_picker.dart create mode 100644 lib/widgets/loading_modal_widget.dart create mode 100644 lib/widgets/responsive.dart diff --git a/assets/heart.json b/assets/heart.json new file mode 100644 index 0000000..c6ab231 --- /dev/null +++ b/assets/heart.json @@ -0,0 +1 @@ +{"v":"5.6.5","fr":30,"ip":0,"op":10,"w":32,"h":32,"nm":"heart","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"heart","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16,16,0],"ix":2},"a":{"a":0,"k":[12,12,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.001,0.001],[2.148,-2.148],[0.001,0],[0,0],[0,0],[2.149,-2.148],[-2.148,-2.148],[0,0],[0,0],[0,0],[0,0],[2.147,2.148]],"o":[[-2.147,-2.148],[0,0],[0,0],[0,0],[-2.148,-2.148],[-2.148,2.148],[0,0],[0,0],[0,0],[0,0],[2.148,-2.147],[-0.001,-0.001]],"v":[[8.84,-7.236],[1.061,-7.237],[1.059,-7.236],[-0.001,-6.176],[-1.061,-7.236],[-8.84,-7.236],[-8.84,0.544],[-7.781,1.604],[-0.001,9.385],[7.779,1.604],[8.84,0.544],[8.842,-7.234]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[12,11.846],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":5,"s":[90,90]},{"t":10,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":10,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"heart Fill","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16,16,0],"ix":2},"a":{"a":0,"k":[12,12,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.001,0.001],[2.148,-2.148],[0.001,0],[0,0],[0,0],[2.149,-2.148],[-2.148,-2.148],[0,0],[0,0],[0,0],[0,0],[2.147,2.148]],"o":[[-2.147,-2.148],[0,0],[0,0],[0,0],[-2.148,-2.148],[-2.148,2.148],[0,0],[0,0],[0,0],[0,0],[2.148,-2.147],[-0.001,-0.001]],"v":[[8.84,-7.236],[1.061,-7.237],[1.059,-7.236],[-0.001,-6.176],[-1.061,-7.236],[-8.84,-7.236],[-8.84,0.544],[-7.781,1.604],[-0.001,9.385],[7.779,1.604],[8.84,0.544],[8.842,-7.234]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[12,11.846],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":2,"s":[50,50]},{"t":10,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[0]},{"t":7,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"fill","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":10,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/assets/loading.gif b/assets/loading.gif new file mode 100644 index 0000000..bfbb814 Binary files /dev/null and b/assets/loading.gif differ diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..95ff20f Binary files /dev/null and b/assets/logo.png differ diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 0000000..8408237 --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/share.json b/assets/share.json new file mode 100644 index 0000000..80d845f --- /dev/null +++ b/assets/share.json @@ -0,0 +1 @@ +{"v":"5.6.5","fr":30,"ip":0,"op":75,"w":32,"h":32,"nm":"share Final","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"share","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[16,16,0],"to":[-2,-1.167,0],"ti":[2,1.167,0]},{"t":44,"s":[4,9,0]}],"ix":2},"a":{"a":0,"k":[12,12,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.657,0],[0,-1.657]],"o":[[0,-1.657],[1.657,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":15,"s":[87]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":15,"s":[86]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[6,10.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14,"s":[100]},{"t":15,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"1 oval up","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.657,0],[0,1.657]],"o":[[0,1.657],[-1.657,0],[0,0]],"v":[[3,-1.5],[0,1.5],[-3,-1.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":15,"s":[13]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":15,"s":[13]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[6,13.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14,"s":[100]},{"t":15,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"1 oval down","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[3.41,-1.99],[-3.41,1.99]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[100]},{"t":29,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[11.938,8.294],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"1 line up","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.657,0],[0,-1.657],[1.656,0],[0.521,0.89],[0,0.551]],"o":[[1.656,0],[0,1.657],[-1.106,0],[-0.259,-0.444],[0,-1.657]],"v":[[0,-3],[3,0],[0,3],[-2.593,1.511],[-3,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[0]},{"t":44,"s":[50]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[100]},{"t":44,"s":[50]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[18,4.747],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":-120,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"2 up oval","np":3,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.415,-1.99],[3.415,1.99]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[0]},{"t":29,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[12.005,15.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"1 line down","np":3,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.105,0],[0,-1.657],[1.657,0],[0,1.657],[-0.26,0.445]],"o":[[1.657,0],[0,1.657],[-1.657,0],[0,-0.551],[0.52,-0.889]],"v":[[0,-3],[3,0],[0,3],[-3,0],[-2.592,-1.512]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[18,19],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":-60,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"2 down oval","np":3,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[3.41,-1.99],[-3.41,1.99]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":44,"s":[100]},{"t":60,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[24,15.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"3 line up","np":3,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.415,-1.99],[3.415,1.99]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":44,"s":[0]},{"t":60,"s":[100]}],"ix":1},"e":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[24.005,22.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"3 line down","np":3,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.657,0],[0,-1.657],[1.657,0],[0.521,0.89],[0,0.551]],"o":[[1.657,0],[0,1.657],[-1.106,0],[-0.259,-0.444],[0,-1.657]],"v":[[0,-3],[3,0],[0,3],[-2.593,1.511],[-3,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":60,"s":[50]},{"t":75,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":60,"s":[50]},{"t":75,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[30,11.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":60,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"3 oval up","np":3,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.106,0],[0,-1.657],[1.656,0],[0,1.657],[-0.259,0.444]],"o":[[1.656,0],[0,1.657],[-1.657,0],[0,-0.551],[0.521,-0.89]],"v":[[0,-3],[3,0],[0,3],[-3,0],[-2.593,-1.511]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":60,"s":[50]},{"t":75,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":60,"s":[50]},{"t":75,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 2","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[30.031,26.028],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":120,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"3 oval down","np":3,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/assets/star.json b/assets/star.json new file mode 100644 index 0000000..f95b2a5 --- /dev/null +++ b/assets/star.json @@ -0,0 +1 @@ +{"v":"5.6.5","fr":30,"ip":0,"op":10,"w":32,"h":32,"nm":"star","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"star","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16,16,0],"ix":2},"a":{"a":0,"k":[12,12,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[0,-9.51],[3.09,-3.25],[10,-2.24],[5,2.63],[6.18,9.51],[0,6.26],[-6.18,9.51],[-5,2.63],[-10,-2.24],[-3.09,-3.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[12,11.51],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":5,"s":[90,90]},{"t":10,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"star","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":10,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"star fill","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16,16,0],"ix":2},"a":{"a":0,"k":[12,12,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[0,-9.51],[3.09,-3.25],[10,-2.24],[5,2.63],[6.18,9.51],[0,6.26],[-6.18,9.51],[-5,2.63],[-10,-2.24],[-3.09,-3.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[12,11.51],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":2,"s":[50,50]},{"t":10,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[0]},{"t":7,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"star","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":10,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/lib/firebase/auth.dart b/lib/firebase/auth.dart new file mode 100644 index 0000000..a876d5b --- /dev/null +++ b/lib/firebase/auth.dart @@ -0,0 +1,83 @@ +import 'dart:io'; + +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; + +import 'storage.dart'; + +class Auth { + final FirebaseAuth _auth = FirebaseAuth.instance; + final GithubAuthProvider _githubProvider = GithubAuthProvider(); + final GoogleAuthProvider _googleAuthProvider = GoogleAuthProvider(); + + User? get currentUser => _auth.currentUser; + + Future createUserWithEmailAndPassword({ + required String email, + required String password, + required String displayName, + required File avatar, + }) async { + try { + UserCredential cred = await _auth.createUserWithEmailAndPassword( + email: email, + password: password, + ); + User? user = cred.user; + if (user != null) { + user.updateDisplayName(displayName); + user.updatePhotoURL(await Storage().uploadAvatar(user.uid, avatar)); + } + return true; + } catch (e) { + if (kDebugMode) print(e); + return false; + } + } + + Future signInWithEmailAndPassword({ + required String email, + required String password, + }) async { + try { + UserCredential cred = await _auth.signInWithEmailAndPassword( + email: email, + password: password, + ); + return cred.user?.emailVerified == true; + } catch (e) { + if (kDebugMode) print(e); + return false; + } + } + + Future signInWithGoogle() async { + try { + await _auth.signInWithProvider(_googleAuthProvider); + return true; + } catch (e) { + if (kDebugMode) print(e); + return false; + } + } + + Future signInWithGithub() async { + try { + await _auth.signInWithProvider(_githubProvider); + return true; + } catch (e) { + if (kDebugMode) print(e); + return false; + } + } + + Future signOut() async { + try { + await _auth.signOut(); + return true; + } catch (e) { + if (kDebugMode) print(e); + return false; + } + } +} diff --git a/lib/firebase/storage.dart b/lib/firebase/storage.dart new file mode 100644 index 0000000..3e0c629 --- /dev/null +++ b/lib/firebase/storage.dart @@ -0,0 +1,16 @@ +import 'dart:io'; + +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:path/path.dart'; + +class Storage { + final FirebaseStorage _storage = FirebaseStorage.instance; + + Future uploadAvatar(String userId, File file) async { + String filename = basename(file.path); + Reference ref = _storage.ref().child(userId).child(filename); + UploadTask task = ref.putFile(file); + await task.whenComplete(() => {}); + return await ref.getDownloadURL(); + } +} diff --git a/lib/main.dart b/lib/main.dart index 0d97e25..a0ff04a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,45 +1,58 @@ +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; -void main() { - runApp(const MyApp()); +import 'providers/theme_provider.dart'; +import 'routes.dart'; +import 'settings/themes.dart'; + +void main(List args) async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + return runApp(const MainContent()); } -class MyApp extends StatelessWidget { - const MyApp({super.key}); +class MainContent extends StatefulWidget { + const MainContent({super.key}); @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'LinkChat', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, - ), - home: const MyHomePage(title: 'LinkChat'), - ); - } + State createState() => _MainContentState(); } -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); +class _MainContentState extends State { + int contador = 0; - final String title; + @override + void initState() { + super.initState(); + contador = 0; + } @override - State createState() => _MyHomePageState(); + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => ThemeProvider()), + ], + child: const LinkChat(), + ); + } } -class _MyHomePageState extends State { +class LinkChat extends StatelessWidget { + const LinkChat({super.key}); + @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - title: Text(widget.title), - ), - body: Center( - child: Text(widget.title), - ), + final ThemeProvider provider = context.watch(); + final ThemeData? theme = provider.theme; + provider.syncFromPrefs(); + return MaterialApp( + theme: theme ?? ThemeSettings.lightTheme, + darkTheme: theme ?? ThemeSettings.darkTheme, + themeMode: ThemeMode.system, + routes: getApplicationRoutes(), + initialRoute: '/login', ); } } diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme_provider.dart new file mode 100644 index 0000000..3a65731 --- /dev/null +++ b/lib/providers/theme_provider.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +import '../settings/preferences.dart'; + +class ThemeProvider with ChangeNotifier { + bool synced = false; + ThemeData? _theme; + + void syncFromPrefs() { + if (synced) return; + Preferences.getTheme().then((t) { + synced = true; + _theme = t; + notifyListeners(); + }); + } + + ThemeData? get theme => _theme; + + set theme(ThemeData? theme) { + Preferences.setTheme(theme); + _theme = theme; + notifyListeners(); + } +} diff --git a/lib/routes.dart b/lib/routes.dart new file mode 100644 index 0000000..9446a01 --- /dev/null +++ b/lib/routes.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +import 'screens/dashboard_screen.dart'; +import 'screens/login_screen.dart'; +import 'screens/onboarding_screen.dart'; +import 'screens/register_screen.dart'; + +Map getApplicationRoutes() { + return { + '/login': (BuildContext context) => const LoginScreen(), + '/register': (BuildContext context) => const RegisterScreen(), + '/onboard': (BuildContext context) => const OnboardingScreen(), + '/dash': (BuildContext context) => const DashboardScreen() + }; +} diff --git a/lib/screens/dashboard_screen.dart b/lib/screens/dashboard_screen.dart new file mode 100644 index 0000000..cb31a58 --- /dev/null +++ b/lib/screens/dashboard_screen.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../firebase/auth.dart'; +import '../providers/theme_provider.dart'; +import '../settings/themes.dart'; + +class DashboardScreen extends StatefulWidget { + const DashboardScreen({super.key}); + + @override + State createState() => _DashboardScreenState(); +} + +class _DashboardScreenState extends State { + late Auth _auth; + + @override + void initState() { + super.initState(); + _auth = Auth(); + } + + @override + Widget build(BuildContext context) { + final ThemeProvider themeProvider = context.watch(); + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar.large( + title: const Text('Inicio'), + ), + const SliverFillRemaining( + child: Placeholder(), + ), + ], + ), + drawer: Drawer( + child: ListView( + children: [ + UserAccountsDrawerHeader( + currentAccountPicture: CircleAvatar( + backgroundImage: _auth.currentUser?.photoURL != null + ? NetworkImage(_auth.currentUser!.photoURL!) + : null, + ), + accountName: Text(_auth.currentUser?.displayName ?? "Lincite"), + accountEmail: _auth.currentUser?.email != null + ? Text(_auth.currentUser!.email!) + : null, + ), + ListTile( + title: const Text('Tema'), + trailing: SegmentedButton( + segments: [ + const ButtonSegment( + value: null, + icon: Icon(Icons.brightness_auto), + ), + ButtonSegment( + value: ThemeSettings.lightTheme, + icon: const Icon(Icons.light_mode), + ), + ButtonSegment( + value: ThemeSettings.darkTheme, + icon: const Icon(Icons.dark_mode)), + ], + selected: {themeProvider.theme}, + onSelectionChanged: ((Set newSelection) { + themeProvider.theme = newSelection.first; + }), + ), + ), + const Divider(), + ListTile( + title: const Text('Cerrar sesión'), + leading: const Icon(Icons.logout), + onTap: () => signOut(context), + ) + ], + ), + ), + floatingActionButton: FloatingActionButton.extended( + label: const Text('Nuevo'), + icon: const Icon(Icons.create), + onPressed: () { + Navigator.of(context).pushNamed('/new'); + }, + ), + ); + } + + void signOut(BuildContext context) { + _auth.signOut().then((success) { + if (success) { + Navigator.of(context).popUntil(ModalRoute.withName('/login')); + } + }); + } +} diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart new file mode 100644 index 0000000..44cef6f --- /dev/null +++ b/lib/screens/login_screen.dart @@ -0,0 +1,171 @@ +import 'package:flutter/material.dart'; +import 'package:social_login_buttons/social_login_buttons.dart'; + +import '../firebase/auth.dart'; +import '../widgets/loading_modal_widget.dart'; +import '../widgets/responsive.dart'; + +class LoginScreen extends StatefulWidget { + const LoginScreen({super.key}); + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + + final Auth _auth = Auth(); + + bool isLoading = false; + + final padding = 16.0; + final spacer = const SizedBox(height: 16.0); + + // TextField controllers + late TextEditingController _emailController; + late TextEditingController _passwordController; + + @override + void initState() { + super.initState(); + _emailController = TextEditingController(); + _passwordController = TextEditingController(); + _controller = AnimationController(vsync: this); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Widget logoItc() => Padding( + padding: EdgeInsets.all(padding * 2), + child: Image.asset('assets/logo.png', height: 120.0), + ); + + Widget loginForm() => Column( + children: [ + spacer, + Container( + width: double.infinity, + alignment: Alignment.centerLeft, + padding: EdgeInsets.symmetric(horizontal: padding), + child: Text( + 'Iniciar sesión', + style: Theme.of(context).textTheme.displaySmall, + textAlign: TextAlign.left, + ), + ), + Card( + margin: EdgeInsets.all(padding), + child: Padding( + padding: EdgeInsets.all(padding), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextField( + controller: _emailController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Correo electrónico', + hintText: 'test@example.com', + ), + keyboardType: TextInputType.emailAddress, + ), + spacer, + TextField( + controller: _passwordController, + obscureText: true, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Contraseña', + ), + ), + spacer, + SocialLoginButton( + buttonType: SocialLoginButtonType.generalLogin, + text: 'Iniciar sesión', + backgroundColor: Theme.of(context).colorScheme.primary, + onPressed: () => onLoginClicked(context), + ), + spacer, + TextButton( + onPressed: () { + Navigator.of(context).pushNamed('/register'); + }, + child: const Text('Crear cuenta'), + ), + ], + ), + ), + ), + ], + ); + + void onLoginClicked(BuildContext context) { + _auth + .signInWithEmailAndPassword( + email: _emailController.text, + password: _passwordController.text, + ) + .then((success) { + setState(() { + isLoading = false; + }); + // TODO: checar si el resultado es true + Navigator.of(context).pushNamed('/dash'); + }); + } + + void onGoogleLoginClicked(BuildContext context) { + _auth.signInWithGoogle().then((success) { + setState(() { + isLoading = false; + }); + if (success) { + Navigator.of(context).pushNamed('/dash'); + } + }); + } + + void onGithubLoginClicked(BuildContext context) { + _auth.signInWithGithub().then((success) { + setState(() { + isLoading = false; + }); + if (success) { + Navigator.of(context).pushNamed('/dash'); + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + SingleChildScrollView( + child: SafeArea( + child: Responsive( + mobile: Column( + children: [logoItc(), loginForm()], + ), + desktop: Row( + children: [ + Expanded(child: logoItc()), + Expanded(child: loginForm()), + ], + ), + ), + ), + ), + isLoading ? const LoadingModal() : const SizedBox.shrink(), + ], + ), + ); + } +} diff --git a/lib/screens/onboarding_screen.dart b/lib/screens/onboarding_screen.dart new file mode 100644 index 0000000..e1755c4 --- /dev/null +++ b/lib/screens/onboarding_screen.dart @@ -0,0 +1,103 @@ +import 'package:concentric_transition/concentric_transition.dart'; +import 'package:flutter/material.dart'; +import 'package:lottie/lottie.dart'; + +import '../widgets/responsive.dart'; + +class PageData { + final String title; + final String icon; + final MaterialColor bgColor; + final Color textColor; + + const PageData({ + required this.title, + required this.icon, + required this.bgColor, + required this.textColor, + }); +} + +class OnboardingScreen extends StatelessWidget { + const OnboardingScreen({super.key}); + + static const pages = [ + PageData( + title: '¡Bienvenidx a LinkChat!', + icon: 'assets/star.json', + bgColor: Colors.deepPurple, + textColor: Colors.white, + ), + PageData( + title: + 'Una nueva experiencia de chat donde solo puedes compartir enlaces', + icon: 'assets/share.json', + bgColor: Colors.red, + textColor: Colors.white, + ), + PageData( + title: 'Guarda tus enlaces favoritos para verlos más tarde', + icon: 'assets/heart.json', + bgColor: Colors.green, + textColor: Colors.black, + ), + ]; + + Widget icon(PageData data) => CircleAvatar( + radius: 100.0, + backgroundColor: data.bgColor.shade200, + child: Lottie.asset( + data.icon, + height: 100.0, + fit: BoxFit.fill, + ), + ); + + Widget text(BuildContext context, PageData data) => Text( + data.title, + textAlign: TextAlign.center, + style: Theme.of(context) + .typography + .englishLike + .headlineMedium! + .copyWith(color: data.textColor), + ); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ConcentricPageView( + colors: pages.map((PageData p) => p.bgColor).toList(), + itemCount: pages.length, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (int index) { + PageData data = pages[index]; + return Center( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Responsive( + mobile: Column( + mainAxisSize: MainAxisSize.min, + children: [ + icon(data), + const SizedBox(height: 50.0), + text(context, data), + ], + ), + desktop: Row( + children: [ + Expanded(child: icon(data)), + Expanded(child: text(context, data)), + ], + ), + ), + ), + ); + }, + onFinish: () { + Navigator.of(context).pushNamed('/dash'); + }, + ), + ); + } +} diff --git a/lib/screens/register_screen.dart b/lib/screens/register_screen.dart new file mode 100644 index 0000000..349cac0 --- /dev/null +++ b/lib/screens/register_screen.dart @@ -0,0 +1,178 @@ +import 'dart:io'; + +import 'package:email_validator/email_validator.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:social_login_buttons/social_login_buttons.dart'; + +import '../firebase/auth.dart'; +import '../widgets/avatar_picker.dart'; +import '../widgets/loading_modal_widget.dart'; + +class RegisterScreen extends StatefulWidget { + const RegisterScreen({super.key}); + + @override + State createState() => _RegisterScreenState(); +} + +class _RegisterScreenState extends State { + bool isLoading = false; + + final padding = 16.0; + final spacer = const SizedBox(height: 16.0); + + XFile? _avatar; + + // TextField controllers + late TextEditingController _nameController; + late TextEditingController _emailController; + late TextEditingController _passwordController; + + final _formKey = GlobalKey(); + + @override + void initState() { + super.initState(); + _nameController = TextEditingController(); + _emailController = TextEditingController(); + _passwordController = TextEditingController(); + } + + bool validateForm() { + if (_formKey.currentState!.validate() && _avatar != null) { + return true; + } + return false; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar.large( + title: const Text('Crear cuenta'), + ), + SliverFillRemaining( + child: Stack( + children: [ + SingleChildScrollView( + child: Column( + children: [ + spacer, + Card( + margin: + EdgeInsets.fromLTRB(padding, 0, padding, padding), + child: Padding( + padding: EdgeInsets.all(padding), + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AvatarPicker( + avatar: _avatar, + onAvatarPicked: (avatar) { + setState(() { + _avatar = avatar; + }); + }, + ), + spacer, + TextFormField( + controller: _nameController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Nombre', + hintText: 'Juan Pérez', + ), + keyboardType: TextInputType.name, + validator: (value) { + if (value == null || value.isEmpty) { + return 'El nombre no debe estar vacío'; + } + return null; + }, + ), + spacer, + TextFormField( + controller: _emailController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Correo electrónico', + hintText: 'test@example.com', + ), + keyboardType: TextInputType.emailAddress, + validator: (value) { + if (value == null || value.isEmpty) { + return 'El correo no debe estar vacío'; + } else if (!EmailValidator.validate( + value)) { + return 'El formato del correo es inválido'; + } + return null; + }, + ), + spacer, + TextFormField( + controller: _passwordController, + obscureText: true, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Contraseña', + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'La contraseña no debe estar vacía'; + } + return null; + }, + ), + spacer, + SocialLoginButton( + buttonType: + SocialLoginButtonType.generalLogin, + text: 'Crear cuenta', + backgroundColor: + Theme.of(context).colorScheme.primary, + onPressed: () => onRegisterClicked(context), + ), + ], + ), + ), + ), + ), + ], + ), + ), + isLoading ? const LoadingModal() : const SizedBox.shrink(), + ], + ), + ) + ], + ), + ); + } + + void onRegisterClicked(BuildContext context) { + setState(() { + isLoading = false; + if (validateForm()) { + Auth() + .createUserWithEmailAndPassword( + email: _emailController.text, + password: _passwordController.text, + displayName: _nameController.text, + avatar: File(_avatar!.path), + ) + .then((success) { + if (success) { + Navigator.of(context).pushNamed('/onboard'); + } + }); + } + }); + } +} diff --git a/lib/settings/preferences.dart b/lib/settings/preferences.dart new file mode 100644 index 0000000..3c0ef5d --- /dev/null +++ b/lib/settings/preferences.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../settings/themes.dart'; + +class Preferences { + static SharedPreferences? _prefs; + + static Future get prefs async { + _prefs ??= await SharedPreferences.getInstance(); + return _prefs!; + } + + static Future getTheme() async { + switch ((await prefs).getString('theme')) { + case 'light': + return ThemeSettings.lightTheme; + case 'dark': + return ThemeSettings.darkTheme; + } + return null; + } + + static void setTheme(ThemeData? theme) { + prefs.then((p) { + if (theme == ThemeSettings.lightTheme) { + p.setString('theme', 'light'); + } else if (theme == ThemeSettings.darkTheme) { + p.setString('theme', 'dark'); + } else { + p.remove('theme'); + } + }); + } +} diff --git a/lib/settings/themes.dart b/lib/settings/themes.dart new file mode 100644 index 0000000..bc489b7 --- /dev/null +++ b/lib/settings/themes.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class ThemeSettings { + static ThemeData lightTheme = ThemeData( + useMaterial3: true, + primaryColor: Colors.purple, + ); + + static ThemeData darkTheme = ThemeData.dark( + useMaterial3: true, + ).copyWith( + colorScheme: const ColorScheme.dark().copyWith( + primary: Colors.purple, + ), + ); +} diff --git a/lib/widgets/avatar_picker.dart b/lib/widgets/avatar_picker.dart new file mode 100644 index 0000000..bdfec6d --- /dev/null +++ b/lib/widgets/avatar_picker.dart @@ -0,0 +1,61 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; + +class AvatarPicker extends StatelessWidget { + final ImagePicker picker = ImagePicker(); + final XFile? avatar; + final Function(XFile? avatar) onAvatarPicked; + + AvatarPicker({ + super.key, + required this.avatar, + required this.onAvatarPicked, + }); + + File? _getAvatarFile() { + return avatar == null ? null : File(avatar!.path); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircleAvatar( + radius: 50.0, + backgroundImage: + _getAvatarFile() == null ? null : FileImage(_getAvatarFile()!), + child: _getAvatarFile() == null + ? const Icon(Icons.person, size: 50.0) + : null, + ), + const SizedBox(width: 18.0), + Column( + children: [ + FilledButton.icon( + icon: const Icon(Icons.camera_alt), + label: const Text('Cámara'), + onPressed: () { + picker.pickImage(source: ImageSource.camera).then((image) { + onAvatarPicked(image); + }); + }, + ), + const SizedBox(height: 16.0), + FilledButton.icon( + icon: const Icon(Icons.collections), + label: const Text('Galería'), + onPressed: () { + picker.pickImage(source: ImageSource.gallery).then((image) { + onAvatarPicked(image); + }); + }, + ) + ], + ) + ], + ); + } +} diff --git a/lib/widgets/loading_modal_widget.dart b/lib/widgets/loading_modal_widget.dart new file mode 100644 index 0000000..c19a411 --- /dev/null +++ b/lib/widgets/loading_modal_widget.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class LoadingModal extends StatelessWidget { + const LoadingModal({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: Card( + shape: CircleBorder(), + child: Padding( + padding: EdgeInsets.all(20.0), + child: SizedBox( + width: 200, + height: 200, + child: CircularProgressIndicator(value: null), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/responsive.dart b/lib/widgets/responsive.dart new file mode 100644 index 0000000..f5f2739 --- /dev/null +++ b/lib/widgets/responsive.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +class Responsive extends StatelessWidget { + final Widget mobile; + final Widget? tablet; + final Widget desktop; + + const Responsive({ + super.key, + required this.mobile, + this.tablet, + required this.desktop, + }); + + static bool isMobile(BuildContext context) => + MediaQuery.of(context).size.width < 576; + + static bool isTablet(BuildContext context) => + MediaQuery.of(context).size.width >= 576 && + MediaQuery.of(context).size.width <= 992; + + static bool isDesktop(BuildContext context) => + MediaQuery.of(context).size.width > 992; + + @override + Widget build(BuildContext context) { + if (isDesktop(context)) { + return desktop; + } else if (tablet != null && isTablet(context)) { + return tablet!; + } + return mobile; + } +} diff --git a/pubspec.lock b/pubspec.lock index 5e79557..28080bd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + archive: + dependency: transitive + description: + name: archive + sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" + url: "https://pub.dev" + source: hosted + version: "3.3.7" async: dependency: transitive description: @@ -73,6 +81,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.1" + concentric_transition: + dependency: "direct main" + description: + name: concentric_transition + sha256: "825191221e4bc6a0cfaf00adbc5cd2cc1333970f61311bce52021f1f68e0a891" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" + url: "https://pub.dev" + source: hosted + version: "0.3.3+4" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + email_validator: + dependency: "direct main" + description: + name: email_validator + sha256: e9a90f27ab2b915a27d7f9c2a7ddda5dd752d6942616ee83529b686fc086221b + url: "https://pub.dev" + source: hosted + version: "2.1.17" fake_async: dependency: transitive description: @@ -81,6 +129,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" firebase_auth: dependency: "direct main" description: @@ -153,6 +217,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.5.1" + firebase_storage: + dependency: "direct main" + description: + name: firebase_storage + sha256: "89ff2dfd353f953fb2bbbe04359ea1a46d643944f310cfed22dadf352fc16976" + url: "https://pub.dev" + source: hosted + version: "11.2.1" + firebase_storage_platform_interface: + dependency: transitive + description: + name: firebase_storage_platform_interface + sha256: c06ccd21c3ed20da6128629ab7d525f7b613caddfcd5466ba4a1ff58655261ac + url: "https://pub.dev" + source: hosted + version: "4.4.1" + firebase_storage_web: + dependency: transitive + description: + name: firebase_storage_web + sha256: "26a039f211b226fc216f9f06f13402bdf08661edb7c42cb1de3bd236afbbbf75" + url: "https://pub.dev" + source: hosted + version: "3.6.1" flutter: dependency: "direct main" description: flutter @@ -166,6 +254,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" + url: "https://pub.dev" + source: hosted + version: "2.0.15" flutter_test: dependency: "direct dev" description: flutter @@ -176,6 +272,14 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + url: "https://pub.dev" + source: hosted + version: "0.13.6" http_parser: dependency: transitive description: @@ -184,6 +288,46 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "9978d3510af4e6a902e545ce19229b926e6de6a1828d6134d3aab2e129a4d270" + url: "https://pub.dev" + source: hosted + version: "0.8.7+5" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: c2f3c66400649bd132f721c88218945d6406f693092b2f741b79ae9cdb046e59 + url: "https://pub.dev" + source: hosted + version: "0.8.6+16" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "98f50d6b9f294c8ba35e25cc0d13b04bfddd25dbc8d32fa9d566a6572f2c081c" + url: "https://pub.dev" + source: hosted + version: "2.1.12" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: d779210bda268a03b57e923fb1e410f32f5c5e708ad256348bcbf1f44f558fd0 + url: "https://pub.dev" + source: hosted + version: "0.8.7+4" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "1991219d9dbc42a99aff77e663af8ca51ced592cd6685c9485e3458302d3d4f8" + url: "https://pub.dev" + source: hosted + version: "2.6.3" js: dependency: transitive description: @@ -200,6 +344,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + lottie: + dependency: "direct main" + description: + name: lottie + sha256: "23522951540d20a57a60202ed7022e6376bed206a4eee1c347a91f58bd57eb9f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" matcher: dependency: transitive description: @@ -224,14 +376,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" - path: + nested: dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + path: + dependency: "direct main" description: name: path sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted version: "1.8.3" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" + url: "https://pub.dev" + source: hosted + version: "2.1.10" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + url: "https://pub.dev" + source: hosted + version: "2.0.6" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + platform: + dependency: transitive + description: + name: platform + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" + source: hosted + version: "3.1.0" plugin_platform_interface: dependency: transitive description: @@ -240,11 +432,99 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" + source: hosted + version: "3.7.3" + process: + dependency: transitive + description: + name: process + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" + source: hosted + version: "4.2.4" + provider: + dependency: "direct main" + description: + name: provider + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" + source: hosted + version: "6.0.5" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d + url: "https://pub.dev" + source: hosted + version: "2.2.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173" + url: "https://pub.dev" + source: hosted + version: "2.2.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + social_login_buttons: + dependency: "direct main" + description: + name: social_login_buttons + sha256: "052b88f1a9fed69430097093c02ed738a0fbf4c5fa4b0ed0ce4e8aa3caf592de" + url: "https://pub.dev" + source: hosted + version: "1.0.7" source_span: dependency: transitive description: @@ -309,6 +589,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + win32: + dependency: transitive + description: + name: win32 + sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + url: "https://pub.dev" + source: hosted + version: "4.1.4" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + url: "https://pub.dev" + source: hosted + version: "1.0.0" sdks: dart: ">=3.0.1 <4.0.0" flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4fac976..1f079b3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,9 +10,18 @@ dependencies: flutter: sdk: flutter + provider: ^6.0.5 + shared_preferences: ^2.1.1 + lottie: ^2.3.2 + concentric_transition: ^1.0.3 + social_login_buttons: ^1.0.7 + image_picker: ^0.8.7 + path: ^1.8.3 + email_validator: ^2.1.17 firebase_core: ^2.13.0 firebase_auth: ^4.6.1 firebase_messaging: ^14.6.1 + firebase_storage: ^11.2.1 cloud_firestore: ^4.7.1 dev_dependencies: @@ -22,3 +31,6 @@ dev_dependencies: flutter: uses-material-design: true + + assets: + - assets/ -- cgit v1.2.3