diff --git a/lib/app.dart b/lib/app.dart index 5eade5c..1cf2d8e 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; import 'package:method_conf_app/providers/conference_provider.dart'; -import 'package:method_conf_app/providers/sponsor_provider_v2.dart'; +import 'package:method_conf_app/providers/sponsor_provider.dart'; import 'package:provider/provider.dart'; import 'package:method_conf_app/providers/session_provider.dart'; import 'package:method_conf_app/providers/speaker_provider.dart'; -import 'package:method_conf_app/providers/sponsor_provider.dart'; import 'package:method_conf_app/theme.dart'; import 'package:method_conf_app/widgets/app_navigation.dart'; @@ -22,10 +21,9 @@ class App extends StatelessWidget { var s = Provider.of(context, listen: false); return SessionProvider(speakerProvider: s); }), - ChangeNotifierProvider(create: (context) => SponsorProvider()), ChangeNotifierProvider(create: (context) { var c = Provider.of(context, listen: false); - return SponsorProviderV2(conferenceProvider: c); + return SponsorProvider(conferenceProvider: c); }) ], child: MaterialApp( diff --git a/lib/data/umbraco/models/api_link_model.dart b/lib/data/umbraco/models/api_link_model.dart new file mode 100644 index 0000000..d5f7f30 --- /dev/null +++ b/lib/data/umbraco/models/api_link_model.dart @@ -0,0 +1,61 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:method_conf_app/data/umbraco/models/api_content_response_model_base.dart'; +import 'package:method_conf_app/data/umbraco/utils.dart'; +import 'package:method_conf_app/env.dart'; + +part 'api_link_model.g.dart'; + +@JsonSerializable() +class ApiLinkModel { + String? url; + String? queryString; + String? title; + String? target; + String? destinationId; + String? destinationType; + ApiContentRouteModel? route; + @JsonKey(readValue: readLowerCase) + LinkType linkType; + + ApiLinkModel({ + this.url, + this.queryString, + this.title, + this.target, + this.destinationId, + this.destinationType, + this.route, + required this.linkType, + }); + + String? get fullUrl { + final path = route?.path; + final u = url; + return switch (linkType) { + LinkType.content when path != null => _getWithQs(_withSitePrefix(path)), + LinkType.external when u != null => _getWithQs(u), + LinkType.media when u != null => _getWithQs(_withSitePrefix(u)), + _ => null, + }; + } + + String _withSitePrefix(String urlStr) { + return Env.umbracoBaseUrl + urlStr; + } + + String _getWithQs(String urlStr) { + final qs = queryString; + return qs != null ? urlStr + qs : urlStr; + } + + factory ApiLinkModel.fromJson(Map json) => + _$ApiLinkModelFromJson(json); + + Map toJson() => _$ApiLinkModelToJson(this); +} + +enum LinkType { + content, + external, + media, +} diff --git a/lib/data/umbraco/models/sponsor_tier.dart b/lib/data/umbraco/models/sponsor_tier.dart index ec5275b..f8e81ce 100644 --- a/lib/data/umbraco/models/sponsor_tier.dart +++ b/lib/data/umbraco/models/sponsor_tier.dart @@ -1,6 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:method_conf_app/data/umbraco/models/api_block_list_model.dart'; import 'package:method_conf_app/data/umbraco/models/sponsor.dart'; +import 'package:method_conf_app/data/umbraco/utils.dart'; part 'sponsor_tier.g.dart'; @@ -21,10 +22,6 @@ class SponsorTier extends ApiElementModel { Map toJson() => _$SponsorTierToJson(this); } -String readLowerCase(Map json, String key) { - return json[key].toString().toLowerCase(); -} - @JsonSerializable() class SponsorTierProperties { String? title; @@ -33,7 +30,6 @@ class SponsorTierProperties { @JsonKey(name: 'sponsors') ApiBlockListModel? sponsorsBlockList; - @JsonKey(includeToJson: false) List get sponsors => sponsorsBlockList?.items .map((item) => item.content) @@ -41,7 +37,6 @@ class SponsorTierProperties { .toList() ?? []; - @JsonKey(includeToJson: false) List get mobileAppSponsors => sponsors .where((sponsor) => sponsor.properties?.mobileAppSponsor ?? false) .toList(); diff --git a/lib/data/umbraco/models/sponsors.dart b/lib/data/umbraco/models/sponsors.dart index b8e8625..a45d92d 100644 --- a/lib/data/umbraco/models/sponsors.dart +++ b/lib/data/umbraco/models/sponsors.dart @@ -1,6 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:method_conf_app/data/umbraco/models/api_block_list_model.dart'; import 'package:method_conf_app/data/umbraco/models/api_content_response_model_base.dart'; +import 'package:method_conf_app/data/umbraco/models/api_link_model.dart'; import 'package:method_conf_app/data/umbraco/models/sponsor_tier.dart'; part 'sponsors.g.dart'; @@ -31,6 +32,8 @@ class Sponsors extends ApiContentResponseModelBase { class SponsorsProperties { @JsonKey(name: 'tiers') ApiBlockListModel? tiersBlockList; + @JsonKey(name: 'opportunitiesUrl') + List opportunitiesUrlList; @JsonKey(includeToJson: false) List get tiers => @@ -40,7 +43,11 @@ class SponsorsProperties { .toList() ?? []; - SponsorsProperties({this.tiersBlockList}); + @JsonKey(includeToJson: false) + ApiLinkModel? get opportunitiesUrl => opportunitiesUrlList.firstOrNull; + + SponsorsProperties( + {this.tiersBlockList, this.opportunitiesUrlList = const []}); factory SponsorsProperties.fromJson(Map json) => _$SponsorsPropertiesFromJson(json); diff --git a/lib/data/umbraco/utils.dart b/lib/data/umbraco/utils.dart new file mode 100644 index 0000000..c6506fa --- /dev/null +++ b/lib/data/umbraco/utils.dart @@ -0,0 +1,3 @@ +String readLowerCase(Map json, String key) { + return json[key].toString().toLowerCase(); +} diff --git a/lib/models/sponsor.dart b/lib/models/sponsor.dart deleted file mode 100644 index 5d34d9b..0000000 --- a/lib/models/sponsor.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part 'sponsor.g.dart'; - -@JsonSerializable() -class Sponsor { - String? title; - String? url; - String? image; - bool? mobileSponsor; - String? background; - - Sponsor({ - this.title, - this.url, - this.image, - this.mobileSponsor, - this.background, - }); - - factory Sponsor.fromJson(Map json) => - _$SponsorFromJson(json); - - Map toJson() => _$SponsorToJson(this); -} diff --git a/lib/providers/sponsor_provider.dart b/lib/providers/sponsor_provider.dart index 73eeeef..ec51acc 100644 --- a/lib/providers/sponsor_provider.dart +++ b/lib/providers/sponsor_provider.dart @@ -2,69 +2,89 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:http/http.dart' as http; +import 'package:method_conf_app/data/umbraco/get_child_nodes_of_type.dart'; +import 'package:method_conf_app/data/umbraco/models/sponsor_tier.dart'; +import 'package:method_conf_app/data/umbraco/models/sponsors.dart'; +import 'package:method_conf_app/providers/conference_provider.dart'; -import 'package:method_conf_app/env.dart'; -import 'package:method_conf_app/models/sponsor.dart'; - -const sponsorKey = 'app-sponsrs'; +const _sponsorsStorageKey = 'app-sponsors-v2'; class SponsorProvider extends ChangeNotifier { - bool _initialFetched = false; + ConferenceProvider conferenceProvider; - List _sponsors = []; + Sponsors? _sponsors; - List get sponsors { - return _sponsors; - } + Sponsors? get sponsors => _sponsors; - set sponsors(List newSponsors) { - _sponsors = newSponsors; + set sponsors(Sponsors? value) { + _sponsors = value; notifyListeners(); } - List get largeSponsors { - return sponsors.where((s) => s.mobileSponsor!).toList(); + List get sponsorTiers => + sponsors?.properties?.tiers + .where( + (tier) => tier.properties?.mobileAppSponsors.isNotEmpty ?? false) + .toList() ?? + []; + + SponsorProvider({required this.conferenceProvider}); + + Future init() async { + await conferenceProvider.init(enableBackgroundRefresh: false); + + sponsors ??= await load(); + + if (sponsors == null) { + await refresh(); + } else { + refresh(); + } } - List get normalSponsors { - return sponsors.where((s) => !s.mobileSponsor!).toList(); + Future load() async { + var prefs = await SharedPreferences.getInstance(); + + final jsonString = prefs.getString(_sponsorsStorageKey); + return jsonString != null + ? Sponsors.fromJson(json.decode(jsonString)) + : null; } - Future fetchInitialSponsors() async { - if (_initialFetched) { + Future store(Sponsors? newSponsors) async { + var prefs = await SharedPreferences.getInstance(); + + if (newSponsors == null) { + await prefs.remove(_sponsorsStorageKey); return; } - var prefs = await SharedPreferences.getInstance(); + await prefs.setString( + _sponsorsStorageKey, json.encode(newSponsors.toJson())); + } - sponsors = prefs - .getStringList(sponsorKey) - ?.map((s) => Sponsor.fromJson(json.decode(s))) - .toList() ?? - []; + Future fetch() async { + var conference = conferenceProvider.conference; - if (sponsors.isNotEmpty) { - // refresh in background if we found some in storage - fetchSponsors(); - } else { - await fetchSponsors(); + if (conference == null) { + return null; } - _initialFetched = true; - } + var item = + await getFirstChildNodeOfType(nodeId: conference.id, type: 'sponsors'); - Future fetchSponsors() async { - var url = Uri.parse('${Env.methodBaseUrl}/sponsors.json'); - var res = await http.get(url); + if (item is! Sponsors) { + return null; + } - sponsors = (json.decode(res.body)['data'] as List) - .map((s) => Sponsor.fromJson(s)) - .toList(); + return item; + } - var prefs = await SharedPreferences.getInstance(); + Future refresh() async { + final newSponsors = await fetch(); + + sponsors = newSponsors; - var sponsorsJson = sponsors.map((s) => json.encode(s.toJson())).toList(); - await prefs.setStringList(sponsorKey, sponsorsJson); + await store(newSponsors); } } diff --git a/lib/providers/sponsor_provider_v2.dart b/lib/providers/sponsor_provider_v2.dart deleted file mode 100644 index 2a1e0e1..0000000 --- a/lib/providers/sponsor_provider_v2.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:method_conf_app/data/umbraco/get_child_nodes_of_type.dart'; -import 'package:method_conf_app/data/umbraco/models/sponsor_tier.dart'; -import 'package:method_conf_app/data/umbraco/models/sponsors.dart'; -import 'package:method_conf_app/providers/conference_provider.dart'; - -const _sponsorsStorageKey = 'app-sponsors-v2'; - -class SponsorProviderV2 extends ChangeNotifier { - ConferenceProvider conferenceProvider; - - Sponsors? _sponsors; - - Sponsors? get sponsors => _sponsors; - - set sponsors(Sponsors? value) { - _sponsors = value; - notifyListeners(); - } - - List get sponsorTiers => - sponsors?.properties?.tiers - .where( - (tier) => tier.properties?.mobileAppSponsors.isNotEmpty ?? false) - .toList() ?? - []; - - SponsorProviderV2({required this.conferenceProvider}); - - Future init() async { - await conferenceProvider.init(enableBackgroundRefresh: false); - - sponsors ??= await load(); - - if (sponsors == null) { - await refresh(); - } else { - refresh(); - } - } - - Future load() async { - var prefs = await SharedPreferences.getInstance(); - - final jsonString = prefs.getString(_sponsorsStorageKey); - return jsonString != null - ? Sponsors.fromJson(json.decode(jsonString)) - : null; - } - - Future store(Sponsors? newSponsors) async { - var prefs = await SharedPreferences.getInstance(); - - if (newSponsors == null) { - await prefs.remove(_sponsorsStorageKey); - return; - } - - await prefs.setString( - _sponsorsStorageKey, json.encode(newSponsors.toJson())); - } - - Future fetch() async { - var conference = conferenceProvider.conference; - - if (conference == null) { - return null; - } - - var item = - await getFirstChildNodeOfType(nodeId: conference.id, type: 'sponsors'); - - if (item is! Sponsors) { - return null; - } - - return item; - } - - Future refresh() async { - final newSponsors = await fetch(); - - sponsors = newSponsors; - - await store(newSponsors); - } -} diff --git a/lib/screens/sponsors_screen.dart b/lib/screens/sponsors_screen.dart index 9e34fac..b57f3b3 100644 --- a/lib/screens/sponsors_screen.dart +++ b/lib/screens/sponsors_screen.dart @@ -1,13 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:method_conf_app/data/umbraco/models/sponsors.dart'; import 'package:provider/provider.dart'; import 'package:quiver/iterables.dart' show partition; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:method_conf_app/data/umbraco/models/conference.dart'; +import 'package:method_conf_app/providers/conference_provider.dart'; import 'package:method_conf_app/data/umbraco/image_url.dart'; import 'package:method_conf_app/data/umbraco/models/sponsor.dart'; import 'package:method_conf_app/data/umbraco/models/sponsor_tier.dart'; -import 'package:method_conf_app/env.dart'; -import 'package:method_conf_app/providers/sponsor_provider_v2.dart'; +import 'package:method_conf_app/providers/sponsor_provider.dart'; import 'package:method_conf_app/widgets/app_banner.dart'; import 'package:method_conf_app/theme.dart'; import 'package:method_conf_app/utils/utils.dart'; @@ -27,15 +29,15 @@ class _SponsorsScreenState extends State { @override void initState() { super.initState(); - var sponsorProvider = - Provider.of(context, listen: false); + var sponsorProvider = Provider.of(context, listen: false); _sponsorsFuture = sponsorProvider.init(); } @override Widget build(BuildContext context) { - var sponsorProviderV2 = Provider.of(context); + var sponsorProvider = Provider.of(context); + var conferenceProvider = Provider.of(context); return AppScreen( title: 'Sponsors', @@ -43,7 +45,7 @@ class _SponsorsScreenState extends State { future: _sponsorsFuture, child: RefreshIndicator( onRefresh: () async { - await sponsorProviderV2.refresh(); + await sponsorProvider.refresh(); }, child: ListView( padding: const EdgeInsets.all(20), @@ -55,7 +57,7 @@ class _SponsorsScreenState extends State { style: TextStyle(fontSize: 18), ), const SizedBox(height: 15), - ...sponsorProviderV2.sponsorTiers.expand((tier) { + ...sponsorProvider.sponsorTiers.expand((tier) { final title = tier.properties?.title; final sponsors = tier.properties?.mobileAppSponsors ?? []; final logoSizes = @@ -77,7 +79,10 @@ class _SponsorsScreenState extends State { ..._buildMediumSponsors(sponsors), ]; }), - ..._buildBanner(), + ..._buildBanner( + conferenceProvider.conference, + sponsorProvider.sponsors, + ), ], ), ), @@ -107,7 +112,6 @@ class _SponsorsScreenState extends State { flex: 8, child: Center( child: OverflowBox( - // maxHeight: sponsor.mobileSponsor! ? 75 : 65, maxHeight: 75, child: logoUrl != null ? CachedNetworkImage( @@ -165,10 +169,12 @@ class _SponsorsScreenState extends State { }).toList(); } - List _buildBanner() { - var eventDate = DateTime.parse(Env.eventDate); + List _buildBanner(Conference? conference, Sponsors? sponsors) { + final eventDate = conference?.properties?.date; + final opportunitiesUrl = sponsors?.properties?.opportunitiesUrl?.fullUrl; - if (eventDate.isBefore(DateTime.now())) { + if (opportunitiesUrl == null || + (eventDate?.isBefore(DateTime.now()) ?? true)) { return [Container()]; } @@ -177,7 +183,7 @@ class _SponsorsScreenState extends State { AppBanner( text: 'Interested in becoming a sponsor?', buttonText: 'SEE OPPORTUNITIES', - onButtonPress: () => launchUrl(Env.sponsorUrl), + onButtonPress: () => launchUrl(opportunitiesUrl), ), ]; }