From 76c4b2b62c972bda86d44729c91e8ba9de0270cc Mon Sep 17 00:00:00 2001 From: Eren Gun Date: Sat, 28 Feb 2026 20:53:58 +0300 Subject: [PATCH 1/5] feat(android): update manifest permissions --- android/app/src/debug/AndroidManifest.xml | 2 +- android/app/src/main/AndroidManifest.xml | 2 +- android/app/src/profile/AndroidManifest.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 399f698..f019305 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -3,5 +3,5 @@ the Flutter tool needs it to communicate with the running application to allow setting breakpoints, to provide hot reload, etc. --> - + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 63bb0fd..b0bca77 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index 399f698..f019305 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -3,5 +3,5 @@ the Flutter tool needs it to communicate with the running application to allow setting breakpoints, to provide hot reload, etc. --> - + From db02be41d7d46468c6f4882472c337597d60806a Mon Sep 17 00:00:00 2001 From: Eren Gun Date: Sat, 28 Feb 2026 20:53:58 +0300 Subject: [PATCH 2/5] chore(iOS): drop MinimumOSVersion from AppFrameworkInfo.plist --- ios/Flutter/AppFrameworkInfo.plist | 2 -- 1 file changed, 2 deletions(-) diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 1dc6cf7..391a902 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 13.0 From fe7a0e250b596c7b074500fdeb2903f61bb677fc Mon Sep 17 00:00:00 2001 From: Eren Gun Date: Sat, 28 Feb 2026 20:53:58 +0300 Subject: [PATCH 3/5] feat(model): add icon field to Brand and update data --- assets/brands.json | 220 +++++++++++++-------------- lib/models/brand.dart | 1 + lib/models/brand.freezed.dart | 39 ++--- lib/models/brand.g.dart | 2 + lib/providers/brands_provider.dart | 2 +- lib/providers/brands_provider.g.dart | 2 +- lib/providers/subs_controller.dart | 2 +- 7 files changed, 137 insertions(+), 131 deletions(-) diff --git a/assets/brands.json b/assets/brands.json index 278bb2a..2290996 100644 --- a/assets/brands.json +++ b/assets/brands.json @@ -1,661 +1,661 @@ [ { "text": "Netflix", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/69/Netflix_logo.svg/500px-Netflix_logo.svg.png", + "icon": "netflix", "category": "Streaming", "country": "US" }, { "text": "Spotify", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Spotify_logo_without_text.svg/500px-Spotify_logo_without_text.svg.png", + "icon": "spotify", "category": "Music", "country": "SE" }, { "text": "YouTube Premium", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/09/YouTube_full-color_icon_%282017%29.svg/500px-YouTube_full-color_icon_%282017%29.svg.png", + "icon": "youtube", "category": "Streaming", "country": "US" }, { "text": "Disney+", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3e/Disney%2B_logo.svg/500px-Disney%2B_logo.svg.png", + "icon": null, "category": "Streaming", "country": "US" }, { "text": "Amazon Prime", - "logo": "https://upload.wikimedia.org/wikipedia/commons/f/f1/Prime_Video.png", + "icon": "amazonprime", "category": "Streaming", "country": "US" }, { "text": "Apple Music", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/Apple_Music_logo.svg/1024px-Apple_Music_logo.svg.png", + "icon": "applemusic", "category": "Music", "country": "US" }, { "text": "Apple One", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Apple_One_logo.svg/1200px-Apple_One_logo.svg.png", + "icon": "apple", "category": "Bundle", "country": "US" }, { "text": "Apple TV+", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/28/Apple_TV_Plus_Logo.svg/500px-Apple_TV_Plus_Logo.svg.png", + "icon": "appletv", "category": "Streaming", "country": "US" }, { "text": "iCloud+", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1c/ICloud_logo.svg/500px-ICloud_logo.svg.png", + "icon": "icloud", "category": "Cloud", "country": "US" }, { "text": "Google One", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/12/Google_One_logo_%282018%29.svg/1200px-Google_One_logo_%282018%29.svg.png", + "icon": "google", "category": "Cloud", "country": "US" }, { "text": "Microsoft 365", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/Microsoft_365_%282022%29.svg/1091px-Microsoft_365_%282022%29.svg.png", + "icon": null, "category": "Productivity", "country": "US" }, { "text": "Xbox Game Pass", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/Xbox_Game_Pass_logo_-_colored_version.svg/1200px-Xbox_Game_Pass_logo_-_colored_version.svg.png", + "icon": null, "category": "Gaming", "country": "US" }, { "text": "PlayStation Plus", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f4/PlayStation_Plus_second_logo_and_wordmark.svg/1200px-PlayStation_Plus_second_logo_and_wordmark.svg.png", + "icon": "playstation", "category": "Gaming", "country": "JP" }, { "text": "Nintendo Switch Online", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Nintendo_Switch_Online_logo.svg/1200px-Nintendo_Switch_Online_logo.svg.png", + "icon": null, "category": "Gaming", "country": "JP" }, { "text": "Adobe Creative Cloud", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Adobe_Creative_Cloud_rainbow_icon.svg/512px-Adobe_Creative_Cloud_rainbow_icon.svg.png", + "icon": null, "category": "Productivity", "country": "US" }, { "text": "ChatGPT Plus", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/04/ChatGPT_logo.svg/500px-ChatGPT_logo.svg.png", + "icon": "openai", "category": "AI", "country": "US" }, { "text": "Claude Pro", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Anthropic_logo.svg/500px-Anthropic_logo.svg.png", + "icon": "anthropic", "category": "AI", "country": "US" }, { "text": "GitHub Copilot", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Octicons-mark-github.svg/500px-Octicons-mark-github.svg.png", + "icon": "github", "category": "Developer", "country": "US" }, { "text": "Midjourney", - "logo": "https://upload.wikimedia.org/wikipedia/commons/e/ed/Midjourney_Emblem.png", + "icon": null, "category": "AI", "country": "US" }, { "text": "Duolingo", - "logo": "https://logo.clearbit.com/duolingo.com", + "icon": "duolingo", "category": "Education", "country": "US" }, { "text": "Hulu", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f9/Hulu_logo_%282018%29.svg/1200px-Hulu_logo_%282018%29.svg.png", + "icon": null, "category": "Streaming", "country": "US" }, { "text": "Max (HBO)", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/ce/Max_logo.svg/500px-Max_logo.svg.png", + "icon": null, "category": "Streaming", "country": "US" }, { "text": "Peacock", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/NBCUniversal_Peacock_Logo.svg/500px-NBCUniversal_Peacock_Logo.svg.png", + "icon": null, "category": "Streaming", "country": "US" }, { "text": "Paramount+", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Paramount_Plus.svg/500px-Paramount_Plus.svg.png", + "icon": null, "category": "Streaming", "country": "US" }, { "text": "Audible", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d2/Audible_logo.svg/1200px-Audible_logo.svg.png", + "icon": "audible", "category": "Books", "country": "US" }, { "text": "Kindle Unlimited", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/20/Amazon_Kindle_logo.svg/1200px-Amazon_Kindle_logo.svg.png", + "icon": null, "category": "Books", "country": "US" }, { "text": "Dropbox", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Dropbox_Icon.svg/500px-Dropbox_Icon.svg.png", + "icon": "dropbox", "category": "Cloud", "country": "US" }, { "text": "Slack", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/Slack_icon_2019.svg/500px-Slack_icon_2019.svg.png", + "icon": "slack", "category": "Productivity", "country": "US" }, { "text": "Zoom", - "logo": "https://logo.clearbit.com/zoom.us", + "icon": "zoom", "category": "Productivity", "country": "US" }, { "text": "Discord Nitro", - "logo": "https://logo.clearbit.com/discord.com", + "icon": "discord", "category": "Social", "country": "US" }, { "text": "Twitch", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/26/Twitch_logo.svg/500px-Twitch_logo.svg.png", + "icon": "twitch", "category": "Streaming", "country": "US" }, { "text": "Patreon", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/94/Patreon_logo.svg/500px-Patreon_logo.svg.png", + "icon": "patreon", "category": "Social", "country": "US" }, { "text": "Canva", - "logo": "https://logo.clearbit.com/canva.com", + "icon": "canva", "category": "Design", "country": "AU" }, { "text": "Figma", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/Figma-logo.svg/500px-Figma-logo.svg.png", + "icon": "figma", "category": "Design", "country": "US" }, { "text": "Notion", - "logo": "https://upload.wikimedia.org/wikipedia/commons/4/45/Notion_app_logo.png", + "icon": "notion", "category": "Productivity", "country": "US" }, { "text": "Evernote", - "logo": "https://logo.clearbit.com/evernote.com", + "icon": "evernote", "category": "Productivity", "country": "US" }, { "text": "Todoist", - "logo": "https://logo.clearbit.com/todoist.com", + "icon": "todoist", "category": "Productivity", "country": "US" }, { "text": "NordVPN", - "logo": "https://logo.clearbit.com/nordvpn.com", + "icon": "nordvpn", "category": "Security", "country": "PA" }, { "text": "ExpressVPN", - "logo": "https://logo.clearbit.com/expressvpn.com", + "icon": "expressvpn", "category": "Security", "country": "VG" }, { "text": "Dashlane", - "logo": "https://logo.clearbit.com/dashlane.com", + "icon": "dashlane", "category": "Security", "country": "US" }, { "text": "1Password", - "logo": "https://logo.clearbit.com/1password.com", + "icon": "n1password", "category": "Security", "country": "CA" }, { "text": "Peloton", - "logo": "https://logo.clearbit.com/onepeloton.com", + "icon": "peloton", "category": "Fitness", "country": "US" }, { "text": "Strava", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/cb/Strava_Logo.svg/500px-Strava_Logo.svg.png", + "icon": "strava", "category": "Fitness", "country": "US" }, { "text": "Fitbit Premium", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/Fitbit_logo.svg/500px-Fitbit_logo.svg.png", + "icon": "fitbit", "category": "Fitness", "country": "US" }, { "text": "Calm", - "logo": "https://logo.clearbit.com/calm.com", + "icon": null, "category": "Health", "country": "US" }, { "text": "Headspace", - "logo": "https://logo.clearbit.com/headspace.com", + "icon": "headspace", "category": "Health", "country": "US" }, { "text": "Uber One", - "logo": "https://upload.wikimedia.org/wikipedia/commons/c/cc/Uber_logo_2018.png", + "icon": "uber", "category": "Transport", "country": "US" }, { "text": "Lyft Pink", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/Lyft_logo.svg/500px-Lyft_logo.svg.png", + "icon": "lyft", "category": "Transport", "country": "US" }, { "text": "DoorDash DashPass", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/DoorDash_Logo.svg/1200px-DoorDash_Logo.svg.png", + "icon": "doordash", "category": "Food", "country": "US" }, { "text": "Grubhub+", - "logo": "https://logo.clearbit.com/grubhub.com", + "icon": "grubhub", "category": "Food", "country": "US" }, { "text": "Instacart+", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9f/Instacart_logo_and_wordmark.svg/500px-Instacart_logo_and_wordmark.svg.png", + "icon": "instacart", "category": "Food", "country": "US" }, { "text": "Walmart+", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Walmart_logo_%282008%29.svg/1200px-Walmart_logo_%282008%29.svg.png", + "icon": "walmart", "category": "Shopping", "country": "US" }, { "text": "Tinder Gold", - "logo": "https://logo.clearbit.com/tinder.com", + "icon": "tinder", "category": "Dating", "country": "US" }, { "text": "Bumble Boost", - "logo": "https://logo.clearbit.com/bumble.com", + "icon": null, "category": "Dating", "country": "US" }, { "text": "Hinge Preferred", - "logo": "https://logo.clearbit.com/hinge.co", + "icon": null, "category": "Dating", "country": "US" }, { "text": "LinkedIn Premium", - "logo": "https://upload.wikimedia.org/wikipedia/commons/c/ca/LinkedIn_logo_initials.png", + "icon": null, "category": "Career", "country": "US" }, { "text": "X Premium", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/ce/X_logo_2023.svg/500px-X_logo_2023.svg.png", + "icon": "x", "category": "Social", "country": "US" }, { "text": "Snapchat+", - "logo": "https://logo.clearbit.com/snapchat.com", + "icon": "snapchat", "category": "Social", "country": "US" }, { "text": "Medium", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Medium_logo_Monogram.svg/500px-Medium_logo_Monogram.svg.png", + "icon": "medium", "category": "Reading", "country": "US" }, { "text": "New York Times", - "logo": "https://upload.wikimedia.org/wikipedia/commons/7/77/The_New_York_Times_logo.png", + "icon": "newyorktimes", "category": "News", "country": "US" }, { "text": "Wall Street Journal", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c4/The_Wall_Street_Journal_Logo.svg/1200px-The_Wall_Street_Journal_Logo.svg.png", + "icon": null, "category": "News", "country": "US" }, { "text": "Washington Post", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/The_Washington_Post_logo.svg/1200px-The_Washington_Post_logo.svg.png", + "icon": null, "category": "News", "country": "US" }, { "text": "The Guardian", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/75/The_Guardian_2018.svg/500px-The_Guardian_2018.svg.png", + "icon": null, "category": "News", "country": "GB" }, { "text": "Financial Times", - "logo": "https://logo.clearbit.com/ft.com", + "icon": null, "category": "News", "country": "GB" }, { "text": "HelloFresh", - "logo": "https://logo.clearbit.com/hellofresh.com", + "icon": "hellofresh", "category": "Food", "country": "DE" }, { "text": "Blue Apron", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/70/Blue_Apron_logo.svg/1200px-Blue_Apron_logo.svg.png", + "icon": null, "category": "Food", "country": "US" }, { "text": "Ipsy", - "logo": "https://logo.clearbit.com/ipsy.com", + "icon": null, "category": "Beauty", "country": "US" }, { "text": "Birchbox", - "logo": "https://cdn-icons-png.flaticon.com/512/5977/5977575.png", + "icon": null, "category": "Beauty", "country": "US" }, { "text": "Stitch Fix", - "logo": "https://logo.clearbit.com/stitchfix.com", + "icon": null, "category": "Fashion", "country": "US" }, { "text": "Rent The Runway", - "logo": "https://1000logos.net/wp-content/uploads/2021/05/Rent-the-Runway-logo.png", + "icon": null, "category": "Fashion", "country": "US" }, { "text": "Fabletics", - "logo": "https://logo.clearbit.com/fabletics.com", + "icon": null, "category": "Fashion", "country": "US" }, { "text": "BarkBox", - "logo": "https://seeklogo.com/images/B/barkbox-logo-B114D62002-seeklogo.com.png", + "icon": null, "category": "Pet", "country": "US" }, { "text": "FabFitFun", - "logo": "https://1000logos.net/wp-content/uploads/2023/01/FabFitFun-Logo.png", + "icon": null, "category": "Lifestyle", "country": "US" }, { "text": "Crunchyroll", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Crunchyroll_Logo.svg/500px-Crunchyroll_Logo.svg.png", + "icon": "crunchyroll", "category": "Streaming", "country": "US" }, { "text": "Funimation", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Funimation_2016.svg/1200px-Funimation_2016.svg.png", + "icon": null, "category": "Streaming", "country": "US" }, { "text": "Shudder", - "logo": "https://logo.clearbit.com/shudder.com", + "icon": null, "category": "Streaming", "country": "US" }, { "text": "Criterion Channel", - "logo": "https://logo.clearbit.com/criterionchannel.com", + "icon": null, "category": "Streaming", "country": "US" }, { "text": "DAZN", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/DAZN_Logo_Master.svg/1200px-DAZN_Logo_Master.svg.png", + "icon": "dazn", "category": "Sports", "country": "GB" }, { "text": "ESPN+", - "logo": "https://logo.clearbit.com/espn.com", + "icon": null, "category": "Sports", "country": "US" }, { "text": "NBA League Pass", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/NBA_script.svg/1200px-NBA_script.svg.png", + "icon": "nba", "category": "Sports", "country": "US" }, { "text": "MLB.TV", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a6/Major_League_Baseball_logo.svg/500px-Major_League_Baseball_logo.svg.png", + "icon": null, "category": "Sports", "country": "US" }, { "text": "Coursera Plus", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Coursera-Logo_600x600.svg/500px-Coursera-Logo_600x600.svg.png", + "icon": "coursera", "category": "Education", "country": "US" }, { "text": "Skillshare", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c0/Skillshare_logo_2020.svg/1200px-Skillshare_logo_2020.svg.png", + "icon": "skillshare", "category": "Education", "country": "US" }, { "text": "MasterClass", - "logo": "https://logo.clearbit.com/masterclass.com", + "icon": null, "category": "Education", "country": "US" }, { "text": "Udemy", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Udemy_logo.svg/500px-Udemy_logo.svg.png", + "icon": "udemy", "category": "Education", "country": "US" }, { "text": "Scribd", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8d/Scribd_logo.svg/1200px-Scribd_logo.svg.png", + "icon": "scribd", "category": "Books", "country": "US" }, { "text": "Blinkist", - "logo": "https://logo.clearbit.com/blinkist.com", + "icon": null, "category": "Books", "country": "DE" }, { "text": "Tidal", - "logo": "https://logo.clearbit.com/tidal.com", + "icon": "tidal", "category": "Music", "country": "NO" }, { "text": "Deezer", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Deezer_logo_2019.svg/1200px-Deezer_logo_2019.svg.png", + "icon": null, "category": "Music", "country": "FR" }, { "text": "SoundCloud Go+", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Soundcloud_logo.svg/1200px-Soundcloud_logo.svg.png", + "icon": "soundcloud", "category": "Music", "country": "DE" }, { "text": "Pandora Plus", - "logo": "https://logo.clearbit.com/pandora.com", + "icon": null, "category": "Music", "country": "US" }, { "text": "Grammarly Premium", - "logo": "https://logo.clearbit.com/grammarly.com", + "icon": "grammarly", "category": "Productivity", "country": "US" }, { "text": "Asana", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Asana_logo.svg/500px-Asana_logo.svg.png", + "icon": "asana", "category": "Productivity", "country": "US" }, { "text": "Trello", - "logo": "https://logo.clearbit.com/trello.com", + "icon": "trello", "category": "Productivity", "country": "US" }, { "text": "Monday.com", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c6/Monday_logo.svg/1200px-Monday_logo.svg.png", + "icon": null, "category": "Productivity", "country": "IL" }, { "text": "ClickUp", - "logo": "https://logo.clearbit.com/clickup.com", + "icon": "clickup", "category": "Productivity", "country": "US" }, { "text": "EA Play", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/EA_Play_logo.svg/1200px-EA_Play_logo.svg.png", + "icon": "ea", "category": "Gaming", "country": "US" }, { "text": "Ubisoft+", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Ubisoft_logo.svg/500px-Ubisoft_logo.svg.png", + "icon": "ubisoft", "category": "Gaming", "country": "FR" }, { "text": "GeForce Now", - "logo": "https://logo.clearbit.com/nvidia.com", + "icon": "nvidia", "category": "Gaming", "country": "US" }, { "text": "Mailchimp", - "logo": "https://logo.clearbit.com/mailchimp.com", + "icon": "mailchimp", "category": "Marketing", "country": "US" }, { "text": "Shopify", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/Shopify_logo_2018.svg/500px-Shopify_logo_2018.svg.png", + "icon": "shopify", "category": "E-commerce", "country": "CA" }, { "text": "Wix", - "logo": "https://logo.clearbit.com/wix.com", + "icon": "wix", "category": "Website", "country": "IL" }, { "text": "Squarespace", - "logo": "https://logo.clearbit.com/squarespace.com", + "icon": "squarespace", "category": "Website", "country": "US" }, { "text": "Surfshark", - "logo": "https://logo.clearbit.com/surfshark.com", + "icon": "surfshark", "category": "Security", "country": "NL" }, { "text": "LastPass", - "logo": "https://logo.clearbit.com/lastpass.com", + "icon": "lastpass", "category": "Security", "country": "US" }, { "text": "Bitwarden Premium", - "logo": "https://logo.clearbit.com/bitwarden.com", + "icon": "bitwarden", "category": "Security", "country": "US" }, { "text": "Proton Mail", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/ProtonMail_logo.svg/1200px-ProtonMail_logo.svg.png", + "icon": "protonmail", "category": "Cloud", "country": "CH" }, { "text": "Proton VPN", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/ProtonMail_logo.svg/1200px-ProtonMail_logo.svg.png", + "icon": "protonvpn", "category": "Security", "country": "CH" }, { "text": "Mullvad VPN", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/ca/Mullvad_logo.svg/1200px-Mullvad_logo.svg.png", + "icon": "mullvad", "category": "Security", "country": "SE" }, { "text": "Standard Notes", - "logo": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Standard_Notes_Logo.png/600px-Standard_Notes_Logo.png", + "icon": null, "category": "Productivity", "country": "US" } diff --git a/lib/models/brand.dart b/lib/models/brand.dart index d125b76..3d18836 100644 --- a/lib/models/brand.dart +++ b/lib/models/brand.dart @@ -8,6 +8,7 @@ abstract class Brand with _$Brand { const factory Brand({ required String text, String? logo, + String? icon, String? category, String? name, String? country, diff --git a/lib/models/brand.freezed.dart b/lib/models/brand.freezed.dart index dc54108..a1520d4 100644 --- a/lib/models/brand.freezed.dart +++ b/lib/models/brand.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$Brand { - String get text; String? get logo; String? get category; String? get name; String? get country; String? get desc; bool? get isNative; + String get text; String? get logo; String? get icon; String? get category; String? get name; String? get country; String? get desc; bool? get isNative; /// Create a copy of Brand /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -28,16 +28,16 @@ $BrandCopyWith get copyWith => _$BrandCopyWithImpl(this as Brand, @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is Brand&&(identical(other.text, text) || other.text == text)&&(identical(other.logo, logo) || other.logo == logo)&&(identical(other.category, category) || other.category == category)&&(identical(other.name, name) || other.name == name)&&(identical(other.country, country) || other.country == country)&&(identical(other.desc, desc) || other.desc == desc)&&(identical(other.isNative, isNative) || other.isNative == isNative)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is Brand&&(identical(other.text, text) || other.text == text)&&(identical(other.logo, logo) || other.logo == logo)&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.category, category) || other.category == category)&&(identical(other.name, name) || other.name == name)&&(identical(other.country, country) || other.country == country)&&(identical(other.desc, desc) || other.desc == desc)&&(identical(other.isNative, isNative) || other.isNative == isNative)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,text,logo,category,name,country,desc,isNative); +int get hashCode => Object.hash(runtimeType,text,logo,icon,category,name,country,desc,isNative); @override String toString() { - return 'Brand(text: $text, logo: $logo, category: $category, name: $name, country: $country, desc: $desc, isNative: $isNative)'; + return 'Brand(text: $text, logo: $logo, icon: $icon, category: $category, name: $name, country: $country, desc: $desc, isNative: $isNative)'; } @@ -48,7 +48,7 @@ abstract mixin class $BrandCopyWith<$Res> { factory $BrandCopyWith(Brand value, $Res Function(Brand) _then) = _$BrandCopyWithImpl; @useResult $Res call({ - String text, String? logo, String? category, String? name, String? country, String? desc, bool? isNative + String text, String? logo, String? icon, String? category, String? name, String? country, String? desc, bool? isNative }); @@ -65,10 +65,11 @@ class _$BrandCopyWithImpl<$Res> /// Create a copy of Brand /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? text = null,Object? logo = freezed,Object? category = freezed,Object? name = freezed,Object? country = freezed,Object? desc = freezed,Object? isNative = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? text = null,Object? logo = freezed,Object? icon = freezed,Object? category = freezed,Object? name = freezed,Object? country = freezed,Object? desc = freezed,Object? isNative = freezed,}) { return _then(_self.copyWith( text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable as String,logo: freezed == logo ? _self.logo : logo // ignore: cast_nullable_to_non_nullable +as String?,icon: freezed == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable as String?,category: freezed == category ? _self.category : category // ignore: cast_nullable_to_non_nullable as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String?,country: freezed == country ? _self.country : country // ignore: cast_nullable_to_non_nullable @@ -159,10 +160,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String text, String? logo, String? category, String? name, String? country, String? desc, bool? isNative)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String text, String? logo, String? icon, String? category, String? name, String? country, String? desc, bool? isNative)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _Brand() when $default != null: -return $default(_that.text,_that.logo,_that.category,_that.name,_that.country,_that.desc,_that.isNative);case _: +return $default(_that.text,_that.logo,_that.icon,_that.category,_that.name,_that.country,_that.desc,_that.isNative);case _: return orElse(); } @@ -180,10 +181,10 @@ return $default(_that.text,_that.logo,_that.category,_that.name,_that.country,_t /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String text, String? logo, String? category, String? name, String? country, String? desc, bool? isNative) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String text, String? logo, String? icon, String? category, String? name, String? country, String? desc, bool? isNative) $default,) {final _that = this; switch (_that) { case _Brand(): -return $default(_that.text,_that.logo,_that.category,_that.name,_that.country,_that.desc,_that.isNative);case _: +return $default(_that.text,_that.logo,_that.icon,_that.category,_that.name,_that.country,_that.desc,_that.isNative);case _: throw StateError('Unexpected subclass'); } @@ -200,10 +201,10 @@ return $default(_that.text,_that.logo,_that.category,_that.name,_that.country,_t /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String text, String? logo, String? category, String? name, String? country, String? desc, bool? isNative)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String text, String? logo, String? icon, String? category, String? name, String? country, String? desc, bool? isNative)? $default,) {final _that = this; switch (_that) { case _Brand() when $default != null: -return $default(_that.text,_that.logo,_that.category,_that.name,_that.country,_that.desc,_that.isNative);case _: +return $default(_that.text,_that.logo,_that.icon,_that.category,_that.name,_that.country,_that.desc,_that.isNative);case _: return null; } @@ -215,11 +216,12 @@ return $default(_that.text,_that.logo,_that.category,_that.name,_that.country,_t @JsonSerializable() class _Brand implements Brand { - const _Brand({required this.text, this.logo, this.category, this.name, this.country, this.desc, this.isNative}); + const _Brand({required this.text, this.logo, this.icon, this.category, this.name, this.country, this.desc, this.isNative}); factory _Brand.fromJson(Map json) => _$BrandFromJson(json); @override final String text; @override final String? logo; +@override final String? icon; @override final String? category; @override final String? name; @override final String? country; @@ -239,16 +241,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _Brand&&(identical(other.text, text) || other.text == text)&&(identical(other.logo, logo) || other.logo == logo)&&(identical(other.category, category) || other.category == category)&&(identical(other.name, name) || other.name == name)&&(identical(other.country, country) || other.country == country)&&(identical(other.desc, desc) || other.desc == desc)&&(identical(other.isNative, isNative) || other.isNative == isNative)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Brand&&(identical(other.text, text) || other.text == text)&&(identical(other.logo, logo) || other.logo == logo)&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.category, category) || other.category == category)&&(identical(other.name, name) || other.name == name)&&(identical(other.country, country) || other.country == country)&&(identical(other.desc, desc) || other.desc == desc)&&(identical(other.isNative, isNative) || other.isNative == isNative)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,text,logo,category,name,country,desc,isNative); +int get hashCode => Object.hash(runtimeType,text,logo,icon,category,name,country,desc,isNative); @override String toString() { - return 'Brand(text: $text, logo: $logo, category: $category, name: $name, country: $country, desc: $desc, isNative: $isNative)'; + return 'Brand(text: $text, logo: $logo, icon: $icon, category: $category, name: $name, country: $country, desc: $desc, isNative: $isNative)'; } @@ -259,7 +261,7 @@ abstract mixin class _$BrandCopyWith<$Res> implements $BrandCopyWith<$Res> { factory _$BrandCopyWith(_Brand value, $Res Function(_Brand) _then) = __$BrandCopyWithImpl; @override @useResult $Res call({ - String text, String? logo, String? category, String? name, String? country, String? desc, bool? isNative + String text, String? logo, String? icon, String? category, String? name, String? country, String? desc, bool? isNative }); @@ -276,10 +278,11 @@ class __$BrandCopyWithImpl<$Res> /// Create a copy of Brand /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? text = null,Object? logo = freezed,Object? category = freezed,Object? name = freezed,Object? country = freezed,Object? desc = freezed,Object? isNative = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? text = null,Object? logo = freezed,Object? icon = freezed,Object? category = freezed,Object? name = freezed,Object? country = freezed,Object? desc = freezed,Object? isNative = freezed,}) { return _then(_Brand( text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable as String,logo: freezed == logo ? _self.logo : logo // ignore: cast_nullable_to_non_nullable +as String?,icon: freezed == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable as String?,category: freezed == category ? _self.category : category // ignore: cast_nullable_to_non_nullable as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String?,country: freezed == country ? _self.country : country // ignore: cast_nullable_to_non_nullable diff --git a/lib/models/brand.g.dart b/lib/models/brand.g.dart index b388105..4ea959f 100644 --- a/lib/models/brand.g.dart +++ b/lib/models/brand.g.dart @@ -9,6 +9,7 @@ part of 'brand.dart'; _Brand _$BrandFromJson(Map json) => _Brand( text: json['text'] as String, logo: json['logo'] as String?, + icon: json['icon'] as String?, category: json['category'] as String?, name: json['name'] as String?, country: json['country'] as String?, @@ -19,6 +20,7 @@ _Brand _$BrandFromJson(Map json) => _Brand( Map _$BrandToJson(_Brand instance) => { 'text': instance.text, 'logo': instance.logo, + 'icon': instance.icon, 'category': instance.category, 'name': instance.name, 'country': instance.country, diff --git a/lib/providers/brands_provider.dart b/lib/providers/brands_provider.dart index 15dfe44..ac12d0e 100644 --- a/lib/providers/brands_provider.dart +++ b/lib/providers/brands_provider.dart @@ -31,7 +31,7 @@ class Brands extends _$Brands { ref.watch(brandsStorageProvider.future), options: StorageOptions( cacheTime: StorageCacheTime.unsafe_forever, - destroyKey: "v1", + destroyKey: "v3", ), ).future; if (state.value != null) { diff --git a/lib/providers/brands_provider.g.dart b/lib/providers/brands_provider.g.dart index 9120122..783a892 100644 --- a/lib/providers/brands_provider.g.dart +++ b/lib/providers/brands_provider.g.dart @@ -75,7 +75,7 @@ final class BrandsProvider extends $AsyncNotifierProvider> { Brands create() => Brands(); } -String _$brandsHash() => r'f605da45e18ac1b72d24644538cac182cbd9953c'; +String _$brandsHash() => r'97f03c4666ac0e5b65ce616e7e19160591ef1157'; @JsonPersist() abstract class _$BrandsBase extends $AsyncNotifier> { diff --git a/lib/providers/subs_controller.dart b/lib/providers/subs_controller.dart index 0f3492e..9635854 100644 --- a/lib/providers/subs_controller.dart +++ b/lib/providers/subs_controller.dart @@ -40,7 +40,7 @@ class SubsController extends _$SubsController { ref.watch(subsStorageProvider.future), options: StorageOptions( cacheTime: StorageCacheTime.unsafe_forever, - destroyKey: "v1", + destroyKey: "v2", ), ).future; scheduleNotification(); From 1eb3cc0867d702abb9d9568822b3da537d53babd Mon Sep 17 00:00:00 2001 From: Eren Gun Date: Sat, 28 Feb 2026 20:53:58 +0300 Subject: [PATCH 4/5] feat(ui): switch to icon-based brand logos --- lib/screens/calendar_screen.dart | 17 ++----- lib/screens/home_screen.dart | 45 +++++------------ lib/utils/brand_utils.dart | 17 +++++++ lib/widgets/add_subs_dialog.dart | 72 ++++------------------------ lib/widgets/brand_logo.dart | 59 +++++++++++++++++++++++ lib/widgets/edit_subs_dialog.dart | 72 ++++------------------------ lib/widgets/pie_chart.dart | 11 ++--- pubspec.lock | 80 ++++++++----------------------- pubspec.yaml | 3 +- 9 files changed, 134 insertions(+), 242 deletions(-) create mode 100644 lib/utils/brand_utils.dart create mode 100644 lib/widgets/brand_logo.dart diff --git a/lib/screens/calendar_screen.dart b/lib/screens/calendar_screen.dart index 125378b..2930429 100644 --- a/lib/screens/calendar_screen.dart +++ b/lib/screens/calendar_screen.dart @@ -1,10 +1,10 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:subs_tracker/models/sub_slice.dart'; import 'package:subs_tracker/providers/subs_controller.dart'; +import 'package:subs_tracker/widgets/brand_logo.dart'; import 'package:table_calendar/table_calendar.dart'; class CalendarScreen extends HookConsumerWidget { @@ -92,17 +92,10 @@ class CalendarScreen extends HookConsumerWidget { itemBuilder: (context, index) { final sub = selectedEvents[index]; return ListTile( - leading: sub.brand?.logo != null - ? CachedNetworkImage( - imageUrl: sub.brand!.logo!, - width: 32, - height: 32, - fit: BoxFit.contain, - errorWidget: (_, _, _) => CircleAvatar( - radius: 16, - backgroundColor: Color(sub.color), - child: Text(sub.name[0].toUpperCase()), - ), + leading: sub.brand != null + ? BrandLogo( + brand: sub.brand, + size: 32, ) : CircleAvatar( radius: 16, diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 7904e14..4e3bfd0 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -7,6 +6,7 @@ import 'package:subs_tracker/models/sub_slice.dart'; import 'package:subs_tracker/providers/settings_controller.dart'; import 'package:subs_tracker/providers/subs_controller.dart'; import 'package:subs_tracker/widgets/add_subs_dialog.dart'; +import 'package:subs_tracker/widgets/brand_logo.dart'; import 'package:subs_tracker/widgets/edit_subs_dialog.dart'; class HomeScreen extends HookConsumerWidget { @@ -327,27 +327,17 @@ class _CompactSliceLeading extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: slice.brand?.logo != null - ? Colors.transparent - : Color(slice.color), - borderRadius: BorderRadius.circular(6), - ), - child: slice.brand?.logo != null - ? ClipRRect( + return slice.brand != null + ? BrandLogo(brand: slice.brand, size: 40) + : Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Color(slice.color), borderRadius: BorderRadius.circular(6), - child: CachedNetworkImage( - imageUrl: slice.brand!.logo!, - fit: BoxFit.contain, - placeholder: (_, _) => const _SliceLogoPlaceholder(), - errorWidget: (_, _, _) => SubAvatar(s: slice), - ), - ) - : SubAvatar(s: slice), - ); + ), + child: SubAvatar(s: slice), + ); } } @@ -368,17 +358,4 @@ class SubAvatar extends StatelessWidget { } } -class _SliceLogoPlaceholder extends StatelessWidget { - const _SliceLogoPlaceholder(); - @override - Widget build(BuildContext context) { - return const Center( - child: SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator(strokeWidth: 2), - ), - ); - } -} diff --git a/lib/utils/brand_utils.dart b/lib/utils/brand_utils.dart new file mode 100644 index 0000000..834aece --- /dev/null +++ b/lib/utils/brand_utils.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:simple_icons/simple_icons.dart'; +import 'package:subs_tracker/models/brand.dart'; + +/// Extension on [Brand] to derive icon data from Simple Icons. +/// +/// Uses the built-in [SimpleIcons.values] and [SimpleIconColors.values] +/// lookup maps, so no manual mapping is needed. +extension BrandIconData on Brand { + /// Returns the [IconData] for this brand from Simple Icons, or null. + IconData? get iconData => + icon != null ? SimpleIcons.values[icon!] : null; + + /// Returns the brand color from Simple Icons, or null. + Color? get iconColor => + icon != null ? SimpleIconColors.values[icon!] : null; +} diff --git a/lib/widgets/add_subs_dialog.dart b/lib/widgets/add_subs_dialog.dart index b335e56..efb0b67 100644 --- a/lib/widgets/add_subs_dialog.dart +++ b/lib/widgets/add_subs_dialog.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart'; @@ -9,6 +8,7 @@ import 'package:subs_tracker/models/sub_slice.dart'; import 'package:subs_tracker/providers/brands_provider.dart'; import 'package:subs_tracker/providers/subs_controller.dart'; import 'package:subs_tracker/utils/color_palette.dart'; +import 'package:subs_tracker/widgets/brand_logo.dart'; class AddSubsDialog extends HookConsumerWidget { const AddSubsDialog({super.key}); @@ -132,35 +132,9 @@ class AddSubsDialog extends HookConsumerWidget { prefixIcon: draftSlice.value.brand != null ? Padding( padding: const EdgeInsets.all(8), - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: SizedBox.square( - dimension: 32, - child: - draftSlice.value.brand!.logo != - null - ? CachedNetworkImage( - imageUrl: draftSlice - .value - .brand! - .logo!, - fit: BoxFit.contain, - placeholder: (_, _) => - const _LogoPlaceholder(), - errorWidget: (_, _, _) => - const Icon( - Icons.business, - ), - ) - : Container( - color: Theme.of(context) - .colorScheme - .surfaceContainerHighest, - child: const Icon( - Icons.business, - ), - ), - ), + child: BrandLogo( + brand: draftSlice.value.brand, + size: 32, ), ) : const Icon(Icons.search), @@ -211,27 +185,10 @@ class AddSubsDialog extends HookConsumerWidget { itemBuilder: (context, index) { final Brand option = options.elementAt(index); return ListTile( - leading: option.logo != null - ? ClipRRect( - borderRadius: BorderRadius.circular( - 8, - ), - child: CachedNetworkImage( - imageUrl: option.logo!, - width: 40, - height: 40, - fit: BoxFit.cover, - placeholder: (_, _) => - const _LogoPlaceholder(), - errorWidget: (_, _, _) => - const Icon( - Icons.image_not_supported, - ), - ), - ) - : const CircleAvatar( - child: Icon(Icons.business), - ), + leading: BrandLogo( + brand: option, + size: 40, + ), title: Text(option.text), subtitle: option.category != null || @@ -499,17 +456,4 @@ class AddSubsDialog extends HookConsumerWidget { } } -class _LogoPlaceholder extends StatelessWidget { - const _LogoPlaceholder(); - @override - Widget build(BuildContext context) { - return const Center( - child: SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ), - ); - } -} diff --git a/lib/widgets/brand_logo.dart b/lib/widgets/brand_logo.dart new file mode 100644 index 0000000..ee6a4af --- /dev/null +++ b/lib/widgets/brand_logo.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:subs_tracker/models/brand.dart'; +import 'package:subs_tracker/utils/brand_utils.dart'; + +/// A reusable widget that displays a brand icon from Simple Icons, +/// or a fallback Material icon if no Simple Icons match is available. +class BrandLogo extends StatelessWidget { + const BrandLogo({ + super.key, + required this.brand, + this.size = 32, + }); + + final Brand? brand; + final double size; + + @override + Widget build(BuildContext context) { + final iconData = brand?.iconData; + + if (iconData != null) { + final color = brand!.iconColor ?? + Theme.of(context).colorScheme.onSurface; + // Use the brand-colored icon on a subtle background + return Container( + width: size, + height: size, + decoration: BoxDecoration( + color: color.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(size * 0.2), + ), + child: Center( + child: Icon( + iconData, + size: size * 0.65, + color: color, + ), + ), + ); + } + + // Fallback for brands without a Simple Icons match + return Container( + width: size, + height: size, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(size * 0.2), + ), + child: Center( + child: Icon( + Icons.subscriptions_outlined, + size: size * 0.55, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ); + } +} diff --git a/lib/widgets/edit_subs_dialog.dart b/lib/widgets/edit_subs_dialog.dart index 94ad2e9..64772d2 100644 --- a/lib/widgets/edit_subs_dialog.dart +++ b/lib/widgets/edit_subs_dialog.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart'; @@ -9,6 +8,7 @@ import 'package:subs_tracker/models/sub_slice.dart'; import 'package:subs_tracker/providers/brands_provider.dart'; import 'package:subs_tracker/providers/subs_controller.dart'; import 'package:subs_tracker/utils/color_palette.dart'; +import 'package:subs_tracker/widgets/brand_logo.dart'; class EditSubsDialog extends HookConsumerWidget { const EditSubsDialog({ @@ -131,35 +131,9 @@ class EditSubsDialog extends HookConsumerWidget { prefixIcon: draftSlice.value.brand != null ? Padding( padding: const EdgeInsets.all(8), - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: SizedBox.square( - dimension: 32, - child: - draftSlice.value.brand!.logo != - null - ? CachedNetworkImage( - imageUrl: draftSlice - .value - .brand! - .logo!, - fit: BoxFit.contain, - placeholder: (_, _) => - const _LogoPlaceholder(), - errorWidget: (_, _, _) => - const Icon( - Icons.business, - ), - ) - : Container( - color: Theme.of(context) - .colorScheme - .surfaceContainerHighest, - child: const Icon( - Icons.business, - ), - ), - ), + child: BrandLogo( + brand: draftSlice.value.brand, + size: 32, ), ) : const Icon(Icons.search), @@ -210,27 +184,10 @@ class EditSubsDialog extends HookConsumerWidget { itemBuilder: (context, index) { final Brand option = options.elementAt(index); return ListTile( - leading: option.logo != null - ? ClipRRect( - borderRadius: BorderRadius.circular( - 8, - ), - child: CachedNetworkImage( - imageUrl: option.logo!, - width: 40, - height: 40, - fit: BoxFit.cover, - placeholder: (_, _) => - const _LogoPlaceholder(), - errorWidget: (_, _, _) => - const Icon( - Icons.image_not_supported, - ), - ), - ) - : const CircleAvatar( - child: Icon(Icons.business), - ), + leading: BrandLogo( + brand: option, + size: 40, + ), title: Text(option.text), subtitle: option.category != null || @@ -499,17 +456,4 @@ class EditSubsDialog extends HookConsumerWidget { } } -class _LogoPlaceholder extends StatelessWidget { - const _LogoPlaceholder(); - @override - Widget build(BuildContext context) { - return const Center( - child: SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ), - ); - } -} diff --git a/lib/widgets/pie_chart.dart b/lib/widgets/pie_chart.dart index db437b2..f0b6377 100644 --- a/lib/widgets/pie_chart.dart +++ b/lib/widgets/pie_chart.dart @@ -6,6 +6,7 @@ import 'package:subs_tracker/models/brand.dart'; import 'package:subs_tracker/models/sub_slice.dart'; import 'package:subs_tracker/providers/subs_controller.dart'; import 'package:subs_tracker/utils/color_utils.dart'; +import 'package:subs_tracker/widgets/brand_logo.dart'; class SubsPie extends ConsumerStatefulWidget { const SubsPie({super.key}); @@ -100,13 +101,9 @@ class _Badge extends StatelessWidget { ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: brand?.logo != null ? Image.network( - brand!.logo!, - width: 40, - height: 20, - fit: BoxFit.contain, - errorBuilder: (_, _, _) => - const Icon(Icons.business, size: 16), + child: brand != null ? BrandLogo( + brand: brand, + size: 28, ) : Flexible( child: Text( diff --git a/pubspec.lock b/pubspec.lock index 53794a5..70d1592 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,38 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.12.1" - cached_network_image: - dependency: "direct main" - description: - name: cached_network_image - sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" - url: "https://pub.dev" - source: hosted - version: "3.4.1" - cached_network_image_platform_interface: - dependency: transitive - description: - name: cached_network_image_platform_interface - sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" - url: "https://pub.dev" - source: hosted - version: "4.1.1" - cached_network_image_web: - dependency: transitive - description: - name: cached_network_image_web - sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" - url: "https://pub.dev" - source: hosted - version: "1.3.1" characters: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -414,14 +390,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_cache_manager: - dependency: transitive - description: - name: flutter_cache_manager - sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" - url: "https://pub.dev" - source: hosted - version: "3.4.1" flutter_colorpicker: dependency: "direct main" description: @@ -685,14 +653,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" - js: - dependency: transitive - description: - name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.dev" - source: hosted - version: "0.7.2" json_annotation: dependency: "direct main" description: @@ -761,18 +721,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.18" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: @@ -805,14 +765,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" - octo_image: - dependency: transitive - description: - name: octo_image - sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" package_config: dependency: transitive description: @@ -1141,6 +1093,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4+2" + simple_icons: + dependency: "direct main" + description: + name: simple_icons + sha256: "2ca3cd79c9f12e97a8588cae0f342609f19fd2e82315356cb09b5c4987ad0808" + url: "https://pub.dev" + source: hosted + version: "14.6.1" sky_engine: dependency: transitive description: flutter @@ -1294,26 +1254,26 @@ packages: dependency: transitive description: name: test - sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + sha256: "77cc98ea27006c84e71a7356cf3daf9ddbde2d91d84f77dbfe64cf0e4d9611ae" url: "https://pub.dev" source: hosted - version: "1.26.3" + version: "1.28.0" test_api: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "19a78f63e83d3a61f00826d09bc2f60e191bf3504183c001262be6ac75589fb8" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.8" test_core: dependency: transitive description: name: test_core - sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + sha256: f1072617a6657e5fc09662e721307f7fb009b4ed89b19f47175d11d5254a62d4 url: "https://pub.dev" source: hosted - version: "0.6.12" + version: "0.6.14" timezone: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 9f98706..fa8b8a9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,7 +33,6 @@ dependencies: sqflite: ^2.4.2 path: ^1.9.1 flutter_colorpicker: ^1.0.3 - cached_network_image: ^3.4.1 flutter_local_notifications: ^19.5.0 timezone: ^0.10.1 go_router: ^17.0.1 @@ -47,6 +46,7 @@ dependencies: crypto: ^3.0.7 shared_preferences: ^2.5.3 easy_localization: ^3.0.8 + simple_icons: ^14.6.1 dev_dependencies: flutter_test: @@ -85,6 +85,7 @@ flutter: assets: - assets/ - assets/translations/ + # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg From 9d0a9acecf6b39c41abefc0ad91d46ce01e04a25 Mon Sep 17 00:00:00 2001 From: Eren Gun Date: Sat, 28 Feb 2026 20:54:50 +0300 Subject: [PATCH 5/5] chore: update Flutter version to 3.41.0 --- .flutter-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flutter-version b/.flutter-version index bfe4261..371986f 100644 --- a/.flutter-version +++ b/.flutter-version @@ -1 +1 @@ -3.38.6 +3.41.0