diff --git a/lib/src/enums/enums.dart b/lib/src/enums/enums.dart index e0404450..f917dbae 100644 --- a/lib/src/enums/enums.dart +++ b/lib/src/enums/enums.dart @@ -12,9 +12,9 @@ export 'dashboard_user_role.dart'; export 'device_platform.dart'; export 'feed_decorator_category.dart'; export 'feed_decorator_type.dart'; -export 'headline_density.dart'; -export 'headline_image_style.dart'; -export 'in_article_ad_slot_type.dart'; +export 'feed_item_click_behavior.dart'; +export 'feed_item_density.dart'; +export 'feed_item_image_style.dart'; export 'push_notification_provider.dart'; export 'push_notification_subscription_delivery_type.dart'; export 'sort_order.dart'; diff --git a/lib/src/enums/feed_item_click_behavior.dart b/lib/src/enums/feed_item_click_behavior.dart new file mode 100644 index 00000000..74354aba --- /dev/null +++ b/lib/src/enums/feed_item_click_behavior.dart @@ -0,0 +1,17 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// {@template feed_item_click_behavior} +/// Defines how a feed item click should be handled. +/// {@endtemplate} +@JsonEnum() +enum FeedItemClickBehavior { + /// Adhere to the behavior defined by the admin in the remote config. + @JsonValue('default') + defaultBehavior, + + @JsonValue('internalNavigation') + internalNavigation, + + @JsonValue('externalNavigation') + externalNavigation, +} diff --git a/lib/src/enums/headline_density.dart b/lib/src/enums/feed_item_density.dart similarity index 66% rename from lib/src/enums/headline_density.dart rename to lib/src/enums/feed_item_density.dart index 8538b2ae..18bc2c5c 100644 --- a/lib/src/enums/headline_density.dart +++ b/lib/src/enums/feed_item_density.dart @@ -1,5 +1,5 @@ -/// Defines how densely headline information should be presented. -enum HeadlineDensity { +/// Defines how densely feed item information should be presented. +enum FeedItemDensity { /// Minimal spacing, smaller title font. compact, diff --git a/lib/src/enums/headline_image_style.dart b/lib/src/enums/feed_item_image_style.dart similarity index 68% rename from lib/src/enums/headline_image_style.dart rename to lib/src/enums/feed_item_image_style.dart index 155259a2..b923a081 100644 --- a/lib/src/enums/headline_image_style.dart +++ b/lib/src/enums/feed_item_image_style.dart @@ -1,5 +1,5 @@ -/// Defines how images should be displayed in the headline feed. -enum HeadlineImageStyle { +/// Defines how a feed item image should be displayed. +enum FeedItemImageStyle { /// No image shown in the feed. hidden, diff --git a/lib/src/enums/in_article_ad_slot_type.dart b/lib/src/enums/in_article_ad_slot_type.dart deleted file mode 100644 index d90c1d24..00000000 --- a/lib/src/enums/in_article_ad_slot_type.dart +++ /dev/null @@ -1,10 +0,0 @@ -/// {@template in_article_ad_slot_type} -/// Defines specific, standard locations for ads within an article's content. -/// {@endtemplate} -enum InArticleAdSlotType { - /// Ad placed just before a "Continue Reading" button. - aboveArticleContinueReadingButton, - - /// Ad placed just after a "Continue Reading" button. - belowArticleContinueReadingButton, -} diff --git a/lib/src/fixtures/user_app_settings.dart b/lib/src/fixtures/app_settings.dart similarity index 67% rename from lib/src/fixtures/user_app_settings.dart rename to lib/src/fixtures/app_settings.dart index fc08c66d..785bcfb2 100644 --- a/lib/src/fixtures/user_app_settings.dart +++ b/lib/src/fixtures/app_settings.dart @@ -1,8 +1,8 @@ import 'package:core/core.dart'; -/// User App Settings Demo Data -final List userAppSettingsFixturesData = [ - UserAppSettings( +/// App Settings Demo Data +final List appSettingsFixturesData = [ + AppSettings( id: kAdminUserId, displaySettings: const DisplaySettings( baseTheme: AppBaseTheme.system, @@ -20,14 +20,13 @@ final List userAppSettingsFixturesData = [ updatedAt: DateTime.now(), status: ContentStatus.active, ), - feedPreferences: const FeedDisplayPreferences( - headlineDensity: HeadlineDensity.standard, - headlineImageStyle: HeadlineImageStyle.smallThumbnail, - showSourceInHeadlineFeed: true, - showPublishDateInHeadlineFeed: true, + feedSettings: const FeedSettings( + feedItemDensity: FeedItemDensity.standard, + feedItemImageStyle: FeedItemImageStyle.smallThumbnail, + feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, ), ), - UserAppSettings( + AppSettings( id: kUser1Id, displaySettings: const DisplaySettings( baseTheme: AppBaseTheme.system, @@ -45,14 +44,13 @@ final List userAppSettingsFixturesData = [ updatedAt: DateTime.now(), status: ContentStatus.active, ), - feedPreferences: const FeedDisplayPreferences( - headlineDensity: HeadlineDensity.standard, - headlineImageStyle: HeadlineImageStyle.smallThumbnail, - showSourceInHeadlineFeed: true, - showPublishDateInHeadlineFeed: true, + feedSettings: const FeedSettings( + feedItemDensity: FeedItemDensity.standard, + feedItemImageStyle: FeedItemImageStyle.smallThumbnail, + feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, ), ), - UserAppSettings( + AppSettings( id: kUser2Id, displaySettings: const DisplaySettings( baseTheme: AppBaseTheme.dark, @@ -70,17 +68,16 @@ final List userAppSettingsFixturesData = [ updatedAt: DateTime.now(), status: ContentStatus.active, ), - feedPreferences: const FeedDisplayPreferences( - headlineDensity: HeadlineDensity.compact, - headlineImageStyle: HeadlineImageStyle.largeThumbnail, - showSourceInHeadlineFeed: true, - showPublishDateInHeadlineFeed: true, + feedSettings: const FeedSettings( + feedItemDensity: FeedItemDensity.compact, + feedItemImageStyle: FeedItemImageStyle.largeThumbnail, + feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, ), ), // Add settings for users 3-10, copying the admin's settings for simplicity ...List.generate( 8, - (index) => UserAppSettings( + (index) => AppSettings( id: [ kUser3Id, kUser4Id, @@ -107,11 +104,10 @@ final List userAppSettingsFixturesData = [ updatedAt: DateTime.now(), status: ContentStatus.active, ), - feedPreferences: const FeedDisplayPreferences( - headlineDensity: HeadlineDensity.standard, - headlineImageStyle: HeadlineImageStyle.smallThumbnail, - showSourceInHeadlineFeed: true, - showPublishDateInHeadlineFeed: true, + feedSettings: const FeedSettings( + feedItemDensity: FeedItemDensity.standard, + feedItemImageStyle: FeedItemImageStyle.smallThumbnail, + feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, ), ), ), diff --git a/lib/src/fixtures/fixtures.dart b/lib/src/fixtures/fixtures.dart index b9b0500b..99029356 100644 --- a/lib/src/fixtures/fixtures.dart +++ b/lib/src/fixtures/fixtures.dart @@ -1,3 +1,4 @@ +export 'app_settings.dart'; export 'countries.dart'; export 'dashboard_summary.dart'; export 'fixture_ids.dart'; @@ -9,6 +10,5 @@ export 'saved_headline_filters.dart'; export 'saved_source_filters.dart'; export 'sources.dart'; export 'topics.dart'; -export 'user_app_settings.dart'; export 'user_content_preferences.dart'; export 'users.dart'; diff --git a/lib/src/fixtures/headlines.dart b/lib/src/fixtures/headlines.dart index 5df59934..721179eb 100644 --- a/lib/src/fixtures/headlines.dart +++ b/lib/src/fixtures/headlines.dart @@ -11,8 +11,6 @@ final headlinesFixturesData = [ id: kHeadlineId1, isBreaking: false, title: 'AI Breakthrough: New Model Achieves Human-Level Performance', - excerpt: - 'Researchers announce a significant leap in artificial intelligence, with a new model demonstrating unprecedented capabilities.', url: 'https://example.com/news/ai-breakthrough-1', imageUrl: 'https://picsum.photos/seed/kHeadlineId1/800/600', source: sourcesFixturesData[0], // TechCrunch @@ -26,8 +24,6 @@ final headlinesFixturesData = [ id: kHeadlineId2, isBreaking: false, title: 'Local Team Wins Championship in Thrilling Final', - excerpt: - 'The city celebrates as the underdog team clinches the national championship in a nail-biting finish.', url: 'https://example.com/news/sports-championship-2', imageUrl: 'https://picsum.photos/seed/kHeadlineId2/800/600', source: sourcesFixturesData[1], // BBC News @@ -41,8 +37,6 @@ final headlinesFixturesData = [ id: kHeadlineId3, isBreaking: false, title: 'Global Leaders Meet to Discuss Climate Change Policies', - excerpt: - 'A summit of world leaders convenes to address urgent climate change issues and propose new international agreements.', url: 'https://example.com/news/politics-climate-3', imageUrl: 'https://picsum.photos/seed/kHeadlineId3/800/600', source: sourcesFixturesData[2], // The New York Times @@ -56,8 +50,6 @@ final headlinesFixturesData = [ id: kHeadlineId4, isBreaking: true, title: 'New Planet Discovered in Distant Galaxy', - excerpt: - 'Astronomers confirm the existence of a new exoplanet, sparking excitement in the scientific community.', url: 'https://example.com/news/science-planet-4', imageUrl: 'https://picsum.photos/seed/kHeadlineId4/800/600', source: sourcesFixturesData[3], // The Guardian @@ -71,8 +63,6 @@ final headlinesFixturesData = [ id: kHeadlineId5, isBreaking: false, title: 'Breakthrough in Cancer Research Offers New Hope', - excerpt: - 'A new study reveals a promising treatment approach for a common type of cancer, moving closer to a cure.', url: 'https://example.com/news/health-cancer-5', imageUrl: 'https://picsum.photos/seed/kHeadlineId5/800/600', source: sourcesFixturesData[4], // CNN @@ -86,8 +76,6 @@ final headlinesFixturesData = [ id: kHeadlineId6, isBreaking: false, title: 'Blockbuster Movie Breaks Box Office Records', - excerpt: - 'The highly anticipated film shatters previous box office records in its opening weekend, delighting fans worldwide.', url: 'https://example.com/news/entertainment-movie-6', imageUrl: 'https://picsum.photos/seed/kHeadlineId6/800/600', source: sourcesFixturesData[5], // Reuters @@ -101,8 +89,6 @@ final headlinesFixturesData = [ id: kHeadlineId7, isBreaking: false, title: 'Stock Market Reaches All-Time High Amid Economic Boom', - excerpt: - 'Major indices surge as strong economic data and corporate earnings drive investor confidence.', url: 'https://example.com/news/business-market-7', imageUrl: 'https://picsum.photos/seed/kHeadlineId7/800/600', source: sourcesFixturesData[6], // Al Jazeera English @@ -116,8 +102,6 @@ final headlinesFixturesData = [ id: kHeadlineId8, isBreaking: false, title: 'New Travel Restrictions Lifted for Popular Destinations', - excerpt: - 'Governments ease travel advisories, opening up new opportunities for international tourism.', url: 'https://example.com/news/travel-restrictions-8', imageUrl: 'https://picsum.photos/seed/kHeadlineId8/800/600', source: sourcesFixturesData[7], // Xinhua News Agency @@ -131,8 +115,6 @@ final headlinesFixturesData = [ id: kHeadlineId9, isBreaking: false, title: 'Michelin Star Chef Opens New Restaurant in City Center', - excerpt: - 'A world-renowned chef brings their culinary expertise to the city with a highly anticipated new dining establishment.', url: 'https://example.com/news/food-restaurant-9', imageUrl: 'https://picsum.photos/seed/kHeadlineId9/800/600', source: sourcesFixturesData[8], // The Times of India @@ -146,8 +128,6 @@ final headlinesFixturesData = [ id: kHeadlineId10, isBreaking: false, title: 'Innovative Teaching Methods Boost Student Engagement', - excerpt: - 'Schools adopting new pedagogical approaches report significant improvements in student participation and learning outcomes.', url: 'https://example.com/news/education-methods-10', imageUrl: 'https://picsum.photos/seed/kHeadlineId10/800/600', source: sourcesFixturesData[9], // Folha de S.Paulo @@ -161,8 +141,6 @@ final headlinesFixturesData = [ id: kHeadlineId11, isBreaking: false, title: 'Cybersecurity Firms Warn of New Global Threat', - excerpt: - 'Experts advise immediate updates as a sophisticated new malware strain targets critical infrastructure worldwide.', url: 'https://example.com/news/cybersecurity-threat-11', imageUrl: 'https://picsum.photos/seed/kHeadlineId11/800/600', source: sourcesFixturesData[0], // TechCrunch @@ -176,8 +154,6 @@ final headlinesFixturesData = [ id: kHeadlineId12, isBreaking: false, title: 'Olympics Committee Announces Host City for 2032 Games', - excerpt: - 'The highly anticipated decision for the next Summer Olympics host city has been revealed, promising a spectacular event.', url: 'https://example.com/news/sports-olympics-12', imageUrl: 'https://picsum.photos/seed/kHeadlineId12/800/600', source: sourcesFixturesData[1], // BBC News @@ -191,8 +167,6 @@ final headlinesFixturesData = [ id: kHeadlineId13, isBreaking: false, title: 'New Bill Aims to Reform Healthcare System', - excerpt: - 'Legislators introduce a comprehensive bill designed to address rising healthcare costs and expand access to services.', url: 'https://example.com/news/politics-healthcare-13', imageUrl: 'https://picsum.photos/seed/kHeadlineId13/800/600', source: sourcesFixturesData[2], // The New York Times @@ -206,8 +180,6 @@ final headlinesFixturesData = [ id: kHeadlineId14, isBreaking: false, title: 'Archaeologists Uncover Ancient City Ruins', - excerpt: - 'A team of archaeologists makes a groundbreaking discovery, revealing a previously unknown ancient civilization.', url: 'https://example.com/news/science-archaeology-14', imageUrl: 'https://picsum.photos/seed/kHeadlineId14/800/600', source: sourcesFixturesData[3], // The Guardian @@ -221,8 +193,6 @@ final headlinesFixturesData = [ id: kHeadlineId15, isBreaking: false, title: 'Dietary Guidelines Updated for Public Health', - excerpt: - 'New recommendations from health organizations aim to improve public nutrition and combat chronic diseases.', url: 'https://example.com/news/health-diet-15', imageUrl: 'https://picsum.photos/seed/kHeadlineId15/800/600', source: sourcesFixturesData[4], // CNN @@ -236,8 +206,6 @@ final headlinesFixturesData = [ id: kHeadlineId16, isBreaking: false, title: 'Music Festival Announces Star-Studded Lineup', - excerpt: - 'Fans eagerly await the annual music festival as organizers unveil a lineup featuring top artists from various genres.', url: 'https://example.com/news/entertainment-music-16', imageUrl: 'https://picsum.photos/seed/kHeadlineId16/800/600', source: sourcesFixturesData[5], // Reuters @@ -251,8 +219,6 @@ final headlinesFixturesData = [ id: kHeadlineId17, isBreaking: false, title: 'Tech Giant Acquires Startup in Multi-Billion Dollar Deal', - excerpt: - 'A major technology company expands its portfolio with the acquisition of a promising startup, signaling market consolidation.', url: 'https://example.com/news/business-acquisition-17', imageUrl: 'https://picsum.photos/seed/kHeadlineId17/800/600', source: sourcesFixturesData[6], // Al Jazeera English @@ -266,8 +232,6 @@ final headlinesFixturesData = [ id: kHeadlineId18, isBreaking: false, title: 'Space Tourism Takes Off: First Commercial Flights Announced', - excerpt: - 'The era of space tourism begins as companies unveil plans for regular commercial flights to orbit.', url: 'https://example.com/news/travel-space-18', imageUrl: 'https://picsum.photos/seed/kHeadlineId18/800/600', source: sourcesFixturesData[7], // Xinhua News Agency @@ -281,8 +245,6 @@ final headlinesFixturesData = [ id: kHeadlineId19, isBreaking: false, title: 'Future of Food: Lab-Grown Meat Gains Popularity', - excerpt: - 'As sustainability concerns grow, lab-grown meat alternatives are becoming a staple in modern diets.', url: 'https://example.com/news/food-lab-meat-19', imageUrl: 'https://picsum.photos/seed/kHeadlineId19/800/600', source: sourcesFixturesData[8], // The Times of India @@ -296,8 +258,6 @@ final headlinesFixturesData = [ id: kHeadlineId20, isBreaking: false, title: 'Online Learning Platforms See Surge in Enrollment', - excerpt: - 'The shift to digital education continues as more students opt for flexible online courses and certifications.', url: 'https://example.com/news/education-online-20', imageUrl: 'https://picsum.photos/seed/kHeadlineId20/800/600', source: sourcesFixturesData[9], // Folha de S.Paulo @@ -311,8 +271,6 @@ final headlinesFixturesData = [ id: kHeadlineId21, isBreaking: false, title: 'Quantum Computing Achieves New Milestone', - excerpt: - 'Scientists report a significant advancement in quantum computing, bringing the technology closer to practical applications.', url: 'https://example.com/news/tech-quantum-21', imageUrl: 'https://picsum.photos/seed/kHeadlineId21/800/600', source: sourcesFixturesData[0], // TechCrunch @@ -326,8 +284,6 @@ final headlinesFixturesData = [ id: kHeadlineId22, isBreaking: false, title: 'World Cup Qualifiers: Unexpected Upsets Shake Rankings', - excerpt: - 'Several top-ranked teams suffer surprising defeats in the latest World Cup qualifiers, reshuffling the global football landscape.', url: 'https://example.com/news/sports-worldcup-22', imageUrl: 'https://picsum.photos/seed/kHeadlineId22/800/600', source: sourcesFixturesData[1], // BBC News @@ -341,8 +297,6 @@ final headlinesFixturesData = [ id: kHeadlineId23, isBreaking: false, title: 'Election Results: New Government Takes Power', - excerpt: - 'Following a closely contested election, a new political party forms the government, promising significant policy changes.', url: 'https://example.com/news/politics-election-23', imageUrl: 'https://picsum.photos/seed/kHeadlineId23/800/600', source: sourcesFixturesData[2], // The New York Times @@ -356,8 +310,6 @@ final headlinesFixturesData = [ id: kHeadlineId24, isBreaking: false, title: 'Breakthrough in Fusion Energy Research Announced', - excerpt: - 'Scientists achieve a major milestone in fusion energy, bringing clean, limitless power closer to reality.', url: 'https://example.com/news/science-fusion-24', imageUrl: 'https://picsum.photos/seed/kHeadlineId24/800/600', source: sourcesFixturesData[3], // The Guardian @@ -371,8 +323,6 @@ final headlinesFixturesData = [ id: kHeadlineId25, isBreaking: false, title: 'Mental Health Awareness Campaign Launched Globally', - excerpt: - 'A new international initiative aims to destigmatize mental health issues and provide greater support resources.', url: 'https://example.com/news/health-mental-25', imageUrl: 'https://picsum.photos/seed/kHeadlineId25/800/600', source: sourcesFixturesData[4], // CNN @@ -386,8 +336,6 @@ final headlinesFixturesData = [ id: kHeadlineId26, isBreaking: false, title: 'Gaming Industry Sees Record Growth in Virtual Reality', - excerpt: - 'The virtual reality sector of the gaming industry experiences unprecedented expansion, driven by new hardware and immersive titles.', url: 'https://example.com/news/entertainment-vr-26', imageUrl: 'https://picsum.photos/seed/kHeadlineId26/800/600', source: sourcesFixturesData[5], // Reuters @@ -401,8 +349,6 @@ final headlinesFixturesData = [ id: kHeadlineId27, isBreaking: false, title: 'Global Supply Chain Disruptions Impacting Consumer Goods', - excerpt: - 'Ongoing challenges in global logistics are leading to shortages and price increases for a wide range of consumer products.', url: 'https://example.com/news/business-supplychain-27', imageUrl: 'https://picsum.photos/seed/kHeadlineId27/800/600', source: sourcesFixturesData[6], // Al Jazeera English @@ -416,8 +362,6 @@ final headlinesFixturesData = [ id: kHeadlineId28, isBreaking: false, title: 'Arctic Expedition Discovers New Marine Species', - excerpt: - 'Scientists on an Arctic research mission identify several previously unknown species of marine life, highlighting biodiversity.', url: 'https://example.com/news/travel-arctic-28', imageUrl: 'https://picsum.photos/seed/kHeadlineId28/800/600', source: sourcesFixturesData[7], // Xinhua News Agency @@ -431,8 +375,6 @@ final headlinesFixturesData = [ id: kHeadlineId29, isBreaking: false, title: 'Rise of Plant-Based Cuisine: New Restaurants Open', - excerpt: - 'The culinary scene is embracing plant-based diets with an increasing number of restaurants specializing in vegan and vegetarian dishes.', url: 'https://example.com/news/food-plantbased-29', imageUrl: 'https://picsum.photos/seed/kHeadlineId29/800/600', source: sourcesFixturesData[8], // The Times of India @@ -446,8 +388,6 @@ final headlinesFixturesData = [ id: kHeadlineId30, isBreaking: false, title: 'Education Technology Transforms Classrooms', - excerpt: - 'New digital tools and platforms are revolutionizing traditional classroom settings, enhancing interactive learning experiences.', url: 'https://example.com/news/education-edtech-30', imageUrl: 'https://picsum.photos/seed/kHeadlineId30/800/600', source: sourcesFixturesData[9], // Folha de S.Paulo @@ -461,8 +401,6 @@ final headlinesFixturesData = [ id: kHeadlineId31, isBreaking: false, title: 'SpaceX Launches New Satellite Constellation', - excerpt: - "Elon Musk's SpaceX successfully deploys a new batch of Starlink satellites, expanding global internet coverage.", url: 'https://example.com/news/tech-spacex-31', imageUrl: 'https://picsum.photos/seed/kHeadlineId31/800/600', source: sourcesFixturesData[0], // TechCrunch @@ -476,8 +414,6 @@ final headlinesFixturesData = [ id: kHeadlineId32, isBreaking: false, title: 'Football Legend Announces Retirement', - excerpt: - 'A celebrated football player declares their retirement, marking the end of an illustrious career.', url: 'https://example.com/news/sports-retirement-32', imageUrl: 'https://picsum.photos/seed/kHeadlineId32/800/600', source: sourcesFixturesData[1], // BBC News @@ -491,8 +427,6 @@ final headlinesFixturesData = [ id: kHeadlineId33, isBreaking: false, title: 'G7 Summit Concludes with Joint Statement on Global Economy', - excerpt: - 'Leaders from the G7 nations issue a unified statement addressing economic challenges and future cooperation.', url: 'https://example.com/news/politics-g7-33', imageUrl: 'https://picsum.photos/seed/kHeadlineId33/800/600', source: sourcesFixturesData[2], // The New York Times @@ -506,8 +440,6 @@ final headlinesFixturesData = [ id: kHeadlineId34, isBreaking: false, title: "Breakthrough in Alzheimer's Research Offers New Treatment Path", - excerpt: - "Scientists identify a novel therapeutic target for Alzheimer's disease, paving the way for more effective treatments.", url: 'https://example.com/news/science-alzheimers-34', imageUrl: 'https://picsum.photos/seed/kHeadlineId34/800/600', source: sourcesFixturesData[3], // The Guardian @@ -521,8 +453,6 @@ final headlinesFixturesData = [ id: kHeadlineId35, isBreaking: false, title: 'Global Vaccination Campaign Reaches Billions', - excerpt: - 'International efforts to vaccinate the world population against a new virus achieve unprecedented reach.', url: 'https://example.com/news/health-vaccine-35', imageUrl: 'https://picsum.photos/seed/kHeadlineId35/800/600', source: sourcesFixturesData[4], // CNN @@ -536,8 +466,6 @@ final headlinesFixturesData = [ id: kHeadlineId36, isBreaking: false, title: 'Streaming Wars Intensify with New Platform Launches', - excerpt: - 'The competition in the streaming market heats up as several new services enter the fray, offering diverse content.', url: 'https://example.com/news/entertainment-streaming-36', imageUrl: 'https://picsum.photos/seed/kHeadlineId36/800/600', source: sourcesFixturesData[5], // Reuters @@ -551,8 +479,6 @@ final headlinesFixturesData = [ id: kHeadlineId37, isBreaking: false, title: 'Cryptocurrency Market Experiences Major Volatility', - excerpt: - 'Digital currency values fluctuate wildly, prompting investors to reassess their strategies.', url: 'https://example.com/news/business-crypto-37', imageUrl: 'https://picsum.photos/seed/kHeadlineId37/800/600', source: sourcesFixturesData[6], // Al Jazeera English @@ -566,8 +492,6 @@ final headlinesFixturesData = [ id: kHeadlineId38, isBreaking: false, title: 'Sustainable Tourism Initiatives Gain Momentum', - excerpt: - 'Travel industry shifts towards eco-friendly practices, offering responsible options for environmentally conscious travelers.', url: 'https://example.com/news/travel-sustainable-38', imageUrl: 'https://picsum.photos/seed/kHeadlineId38/800/600', source: sourcesFixturesData[7], // Xinhua News Agency @@ -581,8 +505,6 @@ final headlinesFixturesData = [ id: kHeadlineId39, isBreaking: false, title: 'Food Security Summit Addresses Global Hunger', - excerpt: - 'International conference focuses on strategies to combat food insecurity and ensure equitable access to nutrition worldwide.', url: 'https://example.com/news/food-security-39', imageUrl: 'https://picsum.photos/seed/kHeadlineId39/800/600', source: sourcesFixturesData[8], // The Times of India @@ -596,8 +518,6 @@ final headlinesFixturesData = [ id: kHeadlineId40, isBreaking: false, title: 'Robotics in Education: New Tools for Learning', - excerpt: - 'Schools integrate advanced robotics into their curriculum, providing hands-on learning experiences for students.', url: 'https://example.com/news/education-robotics-40', imageUrl: 'https://picsum.photos/seed/kHeadlineId40/800/600', source: sourcesFixturesData[9], // Folha de S.Paulo @@ -611,8 +531,6 @@ final headlinesFixturesData = [ id: kHeadlineId41, isBreaking: false, title: 'AI Ethics Debate Intensifies Among Tech Leaders', - excerpt: - 'Discussions around the ethical implications of artificial intelligence gain traction, with calls for stricter regulations.', url: 'https://example.com/news/tech-ethics-41', imageUrl: 'https://picsum.photos/seed/kHeadlineId41/800/600', source: sourcesFixturesData[0], // TechCrunch @@ -626,8 +544,6 @@ final headlinesFixturesData = [ id: kHeadlineId42, isBreaking: false, title: 'Esports Industry Sees Massive Investment Boom', - excerpt: - 'The competitive gaming sector attracts record investments, solidifying its position as a major entertainment industry.', url: 'https://example.com/news/sports-esports-42', imageUrl: 'https://picsum.photos/seed/kHeadlineId42/800/600', source: sourcesFixturesData[1], // BBC News @@ -641,8 +557,6 @@ final headlinesFixturesData = [ id: kHeadlineId43, isBreaking: false, title: 'International Sanctions Imposed on Rogue State', - excerpt: - "Global powers unite to impose new economic sanctions in response to a nation's controversial actions.", url: 'https://example.com/news/politics-sanctions-43', imageUrl: 'https://picsum.photos/seed/kHeadlineId43/800/600', source: sourcesFixturesData[2], // The New York Times @@ -656,8 +570,6 @@ final headlinesFixturesData = [ id: kHeadlineId44, isBreaking: false, title: 'New Species of Deep-Sea Creature Discovered', - excerpt: - 'Oceanographers exploring the deepest parts of the ocean encounter a never-before-seen marine organism.', url: 'https://example.com/news/science-deepsea-44', imageUrl: 'https://picsum.photos/seed/kHeadlineId44/800/600', source: sourcesFixturesData[3], // The Guardian @@ -671,8 +583,6 @@ final headlinesFixturesData = [ id: kHeadlineId45, isBreaking: false, title: 'Global Health Crisis: New Pandemic Preparedness Plan', - excerpt: - 'International health organizations unveil a comprehensive strategy to prevent and respond to future pandemics.', url: 'https://example.com/news/health-pandemic-45', imageUrl: 'https://picsum.photos/seed/kHeadlineId45/800/600', source: sourcesFixturesData[4], // CNN @@ -686,8 +596,6 @@ final headlinesFixturesData = [ id: kHeadlineId46, isBreaking: false, title: 'Hollywood Strikes Continue: Impact on Film Production', - excerpt: - 'Ongoing labor disputes in Hollywood lead to widespread production halts, affecting upcoming movie and TV releases.', url: 'https://example.com/news/entertainment-strikes-46', imageUrl: 'https://picsum.photos/seed/kHeadlineId46/800/600', source: sourcesFixturesData[5], // Reuters @@ -701,8 +609,6 @@ final headlinesFixturesData = [ id: kHeadlineId47, isBreaking: false, title: 'Emerging Markets Show Strong Economic Resilience', - excerpt: - 'Despite global uncertainties, several emerging economies demonstrate robust growth and attract foreign investment.', url: 'https://example.com/news/business-emerging-47', imageUrl: 'https://picsum.photos/seed/kHeadlineId47/800/600', source: sourcesFixturesData[6], // Al Jazeera English @@ -716,8 +622,6 @@ final headlinesFixturesData = [ id: kHeadlineId48, isBreaking: false, title: 'Adventure Tourism Booms in Remote Regions', - excerpt: - 'Travelers seek unique experiences in off-the-beaten-path destinations, boosting local economies in remote areas.', url: 'https://example.com/news/travel-adventure-48', imageUrl: 'https://picsum.photos/seed/kHeadlineId48/800/600', source: sourcesFixturesData[7], // Xinhua News Agency @@ -731,8 +635,6 @@ final headlinesFixturesData = [ id: kHeadlineId49, isBreaking: false, title: 'The Rise of Sustainable Food Packaging', - excerpt: - 'Innovations in eco-friendly packaging solutions are transforming the food industry, reducing environmental impact.', url: 'https://example.com/news/food-packaging-49', imageUrl: 'https://picsum.photos/seed/kHeadlineId49/800/600', source: sourcesFixturesData[8], // The Times of India @@ -746,8 +648,6 @@ final headlinesFixturesData = [ id: kHeadlineId50, isBreaking: false, title: 'Personalized Learning: Tailoring Education to Individual Needs', - excerpt: - "New educational models focus on customized learning paths, adapting to each student's pace and preferences.", url: 'https://example.com/news/education-personalized-50', imageUrl: 'https://picsum.photos/seed/kHeadlineId50/800/600', source: sourcesFixturesData[9], // Folha de S.Paulo @@ -765,8 +665,6 @@ final headlinesFixturesData = [ id: kHeadlineId51, isBreaking: false, title: 'City Council Approves New Downtown Development Plan', - excerpt: - 'The San Francisco City Council has given the green light to a major redevelopment project aimed at revitalizing the downtown core.', url: 'https://example.com/news/sf-downtown-plan', imageUrl: 'https://picsum.photos/seed/kHeadlineId51/800/600', // San Francisco Chronicle @@ -781,8 +679,6 @@ final headlinesFixturesData = [ id: kHeadlineId52, isBreaking: false, title: 'Tech Startups Flourish in the Bay Area', - excerpt: - 'A new report shows a significant increase in venture capital funding for tech startups in San Francisco.', url: 'https://example.com/news/sf-tech-boom', imageUrl: 'https://picsum.photos/seed/kHeadlineId52/800/600', // San Francisco Chronicle @@ -797,8 +693,6 @@ final headlinesFixturesData = [ id: kHeadlineId53, isBreaking: false, title: 'Golden Gate Bridge Retrofit Project Begins', - excerpt: - 'A multi-year seismic retrofit project for the Golden Gate Bridge has officially commenced.', url: 'https://example.com/news/ggb-retrofit', imageUrl: 'https://picsum.photos/seed/kHeadlineId53/800/600', // San Francisco Chronicle @@ -813,8 +707,6 @@ final headlinesFixturesData = [ id: kHeadlineId54, isBreaking: false, title: 'Local Chef Wins Prestigious Culinary Award', - excerpt: - 'A San Francisco-based chef has been awarded the coveted "Golden Spoon" for culinary innovation.', url: 'https://example.com/news/sf-chef-award', imageUrl: 'https://picsum.photos/seed/kHeadlineId54/800/600', // San Francisco Chronicle @@ -829,8 +721,6 @@ final headlinesFixturesData = [ id: kHeadlineId55, isBreaking: false, title: 'Warriors Secure Victory in Season Opener', - excerpt: - 'The Golden State Warriors started their season with a decisive win at the Chase Center.', url: 'https://example.com/news/warriors-win', imageUrl: 'https://picsum.photos/seed/kHeadlineId55/800/600', // San Francisco Chronicle @@ -845,8 +735,6 @@ final headlinesFixturesData = [ id: kHeadlineId56, isBreaking: false, title: 'Manchester United Announces New Stadium Expansion Plans', - excerpt: - 'The club has revealed ambitious plans to increase the capacity of Old Trafford.', url: 'https://example.com/news/mu-stadium-expansion', imageUrl: 'https://picsum.photos/seed/kHeadlineId56/800/600', // Manchester Evening News @@ -861,8 +749,6 @@ final headlinesFixturesData = [ id: kHeadlineId57, isBreaking: false, title: 'New Tram Line Opens in Greater Manchester', - excerpt: - 'The new Metrolink line is set to improve public transport links across the region.', url: 'https://example.com/news/manchester-tram-line', imageUrl: 'https://picsum.photos/seed/kHeadlineId57/800/600', // Manchester Evening News @@ -877,8 +763,6 @@ final headlinesFixturesData = [ id: kHeadlineId58, isBreaking: false, title: 'Manchester Tech Hub Attracts Global Talent', - excerpt: - 'A report highlights Manchester as a growing hub for technology and innovation in Europe.', url: 'https://example.com/news/manchester-tech-hub', imageUrl: 'https://picsum.photos/seed/kHeadlineId58/800/600', // Manchester Evening News @@ -893,8 +777,6 @@ final headlinesFixturesData = [ id: kHeadlineId59, isBreaking: false, title: 'Coronation Street Filming Causes Local Buzz', - excerpt: - 'Fans gather as the popular soap opera films on location in central Manchester.', url: 'https://example.com/news/corrie-filming', imageUrl: 'https://picsum.photos/seed/kHeadlineId59/800/600', // Manchester Evening News @@ -909,8 +791,6 @@ final headlinesFixturesData = [ id: kHeadlineId60, isBreaking: false, title: 'Council Debates Clean Air Zone Implementation', - excerpt: - 'Greater Manchester leaders are in talks over the future of the controversial Clean Air Zone.', url: 'https://example.com/news/manc-caz-debate', imageUrl: 'https://picsum.photos/seed/kHeadlineId60/800/600', // Manchester Evening News @@ -925,8 +805,6 @@ final headlinesFixturesData = [ id: kHeadlineId61, isBreaking: false, title: 'Sydney Opera House Announces New Season Lineup', - excerpt: - 'A star-studded lineup of performances has been announced for the upcoming season at the iconic venue.', url: 'https://example.com/news/sydney-opera-season', imageUrl: 'https://picsum.photos/seed/kHeadlineId61/800/600', // The Sydney Morning Herald @@ -941,8 +819,6 @@ final headlinesFixturesData = [ id: kHeadlineId62, isBreaking: false, title: 'Housing Prices in Sydney Continue to Climb', - excerpt: - 'The latest real estate data shows a persistent upward trend in property values across the Sydney metropolitan area.', url: 'https://example.com/news/sydney-housing-prices', imageUrl: 'https://picsum.photos/seed/kHeadlineId62/800/600', // The Sydney Morning Herald @@ -957,8 +833,6 @@ final headlinesFixturesData = [ id: kHeadlineId63, isBreaking: false, title: 'NSW Government Unveils New Infrastructure Projects', - excerpt: - 'The New South Wales government has committed billions to new transport and public works projects.', url: 'https://example.com/news/nsw-infrastructure', imageUrl: 'https://picsum.photos/seed/kHeadlineId63/800/600', // The Sydney Morning Herald @@ -973,8 +847,6 @@ final headlinesFixturesData = [ id: kHeadlineId64, isBreaking: false, title: 'Swans Triumph in AFL Derby Match', - excerpt: - 'The Sydney Swans secured a memorable victory over their local rivals in a heated AFL match.', url: 'https://example.com/news/swans-afl-win', imageUrl: 'https://picsum.photos/seed/kHeadlineId64/800/600', // The Sydney Morning Herald @@ -989,8 +861,6 @@ final headlinesFixturesData = [ id: kHeadlineId65, isBreaking: false, title: 'Bondi Beach Erosion Concerns Prompt Action', - excerpt: - 'Local authorities are exploring new measures to combat coastal erosion at the world-famous Bondi Beach.', url: 'https://example.com/news/bondi-erosion', imageUrl: 'https://picsum.photos/seed/kHeadlineId65/800/600', // The Sydney Morning Herald @@ -1005,8 +875,6 @@ final headlinesFixturesData = [ id: kHeadlineId66, isBreaking: false, title: 'Paris Metro Expansion: New Stations Opened', - excerpt: - 'The Grand Paris Express project reaches a new milestone with the opening of several new metro stations.', url: 'https://example.com/news/paris-metro-expansion', imageUrl: 'https://picsum.photos/seed/kHeadlineId66/800/600', // Le Parisien source: sourcesFixturesData[13], // Le Parisien @@ -1020,8 +888,6 @@ final headlinesFixturesData = [ id: kHeadlineId67, isBreaking: false, title: 'Louvre Museum Unveils New Egyptian Antiquities Wing', - excerpt: - 'A new wing dedicated to ancient Egyptian artifacts has been opened to the public at the Louvre.', url: 'https://example.com/news/louvre-egyptian-wing', imageUrl: 'https://picsum.photos/seed/kHeadlineId67/800/600', // Le Parisien source: sourcesFixturesData[13], // Le Parisien @@ -1035,8 +901,6 @@ final headlinesFixturesData = [ id: kHeadlineId68, isBreaking: false, title: 'Paris Saint-Germain Secures Ligue 1 Title', - excerpt: - 'PSG has been crowned champions of France after a dominant season.', url: 'https://example.com/news/psg-ligue1-title', imageUrl: 'https://picsum.photos/seed/kHeadlineId68/800/600', // Le Parisien source: sourcesFixturesData[13], // Le Parisien @@ -1050,8 +914,6 @@ final headlinesFixturesData = [ id: kHeadlineId69, isBreaking: false, title: 'Mayor of Paris Announces New Green Initiatives', - excerpt: - 'The mayor has outlined a plan to increase green spaces and reduce pollution in the city.', url: 'https://example.com/news/paris-green-initiatives', imageUrl: 'https://picsum.photos/seed/kHeadlineId69/800/600', // Le Parisien source: sourcesFixturesData[13], // Le Parisien @@ -1065,8 +927,6 @@ final headlinesFixturesData = [ id: kHeadlineId70, isBreaking: false, title: 'Paris Fashion Week Highlights New Trends', - excerpt: - "The world's top designers showcased their latest collections during the celebrated Paris Fashion Week.", url: 'https://example.com/news/paris-fashion-week', imageUrl: 'https://picsum.photos/seed/kHeadlineId70/800/600', // Le Parisien source: sourcesFixturesData[13], // Le Parisien @@ -1080,8 +940,6 @@ final headlinesFixturesData = [ id: kHeadlineId71, isBreaking: false, title: 'Toronto Raptors Make Key Trade Ahead of Deadline', - excerpt: - 'The Raptors have made a significant move to bolster their roster for the playoff push.', url: 'https://example.com/news/raptors-trade', imageUrl: 'https://picsum.photos/seed/kHeadlineId71/800/600', // The Toronto Star @@ -1096,8 +954,6 @@ final headlinesFixturesData = [ id: kHeadlineId72, isBreaking: false, title: 'TTC Announces Service Changes for Summer', - excerpt: - 'The Toronto Transit Commission has released its updated schedule and service adjustments for the summer season.', url: 'https://example.com/news/ttc-summer-changes', imageUrl: 'https://picsum.photos/seed/kHeadlineId72/800/600', // The Toronto Star @@ -1112,8 +968,6 @@ final headlinesFixturesData = [ id: kHeadlineId73, isBreaking: false, title: 'Toronto International Film Festival (TIFF) Lineup Revealed', - excerpt: - "Organizers of TIFF have announced a highly anticipated lineup of films for this year's festival.", url: 'https://example.com/news/tiff-lineup', imageUrl: 'https://picsum.photos/seed/kHeadlineId73/800/600', // The Toronto Star @@ -1128,8 +982,6 @@ final headlinesFixturesData = [ id: kHeadlineId74, isBreaking: false, title: 'City of Toronto Grapples with Housing Affordability', - excerpt: - 'City council is debating new policies to address the ongoing housing affordability crisis in Toronto.', url: 'https://example.com/news/toronto-housing-crisis', imageUrl: 'https://picsum.photos/seed/kHeadlineId74/800/600', // The Toronto Star @@ -1144,8 +996,6 @@ final headlinesFixturesData = [ id: kHeadlineId75, isBreaking: false, title: 'New Waterfront Development Project Approved', - excerpt: - "A major new development on Toronto's waterfront has received final approval from the city.", url: 'https://example.com/news/toronto-waterfront-project', imageUrl: 'https://picsum.photos/seed/kHeadlineId75/800/600', // The Toronto Star @@ -1160,8 +1010,6 @@ final headlinesFixturesData = [ id: kHeadlineId76, isBreaking: false, title: 'Berlin Philharmonic Announces New Conductor', - excerpt: - 'The world-renowned orchestra has named a new chief conductor, marking a new era.', url: 'https://example.com/news/berlin-phil-conductor', imageUrl: 'https://picsum.photos/seed/kHeadlineId76/800/600', // Berliner Morgenpost @@ -1176,8 +1024,6 @@ final headlinesFixturesData = [ id: kHeadlineId77, isBreaking: false, title: 'Remnants of Berlin Wall Unearthed During Construction', - excerpt: - 'A previously unknown section of the Berlin Wall has been discovered at a construction site in the city center.', url: 'https://example.com/news/berlin-wall-discovery', imageUrl: 'https://picsum.photos/seed/kHeadlineId77/800/600', // Berliner Morgenpost @@ -1192,8 +1038,6 @@ final headlinesFixturesData = [ id: kHeadlineId78, isBreaking: false, title: 'Hertha BSC Faces Relegation Battle', - excerpt: - 'The Berlin-based football club is in a tough fight to avoid relegation from the Bundesliga.', url: 'https://example.com/news/hertha-bsc-relegation', imageUrl: 'https://picsum.photos/seed/kHeadlineId78/800/600', // Berliner Morgenpost @@ -1208,8 +1052,6 @@ final headlinesFixturesData = [ id: kHeadlineId79, isBreaking: false, title: 'Berlin Senate Approves Rent Control Measures', - excerpt: - 'New measures aimed at controlling rent prices in the German capital have been approved by the Berlin Senate.', url: 'https://example.com/news/berlin-rent-control', imageUrl: 'https://picsum.photos/seed/kHeadlineId79/800/600', // Berliner Morgenpost @@ -1224,8 +1066,6 @@ final headlinesFixturesData = [ id: kHeadlineId80, isBreaking: false, title: 'Brandenburg Airport Reports Record Passenger Numbers', - excerpt: - "Berlin's new airport has reported its busiest month on record, signaling a recovery in air travel.", url: 'https://example.com/news/ber-airport-record', imageUrl: 'https://picsum.photos/seed/kHeadlineId80/800/600', // Berliner Morgenpost @@ -1240,8 +1080,6 @@ final headlinesFixturesData = [ id: kHeadlineId81, isBreaking: false, title: 'Tokyo Government Tackles Aging Population Issues', - excerpt: - 'The Tokyo Metropolitan Government has announced new policies to support its rapidly aging population.', url: 'https://example.com/news/tokyo-aging-population', imageUrl: 'https://picsum.photos/seed/kHeadlineId81/800/600', // The Asahi Shimbun (Tokyo) @@ -1256,8 +1094,6 @@ final headlinesFixturesData = [ id: kHeadlineId82, isBreaking: false, title: 'New Shinkansen Line to Connect Tokyo and Tsuruga', - excerpt: - 'The Hokuriku Shinkansen line has been extended, reducing travel time between Tokyo and the Hokuriku region.', url: 'https://example.com/news/shinkansen-extension', imageUrl: 'https://picsum.photos/seed/kHeadlineId82/800/600', // The Asahi Shimbun (Tokyo) @@ -1272,8 +1108,6 @@ final headlinesFixturesData = [ id: kHeadlineId83, isBreaking: false, title: 'Yomiuri Giants Clinch Central League Pennant', - excerpt: - 'The Tokyo-based Yomiuri Giants have won the Central League pennant in Japanese professional baseball.', url: 'https://example.com/news/giants-win-pennant', imageUrl: 'https://picsum.photos/seed/kHeadlineId83/800/600', // The Asahi Shimbun (Tokyo) @@ -1288,8 +1122,6 @@ final headlinesFixturesData = [ id: kHeadlineId84, isBreaking: false, title: 'Studio Ghibli Announces New Film Project', - excerpt: - 'The celebrated animation studio has announced its first new feature film in several years, exciting fans worldwide.', url: 'https://example.com/news/ghibli-new-film', imageUrl: 'https://picsum.photos/seed/kHeadlineId84/800/600', // The Asahi Shimbun (Tokyo) @@ -1304,8 +1136,6 @@ final headlinesFixturesData = [ id: kHeadlineId85, isBreaking: false, title: "Tokyo's Tsukiji Outer Market Thrives After Relocation", - excerpt: - 'Years after the inner market moved, the Tsukiji Outer Market continues to be a vibrant destination for food lovers.', url: 'https://example.com/news/tsukiji-market-thrives', imageUrl: 'https://picsum.photos/seed/kHeadlineId85/800/600', // The Asahi Shimbun (Tokyo) @@ -1320,8 +1150,6 @@ final headlinesFixturesData = [ id: kHeadlineId86, isBreaking: false, title: 'Mumbai Metro Expands with New Aqua Line', - excerpt: - 'The new Aqua Line of the Mumbai Metro is now operational, aiming to ease traffic congestion in the city.', url: 'https://example.com/news/mumbai-metro-aqua-line', imageUrl: 'https://picsum.photos/seed/kHeadlineId86/800/600', // Hindustan Times (Mumbai) @@ -1336,8 +1164,6 @@ final headlinesFixturesData = [ id: kHeadlineId87, isBreaking: false, title: 'Bollywood Film Shoots Bring Stars to Mumbai Streets', - excerpt: - 'Major Bollywood productions are currently filming across Mumbai, drawing crowds of onlookers.', url: 'https://example.com/news/bollywood-mumbai-shoots', imageUrl: 'https://picsum.photos/seed/kHeadlineId87/800/600', // Hindustan Times (Mumbai) @@ -1352,8 +1178,6 @@ final headlinesFixturesData = [ id: kHeadlineId88, isBreaking: false, title: 'Mumbai Indians Gear Up for IPL Season', - excerpt: - 'The local cricket franchise, Mumbai Indians, has begun its training camp ahead of the new IPL season.', url: 'https://example.com/news/mumbai-indians-ipl', imageUrl: 'https://picsum.photos/seed/kHeadlineId88/800/600', // Hindustan Times (Mumbai) @@ -1368,8 +1192,6 @@ final headlinesFixturesData = [ id: kHeadlineId89, isBreaking: false, title: 'BMC Tackles Monsoon Preparedness in Mumbai', - excerpt: - 'The Brihanmumbai Municipal Corporation (BMC) has outlined its plan for monsoon preparedness to prevent flooding.', url: 'https://example.com/news/bmc-monsoon-prep', imageUrl: 'https://picsum.photos/seed/kHeadlineId89/800/600', // Hindustan Times (Mumbai) @@ -1384,8 +1206,6 @@ final headlinesFixturesData = [ id: kHeadlineId90, isBreaking: false, title: "Mumbai's Financial District Sees New Investments", - excerpt: - 'The Bandra Kurla Complex (BKC) continues to attract major national and international business investments.', url: 'https://example.com/news/mumbai-bkc-investments', imageUrl: 'https://picsum.photos/seed/kHeadlineId90/800/600', // Hindustan Times (Mumbai) @@ -1400,8 +1220,6 @@ final headlinesFixturesData = [ id: kHeadlineId91, isBreaking: false, title: 'Rio Carnival Preparations in Full Swing', - excerpt: - 'Samba schools across Rio de Janeiro are finalizing their preparations for the world-famous Carnival parade.', url: 'https://example.com/news/rio-carnival-prep', imageUrl: 'https://picsum.photos/seed/kHeadlineId91/800/600', // O Globo (Rio de Janeiro) @@ -1416,8 +1234,6 @@ final headlinesFixturesData = [ id: kHeadlineId92, isBreaking: false, title: 'Flamengo Wins Key Match at Maracanã Stadium', - excerpt: - "Rio's beloved football club, Flamengo, celebrated a crucial victory in front of a packed Maracanã stadium.", url: 'https://example.com/news/flamengo-maracana-win', imageUrl: 'https://picsum.photos/seed/kHeadlineId92/800/600', // O Globo (Rio de Janeiro) @@ -1432,8 +1248,6 @@ final headlinesFixturesData = [ id: kHeadlineId93, isBreaking: false, title: 'Security Boosted in Rio Ahead of Major Summit', - excerpt: - 'Security measures are being increased across Rio de Janeiro as the city prepares to host an international summit.', url: 'https://example.com/news/rio-security-boost', imageUrl: 'https://picsum.photos/seed/kHeadlineId93/800/600', // O Globo (Rio de Janeiro) @@ -1448,8 +1262,6 @@ final headlinesFixturesData = [ id: kHeadlineId94, isBreaking: false, title: 'Sugarloaf Mountain Cable Car Undergoes Modernization', - excerpt: - 'The iconic cable car system for Sugarloaf Mountain is being updated with new technology and cabins.', url: 'https://example.com/news/sugarloaf-cable-car-update', imageUrl: 'https://picsum.photos/seed/kHeadlineId94/800/600', // O Globo (Rio de Janeiro) @@ -1464,8 +1276,6 @@ final headlinesFixturesData = [ id: kHeadlineId95, isBreaking: false, title: "Bossa Nova Festival Celebrates Rio's Musical Heritage", - excerpt: - 'A music festival in Ipanema is celebrating the rich history of Bossa Nova, born in the neighborhoods of Rio.', url: 'https://example.com/news/bossa-nova-festival', imageUrl: 'https://picsum.photos/seed/kHeadlineId95/800/600', // O Globo (Rio de Janeiro) @@ -1480,8 +1290,6 @@ final headlinesFixturesData = [ id: kHeadlineId96, isBreaking: false, title: 'Sagrada Família Nears Completion After 140 Years', - excerpt: - "Barcelona's iconic basilica, designed by Gaudí, is entering its final phase of construction.", url: 'https://example.com/news/sagrada-familia-completion', imageUrl: 'https://picsum.photos/seed/kHeadlineId96/800/600', // La Vanguardia (Barcelona) @@ -1496,8 +1304,6 @@ final headlinesFixturesData = [ id: kHeadlineId97, isBreaking: false, title: 'FC Barcelona Presents New Kit at Camp Nou', - excerpt: - 'The football club has unveiled its new home kit for the upcoming La Liga season.', url: 'https://example.com/news/fcb-new-kit', imageUrl: 'https://picsum.photos/seed/kHeadlineId97/800/600', // La Vanguardia (Barcelona) @@ -1512,8 +1318,6 @@ final headlinesFixturesData = [ id: kHeadlineId98, isBreaking: false, title: 'Catalan Government Discusses Tourism Strategy', - excerpt: - 'Leaders in Catalonia are debating a new long-term strategy to manage tourism in Barcelona and the wider region.', url: 'https://example.com/news/catalan-tourism-strategy', imageUrl: 'https://picsum.photos/seed/kHeadlineId98/800/600', // La Vanguardia (Barcelona) @@ -1528,8 +1332,6 @@ final headlinesFixturesData = [ id: kHeadlineId99, isBreaking: false, title: "Barcelona's Tech Scene Booms with New Hub", - excerpt: - 'The 22@ innovation district in Barcelona continues to expand, attracting tech companies from around the globe.', url: 'https://example.com/news/barcelona-tech-hub', imageUrl: 'https://picsum.photos/seed/kHeadlineId99/800/600', // La Vanguardia (Barcelona) @@ -1544,8 +1346,6 @@ final headlinesFixturesData = [ id: kHeadlineId100, isBreaking: false, title: 'La Boqueria Market: A Taste of Barcelona', - excerpt: - 'A feature on the historic La Boqueria market, exploring its culinary delights and cultural significance.', url: 'https://example.com/news/la-boqueria-feature', imageUrl: 'https://picsum.photos/seed/kHeadlineId100/800/600', // La Vanguardia (Barcelona) @@ -1566,8 +1366,6 @@ final headlinesFixturesData = [ id: kHeadlineId101, isBreaking: false, title: 'National Parks See Record Visitor Numbers', - excerpt: - 'A new report from the National Park Service shows a surge in visitors to parks across the USA.', url: 'https://example.com/news/national-parks-visitors', imageUrl: 'https://picsum.photos/seed/kHeadlineId101/800/600', source: sourcesFixturesData[20], // USA Today @@ -1584,8 +1382,6 @@ final headlinesFixturesData = [ id: kHeadlineId106, isBreaking: false, title: 'Canadian Government Announces New Federal Budget', - excerpt: - 'The federal budget includes new spending on healthcare and climate initiatives across Canada.', url: 'https://example.com/news/canada-federal-budget', imageUrl: 'https://picsum.photos/seed/kHeadlineId106/800/600', source: sourcesFixturesData[21], // The Globe and Mail @@ -1604,8 +1400,6 @@ final headlinesFixturesData = [ id: kHeadlineId151, isBreaking: false, title: 'Global Supply Chain Issues Persist', - excerpt: - 'Experts warn that global supply chain disruptions are likely to continue affecting international trade.', url: 'https://example.com/news/global-supply-chain', imageUrl: 'https://picsum.photos/seed/kHeadlineId151/800/600', source: sourcesFixturesData[30], // CNN International @@ -1623,8 +1417,6 @@ final headlinesFixturesData = [ id: kHeadlineId201, isBreaking: false, title: 'World Cup Finals: An Unforgettable Match', - excerpt: - 'The World Cup final delivered a thrilling conclusion to the tournament with a dramatic penalty shootout.', url: 'https://example.com/news/world-cup-final', imageUrl: 'https://picsum.photos/seed/kHeadlineId201/800/600', source: sourcesFixturesData[40], // ESPN @@ -1642,8 +1434,6 @@ final headlinesFixturesData = [ id: kHeadlineId251, isBreaking: false, title: 'The Future of Content and Aggregation', - excerpt: - 'A deep dive into how AI is changing the landscape of content creation and aggregation platforms.', url: 'https://example.com/news/stratechery-content-ai', imageUrl: 'https://picsum.photos/seed/kHeadlineId251/800/600', source: sourcesFixturesData[50], // Stratechery by Ben Thompson @@ -1661,8 +1451,6 @@ final headlinesFixturesData = [ id: kHeadlineId301, isBreaking: false, title: 'President Signs Executive Order on Cybersecurity', - excerpt: - "A new executive order has been signed to strengthen the nation's cybersecurity infrastructure.", url: 'https://example.com/news/wh-cyber-order', imageUrl: 'https://picsum.photos/seed/kHeadlineId301/800/600', source: sourcesFixturesData[60], // WhiteHouse.gov @@ -1680,8 +1468,6 @@ final headlinesFixturesData = [ id: kHeadlineId351, isBreaking: false, title: 'This Week in Tech: A Google News Roundup', - excerpt: - 'Google News aggregates the top technology stories of the week, from AI breakthroughs to new gadget releases.', url: 'https://example.com/news/gnews-tech-roundup', imageUrl: 'https://picsum.photos/seed/kHeadlineId351/800/600', source: sourcesFixturesData[70], // Google News @@ -1699,8 +1485,6 @@ final headlinesFixturesData = [ id: kHeadlineId401, isBreaking: false, title: 'Global Tech Corp Announces Record Quarterly Earnings', - excerpt: - 'Global Tech Corp today announced financial results for its fiscal third quarter, reporting record revenue and profit.', url: 'https://example.com/news/prn-earnings', imageUrl: 'https://picsum.photos/seed/kHeadlineId401/800/600', source: sourcesFixturesData[80], // PR Newswire @@ -1715,8 +1499,6 @@ final headlinesFixturesData = [ id: kHeadlineId411, isBreaking: false, title: 'Phase 3 Trial Results for New Diabetes Drug Published', - excerpt: - 'A new study in The Lancet details the successful phase 3 clinical trial results for a novel type 2 diabetes treatment.', url: 'https://example.com/news/lancet-diabetes-drug', imageUrl: 'https://picsum.photos/seed/kHeadlineId411/800/600', source: sourcesFixturesData[82], // The Lancet diff --git a/lib/src/fixtures/in_app_notifications.dart b/lib/src/fixtures/in_app_notifications.dart index 38e0e063..3cefa960 100644 --- a/lib/src/fixtures/in_app_notifications.dart +++ b/lib/src/fixtures/in_app_notifications.dart @@ -29,15 +29,11 @@ List _generateAdminNotifications() { userId: kAdminUserId, payload: PushNotificationPayload( title: headline.title, - body: headline.excerpt, imageUrl: headline.imageUrl, - data: { - 'notificationId': notificationId, - 'notificationType': - PushNotificationSubscriptionDeliveryType.breakingOnly.name, - 'contentType': 'headline', - 'headlineId': headline.id, - }, + notificationId: notificationId, + notificationType: PushNotificationSubscriptionDeliveryType.breakingOnly, + contentType: ContentType.headline, + contentId: headline.id, ), createdAt: DateTime.now().subtract(Duration(days: index * 2)), readAt: isRead ? DateTime.now().subtract(Duration(hours: index)) : null, diff --git a/lib/src/fixtures/remote_configs.dart b/lib/src/fixtures/remote_configs.dart index c2a2d5b8..3ceb256f 100644 --- a/lib/src/fixtures/remote_configs.dart +++ b/lib/src/fixtures/remote_configs.dart @@ -1,162 +1,158 @@ import 'package:core/core.dart'; -/// A list of initial remote config data to be loaded into the in-memory -/// remote config repository. -final List remoteConfigsFixturesData = [ +final remoteConfigsFixturesData = [ RemoteConfig( id: kRemoteConfigId, createdAt: DateTime.now(), updatedAt: DateTime.now(), - appStatus: const AppStatus( - isUnderMaintenance: false, - latestAppVersion: '1.1.0', - isLatestVersionOnly: false, - iosUpdateUrl: 'https://apps.apple.com/app/example/id1234567890', - androidUpdateUrl: - 'https://play.google.com/store/apps/details?id=com.example.app', - ), - userPreferenceConfig: const UserPreferenceConfig( - // Role-based limits for followed items (topics, sources, countries). - followedItemsLimit: { - AppUserRole.guestUser: 5, - AppUserRole.standardUser: 15, - AppUserRole.premiumUser: 30, - }, - // Role-based limits for the number of saved headlines. - savedHeadlinesLimit: { - AppUserRole.guestUser: 10, - AppUserRole.standardUser: 30, - AppUserRole.premiumUser: 100, - }, - // Role-based limits for saved headline filters. - savedHeadlineFiltersLimit: { - AppUserRole.guestUser: SavedFilterLimits( - total: 3, - pinned: 3, - notificationSubscriptions: { - PushNotificationSubscriptionDeliveryType.breakingOnly: 1, - PushNotificationSubscriptionDeliveryType.dailyDigest: 0, - PushNotificationSubscriptionDeliveryType.weeklyRoundup: 0, - }, - ), - AppUserRole.standardUser: SavedFilterLimits( - total: 10, - pinned: 5, - notificationSubscriptions: { - PushNotificationSubscriptionDeliveryType.breakingOnly: 3, - PushNotificationSubscriptionDeliveryType.dailyDigest: 2, - PushNotificationSubscriptionDeliveryType.weeklyRoundup: 2, - }, - ), - AppUserRole.premiumUser: SavedFilterLimits( - total: 25, - pinned: 10, - notificationSubscriptions: { - PushNotificationSubscriptionDeliveryType.breakingOnly: 10, - PushNotificationSubscriptionDeliveryType.dailyDigest: 10, - PushNotificationSubscriptionDeliveryType.weeklyRoundup: 10, - }, - ), - }, - // Role-based limits for saved source filters. - savedSourceFiltersLimit: { - AppUserRole.guestUser: SavedFilterLimits(total: 3, pinned: 3), - AppUserRole.standardUser: SavedFilterLimits(total: 10, pinned: 5), - AppUserRole.premiumUser: SavedFilterLimits(total: 25, pinned: 10), - }, + app: const AppConfig( + maintenance: MaintenanceConfig(isUnderMaintenance: false), + update: UpdateConfig( + latestAppVersion: '1.1.0', + isLatestVersionOnly: false, + iosUpdateUrl: 'https://apps.apple.com/app/example/id1234567890', + androidUpdateUrl: + 'https://play.google.com/store/apps/details?id=com.example.app', + ), + general: GeneralAppConfig( + termsOfServiceUrl: 'https://example.com/terms', + privacyPolicyUrl: 'https://example.com/privacy', + ), ), - adConfig: const AdConfig( - enabled: true, - primaryAdPlatform: AdPlatformType.demo, - platformAdIdentifiers: { - AdPlatformType.admob: AdPlatformIdentifiers( - feedNativeAdId: 'ca-app-pub-3940256099942544/2247696110', - feedBannerAdId: 'ca-app-pub-3940256099942544/6300978111', - feedToArticleInterstitialAdId: - 'ca-app-pub-3940256099942544/1033173712', - inArticleNativeAdId: 'ca-app-pub-3940256099942544/3986624511', - inArticleBannerAdId: 'ca-app-pub-3940256099942544/6300978111', - ), - AdPlatformType.demo: AdPlatformIdentifiers( - feedNativeAdId: '_', - feedBannerAdId: '_', - feedToArticleInterstitialAdId: '_', - inArticleNativeAdId: '_', - inArticleBannerAdId: '_', - ), - }, - feedAdConfiguration: FeedAdConfiguration( - enabled: true, - adType: AdType.native, - visibleTo: { - AppUserRole.guestUser: FeedAdFrequencyConfig( - adFrequency: 5, - adPlacementInterval: 3, + user: const UserConfig( + limits: UserLimitsConfig( + followedItems: { + AppUserRole.guestUser: 5, + AppUserRole.standardUser: 15, + AppUserRole.premiumUser: 30, + }, + savedHeadlines: { + AppUserRole.guestUser: 10, + AppUserRole.standardUser: 30, + AppUserRole.premiumUser: 100, + }, + savedHeadlineFilters: { + AppUserRole.guestUser: SavedFilterLimits( + total: 3, + pinned: 3, + notificationSubscriptions: { + PushNotificationSubscriptionDeliveryType.breakingOnly: 1, + PushNotificationSubscriptionDeliveryType.dailyDigest: 0, + PushNotificationSubscriptionDeliveryType.weeklyRoundup: 0, + }, ), - AppUserRole.standardUser: FeedAdFrequencyConfig( - adFrequency: 10, - adPlacementInterval: 5, + AppUserRole.standardUser: SavedFilterLimits( + total: 10, + pinned: 5, + notificationSubscriptions: { + PushNotificationSubscriptionDeliveryType.breakingOnly: 3, + PushNotificationSubscriptionDeliveryType.dailyDigest: 2, + PushNotificationSubscriptionDeliveryType.weeklyRoundup: 2, + }, + ), + AppUserRole.premiumUser: SavedFilterLimits( + total: 25, + pinned: 10, + notificationSubscriptions: { + PushNotificationSubscriptionDeliveryType.breakingOnly: 10, + PushNotificationSubscriptionDeliveryType.dailyDigest: 10, + PushNotificationSubscriptionDeliveryType.weeklyRoundup: 10, + }, ), }, - ), - articleAdConfiguration: ArticleAdConfiguration( - enabled: true, - bannerAdShape: BannerAdShape.rectangle, - visibleTo: { - AppUserRole.guestUser: { - InArticleAdSlotType.aboveArticleContinueReadingButton: true, - InArticleAdSlotType.belowArticleContinueReadingButton: true, - }, - AppUserRole.standardUser: { - InArticleAdSlotType.aboveArticleContinueReadingButton: true, - InArticleAdSlotType.belowArticleContinueReadingButton: true, - }, + savedSourceFilters: { + AppUserRole.guestUser: SavedFilterLimits(total: 3, pinned: 3), + AppUserRole.standardUser: SavedFilterLimits(total: 10, pinned: 5), + AppUserRole.premiumUser: SavedFilterLimits(total: 25, pinned: 10), }, ), - interstitialAdConfiguration: InterstitialAdConfiguration( + ), + features: const FeaturesConfig( + ads: AdConfig( enabled: true, - visibleTo: { - AppUserRole.guestUser: InterstitialAdFrequencyConfig( - transitionsBeforeShowingInterstitialAds: 5, + primaryAdPlatform: AdPlatformType.admob, + platformAdIdentifiers: { + AdPlatformType.admob: AdPlatformIdentifiers( + nativeAdId: 'ca-app-pub-3940256099942544/2247696110', + bannerAdId: 'ca-app-pub-3940256099942544/6300978111', + interstitialAdId: 'ca-app-pub-3940256099942544/1033173712', ), - AppUserRole.standardUser: InterstitialAdFrequencyConfig( - transitionsBeforeShowingInterstitialAds: 10, + AdPlatformType.demo: AdPlatformIdentifiers( + nativeAdId: '_', + bannerAdId: '_', + interstitialAdId: '_', ), }, + feedAdConfiguration: FeedAdConfiguration( + enabled: true, + adType: AdType.native, + visibleTo: { + AppUserRole.guestUser: FeedAdFrequencyConfig( + adFrequency: 5, + adPlacementInterval: 3, + ), + AppUserRole.standardUser: FeedAdFrequencyConfig( + adFrequency: 10, + adPlacementInterval: 5, + ), + }, + ), + navigationAdConfiguration: NavigationAdConfiguration( + enabled: true, + visibleTo: { + AppUserRole.guestUser: NavigationAdFrequencyConfig( + internalNavigationsBeforeShowingInterstitialAd: 5, + externalNavigationsBeforeShowingInterstitialAd: 5, + ), + AppUserRole.standardUser: NavigationAdFrequencyConfig( + internalNavigationsBeforeShowingInterstitialAd: 8, + externalNavigationsBeforeShowingInterstitialAd: 8, + ), + }, + ), ), - ), - feedDecoratorConfig: const { - FeedDecoratorType.rateApp: FeedDecoratorConfig( - category: FeedDecoratorCategory.callToAction, - enabled: true, - visibleTo: { - AppUserRole.guestUser: FeedDecoratorRoleConfig(daysBetweenViews: 14), - AppUserRole.standardUser: FeedDecoratorRoleConfig( - daysBetweenViews: 30, + feed: FeedConfig( + itemClickBehavior: FeedItemClickBehavior.internalNavigation, + decorators: { + FeedDecoratorType.rateApp: FeedDecoratorConfig( + category: FeedDecoratorCategory.callToAction, + enabled: true, + visibleTo: { + AppUserRole.guestUser: FeedDecoratorRoleConfig( + daysBetweenViews: 14, + ), + AppUserRole.standardUser: FeedDecoratorRoleConfig( + daysBetweenViews: 30, + ), + AppUserRole.premiumUser: FeedDecoratorRoleConfig( + daysBetweenViews: 0, + ), + }, + ), + FeedDecoratorType.suggestedTopics: FeedDecoratorConfig( + category: FeedDecoratorCategory.contentCollection, + enabled: true, + itemsToDisplay: 5, + visibleTo: { + AppUserRole.guestUser: FeedDecoratorRoleConfig( + daysBetweenViews: 7, + ), + AppUserRole.standardUser: FeedDecoratorRoleConfig( + daysBetweenViews: 14, + ), + }, ), - AppUserRole.premiumUser: FeedDecoratorRoleConfig(daysBetweenViews: 0), }, ), - FeedDecoratorType.suggestedTopics: FeedDecoratorConfig( - category: FeedDecoratorCategory.contentCollection, + pushNotifications: PushNotificationConfig( enabled: true, - itemsToDisplay: 5, - visibleTo: { - AppUserRole.guestUser: FeedDecoratorRoleConfig(daysBetweenViews: 7), - AppUserRole.standardUser: FeedDecoratorRoleConfig( - daysBetweenViews: 14, - ), + primaryProvider: PushNotificationProvider.firebase, + deliveryConfigs: { + PushNotificationSubscriptionDeliveryType.breakingOnly: true, + PushNotificationSubscriptionDeliveryType.dailyDigest: true, + PushNotificationSubscriptionDeliveryType.weeklyRoundup: true, }, ), - }, - pushNotificationConfig: const PushNotificationConfig( - enabled: true, - primaryProvider: PushNotificationProvider.firebase, - deliveryConfigs: { - PushNotificationSubscriptionDeliveryType.breakingOnly: true, - PushNotificationSubscriptionDeliveryType.dailyDigest: true, - PushNotificationSubscriptionDeliveryType.weeklyRoundup: true, - }, ), ), ]; diff --git a/lib/src/models/config/ad_config.dart b/lib/src/models/config/ad_config.dart index 225c6da3..0836a59b 100644 --- a/lib/src/models/config/ad_config.dart +++ b/lib/src/models/config/ad_config.dart @@ -1,8 +1,7 @@ import 'package:core/src/enums/ad_platform_type.dart'; import 'package:core/src/models/config/ad_platform_identifiers.dart'; -import 'package:core/src/models/config/article_ad_configuration.dart'; import 'package:core/src/models/config/feed_ad_configuration.dart'; -import 'package:core/src/models/config/interstitial_ad_configuration.dart'; +import 'package:core/src/models/config/navigation_ad_configuration.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -21,8 +20,7 @@ class AdConfig extends Equatable { required this.primaryAdPlatform, required this.platformAdIdentifiers, required this.feedAdConfiguration, - required this.articleAdConfiguration, - required this.interstitialAdConfiguration, + required this.navigationAdConfiguration, }); /// Creates an [AdConfig] from JSON data. @@ -35,20 +33,17 @@ class AdConfig extends Equatable { /// Global switch to enable or disable all ads in the application. final bool enabled; - /// Global choice: AdMob or Demo. + /// Global choice: AdMob, etc. final AdPlatformType primaryAdPlatform; /// Map to store identifiers for all platforms. final Map platformAdIdentifiers; - /// Configuration for main feed, search feed, similar headlines feed. + /// Configuration for main feed, search feed, etc. final FeedAdConfiguration feedAdConfiguration; - /// Configuration for article page ads (excluding interstitial). - final ArticleAdConfiguration articleAdConfiguration; - - /// Configuration for all interstitial ads. - final InterstitialAdConfiguration interstitialAdConfiguration; + /// Configuration for all navigation ads. + final NavigationAdConfiguration navigationAdConfiguration; @override List get props => [ @@ -56,8 +51,7 @@ class AdConfig extends Equatable { primaryAdPlatform, platformAdIdentifiers, feedAdConfiguration, - articleAdConfiguration, - interstitialAdConfiguration, + navigationAdConfiguration, ]; /// Creates a copy of this [AdConfig] but with the given fields replaced @@ -67,8 +61,7 @@ class AdConfig extends Equatable { AdPlatformType? primaryAdPlatform, Map? platformAdIdentifiers, FeedAdConfiguration? feedAdConfiguration, - ArticleAdConfiguration? articleAdConfiguration, - InterstitialAdConfiguration? interstitialAdConfiguration, + NavigationAdConfiguration? navigationAdConfiguration, }) { return AdConfig( enabled: enabled ?? this.enabled, @@ -76,10 +69,8 @@ class AdConfig extends Equatable { platformAdIdentifiers: platformAdIdentifiers ?? this.platformAdIdentifiers, feedAdConfiguration: feedAdConfiguration ?? this.feedAdConfiguration, - articleAdConfiguration: - articleAdConfiguration ?? this.articleAdConfiguration, - interstitialAdConfiguration: - interstitialAdConfiguration ?? this.interstitialAdConfiguration, + navigationAdConfiguration: + navigationAdConfiguration ?? this.navigationAdConfiguration, ); } } diff --git a/lib/src/models/config/ad_config.g.dart b/lib/src/models/config/ad_config.g.dart index 3c64a10b..415da410 100644 --- a/lib/src/models/config/ad_config.g.dart +++ b/lib/src/models/config/ad_config.g.dart @@ -27,14 +27,9 @@ AdConfig _$AdConfigFromJson(Map json) => 'feedAdConfiguration', (v) => FeedAdConfiguration.fromJson(v as Map), ), - articleAdConfiguration: $checkedConvert( - 'articleAdConfiguration', - (v) => ArticleAdConfiguration.fromJson(v as Map), - ), - interstitialAdConfiguration: $checkedConvert( - 'interstitialAdConfiguration', - (v) => - InterstitialAdConfiguration.fromJson(v as Map), + navigationAdConfiguration: $checkedConvert( + 'navigationAdConfiguration', + (v) => NavigationAdConfiguration.fromJson(v as Map), ), ); return val; @@ -47,8 +42,7 @@ Map _$AdConfigToJson(AdConfig instance) => { (k, e) => MapEntry(_$AdPlatformTypeEnumMap[k]!, e.toJson()), ), 'feedAdConfiguration': instance.feedAdConfiguration.toJson(), - 'articleAdConfiguration': instance.articleAdConfiguration.toJson(), - 'interstitialAdConfiguration': instance.interstitialAdConfiguration.toJson(), + 'navigationAdConfiguration': instance.navigationAdConfiguration.toJson(), }; const _$AdPlatformTypeEnumMap = { diff --git a/lib/src/models/config/ad_platform_identifiers.dart b/lib/src/models/config/ad_platform_identifiers.dart index f0f204d2..06919cde 100644 --- a/lib/src/models/config/ad_platform_identifiers.dart +++ b/lib/src/models/config/ad_platform_identifiers.dart @@ -5,7 +5,7 @@ import 'package:meta/meta.dart'; part 'ad_platform_identifiers.g.dart'; /// {@template ad_platform_identifiers} -/// Holds all ad identifiers for a specific platform (AdMob, Demo etc ). +/// Holds all ad identifiers for a specific platform (AdMob, etc ). /// This object is generic and will be stored in a map in "AdConfig". /// {@endtemplate} @immutable @@ -13,11 +13,9 @@ part 'ad_platform_identifiers.g.dart'; class AdPlatformIdentifiers extends Equatable { /// {@macro ad_platform_identifiers} const AdPlatformIdentifiers({ - this.feedNativeAdId, - this.feedBannerAdId, - this.feedToArticleInterstitialAdId, - this.inArticleNativeAdId, - this.inArticleBannerAdId, + this.nativeAdId, + this.bannerAdId, + this.interstitialAdId, }); /// Creates an [AdPlatformIdentifiers] from JSON data. @@ -27,46 +25,29 @@ class AdPlatformIdentifiers extends Equatable { /// Converts this [AdPlatformIdentifiers] instance to JSON data. Map toJson() => _$AdPlatformIdentifiersToJson(this); - /// ID for native ads in feeds. - final String? feedNativeAdId; + /// ID for native ads. + final String? nativeAdId; - /// ID for banner ads in feeds. - final String? feedBannerAdId; + /// ID for banner ads. + final String? bannerAdId; - /// ID for interstitial ads during feed-to-article transitions. - final String? feedToArticleInterstitialAdId; - - /// ID for native in-article ads. - final String? inArticleNativeAdId; - - /// ID for banner in-article ads. - final String? inArticleBannerAdId; + /// ID for interstitial ads. + final String? interstitialAdId; @override - List get props => [ - feedNativeAdId, - feedBannerAdId, - feedToArticleInterstitialAdId, - inArticleNativeAdId, - inArticleBannerAdId, - ]; + List get props => [nativeAdId, bannerAdId, interstitialAdId]; /// Creates a copy of this [AdPlatformIdentifiers] but with the given fields /// replaced with the new values. AdPlatformIdentifiers copyWith({ - String? feedNativeAdId, - String? feedBannerAdId, - String? feedToArticleInterstitialAdId, - String? inArticleNativeAdId, - String? inArticleBannerAdId, + String? nativeAdId, + String? bannerAdId, + String? interstitialAdId, }) { return AdPlatformIdentifiers( - feedNativeAdId: feedNativeAdId ?? this.feedNativeAdId, - feedBannerAdId: feedBannerAdId ?? this.feedBannerAdId, - feedToArticleInterstitialAdId: - feedToArticleInterstitialAdId ?? this.feedToArticleInterstitialAdId, - inArticleNativeAdId: inArticleNativeAdId ?? this.inArticleNativeAdId, - inArticleBannerAdId: inArticleBannerAdId ?? this.inArticleBannerAdId, + nativeAdId: nativeAdId ?? this.nativeAdId, + bannerAdId: bannerAdId ?? this.bannerAdId, + interstitialAdId: interstitialAdId ?? this.interstitialAdId, ); } } diff --git a/lib/src/models/config/ad_platform_identifiers.g.dart b/lib/src/models/config/ad_platform_identifiers.g.dart index f26d9969..433f3ad2 100644 --- a/lib/src/models/config/ad_platform_identifiers.g.dart +++ b/lib/src/models/config/ad_platform_identifiers.g.dart @@ -10,20 +10,9 @@ AdPlatformIdentifiers _$AdPlatformIdentifiersFromJson( Map json, ) => $checkedCreate('AdPlatformIdentifiers', json, ($checkedConvert) { final val = AdPlatformIdentifiers( - feedNativeAdId: $checkedConvert('feedNativeAdId', (v) => v as String?), - feedBannerAdId: $checkedConvert('feedBannerAdId', (v) => v as String?), - feedToArticleInterstitialAdId: $checkedConvert( - 'feedToArticleInterstitialAdId', - (v) => v as String?, - ), - inArticleNativeAdId: $checkedConvert( - 'inArticleNativeAdId', - (v) => v as String?, - ), - inArticleBannerAdId: $checkedConvert( - 'inArticleBannerAdId', - (v) => v as String?, - ), + nativeAdId: $checkedConvert('nativeAdId', (v) => v as String?), + bannerAdId: $checkedConvert('bannerAdId', (v) => v as String?), + interstitialAdId: $checkedConvert('interstitialAdId', (v) => v as String?), ); return val; }); @@ -31,9 +20,7 @@ AdPlatformIdentifiers _$AdPlatformIdentifiersFromJson( Map _$AdPlatformIdentifiersToJson( AdPlatformIdentifiers instance, ) => { - 'feedNativeAdId': instance.feedNativeAdId, - 'feedBannerAdId': instance.feedBannerAdId, - 'feedToArticleInterstitialAdId': instance.feedToArticleInterstitialAdId, - 'inArticleNativeAdId': instance.inArticleNativeAdId, - 'inArticleBannerAdId': instance.inArticleBannerAdId, + 'nativeAdId': instance.nativeAdId, + 'bannerAdId': instance.bannerAdId, + 'interstitialAdId': instance.interstitialAdId, }; diff --git a/lib/src/models/config/app_config.dart b/lib/src/models/config/app_config.dart new file mode 100644 index 00000000..01bde770 --- /dev/null +++ b/lib/src/models/config/app_config.dart @@ -0,0 +1,55 @@ +import 'package:core/src/models/config/general_app_config.dart'; +import 'package:core/src/models/config/maintenance_config.dart'; +import 'package:core/src/models/config/update_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'app_config.g.dart'; + +/// {@template app_config} +/// A container for all application-level configurations. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class AppConfig extends Equatable { + /// {@macro app_config} + const AppConfig({ + required this.maintenance, + required this.update, + required this.general, + }); + + /// Creates an [AppConfig] from JSON data. + factory AppConfig.fromJson(Map json) => + _$AppConfigFromJson(json); + + /// Configuration for maintenance mode. + final MaintenanceConfig maintenance; + + /// Configuration for application updates. + final UpdateConfig update; + + /// General application settings. + final GeneralAppConfig general; + + /// Converts this [AppConfig] instance to JSON data. + Map toJson() => _$AppConfigToJson(this); + + @override + List get props => [maintenance, update, general]; + + /// Creates a copy of this [AppConfig] but with the given fields + /// replaced with the new values. + AppConfig copyWith({ + MaintenanceConfig? maintenance, + UpdateConfig? update, + GeneralAppConfig? general, + }) { + return AppConfig( + maintenance: maintenance ?? this.maintenance, + update: update ?? this.update, + general: general ?? this.general, + ); + } +} diff --git a/lib/src/models/config/app_config.g.dart b/lib/src/models/config/app_config.g.dart new file mode 100644 index 00000000..a49f838f --- /dev/null +++ b/lib/src/models/config/app_config.g.dart @@ -0,0 +1,32 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AppConfig _$AppConfigFromJson(Map json) => + $checkedCreate('AppConfig', json, ($checkedConvert) { + final val = AppConfig( + maintenance: $checkedConvert( + 'maintenance', + (v) => MaintenanceConfig.fromJson(v as Map), + ), + update: $checkedConvert( + 'update', + (v) => UpdateConfig.fromJson(v as Map), + ), + general: $checkedConvert( + 'general', + (v) => GeneralAppConfig.fromJson(v as Map), + ), + ); + return val; + }); + +Map _$AppConfigToJson(AppConfig instance) => { + 'maintenance': instance.maintenance.toJson(), + 'update': instance.update.toJson(), + 'general': instance.general.toJson(), +}; diff --git a/lib/src/models/config/article_ad_configuration.dart b/lib/src/models/config/article_ad_configuration.dart deleted file mode 100644 index 8cb05f5f..00000000 --- a/lib/src/models/config/article_ad_configuration.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:core/src/enums/app_user_role.dart'; -import 'package:core/src/enums/banner_ad_shape.dart'; -import 'package:core/src/enums/in_article_ad_slot_type.dart'; -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'article_ad_configuration.g.dart'; - -/// {@template article_ad_configuration} -/// Master configuration for all ads on the article page (headline details), -/// excluding interstitial ads which are managed globally. -/// {@endtemplate} -@immutable -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class ArticleAdConfiguration extends Equatable { - /// {@macro article_ad_configuration} - const ArticleAdConfiguration({ - required this.enabled, - required this.bannerAdShape, - required this.visibleTo, - }); - - /// Creates an [ArticleAdConfiguration] from JSON data. - factory ArticleAdConfiguration.fromJson(Map json) => - _$ArticleAdConfigurationFromJson(json); - - /// Converts this [ArticleAdConfiguration] instance to JSON data. - Map toJson() => _$ArticleAdConfigurationToJson(this); - - /// Master switch to enable or disable all in-article ads (excluding interstitial). - final bool enabled; - - /// The preferred shape for banner ads displayed in articles. - final BannerAdShape bannerAdShape; - - /// Explicitly defines which user roles can see in-article ad slots - /// and which specific slots are enabled for them. If a role is not - /// in this map, they will not see in-article ads. - final Map> visibleTo; - - @override - List get props => [enabled, bannerAdShape, visibleTo]; - - /// Creates a copy of this [ArticleAdConfiguration] but with the given fields - /// replaced with the new values. - ArticleAdConfiguration copyWith({ - bool? enabled, - Map>? visibleTo, - BannerAdShape? bannerAdShape, - }) { - return ArticleAdConfiguration( - enabled: enabled ?? this.enabled, - visibleTo: visibleTo ?? this.visibleTo, - bannerAdShape: bannerAdShape ?? this.bannerAdShape, - ); - } -} diff --git a/lib/src/models/config/article_ad_configuration.g.dart b/lib/src/models/config/article_ad_configuration.g.dart deleted file mode 100644 index 83c8a81b..00000000 --- a/lib/src/models/config/article_ad_configuration.g.dart +++ /dev/null @@ -1,65 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'article_ad_configuration.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -ArticleAdConfiguration _$ArticleAdConfigurationFromJson( - Map json, -) => $checkedCreate('ArticleAdConfiguration', json, ($checkedConvert) { - final val = ArticleAdConfiguration( - enabled: $checkedConvert('enabled', (v) => v as bool), - bannerAdShape: $checkedConvert( - 'bannerAdShape', - (v) => $enumDecode(_$BannerAdShapeEnumMap, v), - ), - visibleTo: $checkedConvert( - 'visibleTo', - (v) => (v as Map).map( - (k, e) => MapEntry( - $enumDecode(_$AppUserRoleEnumMap, k), - (e as Map).map( - (k, e) => MapEntry( - $enumDecode(_$InArticleAdSlotTypeEnumMap, k), - e as bool, - ), - ), - ), - ), - ), - ); - return val; -}); - -Map _$ArticleAdConfigurationToJson( - ArticleAdConfiguration instance, -) => { - 'enabled': instance.enabled, - 'bannerAdShape': _$BannerAdShapeEnumMap[instance.bannerAdShape]!, - 'visibleTo': instance.visibleTo.map( - (k, e) => MapEntry( - _$AppUserRoleEnumMap[k]!, - e.map((k, e) => MapEntry(_$InArticleAdSlotTypeEnumMap[k]!, e)), - ), - ), -}; - -const _$BannerAdShapeEnumMap = { - BannerAdShape.square: 'square', - BannerAdShape.rectangle: 'rectangle', -}; - -const _$InArticleAdSlotTypeEnumMap = { - InArticleAdSlotType.aboveArticleContinueReadingButton: - 'aboveArticleContinueReadingButton', - InArticleAdSlotType.belowArticleContinueReadingButton: - 'belowArticleContinueReadingButton', -}; - -const _$AppUserRoleEnumMap = { - AppUserRole.premiumUser: 'premiumUser', - AppUserRole.standardUser: 'standardUser', - AppUserRole.guestUser: 'guestUser', -}; diff --git a/lib/src/models/config/config.dart b/lib/src/models/config/config.dart index b05000e0..a1aa0e82 100644 --- a/lib/src/models/config/config.dart +++ b/lib/src/models/config/config.dart @@ -1,14 +1,19 @@ export 'ad_config.dart'; export 'ad_platform_identifiers.dart'; -export 'app_status.dart'; -export 'article_ad_configuration.dart'; +export 'app_config.dart'; +export 'features_config.dart'; export 'feed_ad_configuration.dart'; export 'feed_ad_frequency_config.dart'; +export 'feed_config.dart'; export 'feed_decorator_config.dart'; export 'feed_decorator_role_config.dart'; -export 'interstitial_ad_configuration.dart'; -export 'interstitial_ad_frequency_config.dart'; +export 'general_app_config.dart'; +export 'maintenance_config.dart'; +export 'navigation_ad_configuration.dart'; +export 'navigation_ad_frequency_config.dart'; export 'push_notification_config.dart'; export 'remote_config.dart'; export 'saved_filter_limits.dart'; -export 'user_preference_config.dart'; +export 'update_config.dart'; +export 'user_config.dart'; +export 'user_limits_config.dart'; diff --git a/lib/src/models/config/features_config.dart b/lib/src/models/config/features_config.dart new file mode 100644 index 00000000..5aa1c9fb --- /dev/null +++ b/lib/src/models/config/features_config.dart @@ -0,0 +1,55 @@ +import 'package:core/src/models/config/ad_config.dart'; +import 'package:core/src/models/config/feed_config.dart'; +import 'package:core/src/models/config/push_notification_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'features_config.g.dart'; + +/// {@template features_config} +/// A container for all user-facing feature configurations. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class FeaturesConfig extends Equatable { + /// {@macro features_config} + const FeaturesConfig({ + required this.ads, + required this.pushNotifications, + required this.feed, + }); + + /// Creates a [FeaturesConfig] from JSON data. + factory FeaturesConfig.fromJson(Map json) => + _$FeaturesConfigFromJson(json); + + /// Configuration for all ad-related features. + final AdConfig ads; + + /// Configuration for the push notification system. + final PushNotificationConfig pushNotifications; + + /// Configuration for all feed-related features. + final FeedConfig feed; + + /// Converts this [FeaturesConfig] instance to JSON data. + Map toJson() => _$FeaturesConfigToJson(this); + + @override + List get props => [ads, pushNotifications, feed]; + + /// Creates a copy of this [FeaturesConfig] but with the given fields + /// replaced with the new values. + FeaturesConfig copyWith({ + AdConfig? ads, + PushNotificationConfig? pushNotifications, + FeedConfig? feed, + }) { + return FeaturesConfig( + ads: ads ?? this.ads, + pushNotifications: pushNotifications ?? this.pushNotifications, + feed: feed ?? this.feed, + ); + } +} diff --git a/lib/src/models/config/features_config.g.dart b/lib/src/models/config/features_config.g.dart new file mode 100644 index 00000000..d6b1a61f --- /dev/null +++ b/lib/src/models/config/features_config.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'features_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FeaturesConfig _$FeaturesConfigFromJson(Map json) => + $checkedCreate('FeaturesConfig', json, ($checkedConvert) { + final val = FeaturesConfig( + ads: $checkedConvert( + 'ads', + (v) => AdConfig.fromJson(v as Map), + ), + pushNotifications: $checkedConvert( + 'pushNotifications', + (v) => PushNotificationConfig.fromJson(v as Map), + ), + feed: $checkedConvert( + 'feed', + (v) => FeedConfig.fromJson(v as Map), + ), + ); + return val; + }); + +Map _$FeaturesConfigToJson(FeaturesConfig instance) => + { + 'ads': instance.ads.toJson(), + 'pushNotifications': instance.pushNotifications.toJson(), + 'feed': instance.feed.toJson(), + }; diff --git a/lib/src/models/config/feed_config.dart b/lib/src/models/config/feed_config.dart new file mode 100644 index 00000000..cd02dbaf --- /dev/null +++ b/lib/src/models/config/feed_config.dart @@ -0,0 +1,46 @@ +import 'package:core/src/enums/feed_decorator_type.dart'; +import 'package:core/src/enums/feed_item_click_behavior.dart'; +import 'package:core/src/models/config/feed_decorator_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'feed_config.g.dart'; + +/// {@template feed_config} +/// A container for all feed-related configurations. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class FeedConfig extends Equatable { + /// {@macro feed_config} + const FeedConfig({required this.itemClickBehavior, required this.decorators}); + + /// Creates a [FeedConfig] from JSON data. + factory FeedConfig.fromJson(Map json) => + _$FeedConfigFromJson(json); + + /// The default behavior when clicking feed items. + final FeedItemClickBehavior itemClickBehavior; + + /// Defines configuration settings for all feed decorators. + final Map decorators; + + /// Converts this [FeedConfig] instance to JSON data. + Map toJson() => _$FeedConfigToJson(this); + + @override + List get props => [itemClickBehavior, decorators]; + + /// Creates a copy of this [FeedConfig] but with the given fields + /// replaced with the new values. + FeedConfig copyWith({ + FeedItemClickBehavior? itemClickBehavior, + Map? decorators, + }) { + return FeedConfig( + itemClickBehavior: itemClickBehavior ?? this.itemClickBehavior, + decorators: decorators ?? this.decorators, + ); + } +} diff --git a/lib/src/models/config/feed_config.g.dart b/lib/src/models/config/feed_config.g.dart new file mode 100644 index 00000000..d83ac514 --- /dev/null +++ b/lib/src/models/config/feed_config.g.dart @@ -0,0 +1,51 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'feed_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FeedConfig _$FeedConfigFromJson(Map json) => + $checkedCreate('FeedConfig', json, ($checkedConvert) { + final val = FeedConfig( + itemClickBehavior: $checkedConvert( + 'itemClickBehavior', + (v) => $enumDecode(_$FeedItemClickBehaviorEnumMap, v), + ), + decorators: $checkedConvert( + 'decorators', + (v) => (v as Map).map( + (k, e) => MapEntry( + $enumDecode(_$FeedDecoratorTypeEnumMap, k), + FeedDecoratorConfig.fromJson(e as Map), + ), + ), + ), + ); + return val; + }); + +Map _$FeedConfigToJson(FeedConfig instance) => + { + 'itemClickBehavior': + _$FeedItemClickBehaviorEnumMap[instance.itemClickBehavior]!, + 'decorators': instance.decorators.map( + (k, e) => MapEntry(_$FeedDecoratorTypeEnumMap[k]!, e.toJson()), + ), + }; + +const _$FeedItemClickBehaviorEnumMap = { + FeedItemClickBehavior.defaultBehavior: 'default', + FeedItemClickBehavior.internalNavigation: 'internalNavigation', + FeedItemClickBehavior.externalNavigation: 'externalNavigation', +}; + +const _$FeedDecoratorTypeEnumMap = { + FeedDecoratorType.linkAccount: 'linkAccount', + FeedDecoratorType.upgrade: 'upgrade', + FeedDecoratorType.rateApp: 'rateApp', + FeedDecoratorType.enableNotifications: 'enableNotifications', + FeedDecoratorType.suggestedTopics: 'suggestedTopics', + FeedDecoratorType.suggestedSources: 'suggestedSources', +}; diff --git a/lib/src/models/config/general_app_config.dart b/lib/src/models/config/general_app_config.dart new file mode 100644 index 00000000..ff1b6baf --- /dev/null +++ b/lib/src/models/config/general_app_config.dart @@ -0,0 +1,46 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'general_app_config.g.dart'; + +/// {@template general_app_config} +/// Defines general application-level settings not covered by other configs. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class GeneralAppConfig extends Equatable { + /// {@macro general_app_config} + const GeneralAppConfig({ + required this.termsOfServiceUrl, + required this.privacyPolicyUrl, + }); + + /// Creates a [GeneralAppConfig] from JSON data. + factory GeneralAppConfig.fromJson(Map json) => + _$GeneralAppConfigFromJson(json); + + /// The URL for the application's Terms of Service page. + final String termsOfServiceUrl; + + /// The URL for the application's Privacy Policy page. + final String privacyPolicyUrl; + + /// Converts this [GeneralAppConfig] instance to JSON data. + Map toJson() => _$GeneralAppConfigToJson(this); + + @override + List get props => [termsOfServiceUrl, privacyPolicyUrl]; + + /// Creates a copy of this [GeneralAppConfig] but with the given fields + /// replaced with the new values. + GeneralAppConfig copyWith({ + String? termsOfServiceUrl, + String? privacyPolicyUrl, + }) { + return GeneralAppConfig( + termsOfServiceUrl: termsOfServiceUrl ?? this.termsOfServiceUrl, + privacyPolicyUrl: privacyPolicyUrl ?? this.privacyPolicyUrl, + ); + } +} diff --git a/lib/src/models/config/general_app_config.g.dart b/lib/src/models/config/general_app_config.g.dart new file mode 100644 index 00000000..86d9ab82 --- /dev/null +++ b/lib/src/models/config/general_app_config.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'general_app_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GeneralAppConfig _$GeneralAppConfigFromJson( + Map json, +) => $checkedCreate('GeneralAppConfig', json, ($checkedConvert) { + final val = GeneralAppConfig( + termsOfServiceUrl: $checkedConvert('termsOfServiceUrl', (v) => v as String), + privacyPolicyUrl: $checkedConvert('privacyPolicyUrl', (v) => v as String), + ); + return val; +}); + +Map _$GeneralAppConfigToJson(GeneralAppConfig instance) => + { + 'termsOfServiceUrl': instance.termsOfServiceUrl, + 'privacyPolicyUrl': instance.privacyPolicyUrl, + }; diff --git a/lib/src/models/config/interstitial_ad_configuration.dart b/lib/src/models/config/interstitial_ad_configuration.dart deleted file mode 100644 index abfcd731..00000000 --- a/lib/src/models/config/interstitial_ad_configuration.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:core/src/enums/ad_type.dart'; -import 'package:core/src/enums/app_user_role.dart'; -import 'package:core/src/models/config/interstitial_ad_frequency_config.dart'; -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'interstitial_ad_configuration.g.dart'; - -/// {@template interstitial_ad_configuration} -/// Master configuration for all interstitial ads across the application. -/// {@endtemplate} -@immutable -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class InterstitialAdConfiguration extends Equatable { - /// {@macro interstitial_ad_configuration} - const InterstitialAdConfiguration({ - required this.enabled, - required this.visibleTo, - this.adType = AdType.interstitial, - }) : assert( - adType == AdType.interstitial, - 'Interstitial ads must be of type interstitial.', - ); - - /// Creates an [InterstitialAdConfiguration] from JSON data. - factory InterstitialAdConfiguration.fromJson(Map json) => - _$InterstitialAdConfigurationFromJson(json); - - /// Converts this [InterstitialAdConfiguration] instance to JSON data. - Map toJson() => _$InterstitialAdConfigurationToJson(this); - - /// Master switch to enable or disable interstitial ads globally. - final bool enabled; - - /// The type of the ad, fixed to [AdType.interstitial]. - final AdType adType; - - /// Explicitly defines which user roles can see this interstitial ad - /// configuration and their specific frequency settings. If a role is not - /// in this map, they will not see interstitial ads. - final Map visibleTo; - - @override - List get props => [enabled, adType, visibleTo]; - - /// Creates a copy of this [InterstitialAdConfiguration] but with - /// the given fields replaced with the new values. - InterstitialAdConfiguration copyWith({ - bool? enabled, - AdType? adType, - Map? visibleTo, - }) { - return InterstitialAdConfiguration( - enabled: enabled ?? this.enabled, - adType: adType ?? this.adType, - visibleTo: visibleTo ?? this.visibleTo, - ); - } -} diff --git a/lib/src/models/config/interstitial_ad_frequency_config.dart b/lib/src/models/config/interstitial_ad_frequency_config.dart deleted file mode 100644 index d03ed8eb..00000000 --- a/lib/src/models/config/interstitial_ad_frequency_config.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'interstitial_ad_frequency_config.g.dart'; - -/// {@template interstitial_ad_frequency_config} -/// Encapsulates ad frequency for interstitial ads shown during page transitions -/// for a specific user role. -/// {@endtemplate} -@immutable -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class InterstitialAdFrequencyConfig extends Equatable { - /// {@macro interstitial_ad_frequency_config} - const InterstitialAdFrequencyConfig({ - required this.transitionsBeforeShowingInterstitialAds, - }); - - /// Creates a [InterstitialAdFrequencyConfig] from JSON data. - factory InterstitialAdFrequencyConfig.fromJson(Map json) => - _$InterstitialAdFrequencyConfigFromJson(json); - - /// Converts this [InterstitialAdFrequencyConfig] instance to JSON data. - Map toJson() => _$InterstitialAdFrequencyConfigToJson(this); - - /// The number of page transitions a user needs to make - /// before an interstitial ad is shown. - final int transitionsBeforeShowingInterstitialAds; - - @override - List get props => [transitionsBeforeShowingInterstitialAds]; - - /// Creates a copy of this [InterstitialAdFrequencyConfig] but with - /// the given fields replaced with the new values. - InterstitialAdFrequencyConfig copyWith({ - int? transitionsBeforeShowingInterstitialAds, - }) { - return InterstitialAdFrequencyConfig( - transitionsBeforeShowingInterstitialAds: - transitionsBeforeShowingInterstitialAds ?? - this.transitionsBeforeShowingInterstitialAds, - ); - } -} diff --git a/lib/src/models/config/interstitial_ad_frequency_config.g.dart b/lib/src/models/config/interstitial_ad_frequency_config.g.dart deleted file mode 100644 index 2a22a80a..00000000 --- a/lib/src/models/config/interstitial_ad_frequency_config.g.dart +++ /dev/null @@ -1,26 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'interstitial_ad_frequency_config.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -InterstitialAdFrequencyConfig _$InterstitialAdFrequencyConfigFromJson( - Map json, -) => $checkedCreate('InterstitialAdFrequencyConfig', json, ($checkedConvert) { - final val = InterstitialAdFrequencyConfig( - transitionsBeforeShowingInterstitialAds: $checkedConvert( - 'transitionsBeforeShowingInterstitialAds', - (v) => (v as num).toInt(), - ), - ); - return val; -}); - -Map _$InterstitialAdFrequencyConfigToJson( - InterstitialAdFrequencyConfig instance, -) => { - 'transitionsBeforeShowingInterstitialAds': - instance.transitionsBeforeShowingInterstitialAds, -}; diff --git a/lib/src/models/config/maintenance_config.dart b/lib/src/models/config/maintenance_config.dart new file mode 100644 index 00000000..bb17be53 --- /dev/null +++ b/lib/src/models/config/maintenance_config.dart @@ -0,0 +1,36 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'maintenance_config.g.dart'; + +/// {@template maintenance_config} +/// Defines configuration settings related to application maintenance status. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class MaintenanceConfig extends Equatable { + /// {@macro maintenance_config} + const MaintenanceConfig({required this.isUnderMaintenance}); + + /// Creates a [MaintenanceConfig] from JSON data. + factory MaintenanceConfig.fromJson(Map json) => + _$MaintenanceConfigFromJson(json); + + /// Indicates if the app is currently under maintenance. + final bool isUnderMaintenance; + + /// Converts this [MaintenanceConfig] instance to JSON data. + Map toJson() => _$MaintenanceConfigToJson(this); + + @override + List get props => [isUnderMaintenance]; + + /// Creates a copy of this [MaintenanceConfig] but with the given fields + /// replaced with the new values. + MaintenanceConfig copyWith({bool? isUnderMaintenance}) { + return MaintenanceConfig( + isUnderMaintenance: isUnderMaintenance ?? this.isUnderMaintenance, + ); + } +} diff --git a/lib/src/models/config/maintenance_config.g.dart b/lib/src/models/config/maintenance_config.g.dart new file mode 100644 index 00000000..fe647457 --- /dev/null +++ b/lib/src/models/config/maintenance_config.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'maintenance_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +MaintenanceConfig _$MaintenanceConfigFromJson(Map json) => + $checkedCreate('MaintenanceConfig', json, ($checkedConvert) { + final val = MaintenanceConfig( + isUnderMaintenance: $checkedConvert( + 'isUnderMaintenance', + (v) => v as bool, + ), + ); + return val; + }); + +Map _$MaintenanceConfigToJson(MaintenanceConfig instance) => + {'isUnderMaintenance': instance.isUnderMaintenance}; diff --git a/lib/src/models/config/navigation_ad_configuration.dart b/lib/src/models/config/navigation_ad_configuration.dart new file mode 100644 index 00000000..85dedc28 --- /dev/null +++ b/lib/src/models/config/navigation_ad_configuration.dart @@ -0,0 +1,60 @@ +import 'package:core/src/enums/ad_type.dart'; +import 'package:core/src/enums/app_user_role.dart'; +import 'package:core/src/models/config/navigation_ad_frequency_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'navigation_ad_configuration.g.dart'; + +/// {@template navigation_ad_configuration} +/// Master configuration for all Navigation ads across the application. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class NavigationAdConfiguration extends Equatable { + /// {@macro navigation_ad_configuration} + const NavigationAdConfiguration({ + required this.enabled, + required this.visibleTo, + this.adType = AdType.interstitial, + }) : assert( + adType == AdType.interstitial, + 'Navigation ads must be of type interstitial.', + ); + + /// Creates an [NavigationAdConfiguration] from JSON data. + factory NavigationAdConfiguration.fromJson(Map json) => + _$NavigationAdConfigurationFromJson(json); + + /// Converts this [NavigationAdConfiguration] instance to JSON data. + Map toJson() => _$NavigationAdConfigurationToJson(this); + + /// Master switch to enable or disable navigation ads globally. + final bool enabled; + + /// The type of the ad, fixed to [AdType.interstitial]. + final AdType adType; + + /// Explicitly defines which user roles can see this navigation ad + /// configuration and their specific frequency settings. If a role is not + /// in this map, they will not see navigation ads. + final Map visibleTo; + + @override + List get props => [enabled, adType, visibleTo]; + + /// Creates a copy of this [NavigationAdConfiguration] but with + /// the given fields replaced with the new values. + NavigationAdConfiguration copyWith({ + bool? enabled, + AdType? adType, + Map? visibleTo, + }) { + return NavigationAdConfiguration( + enabled: enabled ?? this.enabled, + adType: adType ?? this.adType, + visibleTo: visibleTo ?? this.visibleTo, + ); + } +} diff --git a/lib/src/models/config/interstitial_ad_configuration.g.dart b/lib/src/models/config/navigation_ad_configuration.g.dart similarity index 74% rename from lib/src/models/config/interstitial_ad_configuration.g.dart rename to lib/src/models/config/navigation_ad_configuration.g.dart index a62d3c3b..25ac008a 100644 --- a/lib/src/models/config/interstitial_ad_configuration.g.dart +++ b/lib/src/models/config/navigation_ad_configuration.g.dart @@ -1,22 +1,22 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'interstitial_ad_configuration.dart'; +part of 'navigation_ad_configuration.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** -InterstitialAdConfiguration _$InterstitialAdConfigurationFromJson( +NavigationAdConfiguration _$NavigationAdConfigurationFromJson( Map json, -) => $checkedCreate('InterstitialAdConfiguration', json, ($checkedConvert) { - final val = InterstitialAdConfiguration( +) => $checkedCreate('NavigationAdConfiguration', json, ($checkedConvert) { + final val = NavigationAdConfiguration( enabled: $checkedConvert('enabled', (v) => v as bool), visibleTo: $checkedConvert( 'visibleTo', (v) => (v as Map).map( (k, e) => MapEntry( $enumDecode(_$AppUserRoleEnumMap, k), - InterstitialAdFrequencyConfig.fromJson(e as Map), + NavigationAdFrequencyConfig.fromJson(e as Map), ), ), ), @@ -28,8 +28,8 @@ InterstitialAdConfiguration _$InterstitialAdConfigurationFromJson( return val; }); -Map _$InterstitialAdConfigurationToJson( - InterstitialAdConfiguration instance, +Map _$NavigationAdConfigurationToJson( + NavigationAdConfiguration instance, ) => { 'enabled': instance.enabled, 'adType': _$AdTypeEnumMap[instance.adType]!, diff --git a/lib/src/models/config/navigation_ad_frequency_config.dart b/lib/src/models/config/navigation_ad_frequency_config.dart new file mode 100644 index 00000000..517274a4 --- /dev/null +++ b/lib/src/models/config/navigation_ad_frequency_config.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'navigation_ad_frequency_config.g.dart'; + +/// {@template navigation_ad_frequency_config} +/// Encapsulates ad frequency for navigation ads, providing separate controls +/// for ads shown during internal in-app navigation and those triggered by +/// navigating to an external URL. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class NavigationAdFrequencyConfig extends Equatable { + /// {@macro navigation_ad_frequency_config} + const NavigationAdFrequencyConfig({ + required this.internalNavigationsBeforeShowingInterstitialAd, + required this.externalNavigationsBeforeShowingInterstitialAd, + }); + + /// Creates a [NavigationAdFrequencyConfig] from JSON data. + factory NavigationAdFrequencyConfig.fromJson(Map json) => + _$NavigationAdFrequencyConfigFromJson(json); + + /// The number of internal page-to-page navigations a user needs to make + /// within the app before an interstitial ad is shown. + final int internalNavigationsBeforeShowingInterstitialAd; + + /// The number of external navigations a user needs to make + /// within the app before an interstitial ad is shown. + final int externalNavigationsBeforeShowingInterstitialAd; + + @override + List get props => [ + internalNavigationsBeforeShowingInterstitialAd, + externalNavigationsBeforeShowingInterstitialAd, + ]; + + /// Converts this [NavigationAdFrequencyConfig] instance to JSON data. + Map toJson() => _$NavigationAdFrequencyConfigToJson(this); + + /// Creates a copy of this [NavigationAdFrequencyConfig] but with + /// the given fields replaced with the new values. + NavigationAdFrequencyConfig copyWith({ + int? internalNavigationsBeforeShowingInterstitialAd, + int? externalNavigationsBeforeShowingInterstitialAd, + }) { + return NavigationAdFrequencyConfig( + internalNavigationsBeforeShowingInterstitialAd: + internalNavigationsBeforeShowingInterstitialAd ?? + this.internalNavigationsBeforeShowingInterstitialAd, + externalNavigationsBeforeShowingInterstitialAd: + externalNavigationsBeforeShowingInterstitialAd ?? + this.externalNavigationsBeforeShowingInterstitialAd, + ); + } +} diff --git a/lib/src/models/config/navigation_ad_frequency_config.g.dart b/lib/src/models/config/navigation_ad_frequency_config.g.dart new file mode 100644 index 00000000..956455c5 --- /dev/null +++ b/lib/src/models/config/navigation_ad_frequency_config.g.dart @@ -0,0 +1,32 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'navigation_ad_frequency_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +NavigationAdFrequencyConfig _$NavigationAdFrequencyConfigFromJson( + Map json, +) => $checkedCreate('NavigationAdFrequencyConfig', json, ($checkedConvert) { + final val = NavigationAdFrequencyConfig( + internalNavigationsBeforeShowingInterstitialAd: $checkedConvert( + 'internalNavigationsBeforeShowingInterstitialAd', + (v) => (v as num).toInt(), + ), + externalNavigationsBeforeShowingInterstitialAd: $checkedConvert( + 'externalNavigationsBeforeShowingInterstitialAd', + (v) => (v as num).toInt(), + ), + ); + return val; +}); + +Map _$NavigationAdFrequencyConfigToJson( + NavigationAdFrequencyConfig instance, +) => { + 'internalNavigationsBeforeShowingInterstitialAd': + instance.internalNavigationsBeforeShowingInterstitialAd, + 'externalNavigationsBeforeShowingInterstitialAd': + instance.externalNavigationsBeforeShowingInterstitialAd, +}; diff --git a/lib/src/models/config/remote_config.dart b/lib/src/models/config/remote_config.dart index 85df21ff..e9a6e1d8 100644 --- a/lib/src/models/config/remote_config.dart +++ b/lib/src/models/config/remote_config.dart @@ -7,10 +7,8 @@ part 'remote_config.g.dart'; /// Represents the overall application configuration. /// -/// This model serves as a central container for various configuration -/// settings that can be fetched from a remote source. It includes settings -/// for user preference limits, ad display, and feed decorators. -/// +/// This model serves as a central container for various configuration settings +/// that can be fetched from a remote source. /// There should typically be only one instance of this configuration, /// identified by a fixed ID (e.g., 'app_config'). @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) @@ -18,13 +16,11 @@ class RemoteConfig extends Equatable { /// Creates a new [RemoteConfig] instance. const RemoteConfig({ required this.id, - required this.appStatus, - required this.adConfig, - required this.feedDecoratorConfig, - required this.userPreferenceConfig, - required this.pushNotificationConfig, required this.createdAt, required this.updatedAt, + required this.app, + required this.features, + required this.user, }); /// Factory method to create an [RemoteConfig] instance from a JSON map. @@ -34,21 +30,14 @@ class RemoteConfig extends Equatable { /// The unique identifier for this configuration. final String id; - /// Defines configuration settings related to user preference limits. - final UserPreferenceConfig userPreferenceConfig; - - /// Defines configuration settings related to ad display. - final AdConfig adConfig; - - /// Defines configuration settings for all feed decorators. - final Map feedDecoratorConfig; + /// Configuration for application-level settings (maintenance, updates, etc.). + final AppConfig app; - /// Defines configuration settings related to the overall application status - /// (maintenance, updates). - final AppStatus appStatus; + /// Configuration for all user-facing features (ads, feed, notifications). + final FeaturesConfig features; - /// Defines the global configuration for the push notification system. - final PushNotificationConfig pushNotificationConfig; + /// Configuration for user-specific settings (role-based limits, etc.). + final UserConfig user; /// The creation timestamp of the remote config. @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) @@ -64,38 +53,24 @@ class RemoteConfig extends Equatable { /// Creates a new [RemoteConfig] instance with specified changes. RemoteConfig copyWith({ String? id, - UserPreferenceConfig? userPreferenceConfig, - AdConfig? adConfig, - Map? feedDecoratorConfig, - AppStatus? appStatus, - PushNotificationConfig? pushNotificationConfig, DateTime? createdAt, DateTime? updatedAt, + AppConfig? app, + FeaturesConfig? features, + UserConfig? user, }) { return RemoteConfig( id: id ?? this.id, - userPreferenceConfig: userPreferenceConfig ?? this.userPreferenceConfig, - adConfig: adConfig ?? this.adConfig, - feedDecoratorConfig: feedDecoratorConfig ?? this.feedDecoratorConfig, - appStatus: appStatus ?? this.appStatus, - pushNotificationConfig: - pushNotificationConfig ?? this.pushNotificationConfig, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, + app: app ?? this.app, + features: features ?? this.features, + user: user ?? this.user, ); } @override - List get props => [ - id, - userPreferenceConfig, - adConfig, - feedDecoratorConfig, - appStatus, - pushNotificationConfig, - createdAt, - updatedAt, - ]; + List get props => [id, createdAt, updatedAt, app, features, user]; @override bool get stringify => true; diff --git a/lib/src/models/config/remote_config.g.dart b/lib/src/models/config/remote_config.g.dart index 35643788..d48a92fe 100644 --- a/lib/src/models/config/remote_config.g.dart +++ b/lib/src/models/config/remote_config.g.dart @@ -10,31 +10,6 @@ RemoteConfig _$RemoteConfigFromJson(Map json) => $checkedCreate('RemoteConfig', json, ($checkedConvert) { final val = RemoteConfig( id: $checkedConvert('id', (v) => v as String), - appStatus: $checkedConvert( - 'appStatus', - (v) => AppStatus.fromJson(v as Map), - ), - adConfig: $checkedConvert( - 'adConfig', - (v) => AdConfig.fromJson(v as Map), - ), - feedDecoratorConfig: $checkedConvert( - 'feedDecoratorConfig', - (v) => (v as Map).map( - (k, e) => MapEntry( - $enumDecode(_$FeedDecoratorTypeEnumMap, k), - FeedDecoratorConfig.fromJson(e as Map), - ), - ), - ), - userPreferenceConfig: $checkedConvert( - 'userPreferenceConfig', - (v) => UserPreferenceConfig.fromJson(v as Map), - ), - pushNotificationConfig: $checkedConvert( - 'pushNotificationConfig', - (v) => PushNotificationConfig.fromJson(v as Map), - ), createdAt: $checkedConvert( 'createdAt', (v) => dateTimeFromJson(v as String?), @@ -43,6 +18,18 @@ RemoteConfig _$RemoteConfigFromJson(Map json) => 'updatedAt', (v) => dateTimeFromJson(v as String?), ), + app: $checkedConvert( + 'app', + (v) => AppConfig.fromJson(v as Map), + ), + features: $checkedConvert( + 'features', + (v) => FeaturesConfig.fromJson(v as Map), + ), + user: $checkedConvert( + 'user', + (v) => UserConfig.fromJson(v as Map), + ), ); return val; }); @@ -50,22 +37,9 @@ RemoteConfig _$RemoteConfigFromJson(Map json) => Map _$RemoteConfigToJson(RemoteConfig instance) => { 'id': instance.id, - 'userPreferenceConfig': instance.userPreferenceConfig.toJson(), - 'adConfig': instance.adConfig.toJson(), - 'feedDecoratorConfig': instance.feedDecoratorConfig.map( - (k, e) => MapEntry(_$FeedDecoratorTypeEnumMap[k]!, e.toJson()), - ), - 'appStatus': instance.appStatus.toJson(), - 'pushNotificationConfig': instance.pushNotificationConfig.toJson(), + 'app': instance.app.toJson(), + 'features': instance.features.toJson(), + 'user': instance.user.toJson(), 'createdAt': dateTimeToJson(instance.createdAt), 'updatedAt': dateTimeToJson(instance.updatedAt), }; - -const _$FeedDecoratorTypeEnumMap = { - FeedDecoratorType.linkAccount: 'linkAccount', - FeedDecoratorType.upgrade: 'upgrade', - FeedDecoratorType.rateApp: 'rateApp', - FeedDecoratorType.enableNotifications: 'enableNotifications', - FeedDecoratorType.suggestedTopics: 'suggestedTopics', - FeedDecoratorType.suggestedSources: 'suggestedSources', -}; diff --git a/lib/src/models/config/app_status.dart b/lib/src/models/config/update_config.dart similarity index 56% rename from lib/src/models/config/app_status.dart rename to lib/src/models/config/update_config.dart index cd3ed524..ef0059f7 100644 --- a/lib/src/models/config/app_status.dart +++ b/lib/src/models/config/update_config.dart @@ -1,30 +1,31 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; -part 'app_status.g.dart'; +part 'update_config.g.dart'; -/// Represents the application's remote status, including maintenance and update information. +/// {@template update_config} +/// Defines configuration settings related to application updates. +/// {@endtemplate} +@immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class AppStatus extends Equatable { - const AppStatus({ - required this.isUnderMaintenance, +class UpdateConfig extends Equatable { + /// {@macro update_config} + const UpdateConfig({ required this.latestAppVersion, required this.isLatestVersionOnly, required this.iosUpdateUrl, required this.androidUpdateUrl, }); - /// Factory method to create a [AppStatus] instance from a JSON map. - factory AppStatus.fromJson(Map json) => - _$AppStatusFromJson(json); - - /// Indicates if the app is currently under maintenance. - final bool isUnderMaintenance; + /// Creates an [UpdateConfig] from JSON data. + factory UpdateConfig.fromJson(Map json) => + _$UpdateConfigFromJson(json); /// The latest available app version. final String latestAppVersion; - /// Indicates if only the latest version of the app is allowed to run (force update). + /// Indicates if only the latest version of the app is allowed to run. final bool isLatestVersionOnly; /// URL for iOS app updates. @@ -33,35 +34,30 @@ class AppStatus extends Equatable { /// URL for Android app updates. final String androidUpdateUrl; - /// Converts this [AppStatus] instance to a JSON map. - Map toJson() => _$AppStatusToJson(this); + /// Converts this [UpdateConfig] instance to JSON data. + Map toJson() => _$UpdateConfigToJson(this); + + @override + List get props => [ + latestAppVersion, + isLatestVersionOnly, + iosUpdateUrl, + androidUpdateUrl, + ]; - /// Creates a new [AppStatus] instance with specified changes. - AppStatus copyWith({ - bool? isUnderMaintenance, + /// Creates a copy of this [UpdateConfig] but with the given fields + /// replaced with the new values. + UpdateConfig copyWith({ String? latestAppVersion, bool? isLatestVersionOnly, String? iosUpdateUrl, String? androidUpdateUrl, }) { - return AppStatus( - isUnderMaintenance: isUnderMaintenance ?? this.isUnderMaintenance, + return UpdateConfig( latestAppVersion: latestAppVersion ?? this.latestAppVersion, isLatestVersionOnly: isLatestVersionOnly ?? this.isLatestVersionOnly, iosUpdateUrl: iosUpdateUrl ?? this.iosUpdateUrl, androidUpdateUrl: androidUpdateUrl ?? this.androidUpdateUrl, ); } - - @override - List get props => [ - isUnderMaintenance, - latestAppVersion, - isLatestVersionOnly, - iosUpdateUrl, - androidUpdateUrl, - ]; - - @override - bool get stringify => true; } diff --git a/lib/src/models/config/app_status.g.dart b/lib/src/models/config/update_config.g.dart similarity index 52% rename from lib/src/models/config/app_status.g.dart rename to lib/src/models/config/update_config.g.dart index 1fd7bcbe..66cee4a8 100644 --- a/lib/src/models/config/app_status.g.dart +++ b/lib/src/models/config/update_config.g.dart @@ -1,16 +1,15 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'app_status.dart'; +part of 'update_config.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** -AppStatus _$AppStatusFromJson( +UpdateConfig _$UpdateConfigFromJson( Map json, -) => $checkedCreate('AppStatus', json, ($checkedConvert) { - final val = AppStatus( - isUnderMaintenance: $checkedConvert('isUnderMaintenance', (v) => v as bool), +) => $checkedCreate('UpdateConfig', json, ($checkedConvert) { + final val = UpdateConfig( latestAppVersion: $checkedConvert('latestAppVersion', (v) => v as String), isLatestVersionOnly: $checkedConvert( 'isLatestVersionOnly', @@ -22,10 +21,10 @@ AppStatus _$AppStatusFromJson( return val; }); -Map _$AppStatusToJson(AppStatus instance) => { - 'isUnderMaintenance': instance.isUnderMaintenance, - 'latestAppVersion': instance.latestAppVersion, - 'isLatestVersionOnly': instance.isLatestVersionOnly, - 'iosUpdateUrl': instance.iosUpdateUrl, - 'androidUpdateUrl': instance.androidUpdateUrl, -}; +Map _$UpdateConfigToJson(UpdateConfig instance) => + { + 'latestAppVersion': instance.latestAppVersion, + 'isLatestVersionOnly': instance.isLatestVersionOnly, + 'iosUpdateUrl': instance.iosUpdateUrl, + 'androidUpdateUrl': instance.androidUpdateUrl, + }; diff --git a/lib/src/models/config/user_config.dart b/lib/src/models/config/user_config.dart new file mode 100644 index 00000000..adeef1b3 --- /dev/null +++ b/lib/src/models/config/user_config.dart @@ -0,0 +1,35 @@ +import 'package:core/src/models/config/user_limits_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'user_config.g.dart'; + +/// {@template user_config} +/// A container for all user-related configurations. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class UserConfig extends Equatable { + /// {@macro user_config} + const UserConfig({required this.limits}); + + /// Creates a [UserConfig] from JSON data. + factory UserConfig.fromJson(Map json) => + _$UserConfigFromJson(json); + + /// Role-based quantitative limits for user actions. + final UserLimitsConfig limits; + + /// Converts this [UserConfig] instance to JSON data. + Map toJson() => _$UserConfigToJson(this); + + @override + List get props => [limits]; + + /// Creates a copy of this [UserConfig] but with the given fields + /// replaced with the new values. + UserConfig copyWith({UserLimitsConfig? limits}) { + return UserConfig(limits: limits ?? this.limits); + } +} diff --git a/lib/src/models/config/user_config.g.dart b/lib/src/models/config/user_config.g.dart new file mode 100644 index 00000000..9ce87dec --- /dev/null +++ b/lib/src/models/config/user_config.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +UserConfig _$UserConfigFromJson(Map json) => + $checkedCreate('UserConfig', json, ($checkedConvert) { + final val = UserConfig( + limits: $checkedConvert( + 'limits', + (v) => UserLimitsConfig.fromJson(v as Map), + ), + ); + return val; + }); + +Map _$UserConfigToJson(UserConfig instance) => + {'limits': instance.limits.toJson()}; diff --git a/lib/src/models/config/user_limits_config.dart b/lib/src/models/config/user_limits_config.dart new file mode 100644 index 00000000..c871b7ef --- /dev/null +++ b/lib/src/models/config/user_limits_config.dart @@ -0,0 +1,74 @@ +import 'package:core/src/enums/app_user_role.dart'; +import 'package:core/src/models/config/saved_filter_limits.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'user_limits_config.g.dart'; + +/// {@template user_limits_config} +/// Defines role-based quantitative limits for user actions and preferences. +/// +/// This model uses a map-based structure where the key is the [AppUserRole] +/// and the value is the specific limit for that role, ensuring a scalable +/// and maintainable configuration for user-related constraints. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class UserLimitsConfig extends Equatable { + /// {@macro user_limits_config} + const UserLimitsConfig({ + required this.followedItems, + required this.savedHeadlines, + required this.savedHeadlineFilters, + required this.savedSourceFilters, + }); + + /// Creates a [UserLimitsConfig] from JSON data. + factory UserLimitsConfig.fromJson(Map json) => + _$UserLimitsConfigFromJson(json); + + /// Role-based limits for the number of followed items (topics, sources, + /// countries). The limit applies to each category individually. + final Map followedItems; + + /// Role-based limits for the number of saved headlines. + final Map savedHeadlines; + + /// Role-based limits for saved headline filters, using the + /// [SavedFilterLimits] model to define total, pinned, and notification + /// subscription counts. This map defines the limits per user role. + final Map savedHeadlineFilters; + + /// Role-based limits for saved source filters, using the + /// [SavedFilterLimits] model to define total and pinned counts. This map + /// defines the limits per user role. + final Map savedSourceFilters; + + /// Converts this [UserLimitsConfig] instance to JSON data. + Map toJson() => _$UserLimitsConfigToJson(this); + + @override + List get props => [ + followedItems, + savedHeadlines, + savedHeadlineFilters, + savedSourceFilters, + ]; + + /// Creates a copy of this [UserLimitsConfig] but with the given fields + /// replaced with the new values. + UserLimitsConfig copyWith({ + Map? followedItems, + Map? savedHeadlines, + Map? savedHeadlineFilters, + Map? savedSourceFilters, + }) { + return UserLimitsConfig( + followedItems: followedItems ?? this.followedItems, + savedHeadlines: savedHeadlines ?? this.savedHeadlines, + savedHeadlineFilters: savedHeadlineFilters ?? this.savedHeadlineFilters, + savedSourceFilters: savedSourceFilters ?? this.savedSourceFilters, + ); + } +} diff --git a/lib/src/models/config/user_preference_config.g.dart b/lib/src/models/config/user_limits_config.g.dart similarity index 51% rename from lib/src/models/config/user_preference_config.g.dart rename to lib/src/models/config/user_limits_config.g.dart index c0ebcd4e..4f398182 100644 --- a/lib/src/models/config/user_preference_config.g.dart +++ b/lib/src/models/config/user_limits_config.g.dart @@ -1,31 +1,31 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'user_preference_config.dart'; +part of 'user_limits_config.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** -UserPreferenceConfig _$UserPreferenceConfigFromJson( +UserLimitsConfig _$UserLimitsConfigFromJson( Map json, -) => $checkedCreate('UserPreferenceConfig', json, ($checkedConvert) { - final val = UserPreferenceConfig( - followedItemsLimit: $checkedConvert( - 'followedItemsLimit', +) => $checkedCreate('UserLimitsConfig', json, ($checkedConvert) { + final val = UserLimitsConfig( + followedItems: $checkedConvert( + 'followedItems', (v) => (v as Map).map( (k, e) => MapEntry($enumDecode(_$AppUserRoleEnumMap, k), (e as num).toInt()), ), ), - savedHeadlinesLimit: $checkedConvert( - 'savedHeadlinesLimit', + savedHeadlines: $checkedConvert( + 'savedHeadlines', (v) => (v as Map).map( (k, e) => MapEntry($enumDecode(_$AppUserRoleEnumMap, k), (e as num).toInt()), ), ), - savedHeadlineFiltersLimit: $checkedConvert( - 'savedHeadlineFiltersLimit', + savedHeadlineFilters: $checkedConvert( + 'savedHeadlineFilters', (v) => (v as Map).map( (k, e) => MapEntry( $enumDecode(_$AppUserRoleEnumMap, k), @@ -33,8 +33,8 @@ UserPreferenceConfig _$UserPreferenceConfigFromJson( ), ), ), - savedSourceFiltersLimit: $checkedConvert( - 'savedSourceFiltersLimit', + savedSourceFilters: $checkedConvert( + 'savedSourceFilters', (v) => (v as Map).map( (k, e) => MapEntry( $enumDecode(_$AppUserRoleEnumMap, k), @@ -46,22 +46,21 @@ UserPreferenceConfig _$UserPreferenceConfigFromJson( return val; }); -Map _$UserPreferenceConfigToJson( - UserPreferenceConfig instance, -) => { - 'followedItemsLimit': instance.followedItemsLimit.map( - (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e), - ), - 'savedHeadlinesLimit': instance.savedHeadlinesLimit.map( - (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e), - ), - 'savedHeadlineFiltersLimit': instance.savedHeadlineFiltersLimit.map( - (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), - ), - 'savedSourceFiltersLimit': instance.savedSourceFiltersLimit.map( - (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), - ), -}; +Map _$UserLimitsConfigToJson(UserLimitsConfig instance) => + { + 'followedItems': instance.followedItems.map( + (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e), + ), + 'savedHeadlines': instance.savedHeadlines.map( + (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e), + ), + 'savedHeadlineFilters': instance.savedHeadlineFilters.map( + (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), + ), + 'savedSourceFilters': instance.savedSourceFilters.map( + (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), + ), + }; const _$AppUserRoleEnumMap = { AppUserRole.premiumUser: 'premiumUser', diff --git a/lib/src/models/config/user_preference_config.dart b/lib/src/models/config/user_preference_config.dart deleted file mode 100644 index 73d35dcf..00000000 --- a/lib/src/models/config/user_preference_config.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:core/src/enums/app_user_role.dart'; -import 'package:core/src/models/config/remote_config.dart'; -import 'package:core/src/models/config/saved_filter_limits.dart'; -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'user_preference_config.g.dart'; - -/// {@template user_preference_config} -/// Defines configuration for all user preferences, including role-based -/// limits for followed items, saved headlines, and saved filters. -/// -/// This model is part of the overall [RemoteConfig]. It uses a map-based -/// structure where the key is the [AppUserRole] and the value is the specific -/// limit for that role, ensuring a scalable and maintainable configuration. -/// {@endtemplate} -@immutable -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class UserPreferenceConfig extends Equatable { - /// {@macro user_preference_config} - const UserPreferenceConfig({ - required this.followedItemsLimit, - required this.savedHeadlinesLimit, - required this.savedHeadlineFiltersLimit, - required this.savedSourceFiltersLimit, - }); - - /// Factory method to create a [UserPreferenceConfig] instance from a JSON map. - factory UserPreferenceConfig.fromJson(Map json) => - _$UserPreferenceConfigFromJson(json); - - /// Role-based limits for the number of followed items (topics, sources, - /// countries). The limit applies to each category individually. - final Map followedItemsLimit; - - /// Role-based limits for the number of saved headlines. - final Map savedHeadlinesLimit; - - /// Role-based limits for saved headline filters, using the - /// [SavedFilterLimits] model to define total, pinned, and notification - /// subscription counts. This map defines the limits per user role. - final Map savedHeadlineFiltersLimit; - - /// Role-based limits for saved source filters, using the - /// [SavedFilterLimits] model to define total and pinned counts. This map - /// defines the limits per user role. - final Map savedSourceFiltersLimit; - - /// Converts this [UserPreferenceConfig] instance to a JSON map. - Map toJson() => _$UserPreferenceConfigToJson(this); - - /// Creates a copy of this [UserPreferenceConfig] but with the given fields - /// replaced with the new values. - UserPreferenceConfig copyWith({ - Map? followedItemsLimit, - Map? savedHeadlinesLimit, - Map? savedHeadlineFiltersLimit, - Map? savedSourceFiltersLimit, - }) { - return UserPreferenceConfig( - followedItemsLimit: followedItemsLimit ?? this.followedItemsLimit, - savedHeadlinesLimit: savedHeadlinesLimit ?? this.savedHeadlinesLimit, - savedHeadlineFiltersLimit: - savedHeadlineFiltersLimit ?? this.savedHeadlineFiltersLimit, - savedSourceFiltersLimit: - savedSourceFiltersLimit ?? this.savedSourceFiltersLimit, - ); - } - - @override - List get props => [ - followedItemsLimit, - savedHeadlinesLimit, - savedHeadlineFiltersLimit, - savedSourceFiltersLimit, - ]; - - @override - bool get stringify => true; -} diff --git a/lib/src/models/entities/headline.dart b/lib/src/models/entities/headline.dart index 83164a12..1b340c3c 100644 --- a/lib/src/models/entities/headline.dart +++ b/lib/src/models/entities/headline.dart @@ -21,7 +21,6 @@ class Headline extends FeedItem { const Headline({ required this.id, required this.title, - required this.excerpt, required this.url, required this.imageUrl, required this.source, @@ -43,9 +42,6 @@ class Headline extends FeedItem { /// Title of the headline. final String title; - /// Excerpt or snippet of the headline content. - final String excerpt; - /// URL to the full article or content. final String url; @@ -94,7 +90,6 @@ class Headline extends FeedItem { List get props => [ id, title, - excerpt, url, imageUrl, createdAt, @@ -115,7 +110,6 @@ class Headline extends FeedItem { Headline copyWith({ String? id, String? title, - String? excerpt, String? url, String? imageUrl, DateTime? createdAt, @@ -129,7 +123,6 @@ class Headline extends FeedItem { return Headline( id: id ?? this.id, title: title ?? this.title, - excerpt: excerpt ?? this.excerpt, url: url ?? this.url, imageUrl: imageUrl ?? this.imageUrl, createdAt: createdAt ?? this.createdAt, diff --git a/lib/src/models/entities/headline.g.dart b/lib/src/models/entities/headline.g.dart index f45c9ff6..2a5cdb86 100644 --- a/lib/src/models/entities/headline.g.dart +++ b/lib/src/models/entities/headline.g.dart @@ -11,7 +11,6 @@ Headline _$HeadlineFromJson(Map json) => final val = Headline( id: $checkedConvert('id', (v) => v as String), title: $checkedConvert('title', (v) => v as String), - excerpt: $checkedConvert('excerpt', (v) => v as String), url: $checkedConvert('url', (v) => v as String), imageUrl: $checkedConvert('imageUrl', (v) => v as String), source: $checkedConvert( @@ -46,7 +45,6 @@ Headline _$HeadlineFromJson(Map json) => Map _$HeadlineToJson(Headline instance) => { 'id': instance.id, 'title': instance.title, - 'excerpt': instance.excerpt, 'url': instance.url, 'imageUrl': instance.imageUrl, 'source': instance.source.toJson(), diff --git a/lib/src/models/push_notifications/push_notification_payload.dart b/lib/src/models/push_notifications/push_notification_payload.dart index f5b201a5..f45812d3 100644 --- a/lib/src/models/push_notifications/push_notification_payload.dart +++ b/lib/src/models/push_notifications/push_notification_payload.dart @@ -1,3 +1,5 @@ +import 'package:core/src/enums/content_type.dart'; +import 'package:core/src/enums/push_notification_subscription_delivery_type.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -8,7 +10,7 @@ part 'push_notification_payload.g.dart'; /// Represents the generic structure of a push notification message. /// /// This model defines the content of a notification, such as its title and -/// body, and includes a flexible `data` map for custom payloads, typically +/// image, and includes a flexible `data` map for custom payloads, typically /// used for deep-linking or passing additional information to the client app. /// {@endtemplate} @immutable @@ -17,8 +19,10 @@ class PushNotificationPayload extends Equatable { /// {@macro push_notification_payload} const PushNotificationPayload({ required this.title, - required this.body, - required this.data, + required this.notificationId, + required this.notificationType, + required this.contentType, + required this.contentId, this.imageUrl, }); @@ -29,38 +33,51 @@ class PushNotificationPayload extends Equatable { /// The title of the notification. final String title; - /// The main body text of the notification. - final String body; + /// The unique identifier of the notification. + final String notificationId; + + /// The type of notification, which determines its delivery characteristics. + final PushNotificationSubscriptionDeliveryType notificationType; + + /// The type of content the notification is related to (e.g., headline, topic). + final ContentType contentType; + + /// The unique identifier of the content associated with the notification. + final String contentId; /// An optional URL for an image to be displayed in the notification. final String? imageUrl; - /// A map of custom key-value data to be sent with the notification. - /// - /// This is commonly used for deep-linking, allowing the app to navigate to - /// a specific screen or content when the notification is tapped. - /// For example: `{'contentType': 'headline', 'id': 'headline-123'}`. - final Map data; - /// Converts this [PushNotificationPayload] instance to JSON data. Map toJson() => _$PushNotificationPayloadToJson(this); @override - List get props => [title, body, imageUrl, data]; + List get props => [ + title, + notificationId, + notificationType, + contentType, + contentId, + imageUrl, + ]; /// Creates a copy of this [PushNotificationPayload] but with the given fields /// replaced with the new values. PushNotificationPayload copyWith({ String? title, - String? body, + String? notificationId, + PushNotificationSubscriptionDeliveryType? notificationType, + ContentType? contentType, + String? contentId, String? imageUrl, - Map? data, }) { return PushNotificationPayload( title: title ?? this.title, - body: body ?? this.body, + notificationId: notificationId ?? this.notificationId, + notificationType: notificationType ?? this.notificationType, + contentType: contentType ?? this.contentType, + contentId: contentId ?? this.contentId, imageUrl: imageUrl ?? this.imageUrl, - data: data ?? this.data, ); } } diff --git a/lib/src/models/push_notifications/push_notification_payload.g.dart b/lib/src/models/push_notifications/push_notification_payload.g.dart index 2356b9b1..3b394242 100644 --- a/lib/src/models/push_notifications/push_notification_payload.g.dart +++ b/lib/src/models/push_notifications/push_notification_payload.g.dart @@ -11,8 +11,16 @@ PushNotificationPayload _$PushNotificationPayloadFromJson( ) => $checkedCreate('PushNotificationPayload', json, ($checkedConvert) { final val = PushNotificationPayload( title: $checkedConvert('title', (v) => v as String), - body: $checkedConvert('body', (v) => v as String), - data: $checkedConvert('data', (v) => v as Map), + notificationId: $checkedConvert('notificationId', (v) => v as String), + notificationType: $checkedConvert( + 'notificationType', + (v) => $enumDecode(_$PushNotificationSubscriptionDeliveryTypeEnumMap, v), + ), + contentType: $checkedConvert( + 'contentType', + (v) => $enumDecode(_$ContentTypeEnumMap, v), + ), + contentId: $checkedConvert('contentId', (v) => v as String), imageUrl: $checkedConvert('imageUrl', (v) => v as String?), ); return val; @@ -22,7 +30,24 @@ Map _$PushNotificationPayloadToJson( PushNotificationPayload instance, ) => { 'title': instance.title, - 'body': instance.body, + 'notificationId': instance.notificationId, + 'notificationType': + _$PushNotificationSubscriptionDeliveryTypeEnumMap[instance + .notificationType]!, + 'contentType': _$ContentTypeEnumMap[instance.contentType]!, + 'contentId': instance.contentId, 'imageUrl': instance.imageUrl, - 'data': instance.data, +}; + +const _$PushNotificationSubscriptionDeliveryTypeEnumMap = { + PushNotificationSubscriptionDeliveryType.breakingOnly: 'breakingOnly', + PushNotificationSubscriptionDeliveryType.dailyDigest: 'dailyDigest', + PushNotificationSubscriptionDeliveryType.weeklyRoundup: 'weeklyRoundup', +}; + +const _$ContentTypeEnumMap = { + ContentType.headline: 'headline', + ContentType.topic: 'topic', + ContentType.source: 'source', + ContentType.country: 'country', }; diff --git a/lib/src/models/user_settings/user_app_settings.dart b/lib/src/models/user_settings/app_settings.dart similarity index 62% rename from lib/src/models/user_settings/user_app_settings.dart rename to lib/src/models/user_settings/app_settings.dart index 77f0f387..6c9c2905 100644 --- a/lib/src/models/user_settings/user_app_settings.dart +++ b/lib/src/models/user_settings/app_settings.dart @@ -1,13 +1,13 @@ import 'package:core/src/models/entities/language.dart'; import 'package:core/src/models/user_settings/display_settings.dart'; -import 'package:core/src/models/user_settings/feed_display_preferences.dart'; +import 'package:core/src/models/user_settings/feed_settings.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; -part 'user_app_settings.g.dart'; +part 'app_settings.g.dart'; -/// {@template user_app_settings} +/// {@template app_settings} /// Represents a collection of user-specific application settings, /// including display preferences, language selection, and feed display options. /// @@ -16,55 +16,55 @@ part 'user_app_settings.g.dart'; /// {@endtemplate} @immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class UserAppSettings extends Equatable { - /// {@macro user_app_settings} +class AppSettings extends Equatable { + /// {@macro app_settings} /// - /// Creates a new instance of [UserAppSettings]. + /// Creates a new instance of [AppSettings]. /// /// An [id] is required, typically the user's unique identifier. /// Provides sensible defaults for nested settings if not specified. - const UserAppSettings({ + const AppSettings({ required this.id, - required this.displaySettings, required this.language, - required this.feedPreferences, + required this.displaySettings, + required this.feedSettings, }); - /// Factory method to create a [UserAppSettings] instance from a JSON map. - factory UserAppSettings.fromJson(Map json) => - _$UserAppSettingsFromJson(json); + /// Factory method to create a [AppSettings] instance from a JSON map. + factory AppSettings.fromJson(Map json) => + _$AppSettingsFromJson(json); /// The unique identifier for the user settings, typically the user's ID. final String id; - /// User-configurable settings related to the application's visual appearance. - final DisplaySettings displaySettings; - /// The selected application language. final Language language; + /// User-configurable settings related to the application's visual appearance. + final DisplaySettings displaySettings; + /// User-configurable settings for how content feeds are displayed. - final FeedDisplayPreferences feedPreferences; + final FeedSettings feedSettings; - /// Converts this [UserAppSettings] instance to a JSON map. - Map toJson() => _$UserAppSettingsToJson(this); + /// Converts this [AppSettings] instance to a JSON map. + Map toJson() => _$AppSettingsToJson(this); @override - List get props => [id, displaySettings, language, feedPreferences]; + List get props => [id, displaySettings, language, feedSettings]; - /// Creates a copy of this [UserAppSettings] but with the given fields + /// Creates a copy of this [AppSettings] but with the given fields /// replaced with the new values. - UserAppSettings copyWith({ + AppSettings copyWith({ String? id, - DisplaySettings? displaySettings, Language? language, - FeedDisplayPreferences? feedPreferences, + DisplaySettings? displaySettings, + FeedSettings? feedSettings, }) { - return UserAppSettings( + return AppSettings( id: id ?? this.id, - displaySettings: displaySettings ?? this.displaySettings, language: language ?? this.language, - feedPreferences: feedPreferences ?? this.feedPreferences, + displaySettings: displaySettings ?? this.displaySettings, + feedSettings: feedSettings ?? this.feedSettings, ); } } diff --git a/lib/src/models/user_settings/user_app_settings.g.dart b/lib/src/models/user_settings/app_settings.g.dart similarity index 61% rename from lib/src/models/user_settings/user_app_settings.g.dart rename to lib/src/models/user_settings/app_settings.g.dart index b39cddce..0b11fcd4 100644 --- a/lib/src/models/user_settings/user_app_settings.g.dart +++ b/lib/src/models/user_settings/app_settings.g.dart @@ -1,35 +1,35 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'user_app_settings.dart'; +part of 'app_settings.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** -UserAppSettings _$UserAppSettingsFromJson(Map json) => - $checkedCreate('UserAppSettings', json, ($checkedConvert) { - final val = UserAppSettings( +AppSettings _$AppSettingsFromJson(Map json) => + $checkedCreate('AppSettings', json, ($checkedConvert) { + final val = AppSettings( id: $checkedConvert('id', (v) => v as String), - displaySettings: $checkedConvert( - 'displaySettings', - (v) => DisplaySettings.fromJson(v as Map), - ), language: $checkedConvert( 'language', (v) => Language.fromJson(v as Map), ), - feedPreferences: $checkedConvert( - 'feedPreferences', - (v) => FeedDisplayPreferences.fromJson(v as Map), + displaySettings: $checkedConvert( + 'displaySettings', + (v) => DisplaySettings.fromJson(v as Map), + ), + feedSettings: $checkedConvert( + 'feedSettings', + (v) => FeedSettings.fromJson(v as Map), ), ); return val; }); -Map _$UserAppSettingsToJson(UserAppSettings instance) => +Map _$AppSettingsToJson(AppSettings instance) => { 'id': instance.id, - 'displaySettings': instance.displaySettings.toJson(), 'language': instance.language.toJson(), - 'feedPreferences': instance.feedPreferences.toJson(), + 'displaySettings': instance.displaySettings.toJson(), + 'feedSettings': instance.feedSettings.toJson(), }; diff --git a/lib/src/models/user_settings/feed_display_preferences.dart b/lib/src/models/user_settings/feed_display_preferences.dart deleted file mode 100644 index 981447fe..00000000 --- a/lib/src/models/user_settings/feed_display_preferences.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:core/core.dart'; -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'feed_display_preferences.g.dart'; - -/// {@template feed_display_preferences} -/// User preferences for how feeds are displayed. -/// {@endtemplate} -@immutable -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class FeedDisplayPreferences extends Equatable { - /// {@macro feed_display_preferences} - const FeedDisplayPreferences({ - required this.headlineDensity, - required this.headlineImageStyle, - required this.showSourceInHeadlineFeed, - required this.showPublishDateInHeadlineFeed, - }); - - /// Creates a [FeedDisplayPreferences] instance from a JSON map. - factory FeedDisplayPreferences.fromJson(Map json) => - _$FeedDisplayPreferencesFromJson(json); - - /// How densely headline information should be presented. - final HeadlineDensity headlineDensity; - - /// How images should be displayed in the headline feed. - final HeadlineImageStyle headlineImageStyle; - - /// Whether to show the source name directly in the headline feed item. - final bool showSourceInHeadlineFeed; - - /// Whether to show the publish date in the headline feed item. - final bool showPublishDateInHeadlineFeed; - - /// Converts this [FeedDisplayPreferences] instance to a JSON map. - Map toJson() => _$FeedDisplayPreferencesToJson(this); - - /// Creates a copy of this [FeedDisplayPreferences] but with the given fields - /// replaced with the new values. - FeedDisplayPreferences copyWith({ - HeadlineDensity? headlineDensity, - HeadlineImageStyle? headlineImageStyle, - bool? showSourceInHeadlineFeed, - bool? showPublishDateInHeadlineFeed, - }) { - return FeedDisplayPreferences( - headlineDensity: headlineDensity ?? this.headlineDensity, - headlineImageStyle: headlineImageStyle ?? this.headlineImageStyle, - showSourceInHeadlineFeed: - showSourceInHeadlineFeed ?? this.showSourceInHeadlineFeed, - showPublishDateInHeadlineFeed: - showPublishDateInHeadlineFeed ?? this.showPublishDateInHeadlineFeed, - ); - } - - @override - List get props => [ - headlineDensity, - headlineImageStyle, - showSourceInHeadlineFeed, - showPublishDateInHeadlineFeed, - ]; -} diff --git a/lib/src/models/user_settings/feed_display_preferences.g.dart b/lib/src/models/user_settings/feed_display_preferences.g.dart deleted file mode 100644 index 9f3819c4..00000000 --- a/lib/src/models/user_settings/feed_display_preferences.g.dart +++ /dev/null @@ -1,53 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'feed_display_preferences.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -FeedDisplayPreferences _$FeedDisplayPreferencesFromJson( - Map json, -) => $checkedCreate('FeedDisplayPreferences', json, ($checkedConvert) { - final val = FeedDisplayPreferences( - headlineDensity: $checkedConvert( - 'headlineDensity', - (v) => $enumDecode(_$HeadlineDensityEnumMap, v), - ), - headlineImageStyle: $checkedConvert( - 'headlineImageStyle', - (v) => $enumDecode(_$HeadlineImageStyleEnumMap, v), - ), - showSourceInHeadlineFeed: $checkedConvert( - 'showSourceInHeadlineFeed', - (v) => v as bool, - ), - showPublishDateInHeadlineFeed: $checkedConvert( - 'showPublishDateInHeadlineFeed', - (v) => v as bool, - ), - ); - return val; -}); - -Map _$FeedDisplayPreferencesToJson( - FeedDisplayPreferences instance, -) => { - 'headlineDensity': _$HeadlineDensityEnumMap[instance.headlineDensity]!, - 'headlineImageStyle': - _$HeadlineImageStyleEnumMap[instance.headlineImageStyle]!, - 'showSourceInHeadlineFeed': instance.showSourceInHeadlineFeed, - 'showPublishDateInHeadlineFeed': instance.showPublishDateInHeadlineFeed, -}; - -const _$HeadlineDensityEnumMap = { - HeadlineDensity.compact: 'compact', - HeadlineDensity.standard: 'standard', - HeadlineDensity.comfortable: 'comfortable', -}; - -const _$HeadlineImageStyleEnumMap = { - HeadlineImageStyle.hidden: 'hidden', - HeadlineImageStyle.smallThumbnail: 'smallThumbnail', - HeadlineImageStyle.largeThumbnail: 'largeThumbnail', -}; diff --git a/lib/src/models/user_settings/feed_settings.dart b/lib/src/models/user_settings/feed_settings.dart new file mode 100644 index 00000000..7ed33fa4 --- /dev/null +++ b/lib/src/models/user_settings/feed_settings.dart @@ -0,0 +1,58 @@ +import 'package:core/core.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'feed_settings.g.dart'; + +/// {@template feed_settings} +/// User preferences for how feeds are displayed. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class FeedSettings extends Equatable { + /// {@macro feed_settings} + const FeedSettings({ + required this.feedItemDensity, + required this.feedItemImageStyle, + required this.feedItemClickBehavior, + }); + + /// Creates a [FeedSettings] instance from a JSON map. + factory FeedSettings.fromJson(Map json) => + _$FeedSettingsFromJson(json); + + /// How densely feed information should be presented. + final FeedItemDensity feedItemDensity; + + /// How images should be displayed in the feed. + final FeedItemImageStyle feedItemImageStyle; + + /// How feed items links should be opened. + final FeedItemClickBehavior feedItemClickBehavior; + + /// Converts this [FeedSettings] instance to a JSON map. + Map toJson() => _$FeedSettingsToJson(this); + + /// Creates a copy of this [FeedSettings] but with the given fields + /// replaced with the new values. + FeedSettings copyWith({ + FeedItemDensity? feedItemDensity, + FeedItemImageStyle? feedItemImageStyle, + FeedItemClickBehavior? feedItemClickBehavior, + }) { + return FeedSettings( + feedItemDensity: feedItemDensity ?? this.feedItemDensity, + feedItemImageStyle: feedItemImageStyle ?? this.feedItemImageStyle, + feedItemClickBehavior: + feedItemClickBehavior ?? this.feedItemClickBehavior, + ); + } + + @override + List get props => [ + feedItemDensity, + feedItemImageStyle, + feedItemClickBehavior, + ]; +} diff --git a/lib/src/models/user_settings/feed_settings.g.dart b/lib/src/models/user_settings/feed_settings.g.dart new file mode 100644 index 00000000..e08831c4 --- /dev/null +++ b/lib/src/models/user_settings/feed_settings.g.dart @@ -0,0 +1,53 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'feed_settings.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FeedSettings _$FeedSettingsFromJson(Map json) => + $checkedCreate('FeedSettings', json, ($checkedConvert) { + final val = FeedSettings( + feedItemDensity: $checkedConvert( + 'feedItemDensity', + (v) => $enumDecode(_$FeedItemDensityEnumMap, v), + ), + feedItemImageStyle: $checkedConvert( + 'feedItemImageStyle', + (v) => $enumDecode(_$FeedItemImageStyleEnumMap, v), + ), + feedItemClickBehavior: $checkedConvert( + 'feedItemClickBehavior', + (v) => $enumDecode(_$FeedItemClickBehaviorEnumMap, v), + ), + ); + return val; + }); + +Map _$FeedSettingsToJson(FeedSettings instance) => + { + 'feedItemDensity': _$FeedItemDensityEnumMap[instance.feedItemDensity]!, + 'feedItemImageStyle': + _$FeedItemImageStyleEnumMap[instance.feedItemImageStyle]!, + 'feedItemClickBehavior': + _$FeedItemClickBehaviorEnumMap[instance.feedItemClickBehavior]!, + }; + +const _$FeedItemDensityEnumMap = { + FeedItemDensity.compact: 'compact', + FeedItemDensity.standard: 'standard', + FeedItemDensity.comfortable: 'comfortable', +}; + +const _$FeedItemImageStyleEnumMap = { + FeedItemImageStyle.hidden: 'hidden', + FeedItemImageStyle.smallThumbnail: 'smallThumbnail', + FeedItemImageStyle.largeThumbnail: 'largeThumbnail', +}; + +const _$FeedItemClickBehaviorEnumMap = { + FeedItemClickBehavior.defaultBehavior: 'default', + FeedItemClickBehavior.internalNavigation: 'internalNavigation', + FeedItemClickBehavior.externalNavigation: 'externalNavigation', +}; diff --git a/lib/src/models/user_settings/user_settings.dart b/lib/src/models/user_settings/user_settings.dart index 0c1e3ae6..d2711a75 100644 --- a/lib/src/models/user_settings/user_settings.dart +++ b/lib/src/models/user_settings/user_settings.dart @@ -1,3 +1,3 @@ +export 'app_settings.dart'; export 'display_settings.dart'; -export 'feed_display_preferences.dart'; -export 'user_app_settings.dart'; +export 'feed_settings.dart'; diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index b4d5e63a..1c0414e4 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -1 +1,2 @@ export 'json_helpers.dart'; +export 'nullable_date_time_converter.dart'; diff --git a/test/src/enums/feed_item_density_test.dart b/test/src/enums/feed_item_density_test.dart new file mode 100644 index 00000000..38becb59 --- /dev/null +++ b/test/src/enums/feed_item_density_test.dart @@ -0,0 +1,44 @@ +import 'package:core/src/enums/feed_item_density.dart'; +import 'package:test/test.dart'; + +void main() { + group('FeedItemDensity', () { + test('has correct values', () { + expect( + FeedItemDensity.values, + containsAll([ + FeedItemDensity.compact, + FeedItemDensity.standard, + FeedItemDensity.comfortable, + ]), + ); + }); + + test('has correct string values', () { + expect(FeedItemDensity.compact.name, 'compact'); + expect(FeedItemDensity.standard.name, 'standard'); + expect(FeedItemDensity.comfortable.name, 'comfortable'); + }); + + test('can be created from string values', () { + expect(FeedItemDensity.values.byName('compact'), FeedItemDensity.compact); + expect( + FeedItemDensity.values.byName('standard'), + FeedItemDensity.standard, + ); + expect( + FeedItemDensity.values.byName('comfortable'), + FeedItemDensity.comfortable, + ); + }); + + test('has correct toString representation', () { + expect(FeedItemDensity.compact.toString(), 'FeedItemDensity.compact'); + expect(FeedItemDensity.standard.toString(), 'FeedItemDensity.standard'); + expect( + FeedItemDensity.comfortable.toString(), + 'FeedItemDensity.comfortable', + ); + }); + }); +} diff --git a/test/src/enums/feed_item_image_style_test.dart b/test/src/enums/feed_item_image_style_test.dart new file mode 100644 index 00000000..e5601137 --- /dev/null +++ b/test/src/enums/feed_item_image_style_test.dart @@ -0,0 +1,38 @@ +import 'package:core/src/enums/feed_item_image_style.dart'; +import 'package:test/test.dart'; + +void main() { + group('FeedItemImageStyle', () { + test('has correct values', () { + expect( + FeedItemImageStyle.values, + containsAll([ + FeedItemImageStyle.hidden, + FeedItemImageStyle.smallThumbnail, + FeedItemImageStyle.largeThumbnail, + ]), + ); + }); + + test('has correct string values', () { + expect(FeedItemImageStyle.hidden.name, 'hidden'); + expect(FeedItemImageStyle.smallThumbnail.name, 'smallThumbnail'); + expect(FeedItemImageStyle.largeThumbnail.name, 'largeThumbnail'); + }); + + test('can be created from string values', () { + expect( + FeedItemImageStyle.values.byName('hidden'), + FeedItemImageStyle.hidden, + ); + expect( + FeedItemImageStyle.values.byName('smallThumbnail'), + FeedItemImageStyle.smallThumbnail, + ); + expect( + FeedItemImageStyle.values.byName('largeThumbnail'), + FeedItemImageStyle.largeThumbnail, + ); + }); + }); +} diff --git a/test/src/enums/headline_density_test.dart b/test/src/enums/headline_density_test.dart deleted file mode 100644 index d29b64a0..00000000 --- a/test/src/enums/headline_density_test.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:core/src/enums/headline_density.dart'; -import 'package:test/test.dart'; - -void main() { - group('HeadlineDensity', () { - test('has correct values', () { - expect( - HeadlineDensity.values, - containsAll([ - HeadlineDensity.compact, - HeadlineDensity.standard, - HeadlineDensity.comfortable, - ]), - ); - }); - - test('has correct string values', () { - expect(HeadlineDensity.compact.name, 'compact'); - expect(HeadlineDensity.standard.name, 'standard'); - expect(HeadlineDensity.comfortable.name, 'comfortable'); - }); - - test('can be created from string values', () { - expect(HeadlineDensity.values.byName('compact'), HeadlineDensity.compact); - expect( - HeadlineDensity.values.byName('standard'), - HeadlineDensity.standard, - ); - expect( - HeadlineDensity.values.byName('comfortable'), - HeadlineDensity.comfortable, - ); - }); - - test('has correct toString representation', () { - expect(HeadlineDensity.compact.toString(), 'HeadlineDensity.compact'); - expect(HeadlineDensity.standard.toString(), 'HeadlineDensity.standard'); - expect( - HeadlineDensity.comfortable.toString(), - 'HeadlineDensity.comfortable', - ); - }); - }); -} diff --git a/test/src/enums/headline_image_style_test.dart b/test/src/enums/headline_image_style_test.dart deleted file mode 100644 index dc25389f..00000000 --- a/test/src/enums/headline_image_style_test.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:core/src/enums/headline_image_style.dart'; -import 'package:test/test.dart'; - -void main() { - group('HeadlineImageStyle', () { - test('has correct values', () { - expect( - HeadlineImageStyle.values, - containsAll([ - HeadlineImageStyle.hidden, - HeadlineImageStyle.smallThumbnail, - HeadlineImageStyle.largeThumbnail, - ]), - ); - }); - - test('has correct string values', () { - expect(HeadlineImageStyle.hidden.name, 'hidden'); - expect(HeadlineImageStyle.smallThumbnail.name, 'smallThumbnail'); - expect(HeadlineImageStyle.largeThumbnail.name, 'largeThumbnail'); - }); - - test('can be created from string values', () { - expect( - HeadlineImageStyle.values.byName('hidden'), - HeadlineImageStyle.hidden, - ); - expect( - HeadlineImageStyle.values.byName('smallThumbnail'), - HeadlineImageStyle.smallThumbnail, - ); - expect( - HeadlineImageStyle.values.byName('largeThumbnail'), - HeadlineImageStyle.largeThumbnail, - ); - }); - }); -} diff --git a/test/src/enums/in_article_ad_slot_type_test.dart b/test/src/enums/in_article_ad_slot_type_test.dart deleted file mode 100644 index 4d7064a1..00000000 --- a/test/src/enums/in_article_ad_slot_type_test.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:core/src/enums/in_article_ad_slot_type.dart'; -import 'package:test/test.dart'; - -void main() { - group('InArticleAdSlotType', () { - test('has correct string values', () { - expect( - InArticleAdSlotType.aboveArticleContinueReadingButton.name, - 'aboveArticleContinueReadingButton', - ); - expect( - InArticleAdSlotType.belowArticleContinueReadingButton.name, - 'belowArticleContinueReadingButton', - ); - }); - - test('can be created from string values', () { - expect( - InArticleAdSlotType.values.byName('aboveArticleContinueReadingButton'), - InArticleAdSlotType.aboveArticleContinueReadingButton, - ); - expect( - InArticleAdSlotType.values.byName('belowArticleContinueReadingButton'), - InArticleAdSlotType.belowArticleContinueReadingButton, - ); - }); - - test('has correct toString representation', () { - expect( - InArticleAdSlotType.aboveArticleContinueReadingButton.toString(), - 'InArticleAdSlotType.aboveArticleContinueReadingButton', - ); - expect( - InArticleAdSlotType.belowArticleContinueReadingButton.toString(), - 'InArticleAdSlotType.belowArticleContinueReadingButton', - ); - }); - }); -} diff --git a/test/src/models/config/ad_config_test.dart b/test/src/models/config/ad_config_test.dart index 9ffe0e1a..9e53b360 100644 --- a/test/src/models/config/ad_config_test.dart +++ b/test/src/models/config/ad_config_test.dart @@ -3,23 +3,19 @@ import 'package:test/test.dart'; void main() { group('AdConfig', () { - final adConfigFixture = remoteConfigsFixturesData.first.adConfig; + final adConfigFixture = remoteConfigsFixturesData.first.features.ads; test('can be instantiated', () { expect(adConfigFixture, isA()); - expect(adConfigFixture.primaryAdPlatform, AdPlatformType.demo); + expect(adConfigFixture.primaryAdPlatform, AdPlatformType.admob); expect( adConfigFixture.platformAdIdentifiers, isA>(), ); expect(adConfigFixture.feedAdConfiguration, isA()); expect( - adConfigFixture.articleAdConfiguration, - isA(), - ); - expect( - adConfigFixture.interstitialAdConfiguration, - isA(), + adConfigFixture.navigationAdConfiguration, + isA(), ); }); @@ -35,13 +31,13 @@ void main() { feedAdConfiguration: adConfigFixture.feedAdConfiguration.copyWith( enabled: false, ), - interstitialAdConfiguration: adConfigFixture.interstitialAdConfiguration + navigationAdConfiguration: adConfigFixture.navigationAdConfiguration .copyWith(enabled: false), ); expect(updatedConfig.primaryAdPlatform, AdPlatformType.admob); expect(updatedConfig.feedAdConfiguration.enabled, isFalse); - expect(updatedConfig.interstitialAdConfiguration.enabled, isFalse); + expect(updatedConfig.navigationAdConfiguration.enabled, isFalse); expect(updatedConfig, isNot(equals(adConfigFixture))); }); diff --git a/test/src/models/config/ad_platform_identifiers_test.dart b/test/src/models/config/ad_platform_identifiers_test.dart index a15448fc..e63a47d8 100644 --- a/test/src/models/config/ad_platform_identifiers_test.dart +++ b/test/src/models/config/ad_platform_identifiers_test.dart @@ -5,32 +5,22 @@ void main() { group('AdPlatformIdentifiers', () { final admobIdentifiersFixture = remoteConfigsFixturesData .first - .adConfig + .features + .ads .platformAdIdentifiers[AdPlatformType.admob]!; - final demoIdentifiersFixture = remoteConfigsFixturesData - .first - .adConfig - .platformAdIdentifiers[AdPlatformType.demo]!; - test('can be instantiated (AdMob)', () { expect(admobIdentifiersFixture, isA()); expect( - admobIdentifiersFixture.feedNativeAdId, + admobIdentifiersFixture.nativeAdId, 'ca-app-pub-3940256099942544/2247696110', ); expect( - admobIdentifiersFixture.feedToArticleInterstitialAdId, + admobIdentifiersFixture.interstitialAdId, 'ca-app-pub-3940256099942544/1033173712', ); }); - test('can be instantiated (Demo)', () { - expect(demoIdentifiersFixture, isA()); - expect(demoIdentifiersFixture.feedNativeAdId, '_'); - expect(demoIdentifiersFixture.feedToArticleInterstitialAdId, '_'); - }); - test('supports value equality', () { final identifiers1 = admobIdentifiersFixture.copyWith(); final identifiers2 = admobIdentifiersFixture.copyWith(); @@ -39,12 +29,12 @@ void main() { test('copyWith returns a new instance with updated values', () { final updatedIdentifiers = admobIdentifiersFixture.copyWith( - feedNativeAdId: 'new_native_id', - inArticleBannerAdId: 'new_banner_id', + nativeAdId: 'new_native_id', + interstitialAdId: 'new_interstitial_id', ); - expect(updatedIdentifiers.feedNativeAdId, 'new_native_id'); - expect(updatedIdentifiers.inArticleBannerAdId, 'new_banner_id'); + expect(updatedIdentifiers.nativeAdId, 'new_native_id'); + expect(updatedIdentifiers.interstitialAdId, 'new_interstitial_id'); expect(updatedIdentifiers, isNot(equals(admobIdentifiersFixture))); }); @@ -60,12 +50,6 @@ void main() { final result = AdPlatformIdentifiers.fromJson(json); expect(result, equals(admobIdentifiersFixture)); }); - - test('round trip (Demo)', () { - final json = demoIdentifiersFixture.toJson(); - final result = AdPlatformIdentifiers.fromJson(json); - expect(result, equals(demoIdentifiersFixture)); - }); }); }); } diff --git a/test/src/models/config/app_config_test.dart b/test/src/models/config/app_config_test.dart new file mode 100644 index 00000000..11ffa79c --- /dev/null +++ b/test/src/models/config/app_config_test.dart @@ -0,0 +1,57 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('AppConfig', () { + final appConfigFixture = remoteConfigsFixturesData.first.app; + final json = appConfigFixture.toJson(); + + test('can be instantiated', () { + expect(appConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.app; + expect(appConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect( + appConfigFixture.props, + equals([ + appConfigFixture.maintenance, + appConfigFixture.update, + appConfigFixture.general, + ]), + ); + }); + + test('can be created from JSON', () { + final fromJson = AppConfig.fromJson(json); + expect(fromJson, equals(appConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = appConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = appConfigFixture.copyWith( + maintenance: const MaintenanceConfig(isUnderMaintenance: true), + ); + + expect(updatedConfig.maintenance.isUnderMaintenance, isTrue); + expect(updatedConfig.update, equals(appConfigFixture.update)); + expect(updatedConfig, isNot(equals(appConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = appConfigFixture.copyWith(); + expect(copiedConfig, equals(appConfigFixture)); + }, + ); + }); +} diff --git a/test/src/models/config/app_status_test.dart b/test/src/models/config/app_status_test.dart deleted file mode 100644 index 1f73bf71..00000000 --- a/test/src/models/config/app_status_test.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:core/core.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:test/test.dart'; - -void main() { - group('AppStatus', () { - // Derive the test subject from the main remote config fixture. - final appStatusFixture = remoteConfigsFixturesData.first.appStatus; - - test('can be instantiated', () { - expect(appStatusFixture, isNotNull); - }); - - test('supports value equality', () { - final status1 = appStatusFixture.copyWith(); - final status2 = appStatusFixture.copyWith(); - expect(status1, equals(status2)); - }); - - test('copyWith returns a new instance with updated values', () { - final updatedAppStatus = appStatusFixture.copyWith( - isUnderMaintenance: true, - latestAppVersion: '1.0.1', - ); - - expect(updatedAppStatus.isUnderMaintenance, isTrue); - expect(updatedAppStatus.latestAppVersion, '1.0.1'); - expect( - updatedAppStatus.isLatestVersionOnly, - appStatusFixture.isLatestVersionOnly, - ); - expect(updatedAppStatus.iosUpdateUrl, appStatusFixture.iosUpdateUrl); - expect( - updatedAppStatus.androidUpdateUrl, - appStatusFixture.androidUpdateUrl, - ); - expect(updatedAppStatus, isNot(equals(appStatusFixture))); - }); - - test('copyWith returns same instance if no changes', () { - final updatedAppStatus = appStatusFixture.copyWith(); - expect(updatedAppStatus, equals(appStatusFixture)); - }); - - group('fromJson/toJson', () { - test('round trip', () { - final json = appStatusFixture.toJson(); - final result = AppStatus.fromJson(json); - expect(result, equals(appStatusFixture)); - }); - - test( - 'throws FormatException on invalid JSON (missing required field)', - () { - final json = appStatusFixture.toJson()..remove('isLatestVersionOnly'); - expect( - () => AppStatus.fromJson(json), - throwsA(isA()), - ); - }, - ); - }); - }); -} diff --git a/test/src/models/config/article_ad_configuration_test.dart b/test/src/models/config/article_ad_configuration_test.dart deleted file mode 100644 index fa84ad69..00000000 --- a/test/src/models/config/article_ad_configuration_test.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:core/core.dart'; -import 'package:test/test.dart'; - -void main() { - group('ArticleAdConfiguration', () { - final articleAdConfigurationFixture = - remoteConfigsFixturesData.first.adConfig.articleAdConfiguration; - - test('can be instantiated', () { - expect(articleAdConfigurationFixture, isA()); - expect(articleAdConfigurationFixture.enabled, isTrue); - expect( - articleAdConfigurationFixture.bannerAdShape, - BannerAdShape.rectangle, - ); - expect( - articleAdConfigurationFixture.visibleTo, - isA>>(), - ); - }); - - test('supports value equality', () { - final config1 = articleAdConfigurationFixture.copyWith(); - final config2 = articleAdConfigurationFixture.copyWith(); - expect(config1, equals(config2)); - }); - - test('copyWith returns a new instance with updated values', () { - final updatedConfig = articleAdConfigurationFixture.copyWith( - enabled: false, - bannerAdShape: BannerAdShape.square, - visibleTo: { - AppUserRole.guestUser: { - InArticleAdSlotType.aboveArticleContinueReadingButton: false, - }, - }, - ); - - expect(updatedConfig.enabled, isFalse); - expect(updatedConfig.bannerAdShape, BannerAdShape.square); - expect(updatedConfig.visibleTo[AppUserRole.guestUser]!.length, 1); - expect(updatedConfig, isNot(equals(articleAdConfigurationFixture))); - }); - - test('copyWith returns same instance if no changes', () { - final updatedConfig = articleAdConfigurationFixture.copyWith(); - expect(updatedConfig, equals(articleAdConfigurationFixture)); - }); - - group('fromJson/toJson', () { - test('round trip', () { - final json = articleAdConfigurationFixture.toJson(); - final result = ArticleAdConfiguration.fromJson(json); - expect(result, equals(articleAdConfigurationFixture)); - }); - }); - }); -} diff --git a/test/src/models/config/features_config_test.dart b/test/src/models/config/features_config_test.dart new file mode 100644 index 00000000..74329eb8 --- /dev/null +++ b/test/src/models/config/features_config_test.dart @@ -0,0 +1,60 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('FeaturesConfig', () { + final featuresConfigFixture = remoteConfigsFixturesData.first.features; + final json = featuresConfigFixture.toJson(); + + test('can be instantiated', () { + expect(featuresConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.features; + expect(featuresConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect( + featuresConfigFixture.props, + equals([ + featuresConfigFixture.ads, + featuresConfigFixture.pushNotifications, + featuresConfigFixture.feed, + ]), + ); + }); + + test('can be created from JSON', () { + final fromJson = FeaturesConfig.fromJson(json); + expect(fromJson, equals(featuresConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = featuresConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = featuresConfigFixture.copyWith( + ads: featuresConfigFixture.ads.copyWith(enabled: false), + ); + + expect(updatedConfig.ads.enabled, isFalse); + expect( + updatedConfig.pushNotifications, + equals(featuresConfigFixture.pushNotifications), + ); + expect(updatedConfig, isNot(equals(featuresConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = featuresConfigFixture.copyWith(); + expect(copiedConfig, equals(featuresConfigFixture)); + }, + ); + }); +} diff --git a/test/src/models/config/feed_ad_configuration_test.dart b/test/src/models/config/feed_ad_configuration_test.dart index 7dd62d73..34e640e8 100644 --- a/test/src/models/config/feed_ad_configuration_test.dart +++ b/test/src/models/config/feed_ad_configuration_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; void main() { group('FeedAdConfiguration', () { final feedAdConfigurationFixture = - remoteConfigsFixturesData.first.adConfig.feedAdConfiguration; + remoteConfigsFixturesData.first.features.ads.feedAdConfiguration; test('can be instantiated', () { expect(feedAdConfigurationFixture, isA()); diff --git a/test/src/models/config/feed_ad_frequency_config_test.dart b/test/src/models/config/feed_ad_frequency_config_test.dart index da650854..d1a47d90 100644 --- a/test/src/models/config/feed_ad_frequency_config_test.dart +++ b/test/src/models/config/feed_ad_frequency_config_test.dart @@ -6,7 +6,8 @@ void main() { // Access the FeedAdFrequencyConfig from the fixture's visibleTo map final feedAdFrequencyConfigFixture = remoteConfigsFixturesData .first - .adConfig + .features + .ads .feedAdConfiguration .visibleTo[AppUserRole.guestUser]!; diff --git a/test/src/models/config/feed_config_test.dart b/test/src/models/config/feed_config_test.dart new file mode 100644 index 00000000..3b0a6781 --- /dev/null +++ b/test/src/models/config/feed_config_test.dart @@ -0,0 +1,59 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('FeedConfig', () { + final feedConfigFixture = remoteConfigsFixturesData.first.features.feed; + final json = feedConfigFixture.toJson(); + + test('can be instantiated', () { + expect(feedConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.features.feed; + expect(feedConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect( + feedConfigFixture.props, + equals([ + feedConfigFixture.itemClickBehavior, + feedConfigFixture.decorators, + ]), + ); + }); + + test('can be created from JSON', () { + final fromJson = FeedConfig.fromJson(json); + expect(fromJson, equals(feedConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = feedConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = feedConfigFixture.copyWith( + itemClickBehavior: FeedItemClickBehavior.externalNavigation, + ); + + expect( + updatedConfig.itemClickBehavior, + FeedItemClickBehavior.externalNavigation, + ); + expect(updatedConfig.decorators, equals(feedConfigFixture.decorators)); + expect(updatedConfig, isNot(equals(feedConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = feedConfigFixture.copyWith(); + expect(copiedConfig, equals(feedConfigFixture)); + }, + ); + }); +} diff --git a/test/src/models/config/feed_decorator_config_test.dart b/test/src/models/config/feed_decorator_config_test.dart index 07df1fe5..0c4ba652 100644 --- a/test/src/models/config/feed_decorator_config_test.dart +++ b/test/src/models/config/feed_decorator_config_test.dart @@ -5,9 +5,11 @@ void main() { group('FeedDecoratorConfig', () { final remoteConfig = remoteConfigsFixturesData.first; final rateAppDecorator = - remoteConfig.feedDecoratorConfig[FeedDecoratorType.rateApp]!; - final suggestedTopicsDecorator = - remoteConfig.feedDecoratorConfig[FeedDecoratorType.suggestedTopics]!; + remoteConfig.features.feed.decorators[FeedDecoratorType.rateApp]!; + final suggestedTopicsDecorator = remoteConfig + .features + .feed + .decorators[FeedDecoratorType.suggestedTopics]!; test('can be instantiated', () { expect(rateAppDecorator, isA()); diff --git a/test/src/models/config/feed_decorator_role_config_test.dart b/test/src/models/config/feed_decorator_role_config_test.dart index e52ee10d..e4fb261d 100644 --- a/test/src/models/config/feed_decorator_role_config_test.dart +++ b/test/src/models/config/feed_decorator_role_config_test.dart @@ -5,7 +5,7 @@ void main() { group('FeedDecoratorRoleConfig', () { final remoteConfig = remoteConfigsFixturesData.first; final rateAppDecorator = - remoteConfig.feedDecoratorConfig[FeedDecoratorType.rateApp]!; + remoteConfig.features.feed.decorators[FeedDecoratorType.rateApp]!; final guestRoleConfig = rateAppDecorator.visibleTo[AppUserRole.guestUser]!; test('can be instantiated', () { diff --git a/test/src/models/config/general_app_config_test.dart b/test/src/models/config/general_app_config_test.dart new file mode 100644 index 00000000..a906e76c --- /dev/null +++ b/test/src/models/config/general_app_config_test.dart @@ -0,0 +1,59 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('GeneralAppConfig', () { + final generalAppConfigFixture = remoteConfigsFixturesData.first.app.general; + final json = generalAppConfigFixture.toJson(); + + test('can be instantiated', () { + expect(generalAppConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.app.general; + expect(generalAppConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect( + generalAppConfigFixture.props, + equals([ + generalAppConfigFixture.termsOfServiceUrl, + generalAppConfigFixture.privacyPolicyUrl, + ]), + ); + }); + + test('can be created from JSON', () { + final fromJson = GeneralAppConfig.fromJson(json); + expect(fromJson, equals(generalAppConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = generalAppConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = generalAppConfigFixture.copyWith( + termsOfServiceUrl: 'https://example.com/new-terms', + ); + + expect(updatedConfig.termsOfServiceUrl, 'https://example.com/new-terms'); + expect( + updatedConfig.privacyPolicyUrl, + equals(generalAppConfigFixture.privacyPolicyUrl), + ); + expect(updatedConfig, isNot(equals(generalAppConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = generalAppConfigFixture.copyWith(); + expect(copiedConfig, equals(generalAppConfigFixture)); + }, + ); + }); +} diff --git a/test/src/models/config/interstitial_ad_configuration_test.dart b/test/src/models/config/interstitial_ad_configuration_test.dart deleted file mode 100644 index 89b3aa86..00000000 --- a/test/src/models/config/interstitial_ad_configuration_test.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:core/core.dart'; -import 'package:test/test.dart'; - -void main() { - group('InterstitialAdConfiguration', () { - final interstitialAdConfigFixture = - remoteConfigsFixturesData.first.adConfig.interstitialAdConfiguration; - - group('constructor', () { - test('returns correct instance', () { - expect(interstitialAdConfigFixture, isA()); - expect(interstitialAdConfigFixture.enabled, isA()); - expect(interstitialAdConfigFixture.adType, AdType.interstitial); - expect( - interstitialAdConfigFixture.visibleTo, - isA>(), - ); - }); - }); - - group('fromJson/toJson', () { - test('round trip', () { - final json = interstitialAdConfigFixture.toJson(); - final result = InterstitialAdConfiguration.fromJson(json); - expect(result, interstitialAdConfigFixture); - }); - }); - - group('copyWith', () { - test('returns a new instance with updated values', () { - final updatedConfig = interstitialAdConfigFixture.copyWith( - enabled: false, - visibleTo: { - AppUserRole.guestUser: const InterstitialAdFrequencyConfig( - transitionsBeforeShowingInterstitialAds: 10, - ), - }, - ); - - expect(updatedConfig.enabled, false); - expect( - updatedConfig - .visibleTo[AppUserRole.guestUser]! - .transitionsBeforeShowingInterstitialAds, - 10, - ); - expect(updatedConfig, isNot(equals(interstitialAdConfigFixture))); - }); - - test('returns the same instance if no changes are made', () { - final updatedConfig = interstitialAdConfigFixture.copyWith(); - expect(updatedConfig, equals(interstitialAdConfigFixture)); - }); - }); - - group('Equatable', () { - test('instances with the same properties are equal', () { - final config1 = interstitialAdConfigFixture.copyWith(); - final config2 = interstitialAdConfigFixture.copyWith(); - expect(config1, config2); - }); - - test('instances with different properties are not equal', () { - final config1 = interstitialAdConfigFixture.copyWith(); - final config2 = interstitialAdConfigFixture.copyWith(enabled: false); - expect(config1, isNot(equals(config2))); - }); - }); - }); -} diff --git a/test/src/models/config/interstitial_ad_frequency_config_test.dart b/test/src/models/config/interstitial_ad_frequency_config_test.dart deleted file mode 100644 index 2fdc78fa..00000000 --- a/test/src/models/config/interstitial_ad_frequency_config_test.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:core/core.dart'; -import 'package:test/test.dart'; - -void main() { - group('InterstitialAdFrequencyConfig', () { - // Access the InterstitialAdFrequencyConfig from the fixture's visibleTo map - final frequencyConfigFixture = remoteConfigsFixturesData - .first - .adConfig - .interstitialAdConfiguration - .visibleTo[AppUserRole.guestUser]!; - - group('constructor', () { - test('returns correct instance', () { - expect(frequencyConfigFixture, isA()); - expect( - frequencyConfigFixture.transitionsBeforeShowingInterstitialAds, - isA(), - ); - }); - }); - - group('fromJson/toJson', () { - test('round trip', () { - final json = frequencyConfigFixture.toJson(); - final result = InterstitialAdFrequencyConfig.fromJson(json); - expect(result, frequencyConfigFixture); - }); - }); - - group('copyWith', () { - test('returns a new instance with updated values', () { - final updatedConfig = frequencyConfigFixture.copyWith( - transitionsBeforeShowingInterstitialAds: 7, - ); - - expect(updatedConfig.transitionsBeforeShowingInterstitialAds, 7); - expect(updatedConfig, isNot(equals(frequencyConfigFixture))); - }); - - test('returns the same instance if no changes are made', () { - final updatedConfig = frequencyConfigFixture.copyWith(); - expect(updatedConfig, equals(frequencyConfigFixture)); - }); - }); - - group('Equatable', () { - test('instances with the same properties are equal', () { - final config1 = frequencyConfigFixture.copyWith(); - final config2 = frequencyConfigFixture.copyWith(); - expect(config1, config2); - }); - - test('instances with different properties are not equal', () { - final config1 = frequencyConfigFixture.copyWith(); - final config2 = frequencyConfigFixture.copyWith( - transitionsBeforeShowingInterstitialAds: 100, - ); - expect(config1, isNot(equals(config2))); - }); - }); - }); -} diff --git a/test/src/models/config/maintenance_config_test.dart b/test/src/models/config/maintenance_config_test.dart new file mode 100644 index 00000000..39c4cc91 --- /dev/null +++ b/test/src/models/config/maintenance_config_test.dart @@ -0,0 +1,53 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('MaintenanceConfig', () { + final maintenanceConfigFixture = + remoteConfigsFixturesData.first.app.maintenance; + final json = maintenanceConfigFixture.toJson(); + + test('can be instantiated', () { + expect(maintenanceConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.app.maintenance; + expect(maintenanceConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect( + maintenanceConfigFixture.props, + equals([maintenanceConfigFixture.isUnderMaintenance]), + ); + }); + + test('can be created from JSON', () { + final fromJson = MaintenanceConfig.fromJson(json); + expect(fromJson, equals(maintenanceConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = maintenanceConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = maintenanceConfigFixture.copyWith( + isUnderMaintenance: true, + ); + + expect(updatedConfig.isUnderMaintenance, isTrue); + expect(updatedConfig, isNot(equals(maintenanceConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = maintenanceConfigFixture.copyWith(); + expect(copiedConfig, equals(maintenanceConfigFixture)); + }, + ); + }); +} diff --git a/test/src/models/config/push_notification_config_test.dart b/test/src/models/config/push_notification_config_test.dart index e74d711c..01b8579d 100644 --- a/test/src/models/config/push_notification_config_test.dart +++ b/test/src/models/config/push_notification_config_test.dart @@ -6,7 +6,7 @@ void main() { // Retrieve the PushNotificationConfig from the remoteConfigsFixturesData. // This ensures consistency with predefined application configurations. final pushNotificationConfig = - remoteConfigsFixturesData.first.pushNotificationConfig; + remoteConfigsFixturesData.first.features.pushNotifications; // Corresponding JSON representation final json = pushNotificationConfig.toJson(); @@ -14,7 +14,7 @@ void main() { test('supports value equality', () { // Arrange: Create another instance with the same values. final anotherConfig = - remoteConfigsFixturesData.first.pushNotificationConfig; + remoteConfigsFixturesData.first.features.pushNotifications; // Assert: The two instances should be equal. expect(pushNotificationConfig, equals(anotherConfig)); diff --git a/test/src/models/config/remote_config_test.dart b/test/src/models/config/remote_config_test.dart index c22a115e..a0f90efc 100644 --- a/test/src/models/config/remote_config_test.dart +++ b/test/src/models/config/remote_config_test.dart @@ -20,13 +20,11 @@ void main() { remoteConfigFixture.props, equals([ remoteConfigFixture.id, - remoteConfigFixture.userPreferenceConfig, - remoteConfigFixture.adConfig, - remoteConfigFixture.feedDecoratorConfig, - remoteConfigFixture.appStatus, - remoteConfigFixture.pushNotificationConfig, remoteConfigFixture.createdAt, remoteConfigFixture.updatedAt, + remoteConfigFixture.app, + remoteConfigFixture.features, + remoteConfigFixture.user, ]), ); }); @@ -50,35 +48,32 @@ void main() { test('copyWith creates a copy with updated values', () { // Arrange: Define new values for various properties. const newId = 'new_app_config'; - final newAppStatus = remoteConfigFixture.appStatus.copyWith( - isUnderMaintenance: true, + final newApp = remoteConfigFixture.app.copyWith( + maintenance: const MaintenanceConfig(isUnderMaintenance: true), ); - final newPushConfig = remoteConfigFixture.pushNotificationConfig.copyWith( - primaryProvider: PushNotificationProvider.oneSignal, + final newFeatures = remoteConfigFixture.features.copyWith( + pushNotifications: remoteConfigFixture.features.pushNotifications + .copyWith(primaryProvider: PushNotificationProvider.oneSignal), ); // Act: Create a copy with the updated values. final copiedConfig = remoteConfigFixture.copyWith( id: newId, - appStatus: newAppStatus, - pushNotificationConfig: newPushConfig, + app: newApp, + features: newFeatures, ); // Assert: The new instance should have the updated values. expect(copiedConfig.id, equals(newId)); - expect(copiedConfig.appStatus, equals(newAppStatus)); - expect(copiedConfig.pushNotificationConfig, equals(newPushConfig)); + expect(copiedConfig.app, equals(newApp)); + expect(copiedConfig.features, equals(newFeatures)); // Assert: Unchanged properties remain the same. - expect( - copiedConfig.userPreferenceConfig, - equals(remoteConfigFixture.userPreferenceConfig), - ); - expect(copiedConfig.adConfig, equals(remoteConfigFixture.adConfig)); + expect(copiedConfig.user, equals(remoteConfigFixture.user)); // Assert: The original instance remains unchanged. expect(remoteConfigFixture.id, isNot(equals(newId))); - expect(remoteConfigFixture.appStatus, isNot(equals(newAppStatus))); + expect(remoteConfigFixture.app, isNot(equals(newApp))); }); test( diff --git a/test/src/models/config/saved_filter_limits_test.dart b/test/src/models/config/saved_filter_limits_test.dart index d5fdec6c..d3d2db1a 100644 --- a/test/src/models/config/saved_filter_limits_test.dart +++ b/test/src/models/config/saved_filter_limits_test.dart @@ -4,12 +4,14 @@ import 'package:test/test.dart'; void main() { group('SavedFilterLimits', () { final fullModel = remoteConfigsFixturesData[0] - .userPreferenceConfig - .savedHeadlineFiltersLimit[AppUserRole.standardUser]!; + .user + .limits + .savedHeadlineFilters[AppUserRole.standardUser]!; final minimalModel = remoteConfigsFixturesData[0] - .userPreferenceConfig - .savedSourceFiltersLimit[AppUserRole.standardUser]!; + .user + .limits + .savedSourceFilters[AppUserRole.standardUser]!; final fullJson = fullModel.toJson(); final minimalJson = minimalModel.toJson(); diff --git a/test/src/models/config/update_config_test.dart b/test/src/models/config/update_config_test.dart new file mode 100644 index 00000000..bdb4a0f9 --- /dev/null +++ b/test/src/models/config/update_config_test.dart @@ -0,0 +1,63 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('UpdateConfig', () { + final updateConfigFixture = remoteConfigsFixturesData.first.app.update; + final json = updateConfigFixture.toJson(); + + test('can be instantiated', () { + expect(updateConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.app.update; + expect(updateConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect( + updateConfigFixture.props, + equals([ + updateConfigFixture.latestAppVersion, + updateConfigFixture.isLatestVersionOnly, + updateConfigFixture.iosUpdateUrl, + updateConfigFixture.androidUpdateUrl, + ]), + ); + }); + + test('can be created from JSON', () { + final fromJson = UpdateConfig.fromJson(json); + expect(fromJson, equals(updateConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = updateConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = updateConfigFixture.copyWith( + latestAppVersion: '2.0.0', + isLatestVersionOnly: true, + ); + + expect(updatedConfig.latestAppVersion, '2.0.0'); + expect(updatedConfig.isLatestVersionOnly, isTrue); + expect( + updatedConfig.iosUpdateUrl, + equals(updateConfigFixture.iosUpdateUrl), + ); + expect(updatedConfig, isNot(equals(updateConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = updateConfigFixture.copyWith(); + expect(copiedConfig, equals(updateConfigFixture)); + }, + ); + }); +} diff --git a/test/src/models/config/user_config_test.dart b/test/src/models/config/user_config_test.dart new file mode 100644 index 00000000..e27fcdc1 --- /dev/null +++ b/test/src/models/config/user_config_test.dart @@ -0,0 +1,50 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('UserConfig', () { + final userConfigFixture = remoteConfigsFixturesData.first.user; + final json = userConfigFixture.toJson(); + + test('can be instantiated', () { + expect(userConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.user; + expect(userConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect(userConfigFixture.props, equals([userConfigFixture.limits])); + }); + + test('can be created from JSON', () { + final fromJson = UserConfig.fromJson(json); + expect(fromJson, equals(userConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = userConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final newLimits = userConfigFixture.limits.copyWith( + savedHeadlines: {AppUserRole.guestUser: 999}, + ); + final updatedConfig = userConfigFixture.copyWith(limits: newLimits); + + expect(updatedConfig.limits, equals(newLimits)); + expect(updatedConfig, isNot(equals(userConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = userConfigFixture.copyWith(); + expect(copiedConfig, equals(userConfigFixture)); + }, + ); + }); +} diff --git a/test/src/models/config/user_limits_config_test.dart b/test/src/models/config/user_limits_config_test.dart new file mode 100644 index 00000000..761e3ea6 --- /dev/null +++ b/test/src/models/config/user_limits_config_test.dart @@ -0,0 +1,61 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('UserLimitsConfig', () { + final userLimitsConfigFixture = remoteConfigsFixturesData.first.user.limits; + final json = userLimitsConfigFixture.toJson(); + + test('can be instantiated', () { + expect(userLimitsConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.user.limits; + expect(userLimitsConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect( + userLimitsConfigFixture.props, + equals([ + userLimitsConfigFixture.followedItems, + userLimitsConfigFixture.savedHeadlines, + userLimitsConfigFixture.savedHeadlineFilters, + userLimitsConfigFixture.savedSourceFilters, + ]), + ); + }); + + test('can be created from JSON', () { + final fromJson = UserLimitsConfig.fromJson(json); + expect(fromJson, equals(userLimitsConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = userLimitsConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = userLimitsConfigFixture.copyWith( + followedItems: {AppUserRole.guestUser: 100}, + ); + + expect(updatedConfig.followedItems[AppUserRole.guestUser], 100); + expect( + updatedConfig.savedHeadlines, + equals(userLimitsConfigFixture.savedHeadlines), + ); + expect(updatedConfig, isNot(equals(userLimitsConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = userLimitsConfigFixture.copyWith(); + expect(copiedConfig, equals(userLimitsConfigFixture)); + }, + ); + }); +} diff --git a/test/src/models/config/user_preference_config_test.dart b/test/src/models/config/user_preference_config_test.dart deleted file mode 100644 index 88c51f9e..00000000 --- a/test/src/models/config/user_preference_config_test.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:core/core.dart'; -import 'package:test/test.dart'; - -void main() { - group('UserPreferenceConfig', () { - // Derive the test subject from the main remote config fixture. - final userPreferenceConfigFixture = - remoteConfigsFixturesData.first.userPreferenceConfig; - - group('constructor', () { - test('returns correct instance', () { - expect(userPreferenceConfigFixture, isA()); - expect( - userPreferenceConfigFixture.followedItemsLimit[AppUserRole.guestUser], - isA(), - ); - expect( - userPreferenceConfigFixture.savedHeadlinesLimit[AppUserRole - .premiumUser], - isA(), - ); - }); - }); - - group('fromJson/toJson', () { - test('round trip', () { - final json = userPreferenceConfigFixture.toJson(); - final result = UserPreferenceConfig.fromJson(json); - expect(result, userPreferenceConfigFixture); - }); - }); - - group('copyWith', () { - test('returns a new instance with updated values', () { - final newFollowedItemsLimit = Map.of( - userPreferenceConfigFixture.followedItemsLimit, - ); - newFollowedItemsLimit[AppUserRole.guestUser] = 6; - - final newSavedHeadlinesLimit = Map.of( - userPreferenceConfigFixture.savedHeadlinesLimit, - ); - newSavedHeadlinesLimit[AppUserRole.premiumUser] = 101; - - final updatedConfig = userPreferenceConfigFixture.copyWith( - followedItemsLimit: newFollowedItemsLimit, - savedHeadlinesLimit: newSavedHeadlinesLimit, - ); - - expect(updatedConfig.followedItemsLimit[AppUserRole.guestUser], 6); - expect( - updatedConfig.savedHeadlinesLimit[AppUserRole.guestUser], - userPreferenceConfigFixture.savedHeadlinesLimit[AppUserRole - .guestUser], - ); - expect(updatedConfig.savedHeadlinesLimit[AppUserRole.premiumUser], 101); - expect(updatedConfig, isNot(equals(userPreferenceConfigFixture))); - }); - - test('returns the same instance if no changes are made', () { - final updatedConfig = userPreferenceConfigFixture.copyWith(); - expect(updatedConfig, equals(userPreferenceConfigFixture)); - }); - }); - - group('Equatable', () { - test('instances with the same properties are equal', () { - final config1 = userPreferenceConfigFixture.copyWith(); - final config2 = userPreferenceConfigFixture.copyWith(); - expect(config1, config2); - }); - - test('instances with different properties are not equal', () { - final config1 = userPreferenceConfigFixture.copyWith(); - final newFollowedItemsLimit = Map.of( - userPreferenceConfigFixture.followedItemsLimit, - ); - newFollowedItemsLimit[AppUserRole.guestUser] = 99; - final config2 = userPreferenceConfigFixture.copyWith( - followedItemsLimit: newFollowedItemsLimit, - ); - expect(config1, isNot(equals(config2))); - }); - }); - }); -} diff --git a/test/src/models/entities/headline_test.dart b/test/src/models/entities/headline_test.dart index be181d0a..b1d2af23 100644 --- a/test/src/models/entities/headline_test.dart +++ b/test/src/models/entities/headline_test.dart @@ -32,7 +32,6 @@ void main() { expect(copiedHeadline.id, headlineFixture.id); expect(copiedHeadline.title, updatedTitle); - expect(copiedHeadline.excerpt, headlineFixture.excerpt); expect(copiedHeadline.url, updatedUrl); expect(copiedHeadline.imageUrl, headlineFixture.imageUrl); expect(copiedHeadline.source, headlineFixture.source); @@ -63,11 +62,10 @@ void main() { }); test('props list should contain all relevant fields', () { - expect(headlineFixture.props.length, 13); + expect(headlineFixture.props.length, 12); expect(headlineFixture.props, [ headlineFixture.id, headlineFixture.title, - headlineFixture.excerpt, headlineFixture.url, headlineFixture.imageUrl, headlineFixture.createdAt, diff --git a/test/src/models/push_notifications/push_notification_payload_test.dart b/test/src/models/push_notifications/push_notification_payload_test.dart index 0fbdc489..6d3e54b8 100644 --- a/test/src/models/push_notifications/push_notification_payload_test.dart +++ b/test/src/models/push_notifications/push_notification_payload_test.dart @@ -1,38 +1,15 @@ -import 'package:core/src/models/push_notifications/push_notification_payload.dart'; +import 'package:core/core.dart'; import 'package:test/test.dart'; void main() { group('PushNotificationPayload', () { - const title = 'Breaking News'; - const body = 'This is a test breaking news notification.'; - const imageUrl = 'https://example.com/image.jpg'; - const data = { - 'contentType': 'headline', - 'id': 'headline-123', - }; - - const payload = PushNotificationPayload( - title: title, - body: body, - imageUrl: imageUrl, - data: data, - ); - - final json = { - 'title': title, - 'body': body, - 'imageUrl': imageUrl, - 'data': data, - }; + // Use a fixture to ensure consistency and avoid manual setup. + final payload = inAppNotificationsFixturesData.first.payload; + final json = payload.toJson(); test('supports value equality', () { // Arrange: Create another instance with the same values. - const anotherPayload = PushNotificationPayload( - title: title, - body: body, - imageUrl: imageUrl, - data: data, - ); + final anotherPayload = payload.copyWith(); // Assert: The two instances should be equal. expect(payload, equals(anotherPayload)); @@ -40,7 +17,17 @@ void main() { test('props are correct', () { // Assert: The props list should contain all the fields. - expect(payload.props, equals([title, body, imageUrl, data])); + expect( + payload.props, + equals([ + payload.title, + payload.notificationId, + payload.notificationType, + payload.contentType, + payload.contentId, + payload.imageUrl, + ]), + ); }); test('can be created from JSON', () { @@ -62,20 +49,22 @@ void main() { test('copyWith creates a copy with updated values', () { // Arrange: Define the updated values. const newTitle = 'Updated News'; - const newBody = 'Updated body content.'; + const newContentId = 'new-content-id'; // Act: Create a copy with the updated values. - final copiedPayload = payload.copyWith(title: newTitle, body: newBody); + final copiedPayload = payload.copyWith( + title: newTitle, + contentId: newContentId, + ); // Assert: The new instance should have the updated values. expect(copiedPayload.title, equals(newTitle)); - expect(copiedPayload.body, equals(newBody)); - expect(copiedPayload.imageUrl, equals(imageUrl)); - expect(copiedPayload.data, equals(data)); + expect(copiedPayload.contentId, equals(newContentId)); + expect(copiedPayload.imageUrl, equals(payload.imageUrl)); + expect(copiedPayload.notificationId, equals(payload.notificationId)); // Assert: The original instance should remain unchanged. - expect(payload.title, equals(title)); - expect(payload.body, equals(body)); + expect(payload.title, isNot(equals(newTitle))); }); test( diff --git a/test/src/models/user_settings/user_app_settings_test.dart b/test/src/models/user_settings/app_settings_test.dart similarity index 60% rename from test/src/models/user_settings/user_app_settings_test.dart rename to test/src/models/user_settings/app_settings_test.dart index e00979e8..2233a662 100644 --- a/test/src/models/user_settings/user_app_settings_test.dart +++ b/test/src/models/user_settings/app_settings_test.dart @@ -2,35 +2,35 @@ import 'package:core/core.dart'; import 'package:test/test.dart'; void main() { - group('UserAppSettings', () { - final userAppSettingsFixture = userAppSettingsFixturesData.first; + group('AppSettings', () { + final appSettingsFixture = appSettingsFixturesData.first; test('supports value equality', () { - final settings1 = userAppSettingsFixture.copyWith(); - final settings2 = userAppSettingsFixture.copyWith(); + final settings1 = appSettingsFixture.copyWith(); + final settings2 = appSettingsFixture.copyWith(); expect(settings1, equals(settings2)); }); test('props are correct', () { expect( - userAppSettingsFixture.props, + appSettingsFixture.props, equals([ - userAppSettingsFixture.id, - userAppSettingsFixture.displaySettings, - userAppSettingsFixture.language, - userAppSettingsFixture.feedPreferences, + appSettingsFixture.id, + appSettingsFixture.displaySettings, + appSettingsFixture.language, + appSettingsFixture.feedSettings, ]), ); }); group('copyWith', () { test('returns the same object if no arguments are provided', () { - final original = userAppSettingsFixture; + final original = appSettingsFixture; expect(original.copyWith(), equals(original)); }); test('replaces non-null values', () { - final original = userAppSettingsFixture; + final original = appSettingsFixture; final newDisplaySettings = original.displaySettings.copyWith( accentTheme: AppAccentTheme.newsRed, ); @@ -43,28 +43,28 @@ void main() { updatedAt: DateTime.now(), status: ContentStatus.active, ); - final newFeedPreferences = original.feedPreferences.copyWith( - headlineDensity: HeadlineDensity.compact, + final newFeedPreferences = original.feedSettings.copyWith( + feedItemDensity: FeedItemDensity.compact, ); final copied = original.copyWith( displaySettings: newDisplaySettings, language: newLanguage, - feedPreferences: newFeedPreferences, + feedSettings: newFeedPreferences, ); expect(copied.id, original.id); expect(copied.displaySettings, newDisplaySettings); expect(copied.language, newLanguage); - expect(copied.feedPreferences, newFeedPreferences); + expect(copied.feedSettings, newFeedPreferences); }); }); group('fromJson/toJson', () { test('round trip', () { - final original = userAppSettingsFixture; + final original = appSettingsFixture; final json = original.toJson(); - final reconstructed = UserAppSettings.fromJson(json); + final reconstructed = AppSettings.fromJson(json); expect(reconstructed, equals(original)); }); }); diff --git a/test/src/models/user_settings/display_settings_test.dart b/test/src/models/user_settings/display_settings_test.dart index 9178fb49..7fdb63b5 100644 --- a/test/src/models/user_settings/display_settings_test.dart +++ b/test/src/models/user_settings/display_settings_test.dart @@ -5,7 +5,7 @@ void main() { group('DisplaySettings', () { // Derive the test subject from the main app settings fixture. final displaySettingsFixture = - userAppSettingsFixturesData.first.displaySettings; + appSettingsFixturesData.first.displaySettings; test('supports value equality', () { final settings1 = displaySettingsFixture.copyWith(); diff --git a/test/src/models/user_settings/feed_display_preferences_test.dart b/test/src/models/user_settings/feed_display_preferences_test.dart deleted file mode 100644 index cd8951da..00000000 --- a/test/src/models/user_settings/feed_display_preferences_test.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:core/core.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:test/test.dart'; - -void main() { - group('FeedDisplayPreferences', () { - // Derive the test subject from the main app settings fixture. - final feedPreferencesFixture = - userAppSettingsFixturesData.first.feedPreferences; - - test('supports value equality', () { - final preferences1 = feedPreferencesFixture.copyWith(); - final preferences2 = feedPreferencesFixture.copyWith(); - expect(preferences1, equals(preferences2)); - }); - - group('fromJson/toJson', () { - test('round trip', () { - final original = feedPreferencesFixture.copyWith( - headlineDensity: HeadlineDensity.compact, - headlineImageStyle: HeadlineImageStyle.largeThumbnail, - showSourceInHeadlineFeed: false, - showPublishDateInHeadlineFeed: false, - ); - final json = original.toJson(); - final reconstructed = FeedDisplayPreferences.fromJson(json); - expect(reconstructed, equals(original)); - }); - - test( - 'throws CheckedFromJsonException when required fields are missing', - () { - final json = {}; - expect( - () => FeedDisplayPreferences.fromJson(json), - throwsA(isA()), - ); - }, - ); - }); - - group('copyWith', () { - test('returns a new object with updated headlineDensity', () { - final updated = feedPreferencesFixture.copyWith( - headlineDensity: HeadlineDensity.compact, - ); - expect(updated.headlineDensity, HeadlineDensity.compact); - expect( - updated.headlineImageStyle, - feedPreferencesFixture.headlineImageStyle, - ); - }); - - test('returns a new object with updated showSourceInHeadlineFeed', () { - final updated = feedPreferencesFixture.copyWith( - showSourceInHeadlineFeed: false, - ); - expect(updated.showSourceInHeadlineFeed, isFalse); - expect(updated.showPublishDateInHeadlineFeed, isTrue); - }); - }); - }); -} diff --git a/test/src/models/user_settings/feed_settings_test.dart b/test/src/models/user_settings/feed_settings_test.dart new file mode 100644 index 00000000..7475ecaa --- /dev/null +++ b/test/src/models/user_settings/feed_settings_test.dart @@ -0,0 +1,62 @@ +import 'package:core/core.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:test/test.dart'; + +void main() { + group('FeedSettings', () { + // Derive the test subject from the main app settings fixture. + final feedSettingsFixture = appSettingsFixturesData.first.feedSettings; + + test('supports value equality', () { + final preferences1 = feedSettingsFixture.copyWith(); + final preferences2 = feedSettingsFixture.copyWith(); + expect(preferences1, equals(preferences2)); + }); + + group('fromJson/toJson', () { + test('round trip', () { + final original = feedSettingsFixture.copyWith( + feedItemDensity: FeedItemDensity.compact, + feedItemImageStyle: FeedItemImageStyle.largeThumbnail, + ); + final json = original.toJson(); + final reconstructed = FeedSettings.fromJson(json); + expect(reconstructed, equals(original)); + }); + + test( + 'throws CheckedFromJsonException when required fields are missing', + () { + final json = {}; + expect( + () => FeedSettings.fromJson(json), + throwsA(isA()), + ); + }, + ); + }); + + group('copyWith', () { + test('returns a new object with updated headlineDensity', () { + final updated = feedSettingsFixture.copyWith( + feedItemDensity: FeedItemDensity.compact, + ); + expect(updated.feedItemDensity, FeedItemDensity.compact); + expect( + updated.feedItemImageStyle, + feedSettingsFixture.feedItemImageStyle, + ); + }); + + test('returns a new object with updated showSourceInHeadlineFeed', () { + final updated = feedSettingsFixture.copyWith( + feedItemClickBehavior: FeedItemClickBehavior.externalNavigation, + ); + expect( + updated.feedItemClickBehavior, + FeedItemClickBehavior.externalNavigation, + ); + }); + }); + }); +}