diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json
index d8abe1b9..1726d93d 100644
--- a/.fvm/fvm_config.json
+++ b/.fvm/fvm_config.json
@@ -1,4 +1,3 @@
{
- "flutterSdkVersion": "3.13.9",
- "flavors": {}
+ "flutterSdkVersion": "3.13.9"
}
\ No newline at end of file
diff --git a/.fvmrc b/.fvmrc
new file mode 100644
index 00000000..6108f14a
--- /dev/null
+++ b/.fvmrc
@@ -0,0 +1,4 @@
+{
+ "flutter": "3.13.9",
+ "flavors": {}
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 1be2d875..7040cb04 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,4 +46,6 @@ app.*.map.json
/android/app/release
# fvm
-.fvm/flutter_sdk
\ No newline at end of file
+
+# FVM Version Cache
+.fvm/
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index f285aa4a..2951c6d3 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,9 +1,9 @@
{
- "dart.flutterSdkPath": ".fvm/flutter_sdk",
- "search.exclude": {
- "**/.fvm": true
- },
- "files.watcherExclude": {
- "**/.fvm": true
- }
+ "dart.flutterSdkPath": ".fvm/versions/3.13.9",
+ "search.exclude": {
+ "**/.fvm": true
+ },
+ "files.watcherExclude": {
+ "**/.fvm": true
+ }
}
\ No newline at end of file
diff --git a/assets/mock_restaurants.json b/assets/mock_restaurants.json
new file mode 100644
index 00000000..a8f1bcf9
--- /dev/null
+++ b/assets/mock_restaurants.json
@@ -0,0 +1,837 @@
+{
+ "total": 6251,
+ "business": [
+ {
+ "id": "vHz2RLtfUMVRPFmd7VBEHA",
+ "name": "Gordon Ramsay Hell's Kitchen",
+ "price": "$$$",
+ "rating": 4.4,
+ "photos": [
+ "https://s3-media2.fl.yelpcdn.com/bphoto/q771KjLzI5y638leJsnJnQ/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "VzJIMZRW-8lwoFJzk0jAXw",
+ "rating": 5,
+ "user": {
+ "id": "i2dS47auJ-9-OW4xZSPxAA",
+ "image_url": "https://s3-media1.fl.yelpcdn.com/photo/M2AsmeEgwVwpjyaE1lFtIA/o.jpg",
+ "name": "White R."
+ }
+ },
+ {
+ "id": "H85bnGMvTx0ACssHvyCyug",
+ "rating": 5,
+ "user": {
+ "id": "3xfzp3cOhKICnLn0D9ZheA",
+ "image_url": null,
+ "name": "Molly S."
+ }
+ },
+ {
+ "id": "O60EsMnhEmrSs4F54imSkw",
+ "rating": 3,
+ "user": {
+ "id": "C8e7rVhQY6lMYm-yn1luJQ",
+ "image_url": null,
+ "name": "Taylor T."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "New American",
+ "alias": "newamerican"
+ },
+ {
+ "title": "Seafood",
+ "alias": "seafood"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "3570 Las Vegas Blvd S\nLas Vegas, NV 89109"
+ }
+ },
+ {
+ "id": "faPVqws-x-5k2CQKDNtHxw",
+ "name": "Yardbird",
+ "price": "$$",
+ "rating": 4.5,
+ "photos": [
+ "https://s3-media1.fl.yelpcdn.com/bphoto/xYJaanpF3Dl1OovhmpqAYw/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "uIeZrx9X1W0XPKqDicXZew",
+ "rating": 5,
+ "user": {
+ "id": "nvcvPpKYpq-nT7wwAexGYw",
+ "image_url": "https://s3-media1.fl.yelpcdn.com/photo/2_pHFKGZ3-SlBq_HTXp8wg/o.jpg",
+ "name": "Tanner D."
+ }
+ },
+ {
+ "id": "V8KFADRFJnsGUvQ3iRtnig",
+ "rating": 5,
+ "user": {
+ "id": "R_PPnsl0gsIvzhq9JHRCXQ",
+ "image_url": null,
+ "name": "Misha Z."
+ }
+ },
+ {
+ "id": "NTi315CS824pvOsnqZmnww",
+ "rating": 5,
+ "user": {
+ "id": "swpNJGPBG4XCiMH7FeOUZg",
+ "image_url": "https://s3-media2.fl.yelpcdn.com/photo/Tft5UPZtZ4zMNtVgdvbIwQ/o.jpg",
+ "name": "Kimberly Z."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "Southern",
+ "alias": "southern"
+ },
+ {
+ "title": "New American",
+ "alias": "newamerican"
+ },
+ {
+ "title": "Cocktail Bars",
+ "alias": "cocktailbars"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "3355 Las Vegas Blvd S\nLas Vegas, NV 89109"
+ }
+ },
+ {
+ "id": "2iTsRqUsPGRH1li1WVRvKQ",
+ "name": "Carson Kitchen",
+ "price": "$$",
+ "rating": 4.5,
+ "photos": [
+ "https://s3-media2.fl.yelpcdn.com/bphoto/LhaPvLHIrsHu8ZMLgV04OQ/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "59ewmBp3j19Ud3T7Lz4-Ow",
+ "rating": 4,
+ "user": {
+ "id": "7DQSAc84ydnYEP2UMWG0oQ",
+ "image_url": "https://s3-media4.fl.yelpcdn.com/photo/xV_gdMBDART7WwHp8KPlkA/o.jpg",
+ "name": "Shannon P."
+ }
+ },
+ {
+ "id": "tcrSG4NQUQktQNTncUnA8A",
+ "rating": 5,
+ "user": {
+ "id": "8AFvV4hG3IsoG7kyDXRZLw",
+ "image_url": null,
+ "name": "Lonfre A."
+ }
+ },
+ {
+ "id": "SLncTZbrWzvn4QMiOb1brA",
+ "rating": 5,
+ "user": {
+ "id": "RB_lfittmnIRVL-m-4Q5YQ",
+ "image_url": null,
+ "name": "Suanne K."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "New American",
+ "alias": "newamerican"
+ },
+ {
+ "title": "Desserts",
+ "alias": "desserts"
+ },
+ {
+ "title": "Cocktail Bars",
+ "alias": "cocktailbars"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "124 S 6th St\nSte 100\nLas Vegas, NV 89101"
+ }
+ },
+ {
+ "id": "syhA1ugJpyNLaB0MiP19VA",
+ "name": "888 Japanese BBQ",
+ "price": "$$$",
+ "rating": 4.8,
+ "photos": [
+ "https://s3-media1.fl.yelpcdn.com/bphoto/V_zmwCUG1o_vR29xfkb-ng/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "CfkhNXxjrmAG3Rqth2S8PA",
+ "rating": 5,
+ "user": {
+ "id": "Lh3eh4seBFvfTgybYXL1uw",
+ "image_url": "https://s3-media4.fl.yelpcdn.com/photo/oztzeYdcy2lE6edfoXhapQ/o.jpg",
+ "name": "Christine X."
+ }
+ },
+ {
+ "id": "BVXWtpN-Xn4eIjMe2eceQg",
+ "rating": 5,
+ "user": {
+ "id": "azjuxjks4tfg_HCt1D5zqQ",
+ "image_url": "https://s3-media4.fl.yelpcdn.com/photo/dD5Mtong507Zkt91BLKLNQ/o.jpg",
+ "name": "Lexie P."
+ }
+ },
+ {
+ "id": "OqxFOKP3qvm3jnnZJaxOUw",
+ "rating": 5,
+ "user": {
+ "id": "-DG-God4RyXPOsaGmPNDcg",
+ "image_url": null,
+ "name": "Leilani S."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "Barbeque",
+ "alias": "bbq"
+ },
+ {
+ "title": "Japanese",
+ "alias": "japanese"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "3550 S Decatur Blvd\nLas Vegas, NV 89103"
+ }
+ },
+ {
+ "id": "QXV3L_QFGj8r6nWX2kS2hA",
+ "name": "Nacho Daddy",
+ "price": "$$",
+ "rating": 4.4,
+ "photos": [
+ "https://s3-media4.fl.yelpcdn.com/bphoto/pu9doqMplB5x5SEs8ikW6w/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "lXHzKzJfUaMHh3d6L4CcVw",
+ "rating": 3,
+ "user": {
+ "id": "owWvMQ5g6ZokQKyrKHptug",
+ "image_url": null,
+ "name": "Big B."
+ }
+ },
+ {
+ "id": "ZRJNeoKFkVnjWFv7uiUBSg",
+ "rating": 5,
+ "user": {
+ "id": "VtXtUlnnTX78cMQrQJYfJQ",
+ "image_url": "https://s3-media1.fl.yelpcdn.com/photo/gpd9sy4O0ng7HOvfsyn5tA/o.jpg",
+ "name": "Montse C."
+ }
+ },
+ {
+ "id": "31ss_mS7gZ-n87m9WtiO4Q",
+ "rating": 5,
+ "user": {
+ "id": "moPMSTcAmuXV1gGOmrSxCA",
+ "image_url": null,
+ "name": "Samuel B."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "New American",
+ "alias": "newamerican"
+ },
+ {
+ "title": "Mexican",
+ "alias": "mexican"
+ },
+ {
+ "title": "Breakfast & Brunch",
+ "alias": "breakfast_brunch"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "3663 Las Vegas Blvd\nSte 595\nLas Vegas, NV 89109"
+ }
+ },
+ {
+ "id": "I6EDDi4-Eq_XlFghcDCUhw",
+ "name": "Joe's Seafood Prime Steak & Stone Crab",
+ "price": "$$$",
+ "rating": 4.4,
+ "photos": [
+ "https://s3-media1.fl.yelpcdn.com/bphoto/I1GDdV1mWUJM5HTP1PIX6A/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "vT4P__nHekuPUyMaX84iDg",
+ "rating": 5,
+ "user": {
+ "id": "Em4JJUrZwNQmEcOKcgsTdA",
+ "image_url": "https://s3-media2.fl.yelpcdn.com/photo/Z-JwlzFO03ZPg1Ipmhfq6A/o.jpg",
+ "name": "Sara K."
+ }
+ },
+ {
+ "id": "pzndfUZHiKN9udZXpZP3GA",
+ "rating": 4,
+ "user": {
+ "id": "rjV-cGARjLT5NCXE4QoITQ",
+ "image_url": "https://s3-media1.fl.yelpcdn.com/photo/csz_4XilwyqvCgY7-VzSpA/o.jpg",
+ "name": "Eric C."
+ }
+ },
+ {
+ "id": "UspFMU3KmguqGMihIkH5jA",
+ "rating": 5,
+ "user": {
+ "id": "gYgFW1ZF603FuEbqgUEvzw",
+ "image_url": "https://s3-media2.fl.yelpcdn.com/photo/CxRlXjvcv7Icovk1QIFSiQ/o.jpg",
+ "name": "Elaine L."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "Seafood",
+ "alias": "seafood"
+ },
+ {
+ "title": "Steakhouses",
+ "alias": "steak"
+ },
+ {
+ "title": "Wine Bars",
+ "alias": "wine_bars"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "3500 Las Vegas Blvd S\nLas Vegas, NV 89109"
+ }
+ },
+ {
+ "id": "JPfi__QJAaRzmfh5aOyFEw",
+ "name": "Shang Artisan Noodle",
+ "price": "$$",
+ "rating": 4.6,
+ "photos": [
+ "https://s3-media3.fl.yelpcdn.com/bphoto/TqV2TDWH-7Wje5B9Oh1EZw/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "CsuamYmDAWthyn6AxCvXZQ",
+ "rating": 5,
+ "user": {
+ "id": "BHIGJAgBTRD4H01yP0n9Qg",
+ "image_url": null,
+ "name": "Genesis S."
+ }
+ },
+ {
+ "id": "MlbzJT2UhcebcoXtq0kczA",
+ "rating": 5,
+ "user": {
+ "id": "HVwq3FtsOuxQV9DvoPa4RA",
+ "image_url": "https://s3-media4.fl.yelpcdn.com/photo/qptBQ1iGo9kY9wDGF8bFHA/o.jpg",
+ "name": "Jasmine B."
+ }
+ },
+ {
+ "id": "0AG_FlfYBfxlEIQKepSktw",
+ "rating": 5,
+ "user": {
+ "id": "Cco2owhvdzfZ-UAS4tPwKw",
+ "image_url": null,
+ "name": "Audrey D."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "Noodles",
+ "alias": "noodles"
+ },
+ {
+ "title": "Chinese",
+ "alias": "chinese"
+ },
+ {
+ "title": "Soup",
+ "alias": "soup"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "4983 W Flamingo Rd\nSte B\nLas Vegas, NV 89103"
+ }
+ },
+ {
+ "id": "nUpz0YiBsOK7ff9k3vUJ3A",
+ "name": "Buddy V's Ristorante",
+ "price": "$$",
+ "rating": 4.2,
+ "photos": [
+ "https://s3-media1.fl.yelpcdn.com/bphoto/gLHjQg0bjGjr_Jus-BXqDA/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "XvYKeYfYU2mDODBphOlYXA",
+ "rating": 4,
+ "user": {
+ "id": "Vz5KVB8oq1Yd-yBjjUzq7w",
+ "image_url": "https://s3-media2.fl.yelpcdn.com/photo/H-8KWUKW8ujADR3a-LW3wA/o.jpg",
+ "name": "Teresa L."
+ }
+ },
+ {
+ "id": "j3qyXxcVl1N5o01AuCbJkw",
+ "rating": 5,
+ "user": {
+ "id": "VBIGHWAj0lhxHtqEjNgXOw",
+ "image_url": null,
+ "name": "Gary G."
+ }
+ },
+ {
+ "id": "aigNWful677P85ArYZRkqw",
+ "rating": 5,
+ "user": {
+ "id": "yhZ-fJdtaImuUL-lW6e9-g",
+ "image_url": "https://s3-media2.fl.yelpcdn.com/photo/1sU9tinpG1SuG4e5h9EE3g/o.jpg",
+ "name": "Nicole T."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "Italian",
+ "alias": "italian"
+ },
+ {
+ "title": "American",
+ "alias": "tradamerican"
+ },
+ {
+ "title": "Wine Bars",
+ "alias": "wine_bars"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "3327 S Las Vegas Blvd\nLas Vegas, NV 89109"
+ }
+ },
+ {
+ "id": "gOOfBSBZlffCkQ7dr7cpdw",
+ "name": "CHICA",
+ "price": "$$",
+ "rating": 4.3,
+ "photos": [
+ "https://s3-media2.fl.yelpcdn.com/bphoto/FxmtjuzPDiL7vx5KyceWuQ/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "CbF8xDxRzLfCmuDRC6tfPw",
+ "rating": 5,
+ "user": {
+ "id": "iv9NN0Vwy3hH-BKVyM_t-g",
+ "image_url": "https://s3-media1.fl.yelpcdn.com/photo/Irz4u42GVEDRvHJHY4z6Hg/o.jpg",
+ "name": "Nancy L."
+ }
+ },
+ {
+ "id": "2zno95s1q0S55ZnQD-3RHQ",
+ "rating": 4,
+ "user": {
+ "id": "epPsJevS8chv-tvxYDCSew",
+ "image_url": "https://s3-media1.fl.yelpcdn.com/photo/mh4egjwst_25_ChKKjlSBA/o.jpg",
+ "name": "Dianna H."
+ }
+ },
+ {
+ "id": "KpmUpAaEq--1tyGVIOxSwA",
+ "rating": 5,
+ "user": {
+ "id": "a2pHeokH8l7r3T8nXach0A",
+ "image_url": "https://s3-media1.fl.yelpcdn.com/photo/2LLnjIvr0y9yMen135cJKQ/o.jpg",
+ "name": "Kam M."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "Latin American",
+ "alias": "latin"
+ },
+ {
+ "title": "Breakfast & Brunch",
+ "alias": "breakfast_brunch"
+ },
+ {
+ "title": "Cocktail Bars",
+ "alias": "cocktailbars"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "3355 South Las Vegas Blvd\nSte 106\nLas Vegas, NV 89109"
+ }
+ },
+ {
+ "id": "igHYkXZMLAc9UdV5VnR_AA",
+ "name": "Echo & Rig",
+ "price": "$$$",
+ "rating": 4.4,
+ "photos": [
+ "https://s3-media1.fl.yelpcdn.com/bphoto/Q9swks1BO-w-hVskIHrCVg/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "KxB6EqbsUAYcXCbogF0j9A",
+ "rating": 5,
+ "user": {
+ "id": "W3opz1HpIXl2krFLJ53lqg",
+ "image_url": null,
+ "name": "Ming Z."
+ }
+ },
+ {
+ "id": "to14TViy1ksheDKAfHkQ1Q",
+ "rating": 5,
+ "user": {
+ "id": "fe4LgCw7X9TZCocwvr-LTQ",
+ "image_url": "https://s3-media3.fl.yelpcdn.com/photo/JN2tU8aDVk68I3ywHepdkg/o.jpg",
+ "name": "Shane B."
+ }
+ },
+ {
+ "id": "N4t02F-XrnuY_Zf2-tQk1Q",
+ "rating": 4,
+ "user": {
+ "id": "xz81pPXEuon4-7yRg2ptDQ",
+ "image_url": "https://s3-media3.fl.yelpcdn.com/photo/HWfZOqm3rqN8r0QY-4ORzQ/o.jpg",
+ "name": "Grace C."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "Steakhouses",
+ "alias": "steak"
+ },
+ {
+ "title": "Butcher",
+ "alias": "butcher"
+ },
+ {
+ "title": "Tapas/Small Plates",
+ "alias": "tapasmallplates"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "440 S Rampart Blvd\nLas Vegas, NV 89145"
+ }
+ },
+ {
+ "id": "rdE9gg0WB7Z8kRytIMSapg",
+ "name": "Lazy Dog Restaurant & Bar",
+ "price": "$$",
+ "rating": 4.5,
+ "photos": [
+ "https://s3-media2.fl.yelpcdn.com/bphoto/_Wz-fNXawmbBinSf9Ev15g/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "nSRTIxL_xl5m8gykOEq-WQ",
+ "rating": 5,
+ "user": {
+ "id": "zaERxTbPn4TW6f6jynAb_A",
+ "image_url": "https://s3-media1.fl.yelpcdn.com/photo/A7Cov8r811C0tZiVArEn3A/o.jpg",
+ "name": "Kayleene M."
+ }
+ },
+ {
+ "id": "IUYj8Lox9pD1pda-a2rF1w",
+ "rating": 5,
+ "user": {
+ "id": "8ExBbLTrT8Gc1R9r4KhBCg",
+ "image_url": null,
+ "name": "Angelica M."
+ }
+ },
+ {
+ "id": "2rcy8s1va17xnmQut-qkOw",
+ "rating": 5,
+ "user": {
+ "id": "8h9-semB4gW931Q5B1LJ4w",
+ "image_url": null,
+ "name": "mike m."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "New American",
+ "alias": "newamerican"
+ },
+ {
+ "title": "Comfort Food",
+ "alias": "comfortfood"
+ },
+ {
+ "title": "Burgers",
+ "alias": "burgers"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "6509 S Las Vegas Blvd\nLas Vegas, NV 89119"
+ }
+ },
+ {
+ "id": "UidEFF1WpnU4duev4fjPlQ",
+ "name": "Therapy ",
+ "price": "$$",
+ "rating": 4.3,
+ "photos": [
+ "https://s3-media3.fl.yelpcdn.com/bphoto/otaMuPtauoEb6qZzmHlAlQ/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "b0Vc0DyssvQ6eR9zVI2bnQ",
+ "rating": 5,
+ "user": {
+ "id": "rky5o20jykKRbssb4aoo7g",
+ "image_url": null,
+ "name": "doyle W."
+ }
+ },
+ {
+ "id": "rFkhUcAd_toiQF5etzOUFw",
+ "rating": 5,
+ "user": {
+ "id": "TgVjm7u8yWeP7E8E8HLi9w",
+ "image_url": "https://s3-media2.fl.yelpcdn.com/photo/mpciVuTs9rLYXIS2T9Jszg/o.jpg",
+ "name": "Jose P."
+ }
+ },
+ {
+ "id": "VwdNtA6ZVYH2CQ2S8_8V7A",
+ "rating": 3,
+ "user": {
+ "id": "j-g4mLPahUwSNzWzKIIQuA",
+ "image_url": "https://s3-media1.fl.yelpcdn.com/photo/-qUjr9ZJN6lDaGEHjcXLlA/o.jpg",
+ "name": "Kelly M."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "Bars",
+ "alias": "bars"
+ },
+ {
+ "title": "New American",
+ "alias": "newamerican"
+ },
+ {
+ "title": "Dance Clubs",
+ "alias": "danceclubs"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "518 Fremont St\nLas Vegas, NV 89101"
+ }
+ },
+ {
+ "id": "4JNXUYY8wbaaDmk3BPzlWw",
+ "name": "Mon Ami Gabi",
+ "price": "$$$",
+ "rating": 4.2,
+ "photos": [
+ "https://s3-media2.fl.yelpcdn.com/bphoto/cZ75DtuiHsOU-4W3vLsFKA/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "rsFWnc3wXsDeFXa2ATFZiQ",
+ "rating": 5,
+ "user": {
+ "id": "L6TIKUDGuzGRkfvP56-tKA",
+ "image_url": null,
+ "name": "Deneen M."
+ }
+ },
+ {
+ "id": "bPYqNEvJaQxkAeZDmXRCug",
+ "rating": 5,
+ "user": {
+ "id": "4G8bg7wdu_WHezo1c1EYgA",
+ "image_url": "https://s3-media2.fl.yelpcdn.com/photo/jzQtjP49LXCkTH9ANt04KQ/o.jpg",
+ "name": "Rabindra A."
+ }
+ },
+ {
+ "id": "I-CloBpIZZhvpFBaQTOs7g",
+ "rating": 3,
+ "user": {
+ "id": "G9x-ugGo1HI7J2SItKUzMg",
+ "image_url": "https://s3-media3.fl.yelpcdn.com/photo/WRHViSHCN_TvnuaM-LF_wA/o.jpg",
+ "name": "Michelle T."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "French",
+ "alias": "french"
+ },
+ {
+ "title": "Steakhouses",
+ "alias": "steak"
+ },
+ {
+ "title": "Breakfast & Brunch",
+ "alias": "breakfast_brunch"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "3655 Las Vegas Blvd S\nLas Vegas, NV 89109"
+ }
+ },
+ {
+ "id": "JDZ6_yycNQFTpUZzLIKHUg",
+ "name": "El Dorado Cantina - Las Vegas Strip",
+ "price": "$$",
+ "rating": 4.4,
+ "photos": [
+ "https://s3-media2.fl.yelpcdn.com/bphoto/XUohVZ4cdk13GWrUmnQKYQ/o.jpg"
+ ],
+ "reviews": [
+ {
+ "id": "o6bSMlXb1PBUZPZFQYFxWA",
+ "rating": 5,
+ "user": {
+ "id": "sqgULvS3Y3PkZHWaEXDUsw",
+ "image_url": null,
+ "name": "Laurie C."
+ }
+ },
+ {
+ "id": "Yio6qxUapbvBWmR2HX-Q7g",
+ "rating": 5,
+ "user": {
+ "id": "edZ6kTxithgtctw1crIkBA",
+ "image_url": "https://s3-media1.fl.yelpcdn.com/photo/vgHMPe8zIBtiVbQ_sEXjpw/o.jpg",
+ "name": "Liza Faye T."
+ }
+ },
+ {
+ "id": "7e3O21G7nc3fgtXqaunwMA",
+ "rating": 5,
+ "user": {
+ "id": "Fp3ahB16xguTAi9FqtSZtw",
+ "image_url": "https://s3-media2.fl.yelpcdn.com/photo/LqFLe9gQBvS_6PwZNrtWNQ/o.jpg",
+ "name": "Julie N."
+ }
+ }
+ ],
+ "categories": [
+ {
+ "title": "Mexican",
+ "alias": "mexican"
+ },
+ {
+ "title": "Bars",
+ "alias": "bars"
+ },
+ {
+ "title": "Latin American",
+ "alias": "latin"
+ }
+ ],
+ "hours": [
+ {
+ "is_open_now": true
+ }
+ ],
+ "location": {
+ "formatted_address": "3025 Sammy Davis Jr Dr\nLas Vegas, NV 89109"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/devtools_options.yaml b/devtools_options.yaml
new file mode 100644
index 00000000..7e7e7f67
--- /dev/null
+++ b/devtools_options.yaml
@@ -0,0 +1 @@
+extensions:
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
index 9625e105..7c569640 100644
--- a/ios/Flutter/AppFrameworkInfo.plist
+++ b/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 11.0
+ 12.0
diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig
index 592ceee8..ec97fc6f 100644
--- a/ios/Flutter/Debug.xcconfig
+++ b/ios/Flutter/Debug.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig
index 592ceee8..c4855bfe 100644
--- a/ios/Flutter/Release.xcconfig
+++ b/ios/Flutter/Release.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
diff --git a/ios/Podfile b/ios/Podfile
new file mode 100644
index 00000000..f68de6f2
--- /dev/null
+++ b/ios/Podfile
@@ -0,0 +1,44 @@
+# Uncomment this line to define a global platform for your project
+platform :ios, '12.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+ #target 'RunnerTests' do
+ #inherit! :search_paths
+ #end
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+ end
+end
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
new file mode 100644
index 00000000..a41b4536
--- /dev/null
+++ b/ios/Podfile.lock
@@ -0,0 +1,37 @@
+PODS:
+ - Flutter (1.0.0)
+ - path_provider_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - shared_preferences_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - sqflite (0.0.3):
+ - Flutter
+ - FlutterMacOS
+
+DEPENDENCIES:
+ - Flutter (from `Flutter`)
+ - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
+ - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
+ - sqflite (from `.symlinks/plugins/sqflite/darwin`)
+
+EXTERNAL SOURCES:
+ Flutter:
+ :path: Flutter
+ path_provider_foundation:
+ :path: ".symlinks/plugins/path_provider_foundation/darwin"
+ shared_preferences_foundation:
+ :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
+ sqflite:
+ :path: ".symlinks/plugins/sqflite/darwin"
+
+SPEC CHECKSUMS:
+ Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
+ shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
+ sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
+
+PODFILE CHECKSUM: 692bace833b660596368f5e5903a29181eef0f24
+
+COCOAPODS: 1.14.3
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 73cf3f6d..7af7c5c1 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 2878E0C2E481F24C270AE513 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB52062EC8C59B596765BF37 /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@@ -32,9 +33,11 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 5299DCE3EBCE76FBF4DA87D8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 87A197ED848EA95F3C0230A5 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -42,6 +45,8 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ AB52062EC8C59B596765BF37 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ B67B84F5FACC1335F8251268 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -49,12 +54,32 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 2878E0C2E481F24C270AE513 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 0280117EEE41C5E203769462 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 87A197ED848EA95F3C0230A5 /* Pods-Runner.debug.xcconfig */,
+ 5299DCE3EBCE76FBF4DA87D8 /* Pods-Runner.release.xcconfig */,
+ B67B84F5FACC1335F8251268 /* Pods-Runner.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
+ 646BF599FA2E713798490FE8 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ AB52062EC8C59B596765BF37 /* Pods_Runner.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -72,6 +97,8 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
+ 0280117EEE41C5E203769462 /* Pods */,
+ 646BF599FA2E713798490FE8 /* Frameworks */,
);
sourceTree = "";
};
@@ -105,12 +132,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
+ 97CDAE6BF738374ADB54110E /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ CA18C7AE0E75570573B61109 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -200,6 +229,45 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
+ 97CDAE6BF738374ADB54110E /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ CA18C7AE0E75570573B61109 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -275,7 +343,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -352,7 +420,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -401,7 +469,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata
index 1d526a16..21a3cc14 100644
--- a/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ b/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -4,4 +4,7 @@
+
+
diff --git a/lib/common/constants.dart b/lib/common/constants.dart
new file mode 100644
index 00000000..b9779a06
--- /dev/null
+++ b/lib/common/constants.dart
@@ -0,0 +1,4 @@
+const kEmptyString = "";
+const kZeroInt = 0;
+const kZeroDouble = 0.0;
+const kFavoriteRestaurantsKey = "kFavoriteRestaurantsKey";
diff --git a/lib/common/extensions.dart b/lib/common/extensions.dart
new file mode 100644
index 00000000..6fc3d67f
--- /dev/null
+++ b/lib/common/extensions.dart
@@ -0,0 +1,7 @@
+import 'package:flutter/material.dart';
+
+extension Theme1 on BuildContext {
+ ThemeData get theme {
+ return Theme.of(this);
+ }
+}
\ No newline at end of file
diff --git a/lib/custom_widget/custom_app_bar.dart b/lib/custom_widget/custom_app_bar.dart
new file mode 100644
index 00000000..0ddf4765
--- /dev/null
+++ b/lib/custom_widget/custom_app_bar.dart
@@ -0,0 +1,30 @@
+import 'package:flutter/material.dart';
+import 'package:restaurantour/common/constants.dart';
+import 'package:restaurantour/common/extensions.dart';
+
+AppBar getCustomAppBar(
+ BuildContext context,
+ String? title, {
+ List? actions,
+ bool forceMaterialTransparency = true,
+ Widget? leading,
+ PreferredSizeWidget? bottom,
+ Alignment? titleAlignment,
+ }) {
+ return AppBar(
+ leading: leading,
+ title: Align(
+ alignment: titleAlignment ?? Alignment.centerLeft,
+ child: Text(
+ title ?? kEmptyString,
+ style: context.theme.textTheme.headlineSmall?.copyWith(
+ color: Colors.black,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ forceMaterialTransparency: forceMaterialTransparency,
+ actions: actions,
+ bottom: bottom,
+ );
+}
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
index c6ce7473..a4e754c2 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
-import 'package:restaurantour/repositories/yelp_repository.dart';
+import 'package:restaurantour/screens/tabs/tabs_screen.dart';
void main() {
+ WidgetsFlutterBinding.ensureInitialized();
runApp(const Restaurantour());
}
@@ -14,7 +15,21 @@ class Restaurantour extends StatelessWidget {
return MaterialApp(
title: 'RestauranTour',
theme: ThemeData(
- visualDensity: VisualDensity.adaptivePlatformDensity,
+ // colorScheme: ColorScheme.fromSeed(seedColor: Colors.white),
+ useMaterial3: true,
+ appBarTheme: const AppBarTheme(backgroundColor: Colors.white),
+ navigationBarTheme:
+ const NavigationBarThemeData(backgroundColor: Colors.white),
+ floatingActionButtonTheme:
+ const FloatingActionButtonThemeData(backgroundColor: Colors.grey),
+ scaffoldBackgroundColor: Colors.white,
+
+ colorScheme: ColorScheme.fromSeed(
+ seedColor: Colors.green,
+ secondary: Colors.grey,
+ primary: Colors.green,
+ onPrimary: Colors.black87,
+ ),
),
home: const HomePage(),
);
@@ -26,32 +41,6 @@ class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Scaffold(
- body: Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Text('Restaurantour'),
- ElevatedButton(
- child: const Text('Fetch Restaurants'),
- onPressed: () async {
- final yelpRepo = YelpRepository();
-
- try {
- final result = await yelpRepo.getRestaurants();
- if (result != null) {
- print('Fetched ${result.restaurants!.length} restaurants');
- } else {
- print('No restaurants fetched');
- }
- } catch (e) {
- print('Failed to fetch restaurants: $e');
- }
- },
- ),
- ],
- ),
- ),
- );
+ return const TabsScreen();
}
}
diff --git a/lib/repositories/yelp_repository.dart b/lib/repositories/yelp_repository.dart
index f251d7b4..f7ecae55 100644
--- a/lib/repositories/yelp_repository.dart
+++ b/lib/repositories/yelp_repository.dart
@@ -1,8 +1,14 @@
+import 'dart:convert';
+
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+import 'package:restaurantour/common/constants.dart';
import 'package:restaurantour/models/restaurant.dart';
+import 'package:shared_preferences/shared_preferences.dart';
-const _apiKey = '';
+const _apiKey =
+ 'T738X0O1WDOKjwt42uLksMj4P2U0AAPnawfy4ASjPi46IJgXdXAU3kPjoFMKp1p0bor4jpsN7upFVYzCyNPPiHWT6c2i8wA0y7yS92Rk5vrRSa9XeJHBMGq1nHUfZnYx';
class YelpRepository {
late Dio dio;
@@ -16,10 +22,15 @@ class YelpRepository {
headers: {
'Authorization': 'Bearer $_apiKey',
'Content-Type': 'application/graphql',
+ 'accept': 'application/json'
},
),
);
+ void setDio(Dio dio) {
+ this.dio = dio;
+ }
+
/// Returns a response in this shape
/// {
/// "data": {
@@ -64,16 +75,38 @@ class YelpRepository {
'/v3/graphql',
data: _getQuery(offset),
);
- return RestaurantQueryResult.fromJson(response.data!['data']['search']);
+ return RestaurantQueryResult.fromJson(response.data!);
+ } catch (e) {
+ return Future.error(e);
+ }
+ }
+
+ Future getRestaurantsMocked({int offset = 0}) async {
+ try {
+ final String jsonString =
+ await rootBundle.loadString('assets/mock_restaurants.json');
+ final data = jsonDecode(jsonString);
+ print(data);
+ return RestaurantQueryResult.fromJson(data);
} catch (e) {
- return null;
+ return Future.error(e);
}
}
+ Future> getFavoriteRestaurantsIds() async {
+ final prefs = await SharedPreferences.getInstance();
+ return prefs.getStringList(kFavoriteRestaurantsKey) ?? [];
+ }
+
+ Future setFavoriteRestaurantsIds(List restaurantIds) async {
+ final prefs = await SharedPreferences.getInstance();
+ await prefs.setStringList(kFavoriteRestaurantsKey, restaurantIds);
+ }
+
String _getQuery(int offset) {
return '''
query getRestaurants {
- search(location: "Las Vegas", limit: 20, offset: $offset) {
+ search(location: "Las Vegas", limit: 14, offset: $offset) {
total
business {
id
@@ -81,7 +114,7 @@ query getRestaurants {
price
rating
photos
- reviews {
+ reviews(limit: 3) {
id
rating
user {
diff --git a/lib/screens/restaurant_details/restaurant_details_screen.dart b/lib/screens/restaurant_details/restaurant_details_screen.dart
new file mode 100644
index 00000000..3a48668c
--- /dev/null
+++ b/lib/screens/restaurant_details/restaurant_details_screen.dart
@@ -0,0 +1,274 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:restaurantour/common/constants.dart';
+import 'package:restaurantour/common/extensions.dart';
+import 'package:restaurantour/custom_widget/custom_app_bar.dart';
+import 'package:restaurantour/models/restaurant.dart';
+import 'package:restaurantour/screens/restaurant_details/restaurant_details_vm.dart';
+import 'package:restaurantour/screens/restaurant_details/review_item.dart';
+
+class RestaurantDetails extends StatelessWidget {
+ const RestaurantDetails({
+ super.key,
+ required this.restaurant,
+ });
+
+ final Restaurant restaurant;
+
+ static Future push(BuildContext context, Restaurant restaurant) {
+ return Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => RestaurantDetails(
+ restaurant: restaurant,
+ ),
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return ChangeNotifierProvider(
+ create: (context) => RestaurantDetailsVM(),
+ child: Consumer(
+ builder: (context, vm, _) {
+ vm.init(restaurant);
+
+ return Scaffold(
+ appBar: getCustomAppBar(
+ context,
+ restaurant.name,
+ actions: [
+ FutureBuilder(
+ future: vm.isFavorite(),
+ builder: (context, snapshot) {
+ return InkWell(
+ child: Padding(
+ padding: const EdgeInsets.all(8),
+ child: Icon(
+ snapshot.data == false
+ ? Icons.favorite_border
+ : Icons.favorite,
+ size: 28,
+ color: Colors.black,
+ ),
+ ),
+ borderRadius: BorderRadius.circular(24),
+ onTap: () {
+ vm.toggleFavorite();
+ },
+ );
+ },
+ ),
+ const SizedBox(
+ width: 8,
+ ),
+ ],
+ ),
+ body: SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ CachedNetworkImage(
+ imageUrl: restaurant.heroImage,
+ imageBuilder: (context, imageProvider) => Container(
+ height: 440,
+ decoration: BoxDecoration(
+ shape: BoxShape.rectangle,
+ image: DecorationImage(
+ image: imageProvider,
+ fit: BoxFit.cover,
+ ),
+ ),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 24,
+ ),
+ child: Column(
+ children: [
+ const SizedBox(
+ height: 24,
+ ),
+ Row(
+ children: [
+ Text(
+ restaurant.price ?? kEmptyString,
+ style: context.theme.textTheme.bodyLarge,
+ ),
+ const SizedBox(
+ width: 4,
+ ),
+ Text(
+ restaurant.displayCategory,
+ style: context.theme.textTheme.bodyLarge,
+ ),
+ Expanded(
+ child: Align(
+ alignment: Alignment.centerRight,
+ child: Text(
+ restaurant.isOpen ? "Open Now" : "Closed",
+ style: context.theme.textTheme.bodyLarge,
+ ),
+ ),
+ ),
+ const SizedBox(
+ width: 8,
+ ),
+ Icon(
+ Icons.circle,
+ size: 14,
+ color: restaurant.isOpen
+ ? Colors.green.shade300
+ : Colors.red,
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ Divider(
+ color: Colors.grey.shade200,
+ height: 1.5,
+ ),
+ ],
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 24,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(
+ height: 24,
+ ),
+ Text(
+ "Address",
+ style: context.theme.textTheme.bodyLarge,
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ Text(
+ restaurant.location?.formattedAddress ?? kEmptyString,
+ style: context.theme.textTheme.bodyLarge?.copyWith(
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ Divider(
+ color: Colors.grey.shade200,
+ height: 1.5,
+ ),
+ ],
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 24,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(
+ height: 24,
+ ),
+ Text(
+ "Overall Rating",
+ style: context.theme.textTheme.bodyLarge,
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ Row(
+ children: [
+ Text(
+ restaurant.rating?.toString() ?? kEmptyString,
+ style: context.theme.textTheme.headlineLarge
+ ?.copyWith(
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const Padding(
+ padding: EdgeInsets.only(
+ top: 8,
+ ),
+ child: Icon(
+ Icons.star,
+ color: Colors.amber,
+ size: 18,
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ Divider(
+ color: Colors.grey.shade200,
+ height: 1.5,
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ ],
+ ),
+ ),
+ if (vm.showReviews)
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 24,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "${vm.restaurant?.reviews?.length} Reviews",
+ style: context.theme.textTheme.bodyLarge,
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ ListView.separated(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: restaurant.reviews?.length ?? kZeroInt,
+ padding: const EdgeInsets.only(
+ top: 16,
+ right: 8,
+ left: 8,
+ bottom: 36,
+ ),
+ itemBuilder: (context, index) {
+ final review = vm.restaurant?.reviews![index];
+
+ return ReviewItem(
+ rating: review?.rating ?? kZeroInt,
+ userName: review?.user?.name ?? kEmptyString,
+ userImgUrl:
+ review?.user?.imageUrl ?? kEmptyString,
+ );
+ },
+ separatorBuilder: (context, index) =>
+ const SizedBox(
+ height: 10,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ ),
+ );
+ }
+}
diff --git a/lib/screens/restaurant_details/restaurant_details_vm.dart b/lib/screens/restaurant_details/restaurant_details_vm.dart
new file mode 100644
index 00000000..916744a5
--- /dev/null
+++ b/lib/screens/restaurant_details/restaurant_details_vm.dart
@@ -0,0 +1,51 @@
+import 'package:flutter/cupertino.dart';
+import 'package:restaurantour/common/constants.dart';
+import 'package:restaurantour/models/restaurant.dart';
+import 'package:restaurantour/repositories/yelp_repository.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+class RestaurantDetailsVM extends ChangeNotifier {
+ Restaurant? restaurant;
+
+ bool get showReviews => restaurant?.reviews?.isNotEmpty ?? false;
+
+ late final _prefs = SharedPreferences.getInstance();
+
+ var _yelpRepository = YelpRepository();
+
+ void init(Restaurant restaurant) async {
+ this.restaurant = restaurant;
+ }
+
+ Future toggleFavorite() async {
+ final restaurantIds = await _yelpRepository.getFavoriteRestaurantsIds();
+ final restaurantId = restaurant?.id;
+
+ if (restaurantId != null) {
+ if (await isFavorite()) {
+ restaurantIds.remove(restaurantId);
+ } else {
+ restaurantIds.add(restaurantId);
+ }
+ await _yelpRepository.setFavoriteRestaurantsIds(restaurantIds);
+ notifyListeners();
+ }
+ }
+
+ Future isFavorite() async {
+ final prefs = await _prefs;
+ final restaurantsIds = prefs.getStringList(kFavoriteRestaurantsKey) ?? [];
+ final restaurantId = restaurant?.id;
+
+ if (restaurantId != null) {
+ return restaurantsIds.contains(restaurantId);
+ } else {
+ return false;
+ }
+ }
+
+ @visibleForTesting
+ void setYelpRepository(YelpRepository repository) {
+ _yelpRepository = repository;
+ }
+}
diff --git a/lib/screens/restaurant_details/review_item.dart b/lib/screens/restaurant_details/review_item.dart
new file mode 100644
index 00000000..d2f3d7ab
--- /dev/null
+++ b/lib/screens/restaurant_details/review_item.dart
@@ -0,0 +1,83 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_rating_bar/flutter_rating_bar.dart';
+import 'package:restaurantour/common/extensions.dart';
+
+class ReviewItem extends StatelessWidget {
+ const ReviewItem({
+ super.key,
+ required this.rating,
+ required this.userName,
+ required this.userImgUrl,
+ });
+
+ final int rating;
+ final String userName;
+ final String userImgUrl;
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ RatingBar.builder(
+ initialRating: rating.toDouble(),
+ direction: Axis.horizontal,
+ allowHalfRating: true,
+ unratedColor: Colors.amber.withAlpha(50),
+ itemCount: 5,
+ itemSize: 20,
+ ignoreGestures: true,
+ itemBuilder: (context, _) => const Icon(
+ Icons.star,
+ color: Colors.amber,
+ ),
+ onRatingUpdate: (rating) {},
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ Row(
+ children: [
+ if (userImgUrl.isNotEmpty)
+ CachedNetworkImage(
+ imageUrl: userImgUrl,
+ imageBuilder: (context, imageProvider) => Container(
+ width: 50,
+ height: 50,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ image: DecorationImage(
+ image: imageProvider,
+ fit: BoxFit.cover,
+ ),
+ ),
+ ),
+ placeholder: (_, __) => const SizedBox(
+ height: 80,
+ width: 80,
+ ),
+ ),
+ if (userImgUrl.isNotEmpty)
+ const SizedBox(
+ width: 8,
+ ),
+ Expanded(
+ child: Text(
+ userName,
+ style: context.theme.textTheme.bodyLarge,
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ Divider(
+ color: Colors.grey.shade200,
+ height: 1.5,
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/screens/tabs/restaurant_item.dart b/lib/screens/tabs/restaurant_item.dart
new file mode 100644
index 00000000..5611ad4a
--- /dev/null
+++ b/lib/screens/tabs/restaurant_item.dart
@@ -0,0 +1,140 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_rating_bar/flutter_rating_bar.dart';
+import 'package:restaurantour/common/constants.dart';
+import 'package:restaurantour/common/extensions.dart';
+import 'package:restaurantour/models/restaurant.dart';
+
+class RestaurantItem extends StatelessWidget {
+ const RestaurantItem({
+ super.key,
+ required this.restaurant,
+ required this.onTap,
+ });
+
+ final Restaurant restaurant;
+ final Function() onTap;
+
+ @override
+ Widget build(BuildContext context) {
+ return Card(
+ surfaceTintColor: Colors.white,
+ color: Colors.white,
+ shape: const RoundedRectangleBorder(
+ borderRadius: BorderRadius.all(
+ Radius.circular(8.0),
+ ),
+ ),
+ elevation: 4,
+ shadowColor: Colors.grey.shade50,
+ child: InkWell(
+ onTap: onTap,
+ borderRadius: const BorderRadius.all(
+ Radius.circular(8.0),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(12.0),
+ child: Row(
+ children: [
+ CachedNetworkImage(
+ imageUrl: restaurant.heroImage,
+ imageBuilder: (context, imageProvider) => Container(
+ width: 80,
+ height: 80,
+ decoration: BoxDecoration(
+ shape: BoxShape.rectangle,
+ borderRadius: const BorderRadius.all(
+ Radius.circular(8.0),
+ ),
+ image: DecorationImage(
+ image: imageProvider,
+ fit: BoxFit.cover,
+ ),
+ ),
+ ),
+ placeholder: (_, __) => const SizedBox(
+ height: 80,
+ width: 80,
+ ),
+ ),
+ const SizedBox(
+ width: 8,
+ ),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ restaurant.name ?? kEmptyString,
+ style: context.theme.textTheme.bodySmall?.copyWith(
+ fontSize: 20,
+ ),
+ maxLines: 2,
+ overflow: TextOverflow.ellipsis,
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ Row(
+ children: [
+ Text(restaurant.price ?? kEmptyString),
+ const SizedBox(
+ width: 4,
+ ),
+ Text(restaurant.displayCategory),
+ ],
+ ),
+ const SizedBox(
+ height: 4,
+ ),
+ Row(
+ children: [
+ RatingBar.builder(
+ initialRating: restaurant.rating ?? kZeroDouble,
+ direction: Axis.horizontal,
+ allowHalfRating: true,
+ unratedColor: Colors.amber.withAlpha(50),
+ itemCount: 5,
+ itemSize: 20,
+ ignoreGestures: true,
+ itemBuilder: (context, _) => const Icon(
+ Icons.star,
+ color: Colors.amber,
+ ),
+ onRatingUpdate: (rating) {},
+ ),
+ const SizedBox(
+ width: 4,
+ ),
+ Expanded(
+ child: Align(
+ child: Text(
+ restaurant.isOpen ? "Open Now" : "Closed",
+ ),
+ alignment: Alignment.centerRight,
+ ),
+ ),
+ const SizedBox(
+ width: 8,
+ ),
+ Align(
+ child: Icon(
+ Icons.circle,
+ size: 14,
+ color: restaurant.isOpen
+ ? Colors.green.shade300
+ : Colors.red,
+ ),
+ ),
+ ],
+ )
+ ],
+ ),
+ )
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/screens/tabs/restaurants_screen.dart b/lib/screens/tabs/restaurants_screen.dart
new file mode 100644
index 00000000..50c14715
--- /dev/null
+++ b/lib/screens/tabs/restaurants_screen.dart
@@ -0,0 +1,69 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:restaurantour/common/extensions.dart';
+import 'package:restaurantour/models/restaurant.dart';
+import 'package:restaurantour/screens/restaurant_details/restaurant_details_screen.dart';
+import 'package:restaurantour/screens/tabs/restaurant_item.dart';
+import 'package:restaurantour/screens/tabs/tabs_vm.dart';
+
+class RestaurantsScreen extends StatelessWidget {
+ const RestaurantsScreen({
+ super.key,
+ required this.restaurants,
+ required this.loadStatus,
+ });
+
+ final List restaurants;
+ final LoadStatus loadStatus;
+
+ @override
+ Widget build(BuildContext context) {
+ return Consumer(
+ builder: (context, vm, _) {
+ switch (loadStatus) {
+ case LoadStatus.loading:
+ return const Center(
+ child: CircularProgressIndicator(),
+ );
+ case LoadStatus.error:
+ return Center(
+ child: Text(
+ "ERROR!",
+ style: context.theme.textTheme.titleMedium,
+ ),
+ );
+ case LoadStatus.loaded:
+ return ListView.separated(
+ itemCount: restaurants.length,
+ padding: const EdgeInsets.only(
+ top: 16,
+ right: 8,
+ left: 8,
+ bottom: 36,
+ ),
+ itemBuilder: (context, index) {
+ final restaurant = restaurants[index];
+
+ return RestaurantItem(
+ restaurant: restaurant,
+ onTap: () {
+ _openRestaurantDetails(context, restaurant, vm);
+ },
+ );
+ },
+ separatorBuilder: (context, index) => const SizedBox(
+ height: 10,
+ ),
+ );
+ }
+ },
+ );
+ }
+
+ void _openRestaurantDetails(
+ BuildContext context, Restaurant restaurant, TabsVM vm) async {
+ await RestaurantDetails.push(context, restaurant);
+ await vm.getFavoriteRestaurants();
+ vm.forceNotifyListeners();
+ }
+}
diff --git a/lib/screens/tabs/tabs_screen.dart b/lib/screens/tabs/tabs_screen.dart
new file mode 100644
index 00000000..af342e49
--- /dev/null
+++ b/lib/screens/tabs/tabs_screen.dart
@@ -0,0 +1,81 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:restaurantour/custom_widget/custom_app_bar.dart';
+import 'package:restaurantour/screens/tabs/restaurants_screen.dart';
+import 'package:restaurantour/screens/tabs/tabs_vm.dart';
+
+class TabsScreen extends StatefulWidget {
+ const TabsScreen({super.key});
+
+ @override
+ State createState() => _TabsScreenState();
+}
+
+class _TabsScreenState extends State {
+ late final _vm = TabsVM();
+
+ @override
+ void initState() {
+ _vm.getRestaurants();
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return ChangeNotifierProvider.value(
+ value: _vm,
+ child: DefaultTabController(
+ length: 2,
+ child: Scaffold(
+ appBar: getCustomAppBar(
+ context,
+ "RestauranTour",
+ titleAlignment: Alignment.center,
+ bottom: TabBar(
+ overlayColor: MaterialStateProperty.all(Colors.transparent),
+ tabs: const [
+ Tab(
+ child: Text(
+ "All Restautants",
+ style: TextStyle(
+ fontSize: 18,
+ color: Colors.black,
+ ),
+ ),
+ ),
+ Tab(
+ child: Text(
+ "My Favorites",
+ style: TextStyle(
+ fontSize: 18,
+ color: Colors.black,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ body: Container(
+ color: Colors.grey.shade100,
+ child: Consumer(
+ builder: (context, vm, _) {
+ return TabBarView(
+ children: [
+ RestaurantsScreen(
+ loadStatus: vm.loadStatus,
+ restaurants: vm.restaurants,
+ ),
+ RestaurantsScreen(
+ loadStatus: vm.loadStatus,
+ restaurants: vm.favoriteRestaurants,
+ ),
+ ],
+ );
+ },
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/screens/tabs/tabs_vm.dart b/lib/screens/tabs/tabs_vm.dart
new file mode 100644
index 00000000..cb6b3789
--- /dev/null
+++ b/lib/screens/tabs/tabs_vm.dart
@@ -0,0 +1,57 @@
+import 'package:flutter/cupertino.dart';
+import 'package:restaurantour/models/restaurant.dart';
+import 'package:restaurantour/repositories/yelp_repository.dart';
+
+enum LoadStatus { loaded, loading, error }
+
+class TabsVM extends ChangeNotifier {
+ var _yelpRepository = YelpRepository();
+
+ var loadStatus = LoadStatus.loading;
+
+ List restaurants = [];
+ List favoriteRestaurants = [];
+
+ Future getRestaurants() async {
+ try {
+ restaurants.clear();
+
+ final response = await _yelpRepository.getRestaurants();
+ loadStatus = LoadStatus.loaded;
+
+ restaurants = response?.restaurants ?? [];
+ await getFavoriteRestaurants();
+
+ notifyListeners();
+ } catch (e) {
+ debugPrint(e.toString());
+ loadStatus = LoadStatus.error;
+ notifyListeners();
+ }
+ }
+
+ Future getFavoriteRestaurants() async {
+ favoriteRestaurants.clear();
+
+ try {
+ final restaurantIds = await _yelpRepository.getFavoriteRestaurantsIds();
+
+ for (var restaurant in restaurants) {
+ if (restaurantIds.contains(restaurant.id)) {
+ favoriteRestaurants.add(restaurant);
+ }
+ }
+ } catch (e) {
+ debugPrint(e.toString());
+ }
+ }
+
+ void forceNotifyListeners() {
+ notifyListeners();
+ }
+
+ @visibleForTesting
+ void setYelpRepository(YelpRepository repository) {
+ _yelpRepository = repository;
+ }
+}
diff --git a/pubspec.lock b/pubspec.lock
index 0b052c68..9873ffa9 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -5,26 +5,26 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
- sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a
+ sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
url: "https://pub.dev"
source: hosted
- version: "61.0.0"
+ version: "64.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
- sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562
+ sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
url: "https://pub.dev"
source: hosted
- version: "5.13.0"
+ version: "6.2.0"
args:
dependency: transitive
description:
name: args
- sha256: "0bd9a99b6eb96f07af141f0eb53eace8983e8e5aa5de59777aca31684680ef22"
+ sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev"
source: hosted
- version: "2.3.0"
+ version: "2.5.0"
async:
dependency: transitive
description:
@@ -45,10 +45,10 @@ packages:
dependency: transitive
description:
name: build
- sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
+ sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
url: "https://pub.dev"
source: hosted
- version: "2.3.1"
+ version: "2.4.1"
build_config:
dependency: transitive
description:
@@ -61,34 +61,34 @@ packages:
dependency: transitive
description:
name: build_daemon
- sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65"
+ sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
url: "https://pub.dev"
source: hosted
- version: "4.0.0"
+ version: "4.0.1"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
- sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20"
+ sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
url: "https://pub.dev"
source: hosted
- version: "2.2.1"
+ version: "2.4.2"
build_runner:
dependency: "direct dev"
description:
name: build_runner
- sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21"
+ sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev"
source: hosted
- version: "2.4.8"
+ version: "2.4.9"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
- sha256: f4d6244cc071ba842c296cb1c4ee1b31596b9f924300647ac7a1445493471a3f
+ sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799"
url: "https://pub.dev"
source: hosted
- version: "7.2.3"
+ version: "7.3.0"
built_collection:
dependency: transitive
description:
@@ -101,34 +101,50 @@ packages:
dependency: transitive
description:
name: built_value
- sha256: b6c9911b2d670376918d5b8779bc27e0e612a94ec3ff0343689e991d8d0a3b8a
+ sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
url: "https://pub.dev"
source: hosted
- version: "8.1.4"
- characters:
+ version: "8.9.2"
+ cached_network_image:
+ dependency: "direct main"
+ description:
+ name: cached_network_image
+ sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.3.1"
+ cached_network_image_platform_interface:
dependency: transitive
description:
- name: characters
- sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
+ name: cached_network_image_platform_interface
+ sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f"
url: "https://pub.dev"
source: hosted
- version: "1.3.0"
- charcode:
+ version: "4.0.0"
+ cached_network_image_web:
dependency: transitive
description:
- name: charcode
- sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
+ name: cached_network_image_web
+ sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316"
url: "https://pub.dev"
source: hosted
- version: "1.3.1"
+ version: "1.1.1"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
- sha256: dd007e4fb8270916820a0d66e24f619266b60773cddd082c6439341645af2659
+ sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
- version: "2.0.1"
+ version: "2.0.3"
clock:
dependency: transitive
description:
@@ -157,42 +173,42 @@ packages:
dependency: transitive
description:
name: convert
- sha256: f08428ad63615f96a27e34221c65e1a451439b5f26030f78d790f461c686d65d
+ sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
- version: "3.0.1"
+ version: "3.1.1"
crypto:
dependency: transitive
description:
name: crypto
- sha256: cf75650c66c0316274e21d7c43d3dea246273af5955bd94e8184837cd577575c
+ sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
- version: "3.0.1"
+ version: "3.0.3"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
- sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
+ sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
- version: "1.0.6"
+ version: "1.0.8"
dart_style:
dependency: transitive
description:
name: dart_style
- sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55"
+ sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
url: "https://pub.dev"
source: hosted
- version: "2.3.2"
+ version: "2.3.6"
dio:
dependency: "direct main"
description:
name: dio
- sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3"
+ sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5"
url: "https://pub.dev"
source: hosted
- version: "5.4.0"
+ version: "5.4.3+1"
fake_async:
dependency: transitive
description:
@@ -201,27 +217,43 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
+ ffi:
+ dependency: transitive
+ description:
+ name: ffi
+ sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
file:
dependency: transitive
description:
name: file
- sha256: b69516f2c26a5bcac4eee2e32512e1a5205ab312b3536c1c1227b2b942b5f9ad
+ sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
- version: "6.1.2"
+ version: "7.0.0"
fixnum:
dependency: transitive
description:
name: fixnum
- sha256: "6a2ef17156f4dc49684f9d99aaf4a93aba8ac49f5eac861755f5730ddf6e2e4e"
+ sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
- version: "1.0.0"
+ version: "1.1.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
+ flutter_cache_manager:
+ dependency: transitive
+ description:
+ name: flutter_cache_manager
+ sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.3.1"
flutter_lints:
dependency: "direct dev"
description:
@@ -230,35 +262,48 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
+ flutter_rating_bar:
+ dependency: "direct main"
+ description:
+ name: flutter_rating_bar
+ sha256: d2af03469eac832c591a1eba47c91ecc871fe5708e69967073c043b2d775ed93
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.1"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
- sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c
+ sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
url: "https://pub.dev"
source: hosted
- version: "2.0.9"
+ version: "2.0.10+1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
+ flutter_web_plugins:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
- sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
+ sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev"
source: hosted
- version: "3.2.0"
+ version: "4.0.0"
glob:
dependency: transitive
description:
name: glob
- sha256: "8321dd2c0ab0683a91a51307fa844c6db4aa8e3981219b78961672aaab434658"
+ sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev"
source: hosted
- version: "2.0.2"
+ version: "2.1.2"
graphs:
dependency: transitive
description:
@@ -267,38 +312,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.1"
+ http:
+ dependency: transitive
+ description:
+ name: http
+ sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
- sha256: bfb651625e251a88804ad6d596af01ea903544757906addcb2dcdf088b5ea185
+ sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
url: "https://pub.dev"
source: hosted
- version: "3.0.1"
+ version: "3.2.1"
http_parser:
dependency: transitive
description:
name: http_parser
- sha256: e362d639ba3bc07d5a71faebb98cde68c05bfbcfbbb444b60b6f60bb67719185
+ sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
- version: "4.0.0"
+ version: "4.0.2"
io:
dependency: transitive
description:
name: io
- sha256: "0d4c73c3653ab85bf696d51a9657604c900a370549196a91f33e4c39af760852"
+ sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
url: "https://pub.dev"
source: hosted
- version: "1.0.3"
+ version: "1.0.4"
js:
dependency: transitive
description:
name: js
- sha256: d9bdfd70d828eeb352390f81b18d6a354ef2044aa28ef25682079797fa7cd174
+ sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
url: "https://pub.dev"
source: hosted
- version: "0.6.3"
+ version: "0.7.1"
json_annotation:
dependency: "direct main"
description:
@@ -327,10 +380,10 @@ packages:
dependency: transitive
description:
name: logging
- sha256: "293ae2d49fd79d4c04944c3a26dfd313382d5f52e821ec57119230ae16031ad4"
+ sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev"
source: hosted
- version: "1.0.2"
+ version: "1.2.0"
matcher:
dependency: transitive
description:
@@ -359,18 +412,42 @@ packages:
dependency: transitive
description:
name: mime
- sha256: fd5f81041e6a9fc9b9d7fa2cb8a01123f9f5d5d49136e06cb9dc7d33689529f4
+ sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
url: "https://pub.dev"
source: hosted
- version: "1.0.1"
+ version: "1.0.4"
+ mockito:
+ dependency: "direct dev"
+ description:
+ name: mockito
+ sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.4.4"
+ nested:
+ dependency: transitive
+ description:
+ name: nested
+ sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
+ octo_image:
+ dependency: transitive
+ description:
+ name: octo_image
+ sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
package_config:
dependency: transitive
description:
name: package_config
- sha256: a4d5ede5ca9c3d88a2fef1147a078570c861714c806485c596b109819135bc12
+ sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
url: "https://pub.dev"
source: hosted
- version: "2.0.2"
+ version: "2.1.0"
path:
dependency: transitive
description:
@@ -387,54 +464,190 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
+ path_provider:
+ dependency: transitive
+ description:
+ name: path_provider
+ sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.3"
+ path_provider_android:
+ dependency: transitive
+ description:
+ name: path_provider_android
+ sha256: "51f0d2c554cfbc9d6a312ab35152fc77e2f0b758ce9f1a444a3a1e5b8f3c6b7f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.3"
+ path_provider_foundation:
+ dependency: transitive
+ description:
+ name: path_provider_foundation
+ sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
+ path_provider_linux:
+ dependency: transitive
+ description:
+ name: path_provider_linux
+ sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.1"
+ path_provider_platform_interface:
+ dependency: transitive
+ description:
+ name: path_provider_platform_interface
+ sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ path_provider_windows:
+ dependency: transitive
+ description:
+ name: path_provider_windows
+ sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.1"
petitparser:
dependency: transitive
description:
name: petitparser
- sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
+ sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
url: "https://pub.dev"
source: hosted
- version: "6.0.2"
+ version: "5.4.0"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.4"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.8"
pool:
dependency: transitive
description:
name: pool
- sha256: "05955e3de2683e1746222efd14b775df7131139e07695dc8e24650f6b4204504"
+ sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
- version: "1.5.0"
+ version: "1.5.1"
+ provider:
+ dependency: "direct main"
+ description:
+ name: provider
+ sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.2"
pub_semver:
dependency: transitive
description:
name: pub_semver
- sha256: b5a5fcc6425ea43704852ba4453ba94b08c2226c63418a260240c3a054579014
+ sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev"
source: hosted
- version: "2.1.0"
+ version: "2.1.4"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
- sha256: "3686efe4a4613a4449b1a4ae08670aadbd3376f2e78d93e3f8f0919db02a7256"
+ sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
url: "https://pub.dev"
source: hosted
- version: "1.2.0"
+ version: "1.2.3"
+ rxdart:
+ dependency: transitive
+ description:
+ name: rxdart
+ sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.27.7"
+ shared_preferences:
+ dependency: "direct main"
+ description:
+ name: shared_preferences
+ sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.3"
+ shared_preferences_android:
+ dependency: transitive
+ description:
+ name: shared_preferences_android
+ sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.1"
+ shared_preferences_foundation:
+ dependency: transitive
+ description:
+ name: shared_preferences_foundation
+ sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.5"
+ shared_preferences_linux:
+ dependency: transitive
+ description:
+ name: shared_preferences_linux
+ sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
+ shared_preferences_platform_interface:
+ dependency: transitive
+ description:
+ name: shared_preferences_platform_interface
+ sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
+ shared_preferences_web:
+ dependency: transitive
+ description:
+ name: shared_preferences_web
+ sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.1"
+ shared_preferences_windows:
+ dependency: transitive
+ description:
+ name: shared_preferences_windows
+ sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
shelf:
dependency: transitive
description:
name: shelf
- sha256: c240984c924796e055e831a0a36db23be8cb04f170b26df572931ab36418421d
+ sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: "https://pub.dev"
source: hosted
- version: "1.2.0"
+ version: "1.4.1"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
- sha256: fd84910bf7d58db109082edf7326b75322b8f186162028482f53dc892f00332d
+ sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
url: "https://pub.dev"
source: hosted
- version: "1.0.1"
+ version: "1.0.4"
sky_engine:
dependency: transitive
description: flutter
@@ -464,6 +677,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.0"
+ sprintf:
+ dependency: transitive
+ description:
+ name: sprintf
+ sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.0"
+ sqflite:
+ dependency: transitive
+ description:
+ name: sqflite
+ sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
+ sqflite_common:
+ dependency: transitive
+ description:
+ name: sqflite_common
+ sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.3"
stack_trace:
dependency: transitive
description:
@@ -484,10 +721,10 @@ packages:
dependency: transitive
description:
name: stream_transform
- sha256: ed464977cb26a1f41537e177e190c67223dbd9f4f683489b6ab2e5d211ec564e
+ sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
url: "https://pub.dev"
source: hosted
- version: "2.0.0"
+ version: "2.1.0"
string_scanner:
dependency: transitive
description:
@@ -496,6 +733,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
+ synchronized:
+ dependency: transitive
+ description:
+ name: synchronized
+ sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0+1"
term_glyph:
dependency: transitive
description:
@@ -516,42 +761,50 @@ packages:
dependency: transitive
description:
name: timing
- sha256: c386d07d7f5efc613479a7c4d9d64b03710b03cfaa7e8ad5f2bfb295a1f0dfad
+ sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev"
source: hosted
- version: "1.0.0"
+ version: "1.0.1"
typed_data:
dependency: transitive
description:
name: typed_data
- sha256: "53bdf7e979cfbf3e28987552fd72f637e63f3c8724c9e56d9246942dc2fa36ee"
+ sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
- version: "1.3.0"
+ version: "1.3.2"
+ uuid:
+ dependency: transitive
+ description:
+ name: uuid
+ sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.2.2"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
- sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172"
+ sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
url: "https://pub.dev"
source: hosted
- version: "1.1.9+2"
+ version: "1.1.11+1"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
- sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d"
+ sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
url: "https://pub.dev"
source: hosted
- version: "1.1.9+2"
+ version: "1.1.11+1"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
- sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad"
+ sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
url: "https://pub.dev"
source: hosted
- version: "1.1.9+2"
+ version: "1.1.11+1"
vector_math:
dependency: transitive
description:
@@ -564,10 +817,10 @@ packages:
dependency: transitive
description:
name: watcher
- sha256: e42dfcc48f67618344da967b10f62de57e04bae01d9d3af4c2596f3712a88c99
+ sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev"
source: hosted
- version: "1.0.1"
+ version: "1.1.0"
web:
dependency: transitive
description:
@@ -580,26 +833,42 @@ packages:
dependency: transitive
description:
name: web_socket_channel
- sha256: "0c2ada1b1aeb2ad031ca81872add6be049b8cb479262c6ad3c4b0f9c24eaab2f"
+ sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev"
source: hosted
- version: "2.1.0"
+ version: "2.4.0"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.1.1"
+ xdg_directories:
+ dependency: transitive
+ description:
+ name: xdg_directories
+ sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.4"
xml:
dependency: transitive
description:
name: xml
- sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
+ sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
url: "https://pub.dev"
source: hosted
- version: "6.5.0"
+ version: "6.3.0"
yaml:
dependency: transitive
description:
name: yaml
- sha256: "3cee79b1715110341012d27756d9bae38e650588acd38d3f3c610822e1337ace"
+ sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
- version: "3.1.0"
+ version: "3.1.2"
sdks:
- dart: ">=3.2.0 <4.0.0"
- flutter: ">=3.7.0-0"
+ dart: ">=3.2.0-194.0.dev <4.0.0"
+ flutter: ">=3.13.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index be3055e0..0d8ef482 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -7,7 +7,7 @@ version: 1.0.0+1
environment:
- sdk: ">=2.12.0 <3.0.0"
+ sdk: ">=2.17.0 <3.0.0"
dependencies:
flutter:
@@ -16,6 +16,10 @@ dependencies:
dio: ^5.4.0
json_annotation: ^4.8.1
flutter_svg: ^2.0.9
+ provider: ^6.1.2
+ cached_network_image: ^3.3.1
+ flutter_rating_bar: ^4.0.1
+ shared_preferences: ^2.2.3
dev_dependencies:
flutter_test:
@@ -23,8 +27,9 @@ dev_dependencies:
flutter_lints: ^1.0.2
build_runner: ^2.4.8
json_serializable: ^6.7.1
+ mockito: ^5.4.4
flutter:
uses-material-design: true
-# assets:
-# - assets/svg/
\ No newline at end of file
+ assets:
+ - assets/mock_restaurants.json
\ No newline at end of file
diff --git a/test/viewmodel/restaurant_details_vm/restaurant_details_vm_test.dart b/test/viewmodel/restaurant_details_vm/restaurant_details_vm_test.dart
new file mode 100644
index 00000000..8f701aab
--- /dev/null
+++ b/test/viewmodel/restaurant_details_vm/restaurant_details_vm_test.dart
@@ -0,0 +1,142 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:restaurantour/common/constants.dart';
+import 'package:restaurantour/models/restaurant.dart';
+import 'package:restaurantour/repositories/yelp_repository.dart';
+import 'package:restaurantour/screens/restaurant_details/restaurant_details_vm.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+import '../tabs_vm/tabs_vm_test.mocks.dart';
+
+@GenerateMocks([YelpRepository])
+void main() {
+ final _mockYelpRepository = MockYelpRepository();
+
+ setUp(() {
+ WidgetsFlutterBinding.ensureInitialized();
+ });
+
+ test(
+ 'test toggleFavorite add favorite',
+ () async {
+ final vm = RestaurantDetailsVM();
+
+ SharedPreferences.setMockInitialValues(
+ {
+ kFavoriteRestaurantsKey: [],
+ },
+ );
+
+ vm.setYelpRepository(_mockYelpRepository);
+
+ when(
+ _mockYelpRepository.getFavoriteRestaurantsIds(),
+ ).thenAnswer(
+ (_) => Future.value(
+ [],
+ ),
+ );
+
+ final restaurant = Restaurant.fromJson(fakeRestaurant);
+
+ vm.init(restaurant);
+
+ await vm.toggleFavorite();
+
+ verify(_mockYelpRepository.getFavoriteRestaurantsIds());
+ verify(
+ _mockYelpRepository.setFavoriteRestaurantsIds(
+ [restaurant.id ?? kEmptyString],
+ ),
+ );
+ },
+ );
+
+ test(
+ 'test toggleFavorite remove favorite',
+ () async {
+ final vm = RestaurantDetailsVM();
+
+ SharedPreferences.setMockInitialValues(
+ {
+ kFavoriteRestaurantsKey: ["vHz2RLtfUMVRPFmd7VBEHA"],
+ },
+ );
+
+ vm.setYelpRepository(_mockYelpRepository);
+
+ when(
+ _mockYelpRepository.getFavoriteRestaurantsIds(),
+ ).thenAnswer(
+ (_) => Future.value(
+ ["vHz2RLtfUMVRPFmd7VBEHA"],
+ ),
+ );
+
+ final restaurant = Restaurant.fromJson(fakeRestaurant);
+
+ vm.init(restaurant);
+
+ await vm.toggleFavorite();
+
+ verify(_mockYelpRepository.getFavoriteRestaurantsIds());
+ verify(
+ _mockYelpRepository.setFavoriteRestaurantsIds(
+ [],
+ ),
+ );
+ },
+ );
+}
+
+Map get fakeRestaurant => {
+ "id": "vHz2RLtfUMVRPFmd7VBEHA",
+ "name": "Gordon Ramsay Hell's Kitchen",
+ "price": '\$\$\$',
+ "rating": 4.4,
+ "photos": [
+ "https://s3-media2.fl.yelpcdn.com/bphoto/q771KjLzI5y638leJsnJnQ/o.jpg",
+ ],
+ "reviews": [
+ {
+ "id": "VzJIMZRW-8lwoFJzk0jAXw",
+ "rating": 5,
+ "user": {
+ "id": "i2dS47auJ-9-OW4xZSPxAA",
+ "image_url":
+ "https://s3-media1.fl.yelpcdn.com/photo/M2AsmeEgwVwpjyaE1lFtIA/o.jpg",
+ "name": "White R.",
+ },
+ },
+ {
+ "id": "H85bnGMvTx0ACssHvyCyug",
+ "rating": 5,
+ "user": {
+ "id": "3xfzp3cOhKICnLn0D9ZheA",
+ "image_url": null,
+ "name": "Molly S.",
+ },
+ },
+ {
+ "id": "O60EsMnhEmrSs4F54imSkw",
+ "rating": 3,
+ "user": {
+ "id": "C8e7rVhQY6lMYm-yn1luJQ",
+ "image_url": null,
+ "name": "Taylor T.",
+ },
+ }
+ ],
+ "categories": [
+ {"title": "New American", "alias": "newamerican"},
+ {"title": "Seafood", "alias": "seafood"},
+ ],
+ "hours": [
+ {"is_open_now": true},
+ ],
+ "location": {
+ "formatted_address": "3570 Las Vegas Blvd S\nLas Vegas, NV 89109",
+ },
+ };
diff --git a/test/viewmodel/tabs_vm/tabs_vm_test.dart b/test/viewmodel/tabs_vm/tabs_vm_test.dart
new file mode 100644
index 00000000..35c70c55
--- /dev/null
+++ b/test/viewmodel/tabs_vm/tabs_vm_test.dart
@@ -0,0 +1,229 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:restaurantour/common/constants.dart';
+import 'package:restaurantour/models/restaurant.dart';
+import 'package:restaurantour/repositories/yelp_repository.dart';
+import 'package:restaurantour/screens/tabs/tabs_vm.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+import 'tabs_vm_test.mocks.dart';
+
+@GenerateMocks([YelpRepository])
+void main() {
+ final mockYelpRepository = MockYelpRepository();
+ final tabsVM = TabsVM();
+
+ setUp(() {
+ WidgetsFlutterBinding.ensureInitialized();
+ });
+
+ test(
+ 'test getRestaurants Success and getFavoriteRestaurantsIds empty',
+ () async {
+ SharedPreferences.setMockInitialValues(
+ {
+ kFavoriteRestaurantsKey: [],
+ },
+ );
+
+ tabsVM.setYelpRepository(mockYelpRepository);
+
+ when(
+ mockYelpRepository.getRestaurants(),
+ ).thenAnswer(
+ (_) => Future.value(
+ RestaurantQueryResult.fromJson(fakeRestaurants),
+ ),
+ );
+
+ when(
+ mockYelpRepository.getFavoriteRestaurantsIds(),
+ ).thenAnswer(
+ (_) => Future.value(
+ [],
+ ),
+ );
+
+ await tabsVM.getRestaurants();
+
+ assert(tabsVM.restaurants.length == 2);
+ assert(tabsVM.favoriteRestaurants.isEmpty);
+ },
+ );
+
+ test(
+ 'test getRestaurants Success and getFavoriteRestaurantsIds not empty',
+ () async {
+ SharedPreferences.setMockInitialValues(
+ {
+ kFavoriteRestaurantsKey: [],
+ },
+ );
+
+ tabsVM.setYelpRepository(mockYelpRepository);
+
+ when(
+ mockYelpRepository.getRestaurants(),
+ ).thenAnswer(
+ (_) => Future.value(
+ RestaurantQueryResult.fromJson(fakeRestaurants),
+ ),
+ );
+
+ when(
+ mockYelpRepository.getFavoriteRestaurantsIds(),
+ ).thenAnswer(
+ (_) => Future.value(
+ ["vHz2RLtfUMVRPFmd7VBEHA"],
+ ),
+ );
+
+ await tabsVM.getRestaurants();
+ print(tabsVM.restaurants.length);
+
+ assert(tabsVM.restaurants.length == 2);
+ assert(tabsVM.favoriteRestaurants.isNotEmpty);
+ },
+ );
+
+ test(
+ 'test getRestaurants Error',
+ () async {
+ SharedPreferences.setMockInitialValues(
+ {
+ kFavoriteRestaurantsKey: [],
+ },
+ );
+
+ tabsVM.setYelpRepository(mockYelpRepository);
+
+ when(
+ mockYelpRepository.getRestaurants(),
+ ).thenThrow(
+ Exception(),
+ );
+
+ when(
+ mockYelpRepository.getFavoriteRestaurantsIds(),
+ ).thenAnswer(
+ (_) => Future.value(
+ [],
+ ),
+ );
+
+ await tabsVM.getRestaurants();
+ print(tabsVM.restaurants.length);
+
+ assert(tabsVM.restaurants.isEmpty);
+ assert(tabsVM.restaurants.isEmpty);
+ },
+ );
+}
+
+Map get fakeRestaurants => {
+ "total": 6251,
+ "business": [
+ {
+ "id": "vHz2RLtfUMVRPFmd7VBEHA",
+ "name": "Gordon Ramsay Hell's Kitchen",
+ "price": '\$\$\$',
+ "rating": 4.4,
+ "photos": [
+ "https://s3-media2.fl.yelpcdn.com/bphoto/q771KjLzI5y638leJsnJnQ/o.jpg",
+ ],
+ "reviews": [
+ {
+ "id": "VzJIMZRW-8lwoFJzk0jAXw",
+ "rating": 5,
+ "user": {
+ "id": "i2dS47auJ-9-OW4xZSPxAA",
+ "image_url":
+ "https://s3-media1.fl.yelpcdn.com/photo/M2AsmeEgwVwpjyaE1lFtIA/o.jpg",
+ "name": "White R.",
+ },
+ },
+ {
+ "id": "H85bnGMvTx0ACssHvyCyug",
+ "rating": 5,
+ "user": {
+ "id": "3xfzp3cOhKICnLn0D9ZheA",
+ "image_url": null,
+ "name": "Molly S.",
+ },
+ },
+ {
+ "id": "O60EsMnhEmrSs4F54imSkw",
+ "rating": 3,
+ "user": {
+ "id": "C8e7rVhQY6lMYm-yn1luJQ",
+ "image_url": null,
+ "name": "Taylor T.",
+ },
+ }
+ ],
+ "categories": [
+ {"title": "New American", "alias": "newamerican"},
+ {"title": "Seafood", "alias": "seafood"},
+ ],
+ "hours": [
+ {"is_open_now": true},
+ ],
+ "location": {
+ "formatted_address": "3570 Las Vegas Blvd S\nLas Vegas, NV 89109",
+ },
+ },
+ {
+ "id": "faPVqws-x-5k2CQKDNtHxw",
+ "name": "Yardbird",
+ "price": "\$\$",
+ "rating": 4.5,
+ "photos": [
+ "https://s3-media1.fl.yelpcdn.com/bphoto/xYJaanpF3Dl1OovhmpqAYw/o.jpg",
+ ],
+ "reviews": [
+ {
+ "id": "uIeZrx9X1W0XPKqDicXZew",
+ "rating": 5,
+ "user": {
+ "id": "nvcvPpKYpq-nT7wwAexGYw",
+ "image_url":
+ "https://s3-media1.fl.yelpcdn.com/photo/2_pHFKGZ3-SlBq_HTXp8wg/o.jpg",
+ "name": "Tanner D.",
+ },
+ },
+ {
+ "id": "V8KFADRFJnsGUvQ3iRtnig",
+ "rating": 5,
+ "user": {
+ "id": "R_PPnsl0gsIvzhq9JHRCXQ",
+ "image_url": null,
+ "name": "Misha Z.",
+ },
+ },
+ {
+ "id": "NTi315CS824pvOsnqZmnww",
+ "rating": 5,
+ "user": {
+ "id": "swpNJGPBG4XCiMH7FeOUZg",
+ "image_url":
+ "https://s3-media2.fl.yelpcdn.com/photo/Tft5UPZtZ4zMNtVgdvbIwQ/o.jpg",
+ "name": "Kimberly Z.",
+ },
+ }
+ ],
+ "categories": [
+ {"title": "Southern", "alias": "southern"},
+ {"title": "New American", "alias": "newamerican"},
+ {"title": "Cocktail Bars", "alias": "cocktailbars"},
+ ],
+ "hours": [
+ {"is_open_now": true},
+ ],
+ "location": {
+ "formatted_address": "3355 Las Vegas Blvd S\nLas Vegas, NV 89109",
+ },
+ },
+ ],
+ };
diff --git a/test/viewmodel/tabs_vm/tabs_vm_test.mocks.dart b/test/viewmodel/tabs_vm/tabs_vm_test.mocks.dart
new file mode 100644
index 00000000..224e43a7
--- /dev/null
+++ b/test/viewmodel/tabs_vm/tabs_vm_test.mocks.dart
@@ -0,0 +1,871 @@
+// Mocks generated by Mockito 5.4.4 from annotations
+// in restaurantour/test/viewmodel/tabs_vm/tabs_vm_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i4;
+
+import 'package:dio/dio.dart' as _i2;
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:restaurantour/models/restaurant.dart' as _i5;
+import 'package:restaurantour/repositories/yelp_repository.dart' as _i3;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: deprecated_member_use
+// ignore_for_file: deprecated_member_use_from_same_package
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+class _FakeDio_0 extends _i1.SmartFake implements _i2.Dio {
+ _FakeDio_0(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeBaseOptions_1 extends _i1.SmartFake implements _i2.BaseOptions {
+ _FakeBaseOptions_1(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeHttpClientAdapter_2 extends _i1.SmartFake
+ implements _i2.HttpClientAdapter {
+ _FakeHttpClientAdapter_2(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeTransformer_3 extends _i1.SmartFake implements _i2.Transformer {
+ _FakeTransformer_3(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeInterceptors_4 extends _i1.SmartFake implements _i2.Interceptors {
+ _FakeInterceptors_4(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeResponse_5 extends _i1.SmartFake implements _i2.Response {
+ _FakeResponse_5(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+/// A class which mocks [YelpRepository].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockYelpRepository extends _i1.Mock implements _i3.YelpRepository {
+ MockYelpRepository() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i2.Dio get dio => (super.noSuchMethod(
+ Invocation.getter(#dio),
+ returnValue: _FakeDio_0(
+ this,
+ Invocation.getter(#dio),
+ ),
+ ) as _i2.Dio);
+
+ @override
+ set dio(_i2.Dio? _dio) => super.noSuchMethod(
+ Invocation.setter(
+ #dio,
+ _dio,
+ ),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ void setDio(_i2.Dio? dio) => super.noSuchMethod(
+ Invocation.method(
+ #setDio,
+ [dio],
+ ),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ _i4.Future<_i5.RestaurantQueryResult?> getRestaurants({int? offset = 0}) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getRestaurants,
+ [],
+ {#offset: offset},
+ ),
+ returnValue: _i4.Future<_i5.RestaurantQueryResult?>.value(),
+ ) as _i4.Future<_i5.RestaurantQueryResult?>);
+
+ @override
+ _i4.Future<_i5.RestaurantQueryResult?> getRestaurantsMocked(
+ {int? offset = 0}) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getRestaurantsMocked,
+ [],
+ {#offset: offset},
+ ),
+ returnValue: _i4.Future<_i5.RestaurantQueryResult?>.value(),
+ ) as _i4.Future<_i5.RestaurantQueryResult?>);
+
+ @override
+ _i4.Future> getFavoriteRestaurantsIds() => (super.noSuchMethod(
+ Invocation.method(
+ #getFavoriteRestaurantsIds,
+ [],
+ ),
+ returnValue: _i4.Future>.value([]),
+ ) as _i4.Future>);
+
+ @override
+ _i4.Future setFavoriteRestaurantsIds(List? restaurantIds) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setFavoriteRestaurantsIds,
+ [restaurantIds],
+ ),
+ returnValue: _i4.Future.value(),
+ returnValueForMissingStub: _i4.Future.value(),
+ ) as _i4.Future);
+}
+
+/// A class which mocks [Dio].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockDio extends _i1.Mock implements _i2.Dio {
+ MockDio() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i2.BaseOptions get options => (super.noSuchMethod(
+ Invocation.getter(#options),
+ returnValue: _FakeBaseOptions_1(
+ this,
+ Invocation.getter(#options),
+ ),
+ ) as _i2.BaseOptions);
+
+ @override
+ set options(_i2.BaseOptions? _options) => super.noSuchMethod(
+ Invocation.setter(
+ #options,
+ _options,
+ ),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ _i2.HttpClientAdapter get httpClientAdapter => (super.noSuchMethod(
+ Invocation.getter(#httpClientAdapter),
+ returnValue: _FakeHttpClientAdapter_2(
+ this,
+ Invocation.getter(#httpClientAdapter),
+ ),
+ ) as _i2.HttpClientAdapter);
+
+ @override
+ set httpClientAdapter(_i2.HttpClientAdapter? _httpClientAdapter) =>
+ super.noSuchMethod(
+ Invocation.setter(
+ #httpClientAdapter,
+ _httpClientAdapter,
+ ),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ _i2.Transformer get transformer => (super.noSuchMethod(
+ Invocation.getter(#transformer),
+ returnValue: _FakeTransformer_3(
+ this,
+ Invocation.getter(#transformer),
+ ),
+ ) as _i2.Transformer);
+
+ @override
+ set transformer(_i2.Transformer? _transformer) => super.noSuchMethod(
+ Invocation.setter(
+ #transformer,
+ _transformer,
+ ),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ _i2.Interceptors get interceptors => (super.noSuchMethod(
+ Invocation.getter(#interceptors),
+ returnValue: _FakeInterceptors_4(
+ this,
+ Invocation.getter(#interceptors),
+ ),
+ ) as _i2.Interceptors);
+
+ @override
+ void close({bool? force = false}) => super.noSuchMethod(
+ Invocation.method(
+ #close,
+ [],
+ {#force: force},
+ ),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ _i4.Future<_i2.Response> head(
+ String? path, {
+ Object? data,
+ Map? queryParameters,
+ _i2.Options? options,
+ _i2.CancelToken? cancelToken,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #head,
+ [path],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #options: options,
+ #cancelToken: cancelToken,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #head,
+ [path],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #options: options,
+ #cancelToken: cancelToken,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> headUri(
+ Uri? uri, {
+ Object? data,
+ _i2.Options? options,
+ _i2.CancelToken? cancelToken,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #headUri,
+ [uri],
+ {
+ #data: data,
+ #options: options,
+ #cancelToken: cancelToken,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #headUri,
+ [uri],
+ {
+ #data: data,
+ #options: options,
+ #cancelToken: cancelToken,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> get(
+ String? path, {
+ Object? data,
+ Map? queryParameters,
+ _i2.Options? options,
+ _i2.CancelToken? cancelToken,
+ _i2.ProgressCallback? onReceiveProgress,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #get,
+ [path],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #get,
+ [path],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> getUri(
+ Uri? uri, {
+ Object? data,
+ _i2.Options? options,
+ _i2.CancelToken? cancelToken,
+ _i2.ProgressCallback? onReceiveProgress,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getUri,
+ [uri],
+ {
+ #data: data,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #getUri,
+ [uri],
+ {
+ #data: data,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> post(
+ String? path, {
+ Object? data,
+ Map? queryParameters,
+ _i2.Options? options,
+ _i2.CancelToken? cancelToken,
+ _i2.ProgressCallback? onSendProgress,
+ _i2.ProgressCallback? onReceiveProgress,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #post,
+ [path],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #post,
+ [path],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> postUri(
+ Uri? uri, {
+ Object? data,
+ _i2.Options? options,
+ _i2.CancelToken? cancelToken,
+ _i2.ProgressCallback? onSendProgress,
+ _i2.ProgressCallback? onReceiveProgress,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #postUri,
+ [uri],
+ {
+ #data: data,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #postUri,
+ [uri],
+ {
+ #data: data,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> put(
+ String? path, {
+ Object? data,
+ Map? queryParameters,
+ _i2.Options? options,
+ _i2.CancelToken? cancelToken,
+ _i2.ProgressCallback? onSendProgress,
+ _i2.ProgressCallback? onReceiveProgress,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #put,
+ [path],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #put,
+ [path],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> putUri(
+ Uri? uri, {
+ Object? data,
+ _i2.Options? options,
+ _i2.CancelToken? cancelToken,
+ _i2.ProgressCallback? onSendProgress,
+ _i2.ProgressCallback? onReceiveProgress,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #putUri,
+ [uri],
+ {
+ #data: data,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #putUri,
+ [uri],
+ {
+ #data: data,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> patch(
+ String? path, {
+ Object? data,
+ Map? queryParameters,
+ _i2.Options? options,
+ _i2.CancelToken? cancelToken,
+ _i2.ProgressCallback? onSendProgress,
+ _i2.ProgressCallback? onReceiveProgress,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #patch,
+ [path],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #patch,
+ [path],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> patchUri(
+ Uri? uri, {
+ Object? data,
+ _i2.Options? options,
+ _i2.CancelToken? cancelToken,
+ _i2.ProgressCallback? onSendProgress,
+ _i2.ProgressCallback? onReceiveProgress,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #patchUri,
+ [uri],
+ {
+ #data: data,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #patchUri,
+ [uri],
+ {
+ #data: data,
+ #options: options,
+ #cancelToken: cancelToken,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> delete(
+ String? path, {
+ Object? data,
+ Map? queryParameters,
+ _i2.Options? options,
+ _i2.CancelToken? cancelToken,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #delete,
+ [path],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #options: options,
+ #cancelToken: cancelToken,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #delete,
+ [path],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #options: options,
+ #cancelToken: cancelToken,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> deleteUri(
+ Uri? uri, {
+ Object? data,
+ _i2.Options? options,
+ _i2.CancelToken? cancelToken,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #deleteUri,
+ [uri],
+ {
+ #data: data,
+ #options: options,
+ #cancelToken: cancelToken,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #deleteUri,
+ [uri],
+ {
+ #data: data,
+ #options: options,
+ #cancelToken: cancelToken,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> download(
+ String? urlPath,
+ dynamic savePath, {
+ _i2.ProgressCallback? onReceiveProgress,
+ Map? queryParameters,
+ _i2.CancelToken? cancelToken,
+ bool? deleteOnError = true,
+ String? lengthHeader = r'content-length',
+ Object? data,
+ _i2.Options? options,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #download,
+ [
+ urlPath,
+ savePath,
+ ],
+ {
+ #onReceiveProgress: onReceiveProgress,
+ #queryParameters: queryParameters,
+ #cancelToken: cancelToken,
+ #deleteOnError: deleteOnError,
+ #lengthHeader: lengthHeader,
+ #data: data,
+ #options: options,
+ },
+ ),
+ returnValue:
+ _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #download,
+ [
+ urlPath,
+ savePath,
+ ],
+ {
+ #onReceiveProgress: onReceiveProgress,
+ #queryParameters: queryParameters,
+ #cancelToken: cancelToken,
+ #deleteOnError: deleteOnError,
+ #lengthHeader: lengthHeader,
+ #data: data,
+ #options: options,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> downloadUri(
+ Uri? uri,
+ dynamic savePath, {
+ _i2.ProgressCallback? onReceiveProgress,
+ _i2.CancelToken? cancelToken,
+ bool? deleteOnError = true,
+ String? lengthHeader = r'content-length',
+ Object? data,
+ _i2.Options? options,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #downloadUri,
+ [
+ uri,
+ savePath,
+ ],
+ {
+ #onReceiveProgress: onReceiveProgress,
+ #cancelToken: cancelToken,
+ #deleteOnError: deleteOnError,
+ #lengthHeader: lengthHeader,
+ #data: data,
+ #options: options,
+ },
+ ),
+ returnValue:
+ _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #downloadUri,
+ [
+ uri,
+ savePath,
+ ],
+ {
+ #onReceiveProgress: onReceiveProgress,
+ #cancelToken: cancelToken,
+ #deleteOnError: deleteOnError,
+ #lengthHeader: lengthHeader,
+ #data: data,
+ #options: options,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> request(
+ String? url, {
+ Object? data,
+ Map? queryParameters,
+ _i2.CancelToken? cancelToken,
+ _i2.Options? options,
+ _i2.ProgressCallback? onSendProgress,
+ _i2.ProgressCallback? onReceiveProgress,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #request,
+ [url],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #cancelToken: cancelToken,
+ #options: options,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #request,
+ [url],
+ {
+ #data: data,
+ #queryParameters: queryParameters,
+ #cancelToken: cancelToken,
+ #options: options,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> requestUri(
+ Uri? uri, {
+ Object? data,
+ _i2.CancelToken? cancelToken,
+ _i2.Options? options,
+ _i2.ProgressCallback? onSendProgress,
+ _i2.ProgressCallback? onReceiveProgress,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #requestUri,
+ [uri],
+ {
+ #data: data,
+ #cancelToken: cancelToken,
+ #options: options,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #requestUri,
+ [uri],
+ {
+ #data: data,
+ #cancelToken: cancelToken,
+ #options: options,
+ #onSendProgress: onSendProgress,
+ #onReceiveProgress: onReceiveProgress,
+ },
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+
+ @override
+ _i4.Future<_i2.Response> fetch(_i2.RequestOptions? requestOptions) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #fetch,
+ [requestOptions],
+ ),
+ returnValue: _i4.Future<_i2.Response>.value(_FakeResponse_5(
+ this,
+ Invocation.method(
+ #fetch,
+ [requestOptions],
+ ),
+ )),
+ ) as _i4.Future<_i2.Response>);
+}
diff --git a/test/widget/restaurant_details_screen/restaurant_details_screen_test.dart b/test/widget/restaurant_details_screen/restaurant_details_screen_test.dart
new file mode 100644
index 00000000..f8724e7a
--- /dev/null
+++ b/test/widget/restaurant_details_screen/restaurant_details_screen_test.dart
@@ -0,0 +1,57 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_rating_bar/flutter_rating_bar.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:provider/provider.dart';
+import 'package:restaurantour/models/restaurant.dart';
+import 'package:restaurantour/screens/tabs/restaurants_screen.dart';
+import 'package:restaurantour/screens/tabs/tabs_vm.dart';
+
+import '../../viewmodel/tabs_vm/tabs_vm_test.dart';
+
+void main() {
+ testWidgets(
+ 'test RestaurantDetailsScreen',
+ (WidgetTester tester) async {
+ final vm = TabsVM();
+
+ final restaurants =
+ RestaurantQueryResult
+ .fromJson(fakeRestaurants)
+ .restaurants;
+
+ await tester.pumpWidget(
+ MaterialApp(
+ home: ChangeNotifierProvider.value(
+ value: vm,
+ child: RestaurantsScreen(
+ restaurants: restaurants!,
+ loadStatus: LoadStatus.loaded,
+ ),
+ ),
+ ),
+ );
+
+ await tester.pump(
+ Durations.short3,
+ );
+
+ final node = find.byType(InkWell);
+
+ await tester.tap(node.first);
+
+ await tester.pumpAndSettle();
+
+ expect(find.text('\$\$\$'), findsOneWidget);
+ expect(find.text('New American'), findsOneWidget);
+ expect(find.text('Address'), findsOneWidget);
+ expect(find.text('4.4'), findsOneWidget);
+ expect(find.byType(RatingBar), findsNWidgets(3));
+ expect(find.byType(CachedNetworkImage), findsNWidgets(2));
+ expect(find.text('White R.'), findsOneWidget);
+ expect(find.text('Molly S.'), findsOneWidget);
+ expect(find.text('Taylor T.'), findsOneWidget);
+ expect(find.text('Open Now'), findsOneWidget);
+ },
+ );
+}
\ No newline at end of file
diff --git a/test/widget/restaurant_screen/restaurant_screen_test.dart b/test/widget/restaurant_screen/restaurant_screen_test.dart
new file mode 100644
index 00000000..8dc752e4
--- /dev/null
+++ b/test/widget/restaurant_screen/restaurant_screen_test.dart
@@ -0,0 +1,92 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:provider/provider.dart';
+import 'package:restaurantour/models/restaurant.dart';
+import 'package:restaurantour/screens/tabs/restaurants_screen.dart';
+import 'package:restaurantour/screens/tabs/tabs_vm.dart';
+
+import '../../viewmodel/tabs_vm/tabs_vm_test.dart';
+
+void main() {
+ testWidgets(
+ 'test RestaurantScreen loaded',
+ (WidgetTester tester) async {
+ final restaurants =
+ RestaurantQueryResult.fromJson(fakeRestaurants).restaurants;
+
+ await tester.pumpWidget(
+ MaterialApp(
+ home: ChangeNotifierProvider.value(
+ value: TabsVM(),
+ child: RestaurantsScreen(
+ restaurants: restaurants!,
+ loadStatus: LoadStatus.loaded,
+ ),
+ ),
+ ),
+ );
+
+ await tester.pump(
+ Durations.short3,
+ );
+
+ expect(find.text('Gordon Ramsay Hell\'s Kitchen'), findsOneWidget);
+ expect(find.text('Yardbird'), findsOneWidget);
+ },
+ );
+
+ testWidgets(
+ 'test RestaurantScreen loading',
+ (WidgetTester tester) async {
+ final vm = TabsVM();
+ final restaurants =
+ RestaurantQueryResult.fromJson(fakeRestaurants).restaurants;
+
+ await tester.pumpWidget(
+ MaterialApp(
+ home: ChangeNotifierProvider.value(
+ value: vm,
+ child: RestaurantsScreen(
+ restaurants: restaurants!,
+ loadStatus: LoadStatus.loading,
+ ),
+ ),
+ ),
+ );
+
+ await tester.pump(
+ Durations.short3,
+ );
+
+ expect(find.byType(CircularProgressIndicator), findsOneWidget);
+ },
+ );
+
+ testWidgets(
+ 'test RestaurantScreen error',
+ (WidgetTester tester) async {
+ final vm = TabsVM();
+
+ final restaurants =
+ RestaurantQueryResult.fromJson(fakeRestaurants).restaurants;
+
+ await tester.pumpWidget(
+ MaterialApp(
+ home: ChangeNotifierProvider.value(
+ value: vm,
+ child: RestaurantsScreen(
+ restaurants: restaurants!,
+ loadStatus: LoadStatus.error,
+ ),
+ ),
+ ),
+ );
+
+ await tester.pump(
+ Durations.short3,
+ );
+
+ expect(find.bySemanticsLabel("ERROR!"), findsOneWidget);
+ },
+ );
+}