diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 6768dd4ca..9361dc20a 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,18 +1,19 @@
+## Description
-### Motivation and Context
+> Please provide a brief summary of the changes made. Please explain why
+> this change was necessary. Was there a problem or an issue this change
+> will address? What will be improved with this change?
-> Why is this change required? What problem does it solve?
+## Changes Made
-### Description
+> Please detail the modifications made. This could include areas such as
+> code, documentation, structure, or formatting.
-> Please describe the changes and how to test.
+## Checklist
-### Checklist
-
-- The branch should be rebased onto the `develop` branch for whole tests with nighties, but it's not required.*
-- The code followed the code style as `make quality` passes with a green tick on the last commit.
-- [ ] Remote configuration properties have been properly documented (if relevant).
-- [ ] The documentation has been updated (if relevant).
-- [ ] Issues are linked to the PR, if any.
-
-\* The project uses Github merge queue feature, which rebases onto the `develop` branch before squashing and merging the PR.
+- [ ] I have followed the project's style guidelines.
+- [ ] I have performed a self-review of my own changes.
+- [ ] I have made corresponding changes to the documentation.
+- [ ] My changes do not generate new warnings.
+- [ ] I have tested my changes and I am confident that it works as expected and doesn't introduce any known regressions.
+- [ ] I have reviewed the contribution guidelines.
\ No newline at end of file
diff --git a/.github/workflows/pr-closure.yml b/.github/workflows/pr-closure.yml
index 02a649d2b..0141c1d73 100644
--- a/.github/workflows/pr-closure.yml
+++ b/.github/workflows/pr-closure.yml
@@ -42,19 +42,6 @@ jobs:
# Remove "feature/" from the head branch
BUILD_NAME=$(echo "$HEAD_BRANCH" | sed 's/feature\///g')
- # Define environment names
- IOS_ENVIRONMENT=$(echo "playsrg-ios-nightly+$BUILD_NAME" | jq -R -r @uri)
- TVOS_ENVIRONMENT=$(echo "playsrg-tvos-nightly+$BUILD_NAME" | jq -R -r @uri)
-
- echo "Working with iOS environment: $IOS_ENVIRONMENT"
- echo "Working with tvOS environment: $TVOS_ENVIRONMENT"
-
- # Get the latest active deployment for iOS
- IOS_DEPLOYMENT=$(curl -s -H "$AUTHORIZATION" "$DEPLOYMENTS_URL?environment=$IOS_ENVIRONMENT" | jq '.[0]')
-
- # Get the latest active deployment for tvOS
- TVOS_DEPLOYMENT=$(curl -s -H "$AUTHORIZATION" "$DEPLOYMENTS_URL?environment=$TVOS_ENVIRONMENT" | jq '.[0]')
-
# Function to fetch log and environment URLs from status URL
fetch_status_urls() {
STATUS_URL=$1
@@ -65,54 +52,43 @@ jobs:
echo "$LOG_URL,$ENVIRONMENT_URL"
}
- if [ "$IOS_DEPLOYMENT" != "null" ]; then
- DEPLOYMENT_ID=$(echo "$IOS_DEPLOYMENT" | jq -r '.id')
- STATUSES_URL=$(echo "$IOS_DEPLOYMENT" | jq -r '.statuses_url')
-
- echo "Working with iOS deployment ID: $DEPLOYMENT_ID"
+ # Function to set inactive state to the latest deployment
+ set_inactive_latest_deployment() {
+ local ENVIRONMENT=$1
+ local ENVIRONMENT_ENCODED=$(echo "$ENVIRONMENT" | jq -R -r @uri)
- URLS=$(fetch_status_urls "$STATUSES_URL")
- IFS=',' read -r LOG_URL ENVIRONMENT_URL <<< "$URLS"
+ # Get the latest active deployment
+ local DEPLOYMENTS_FULL_URL="$DEPLOYMENTS_URL?environment=$ENVIRONMENT_ENCODED"
+ local DEPLOYMENT_DATA=$(curl -s -H "$AUTHORIZATION" "$DEPLOYMENTS_FULL_URL" | jq '.[0]')
- # Mark the latest deployment for iOS nightly as inactive
- DEPLOYMENT_URL="$DEPLOYMENTS_URL/$DEPLOYMENT_ID/statuses"
- BODY="{\"state\": \"inactive\", \"description\": \"The PR is closed.\", \
- \"log_url\": \"$LOG_URL\", \"environment_url\": \"$ENVIRONMENT_URL\"}"
- RESPONSE=$(curl -s -X POST -H "$AUTHORIZATION" -H "$ACCEPT" "$DEPLOYMENT_URL" -d "$BODY")
+ if [ "$DEPLOYMENT_DATA" != "null" ]; then
+ local DEPLOYMENT_ID=$(echo "$DEPLOYMENT_DATA" | jq -r '.id')
+ local STATUSES_URL=$(echo "$DEPLOYMENT_DATA" | jq -r '.statuses_url')
- # Extract information from the response
- RESPONSE_STATE=$(echo "$RESPONSE" | jq -r '.state')
- RESPONSE_ENVIRONMENT=$(echo "$RESPONSE" | jq -r '.environment')
+ echo "Working with $ENVIRONMENT deployment ID: $DEPLOYMENT_ID"
- # Output the information
- echo "-> Update $RESPONSE_ENVIRONMENT latest deployment to $RESPONSE_STATE state."
- else
- IOS_ENVIRONMENT=$(printf '%b' "${IOS_ENVIRONMENT//%/\\x}")
- echo "-> No deployment for $IOS_ENVIRONMENT environment. No update."
- fi
-
- if [ "$TVOS_DEPLOYMENT" != "null" ]; then
- DEPLOYMENT_ID=$(echo "$TVOS_DEPLOYMENT" | jq -r '.id')
- STATUSES_URL=$(echo "$TVOS_DEPLOYMENT" | jq -r '.statuses_url')
-
- echo "Working with tvOS deployment ID: $DEPLOYMENT_ID"
+ local URLS=$(fetch_status_urls "$STATUSES_URL")
+ IFS=',' read -r LOG_URL ENVIRONMENT_URL <<< "$URLS"
- URLS=$(fetch_status_urls "$STATUSES_URL")
- IFS=',' read -r LOG_URL ENVIRONMENT_URL <<< "$URLS"
+ # Mark the latest deployment as inactive
+ local DEPLOYMENT_URL="$DEPLOYMENTS_URL/$DEPLOYMENT_ID/statuses"
+ local BODY="{\"state\": \"inactive\", \"description\": \"The PR is closed.\", \
+ \"log_url\": \"$LOG_URL\", \"environment_url\": \"$ENVIRONMENT_URL\"}"
+ local RESPONSE=$(curl -s -X POST -H "$AUTHORIZATION" -H "$ACCEPT" "$DEPLOYMENT_URL" -d "$BODY")
- # Mark the latest deployment for tvOS nightly as inactive
- DEPLOYMENT_URL="$DEPLOYMENTS_URL/$DEPLOYMENT_ID/statuses"
- BODY="{\"state\": \"inactive\", \"description\": \"The PR is closed.\", \
- \"log_url\": \"$LOG_URL\", \"environment_url\": \"$ENVIRONMENT_URL\"}"
- RESPONSE=$(curl -s -X POST -H "$AUTHORIZATION" -H "$ACCEPT" "$DEPLOYMENT_URL" -d "$BODY")
+ # Extract information from the response
+ local RESPONSE_STATE=$(echo "$RESPONSE" | jq -r '.state')
+ local RESPONSE_ENVIRONMENT=$(echo "$RESPONSE" | jq -r '.environment')
- # Extract information from the response
- RESPONSE_STATE=$(echo "$RESPONSE" | jq -r '.state')
- RESPONSE_ENVIRONMENT=$(echo "$RESPONSE" | jq -r '.environment')
+ # Output the information
+ echo "-> Update $RESPONSE_ENVIRONMENT latest deployment to $RESPONSE_STATE state."
+ else
+ echo "-> No deployment for $ENVIRONMENT environment. No update."
+ fi
+ }
- # Output the information
- echo "-> Update $RESPONSE_ENVIRONMENT latest deployment to $RESPONSE_STATE state."
- else
- TVOS_ENVIRONMENT=$(printf '%b' "${TVOS_ENVIRONMENT//%/\\x}")
- echo "-> No deployment for $TVOS_ENVIRONMENT environment. No update."
- fi
+ # Set inactive state to the latest deployment of branch environments
+ set_inactive_latest_deployment "playsrg-ios-nightly+$BUILD_NAME"
+ set_inactive_latest_deployment "playsrg-tvos-nightly+$BUILD_NAME"
+ set_inactive_latest_deployment "playsrg-ios-beta+$BUILD_NAME"
+ set_inactive_latest_deployment "playsrg-tvos-beta+$BUILD_NAME"
\ No newline at end of file
diff --git a/.issuetracker b/.issuetracker
new file mode 100644
index 000000000..15cb00fda
--- /dev/null
+++ b/.issuetracker
@@ -0,0 +1,11 @@
+# Integration with Issue Tracker
+#
+# (note that '\' need to be escaped).
+
+[issuetracker "GitHub Rule"]
+ regex = "#(\\d+)"
+ url = "https://github.com/srgssr/playsrg-apple/issues/$1"
+
+[issuetracker "Jira Rule"]
+ regex = "(PLAYSRG|PLAY|PLAYRTS|SMAC|ADI)-(\\d+)"
+ url = "https://srgssr-ch.atlassian.net/browse/$1-$2"
diff --git a/Application/Resources/Apps/Play RSI/ApplicationConfiguration.json b/Application/Resources/Apps/Play RSI/ApplicationConfiguration.json
index d326b93a6..ee972a097 100755
--- a/Application/Resources/Apps/Play RSI/ApplicationConfiguration.json
+++ b/Application/Resources/Apps/Play RSI/ApplicationConfiguration.json
@@ -20,7 +20,8 @@
"whatsNewURL": "https://srgssr.github.io/playsrg-apple/releases/release_notes-ios-rsi.html",
"radioChannels": "[{\"uid\":\"rete-uno\",\"name\":\"Rete Uno\",\"resourceUid\":\"rete_uno\",\"songsViewStyle\":\"collapsed\",\"color\":\"#0074C2\",\"secondColor\":\"#54B8EF\"},{\"uid\":\"rete-due\",\"name\":\"Rete Due\",\"resourceUid\":\"rete_due\",\"songsViewStyle\":\"collapsed\",\"color\":\"#06A73B\",\"secondColor\":\"#30E96B\"},{\"uid\":\"rete-tre\",\"name\":\"Rete Tre\",\"resourceUid\":\"rete_tre\",\"songsViewStyle\":\"collapsed\",\"color\":\"#A4BB1B\",\"secondColor\":\"#DEF355\"},{\"uid\":\"podcast\",\"name\":\"Podcast\",\"resourceUid\":\"rsi_podcast\",\"color\":\"#333333\",\"homeSections\":\"radioLatest,radioFavoriteShows,radioLatestEpisodesFromFavorites,radioResumePlayback,radioMostPopular,radioWatchLater,radioAllShows\"}]",
"tvChannels": "[{\"uid\":\"la1\",\"name\":\"LA 1\",\"resourceUid\":\"la1\",\"color\":\"#FF9120\",\"secondColor\":\"#E15100\"},{\"uid\":\"la2\",\"name\":\"LA 2\",\"resourceUid\":\"la2\",\"color\":\"#FFCF2F\",\"secondColor\":\"#F38A0D\"},{\"uid\":\"143932a79bb5a123a646b68b1d1188d7ae493e5b\",\"name\":\"RTS 1\",\"resourceUid\":\"rts_un\",\"color\":\"#00D6F3\",\"secondColor\":\"#00B6F0\",\"titleColor\":\"#161616\"},{\"uid\":\"d7dfff28deee44e1d3c49a3d37d36d492b29671b\",\"name\":\"RTS 2\",\"resourceUid\":\"rts_deux\",\"color\":\"#BB66FF\",\"secondColor\":\"#782EB5\"},{\"uid\":\"5d332a26e06d08eec8ad385d566187df72955623\",\"name\":\"RTS Info\",\"resourceUid\":\"rts_info\",\"color\":\"#3787FF\",\"secondColor\":\"#153567\"},{\"uid\":\"23FFBE1B-65CE-4188-ADD2-C724186C2C9F\",\"name\":\"SRF 1\",\"resourceUid\":\"tv_srf1\",\"color\":\"#C91024\",\"secondColor\":\"#8D0614\"},{\"uid\":\"E4D5AD08-C1E8-46A3-BB58-4875051D60D2\",\"name\":\"SRF zwei\",\"resourceUid\":\"tv_srf2\",\"color\":\"#FFB600\",\"secondColor\":\"#ED7004\",\"titleColor\":\"#161616\",\"hasDarkStatusBar\":true},{\"uid\":\"34c2819e-e715-43d7-9026-40a443152a97\",\"name\":\"SRF info\",\"resourceUid\":\"tv_srf_info\",\"color\":\"#AF001E\",\"secondColor\":\"#830512\"}]",
- "satelliteRadioChannels": "[{\"uid\":\"rsp\",\"name\":\"Radio Swiss Pop\",\"resourceUid\":\"rsp\",\"songsViewStyle\":\"expanded\",\"color\":\"#F01F73\",\"secondColor\":\"#D31A3C\",\"homepageHidden\":true},{\"uid\":\"rsc-it\",\"name\":\"Radio Swiss Classic\",\"resourceUid\":\"rsc\",\"songsViewStyle\":\"expanded\",\"color\":\"#09A1DE\",\"secondColor\":\"#036E99\",\"homepageHidden\":true},{\"uid\":\"rsj\",\"name\":\"Radio Swiss Jazz\",\"resourceUid\":\"rsj\",\"songsViewStyle\":\"expanded\",\"color\":\"#F7B222\",\"secondColor\":\"#CC7A00\",\"homepageHidden\":true}]",
+ "satelliteRadioChannels": "[{\"uid\":\"rsp\",\"name\":\"Radio Swiss Pop\",\"resourceUid\":\"rsp\",\"songsViewStyle\":\"expanded\",\"color\":\"#F01F73\",\"secondColor\":\"#D31A3C\",\"homepageHidden\":true, \"shareURL\":\"https://www.radioswisspop.ch/it\"},{\"uid\":\"rsc-it\",\"name\":\"Radio Swiss Classic\",\"resourceUid\":\"rsc\",\"songsViewStyle\":\"expanded\",\"color\":\"#09A1DE\",\"secondColor\":\"#036E99\",\"homepageHidden\":true, \"shareURL\":\"https://www.radioswissclassic.ch/it\"},{\"uid\":\"rsj\",\"name\":\"Radio Swiss Jazz\",\"resourceUid\":\"rsj\",\"songsViewStyle\":\"expanded\",\"color\":\"#F7B222\",\"secondColor\":\"#CC7A00\",\"homepageHidden\":true, \"shareURL\":\"https://www.radioswissjazz.ch/it\"}]",
+ "topicColors": "{\"urn:rsi:topic:tv:1\":{\"firstColor\":\"#c01232\",\"secondColor\":\"#480010\"},\"urn:rsi:topic:tv:4\":{\"firstColor\":\"#d7b447\",\"secondColor\":\"#b62019\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:7\":{\"firstColor\":\"#da2146\",\"secondColor\":\"#2d38c0\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:8\":{\"firstColor\":\"#cd4023\",\"secondColor\":\"#90062e\"},\"urn:rsi:topic:tv:11\":{\"firstColor\":\"#dea706\",\"secondColor\":\"#bd2e5e\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:40\":{\"firstColor\":\"#44bda8\",\"secondColor\":\"#00324e\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:80\":{\"firstColor\":\"#1f509d\",\"secondColor\":\"#121a37\"},\"urn:rsi:topic:tv:90\":{\"firstColor\":\"#738dae\",\"secondColor\":\"#3a465e\"},\"urn:rsi:topic:tv:100\":{\"firstColor\":\"#d75959\",\"secondColor\":\"#29336c\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:600\":{\"firstColor\":\"#27DCF9\",\"secondColor\":\"#932387\"},\"urn:rsi:topic:tv:6000\":{\"firstColor\":\"#02cde9\",\"secondColor\":\"#011844\"},\"urn:rtr:topic:tv:2d48ba80-566c-4359-9e8d-8d9b2d570e0a\":{\"firstColor\":\"#00A1A1\",\"secondColor\":\"#04575B\"},\"urn:rtr:topic:tv:7d7f21be-6727-4939-9126-5bca25eb3a49\":{\"firstColor\":\"#80D2E3\",\"secondColor\":\"#003D58\"},\"urn:rtr:topic:tv:20e7478f-1ea1-49c3-81c2-5f157d6ff092\":{\"firstColor\":\"#340101\",\"secondColor\":\"#8F0E0F\"},\"urn:rtr:topic:tv:50bb90d6-41af-4bbd-b92c-6ef5db16a9b3\":{\"firstColor\":\"#8A0533\",\"secondColor\":\"#812626\"},\"urn:rtr:topic:tv:c50140e7-5740-4c44-abd0-0f7d9ea68da7\":{\"firstColor\":\"#A6A6A7\",\"secondColor\":\"#2C2B2D\"},\"urn:rtr:topic:tv:dfb7ae6d-cb73-431b-a817-b1663ec2f58a\":{\"firstColor\":\"#00F8CC\",\"secondColor\":\"#018864\"},\"urn:rts:topic:tv:623\":{\"firstColor\":\"#5C845B\",\"secondColor\":\"#16280F\"},\"urn:rts:topic:tv:665\":{\"firstColor\":\"#3787FF\",\"secondColor\":\"#0A1C33\"},\"urn:rts:topic:tv:1095\":{\"firstColor\":\"#F5F500\",\"secondColor\":\"#BEB405\",\"reduceBrightness\":true},\"urn:rts:topic:tv:1353\":{\"firstColor\":\"#084165\",\"secondColor\":\"#140953\"},\"urn:rts:topic:tv:2743\":{\"firstColor\":\"#BCF6FF\",\"secondColor\":\"#00D0EF\",\"reduceBrightness\":true},\"urn:rts:topic:tv:10193\":{\"firstColor\":\"#EB2350\",\"secondColor\":\"#A61637\"},\"urn:rts:topic:tv:54537\":{\"firstColor\":\"#FFE03E\",\"secondColor\":\"#F98E73\",\"reduceBrightness\":true},\"urn:rts:topic:tv:59220\":{\"firstColor\":\"#492b63\",\"secondColor\":\"#271633\"},\"urn:rts:topic:tv:67132\":{\"firstColor\":\"#415FAF\",\"secondColor\":\"#23376B\"},\"urn:srf:topic:tv:1d7d9cfb-6682-4d5b-9e36-322e8fa93c03\":{\"firstColor\":\"#00A4B3\",\"secondColor\":\"#006973\"},\"urn:srf:topic:tv:4acf86dd-7ff7-45d3-baf8-33375340d976\":{\"firstColor\":\"#3f4b70\",\"secondColor\":\"#131a2d\"},\"urn:srf:topic:tv:9a79b1de-cde8-4528-b304-d1ae1363f52f\":{\"firstColor\":\"#836fcd\",\"secondColor\":\"#36343f\"},\"urn:srf:topic:tv:63f937e4-859e-42c4-a430-bdb74dd09645\":{\"firstColor\":\"#4480a2\",\"secondColor\":\"#20182c\"},\"urn:srf:topic:tv:67f812fd-19a3-4c22-9e6b-ec36e65a4703\":{\"firstColor\":\"#bb3966\",\"secondColor\":\"#190406\"},\"urn:srf:topic:tv:593eb926-d892-41ba-8b1f-eccbcfd7f15f\":{\"firstColor\":\"#2bbf9b\",\"secondColor\":\"#02291e\"},\"urn:srf:topic:tv:649e36d7-ff57-41c8-9c1b-7892daf15e78\":{\"firstColor\":\"#FF0037\",\"secondColor\":\"#AF001E\"},\"urn:srf:topic:tv:882cb264-cf81-4a9c-b660-d42519b7ce28\":{\"firstColor\":\"#c91d7d\",\"secondColor\":\"#31041f\"},\"urn:srf:topic:tv:43741c59-317e-458b-ac38-c2b1c065c865\":{\"firstColor\":\"#0075ad\",\"secondColor\":\"#000022\"},\"urn:srf:topic:tv:516421f0-ec89-43ba-823b-1b5ceec262f3\":{\"firstColor\":\"#5FB281\",\"secondColor\":\"#154e60\"},\"urn:srf:topic:tv:641223fa-f112-4d98-8aec-cb22262a1182\":{\"firstColor\":\"#c55cee\",\"secondColor\":\"#0c1c68\"},\"urn:srf:topic:tv:a2d97206-0b85-4226-8afe-06e86ebd05b2\":{\"firstColor\":\"#9fc885\",\"secondColor\":\"#20281a\"},\"urn:srf:topic:tv:a709c610-b275-4c0c-a496-cba304c36712\":{\"firstColor\":\"#b3131d\",\"secondColor\":\"#3e0b14\"},\"urn:srf:topic:tv:b58dcf14-96ac-4046-8676-fd8a942c0e88\":{\"firstColor\":\"#7081b0\",\"secondColor\":\"#202020\"},\"urn:srf:topic:tv:bb7b21e0-1056-4e28-bac3-c610393b5b0f\":{\"firstColor\":\"#3c788e\",\"secondColor\":\"#1b3e48\"},\"urn:srf:topic:tv:e52080fc-f36b-481e-955f-071b6c8d6dc3\":{\"firstColor\":\"#ff6778\",\"secondColor\":\"#920a1a\",\"reduceBrightness\":true},\"urn:srf:topic:tv:fa793c13-bebc-41b9-9710-bf8a34192c15\":{\"firstColor\":\"#baead5\",\"secondColor\":\"#010b40\",\"reduceBrightness\":true}}",
"continuousPlaybackPlayerViewTransitionDuration": 10,
"continuousPlaybackForegroundTransitionDuration": 0,
"continuousPlaybackBackgroundTransitionDuration": 0,
diff --git a/Application/Resources/Apps/Play RSI/it.lproj/Localizable.strings b/Application/Resources/Apps/Play RSI/it.lproj/Localizable.strings
index dad370f37..0dc770275 100755
--- a/Application/Resources/Apps/Play RSI/it.lproj/Localizable.strings
+++ b/Application/Resources/Apps/Play RSI/it.lproj/Localizable.strings
@@ -738,7 +738,8 @@
Error message when a media cannot be opened via Handoff, deep linking or a push notification */
"The media cannot be opened." = "Il media non può essere riprodotto.";
-/* Error message when a page cannot be opened via Handoff, deep linking or a push notification
+/* Error message when a page cannot be opened from a page section title
+ Error message when a page cannot be opened via Handoff, deep linking or a push notification
Error message when a topic cannot be opened via Handoff, deep linking or a push notification */
"The page cannot be opened." = "La pagina non può essere aperta.";
diff --git a/Application/Resources/Apps/Play RTR/ApplicationConfiguration.json b/Application/Resources/Apps/Play RTR/ApplicationConfiguration.json
index 01e39c073..e7c91787e 100755
--- a/Application/Resources/Apps/Play RTR/ApplicationConfiguration.json
+++ b/Application/Resources/Apps/Play RTR/ApplicationConfiguration.json
@@ -18,7 +18,8 @@
"whatsNewURL": "https://srgssr.github.io/playsrg-apple/releases/release_notes-ios-rtr.html",
"radioChannels": "[{\"uid\":\"12fb886e-b7aa-4e55-beb2-45dbc619f3c4\",\"name\":\"Radio RTR\",\"resourceUid\":\"radio_rtr\",\"songsViewStyle\":\"expanded\",\"color\":\"#AF001D\",\"secondColor\":\"#9B001B\"}]",
"tvChannels": "[{\"uid\":\"la1\",\"name\":\"LA 1\",\"resourceUid\":\"la1\",\"color\":\"#FF9120\",\"secondColor\":\"#E15100\"},{\"uid\":\"la2\",\"name\":\"LA 2\",\"resourceUid\":\"la2\",\"color\":\"#FFCF2F\",\"secondColor\":\"#F38A0D\"},{\"uid\":\"f5dc82ed-4564-4223-903f-0bf6a13c5620\",\"name\":\"RTR auf SRF 1\",\"resourceUid\":\"rtr_srf1\",\"color\":\"#C91024\",\"secondColor\":\"#8D0614\"},{\"uid\":\"80bdf859-b58d-421d-bb27-ce1fba4637a7\",\"name\":\"RTR auf SRF Info\",\"resourceUid\":\"rtr_srf_info\",\"color\":\"#AF001E\",\"secondColor\":\"#830512\"},{\"uid\":\"2541c864-f883-4b80-9459-e1026e0e692e\",\"name\":\"RTR auf SRF 2\",\"resourceUid\":\"rtr_srf2\",\"color\":\"#FFB600\",\"secondColor\":\"#ED7004\",\"titleColor\":\"#333333\",\"hasDarkStatusBar\":true},{\"uid\":\"143932a79bb5a123a646b68b1d1188d7ae493e5b\",\"name\":\"RTS 1\",\"resourceUid\":\"rts_un\",\"color\":\"#00D6F3\",\"secondColor\":\"#00B6F0\",\"titleColor\":\"#161616\"},{\"uid\":\"d7dfff28deee44e1d3c49a3d37d36d492b29671b\",\"name\":\"RTS 2\",\"resourceUid\":\"rts_deux\",\"color\":\"#BB66FF\",\"secondColor\":\"#782EB5\"},{\"uid\":\"5d332a26e06d08eec8ad385d566187df72955623\",\"name\":\"RTS Info\",\"resourceUid\":\"rts_info\",\"color\":\"#3787FF\",\"secondColor\":\"#153567\"}]",
- "satelliteRadioChannels": "[{\"uid\":\"rsp\",\"name\":\"Radio Swiss Pop\",\"resourceUid\":\"rsp\",\"songsViewStyle\":\"expanded\",\"color\":\"#F01F73\",\"secondColor\":\"#D31A3C\",\"homepageHidden\":true},{\"uid\":\"rsc-de\",\"name\":\"Radio Swiss Classic\",\"resourceUid\":\"rsc\",\"songsViewStyle\":\"expanded\",\"color\":\"#09A1DE\",\"secondColor\":\"#036E99\",\"homepageHidden\":true},{\"uid\":\"rsj\",\"name\":\"Radio Swiss Jazz\",\"resourceUid\":\"rsj\",\"songsViewStyle\":\"expanded\",\"color\":\"#F7B222\",\"secondColor\":\"#CC7A00\",\"homepageHidden\":true}]",
+ "satelliteRadioChannels": "[{\"uid\":\"rsp\",\"name\":\"Radio Swiss Pop\",\"resourceUid\":\"rsp\",\"songsViewStyle\":\"expanded\",\"color\":\"#F01F73\",\"secondColor\":\"#D31A3C\",\"homepageHidden\":true, \"shareURL\":\"https://www.radioswisspop.ch/de\"},{\"uid\":\"rsc-de\",\"name\":\"Radio Swiss Classic\",\"resourceUid\":\"rsc\",\"songsViewStyle\":\"expanded\",\"color\":\"#09A1DE\",\"secondColor\":\"#036E99\",\"homepageHidden\":true, \"shareURL\":\"https://www.radioswissclassic.ch/de\"},{\"uid\":\"rsj\",\"name\":\"Radio Swiss Jazz\",\"resourceUid\":\"rsj\",\"songsViewStyle\":\"expanded\",\"color\":\"#F7B222\",\"secondColor\":\"#CC7A00\",\"homepageHidden\":true, \"shareURL\":\"https://www.radioswissjazz.ch/de\"}]",
+ "topicColors": "{\"urn:rsi:topic:tv:1\":{\"firstColor\":\"#c01232\",\"secondColor\":\"#480010\"},\"urn:rsi:topic:tv:4\":{\"firstColor\":\"#d7b447\",\"secondColor\":\"#b62019\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:7\":{\"firstColor\":\"#da2146\",\"secondColor\":\"#2d38c0\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:8\":{\"firstColor\":\"#cd4023\",\"secondColor\":\"#90062e\"},\"urn:rsi:topic:tv:11\":{\"firstColor\":\"#dea706\",\"secondColor\":\"#bd2e5e\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:40\":{\"firstColor\":\"#44bda8\",\"secondColor\":\"#00324e\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:80\":{\"firstColor\":\"#1f509d\",\"secondColor\":\"#121a37\"},\"urn:rsi:topic:tv:90\":{\"firstColor\":\"#738dae\",\"secondColor\":\"#3a465e\"},\"urn:rsi:topic:tv:100\":{\"firstColor\":\"#d75959\",\"secondColor\":\"#29336c\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:600\":{\"firstColor\":\"#27DCF9\",\"secondColor\":\"#932387\"},\"urn:rsi:topic:tv:6000\":{\"firstColor\":\"#02cde9\",\"secondColor\":\"#011844\"},\"urn:rtr:topic:tv:2d48ba80-566c-4359-9e8d-8d9b2d570e0a\":{\"firstColor\":\"#00A1A1\",\"secondColor\":\"#04575B\"},\"urn:rtr:topic:tv:7d7f21be-6727-4939-9126-5bca25eb3a49\":{\"firstColor\":\"#80D2E3\",\"secondColor\":\"#003D58\"},\"urn:rtr:topic:tv:20e7478f-1ea1-49c3-81c2-5f157d6ff092\":{\"firstColor\":\"#340101\",\"secondColor\":\"#8F0E0F\"},\"urn:rtr:topic:tv:50bb90d6-41af-4bbd-b92c-6ef5db16a9b3\":{\"firstColor\":\"#8A0533\",\"secondColor\":\"#812626\"},\"urn:rtr:topic:tv:c50140e7-5740-4c44-abd0-0f7d9ea68da7\":{\"firstColor\":\"#A6A6A7\",\"secondColor\":\"#2C2B2D\"},\"urn:rtr:topic:tv:dfb7ae6d-cb73-431b-a817-b1663ec2f58a\":{\"firstColor\":\"#00F8CC\",\"secondColor\":\"#018864\"},\"urn:rts:topic:tv:623\":{\"firstColor\":\"#5C845B\",\"secondColor\":\"#16280F\"},\"urn:rts:topic:tv:665\":{\"firstColor\":\"#3787FF\",\"secondColor\":\"#0A1C33\"},\"urn:rts:topic:tv:1095\":{\"firstColor\":\"#F5F500\",\"secondColor\":\"#BEB405\",\"reduceBrightness\":true},\"urn:rts:topic:tv:1353\":{\"firstColor\":\"#084165\",\"secondColor\":\"#140953\"},\"urn:rts:topic:tv:2743\":{\"firstColor\":\"#BCF6FF\",\"secondColor\":\"#00D0EF\",\"reduceBrightness\":true},\"urn:rts:topic:tv:10193\":{\"firstColor\":\"#EB2350\",\"secondColor\":\"#A61637\"},\"urn:rts:topic:tv:54537\":{\"firstColor\":\"#FFE03E\",\"secondColor\":\"#F98E73\",\"reduceBrightness\":true},\"urn:rts:topic:tv:59220\":{\"firstColor\":\"#492b63\",\"secondColor\":\"#271633\"},\"urn:rts:topic:tv:67132\":{\"firstColor\":\"#415FAF\",\"secondColor\":\"#23376B\"},\"urn:srf:topic:tv:1d7d9cfb-6682-4d5b-9e36-322e8fa93c03\":{\"firstColor\":\"#00A4B3\",\"secondColor\":\"#006973\"},\"urn:srf:topic:tv:4acf86dd-7ff7-45d3-baf8-33375340d976\":{\"firstColor\":\"#3f4b70\",\"secondColor\":\"#131a2d\"},\"urn:srf:topic:tv:9a79b1de-cde8-4528-b304-d1ae1363f52f\":{\"firstColor\":\"#836fcd\",\"secondColor\":\"#36343f\"},\"urn:srf:topic:tv:63f937e4-859e-42c4-a430-bdb74dd09645\":{\"firstColor\":\"#4480a2\",\"secondColor\":\"#20182c\"},\"urn:srf:topic:tv:67f812fd-19a3-4c22-9e6b-ec36e65a4703\":{\"firstColor\":\"#bb3966\",\"secondColor\":\"#190406\"},\"urn:srf:topic:tv:593eb926-d892-41ba-8b1f-eccbcfd7f15f\":{\"firstColor\":\"#2bbf9b\",\"secondColor\":\"#02291e\"},\"urn:srf:topic:tv:649e36d7-ff57-41c8-9c1b-7892daf15e78\":{\"firstColor\":\"#FF0037\",\"secondColor\":\"#AF001E\"},\"urn:srf:topic:tv:882cb264-cf81-4a9c-b660-d42519b7ce28\":{\"firstColor\":\"#c91d7d\",\"secondColor\":\"#31041f\"},\"urn:srf:topic:tv:43741c59-317e-458b-ac38-c2b1c065c865\":{\"firstColor\":\"#0075ad\",\"secondColor\":\"#000022\"},\"urn:srf:topic:tv:516421f0-ec89-43ba-823b-1b5ceec262f3\":{\"firstColor\":\"#5FB281\",\"secondColor\":\"#154e60\"},\"urn:srf:topic:tv:641223fa-f112-4d98-8aec-cb22262a1182\":{\"firstColor\":\"#c55cee\",\"secondColor\":\"#0c1c68\"},\"urn:srf:topic:tv:a2d97206-0b85-4226-8afe-06e86ebd05b2\":{\"firstColor\":\"#9fc885\",\"secondColor\":\"#20281a\"},\"urn:srf:topic:tv:a709c610-b275-4c0c-a496-cba304c36712\":{\"firstColor\":\"#b3131d\",\"secondColor\":\"#3e0b14\"},\"urn:srf:topic:tv:b58dcf14-96ac-4046-8676-fd8a942c0e88\":{\"firstColor\":\"#7081b0\",\"secondColor\":\"#202020\"},\"urn:srf:topic:tv:bb7b21e0-1056-4e28-bac3-c610393b5b0f\":{\"firstColor\":\"#3c788e\",\"secondColor\":\"#1b3e48\"},\"urn:srf:topic:tv:e52080fc-f36b-481e-955f-071b6c8d6dc3\":{\"firstColor\":\"#ff6778\",\"secondColor\":\"#920a1a\",\"reduceBrightness\":true},\"urn:srf:topic:tv:fa793c13-bebc-41b9-9710-bf8a34192c15\":{\"firstColor\":\"#baead5\",\"secondColor\":\"#010b40\",\"reduceBrightness\":true}}",
"continuousPlaybackPlayerViewTransitionDuration": 10,
"continuousPlaybackForegroundTransitionDuration": 0,
"continuousPlaybackBackgroundTransitionDuration": 0,
diff --git a/Application/Resources/Apps/Play RTR/rm.lproj/Localizable.strings b/Application/Resources/Apps/Play RTR/rm.lproj/Localizable.strings
index 94c1ab5d8..1e23768a3 100755
--- a/Application/Resources/Apps/Play RTR/rm.lproj/Localizable.strings
+++ b/Application/Resources/Apps/Play RTR/rm.lproj/Localizable.strings
@@ -309,7 +309,7 @@
"Image credit: %@" = "Maletg:%@";
/* Short text replacing date for a web first content. */
-"In advance" = "prepublicaziun";
+"In advance" = "Prepremiera";
/* Information section header */
"Information" = "Infurmaziun";
@@ -738,7 +738,8 @@
Error message when a media cannot be opened via Handoff, deep linking or a push notification */
"The media cannot be opened." = "Il medium na po betg vegnir avert.";
-/* Error message when a page cannot be opened via Handoff, deep linking or a push notification
+/* Error message when a page cannot be opened from a page section title
+ Error message when a page cannot be opened via Handoff, deep linking or a push notification
Error message when a topic cannot be opened via Handoff, deep linking or a push notification */
"The page cannot be opened." = "La preschentaziun na po betg vegnir averta.";
diff --git a/Application/Resources/Apps/Play RTS/ApplicationConfiguration.json b/Application/Resources/Apps/Play RTS/ApplicationConfiguration.json
index fa7a2ffc1..53c478300 100755
--- a/Application/Resources/Apps/Play RTS/ApplicationConfiguration.json
+++ b/Application/Resources/Apps/Play RTS/ApplicationConfiguration.json
@@ -23,7 +23,8 @@
"whatsNewURL": "https://srgssr.github.io/playsrg-apple/releases/release_notes-ios-rts.html",
"radioChannels": "[{\"uid\":\"a9e7621504c6959e35c3ecbe7f6bed0446cdf8da\",\"name\":\"La 1ère\",\"resourceUid\":\"la1ere\",\"songsViewStyle\":\"collapsed\",\"color\":\"#E20026\",\"secondColor\":\"#5A285B\"},{\"uid\":\"a83f29dee7a5d0d3f9fccdb9c92161b1afb512db\",\"name\":\"Espace 2\",\"resourceUid\":\"espace2\",\"songsViewStyle\":\"collapsed\",\"color\":\"#0071CE\",\"secondColor\":\"#23B7C1\"},{\"uid\":\"8ceb28d9b3f1dd876d1df1780f908578cbefc3d7\",\"name\":\"Couleur 3\",\"resourceUid\":\"couleur3\",\"songsViewStyle\":\"collapsed\",\"color\":\"#E60096\",\"secondColor\":\"#FB5952\"},{\"uid\":\"f8517e5319a515e013551eea15aa114fa5cfbc3a\",\"name\":\"Option Musique\",\"resourceUid\":\"option_musique\",\"songsViewStyle\":\"expanded\",\"color\":\"#00CC99\",\"secondColor\":\"#CBC57A\"},{\"uid\":\"123456789101112131415161718192021222324x\",\"name\":\"Podcasts Originaux\",\"resourceUid\":\"podcasts_originaux\",\"color\":\"#A550F9\",\"homeSections\":\"radioLatestEpisodes,radioShowsAccess,radioFavoriteShows,radioLatestEpisodesFromFavorites,radioResumePlayback,radioMostPopular,radioWatchLater,radioAllShows\"}]",
"tvChannels": "[{\"uid\":\"la1\",\"name\":\"LA 1\",\"resourceUid\":\"la1\",\"color\":\"#FF9120\",\"secondColor\":\"#E15100\"},{\"uid\":\"la2\",\"name\":\"LA 2\",\"resourceUid\":\"la2\",\"color\":\"#FFCF2F\",\"secondColor\":\"#F38A0D\"},{\"uid\":\"143932a79bb5a123a646b68b1d1188d7ae493e5b\",\"name\":\"RTS 1\",\"resourceUid\":\"rts_un\",\"color\":\"#00D6F3\",\"secondColor\":\"#00B6F0\",\"titleColor\":\"#161616\"},{\"uid\":\"d7dfff28deee44e1d3c49a3d37d36d492b29671b\",\"name\":\"RTS 2\",\"resourceUid\":\"rts_deux\",\"color\":\"#BB66FF\",\"secondColor\":\"#782EB5\"},{\"uid\":\"5d332a26e06d08eec8ad385d566187df72955623\",\"name\":\"RTS Info\",\"resourceUid\":\"rts_info\",\"color\":\"#3787FF\",\"secondColor\":\"#153567\"},{\"uid\":\"23FFBE1B-65CE-4188-ADD2-C724186C2C9F\",\"name\":\"SRF 1\",\"resourceUid\":\"tv_srf1\",\"color\":\"#C91024\",\"secondColor\":\"#8D0614\"},{\"uid\":\"E4D5AD08-C1E8-46A3-BB58-4875051D60D2\",\"name\":\"SRF zwei\",\"resourceUid\":\"tv_srf2\",\"color\":\"#FFB600\",\"secondColor\":\"#ED7004\",\"titleColor\":\"#161616\",\"hasDarkStatusBar\":true},{\"uid\":\"34c2819e-e715-43d7-9026-40a443152a97\",\"name\":\"SRF info\",\"resourceUid\":\"tv_srf_info\",\"color\":\"#AF001E\",\"secondColor\":\"#830512\"}]",
- "satelliteRadioChannels": "[{\"uid\":\"rsp\",\"name\":\"Radio Swiss Pop\",\"resourceUid\":\"rsp\",\"songsViewStyle\":\"expanded\",\"color\":\"#F01F73\",\"secondColor\":\"#D31A3C\",\"homepageHidden\":true},{\"uid\":\"rsc-fr\",\"name\":\"Radio Swiss Classic\",\"resourceUid\":\"rsc\",\"songsViewStyle\":\"expanded\",\"color\":\"#09A1DE\",\"secondColor\":\"#036E99\",\"homepageHidden\":true},{\"uid\":\"rsj\",\"name\":\"Radio Swiss Jazz\",\"resourceUid\":\"rsj\",\"songsViewStyle\":\"expanded\",\"color\":\"#F7B222\",\"secondColor\":\"#CC7A00\",\"homepageHidden\":true}]",
+ "satelliteRadioChannels": "[{\"uid\":\"rsp\",\"name\":\"Radio Swiss Pop\",\"resourceUid\":\"rsp\",\"songsViewStyle\":\"expanded\",\"color\":\"#F01F73\",\"secondColor\":\"#D31A3C\",\"homepageHidden\":true, \"shareURL\":\"https://www.radioswisspop.ch/fr\"},{\"uid\":\"rsc-fr\",\"name\":\"Radio Swiss Classic\",\"resourceUid\":\"rsc\",\"songsViewStyle\":\"expanded\",\"color\":\"#09A1DE\",\"secondColor\":\"#036E99\",\"homepageHidden\":true, \"shareURL\":\"https://www.radioswissclassic.ch/fr\"},{\"uid\":\"rsj\",\"name\":\"Radio Swiss Jazz\",\"resourceUid\":\"rsj\",\"songsViewStyle\":\"expanded\",\"color\":\"#F7B222\",\"secondColor\":\"#CC7A00\",\"homepageHidden\":true, \"shareURL\":\"https://www.radioswissjazz.ch/fr\"}]",
+ "topicColors": "{\"urn:rsi:topic:tv:1\":{\"firstColor\":\"#c01232\",\"secondColor\":\"#480010\"},\"urn:rsi:topic:tv:4\":{\"firstColor\":\"#d7b447\",\"secondColor\":\"#b62019\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:7\":{\"firstColor\":\"#da2146\",\"secondColor\":\"#2d38c0\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:8\":{\"firstColor\":\"#cd4023\",\"secondColor\":\"#90062e\"},\"urn:rsi:topic:tv:11\":{\"firstColor\":\"#dea706\",\"secondColor\":\"#bd2e5e\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:40\":{\"firstColor\":\"#44bda8\",\"secondColor\":\"#00324e\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:80\":{\"firstColor\":\"#1f509d\",\"secondColor\":\"#121a37\"},\"urn:rsi:topic:tv:90\":{\"firstColor\":\"#738dae\",\"secondColor\":\"#3a465e\"},\"urn:rsi:topic:tv:100\":{\"firstColor\":\"#d75959\",\"secondColor\":\"#29336c\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:600\":{\"firstColor\":\"#27DCF9\",\"secondColor\":\"#932387\"},\"urn:rsi:topic:tv:6000\":{\"firstColor\":\"#02cde9\",\"secondColor\":\"#011844\"},\"urn:rtr:topic:tv:2d48ba80-566c-4359-9e8d-8d9b2d570e0a\":{\"firstColor\":\"#00A1A1\",\"secondColor\":\"#04575B\"},\"urn:rtr:topic:tv:7d7f21be-6727-4939-9126-5bca25eb3a49\":{\"firstColor\":\"#80D2E3\",\"secondColor\":\"#003D58\"},\"urn:rtr:topic:tv:20e7478f-1ea1-49c3-81c2-5f157d6ff092\":{\"firstColor\":\"#340101\",\"secondColor\":\"#8F0E0F\"},\"urn:rtr:topic:tv:50bb90d6-41af-4bbd-b92c-6ef5db16a9b3\":{\"firstColor\":\"#8A0533\",\"secondColor\":\"#812626\"},\"urn:rtr:topic:tv:c50140e7-5740-4c44-abd0-0f7d9ea68da7\":{\"firstColor\":\"#A6A6A7\",\"secondColor\":\"#2C2B2D\"},\"urn:rtr:topic:tv:dfb7ae6d-cb73-431b-a817-b1663ec2f58a\":{\"firstColor\":\"#00F8CC\",\"secondColor\":\"#018864\"},\"urn:rts:topic:tv:623\":{\"firstColor\":\"#5C845B\",\"secondColor\":\"#16280F\"},\"urn:rts:topic:tv:665\":{\"firstColor\":\"#3787FF\",\"secondColor\":\"#0A1C33\"},\"urn:rts:topic:tv:1095\":{\"firstColor\":\"#F5F500\",\"secondColor\":\"#BEB405\",\"reduceBrightness\":true},\"urn:rts:topic:tv:1353\":{\"firstColor\":\"#084165\",\"secondColor\":\"#140953\"},\"urn:rts:topic:tv:2743\":{\"firstColor\":\"#BCF6FF\",\"secondColor\":\"#00D0EF\",\"reduceBrightness\":true},\"urn:rts:topic:tv:10193\":{\"firstColor\":\"#EB2350\",\"secondColor\":\"#A61637\"},\"urn:rts:topic:tv:54537\":{\"firstColor\":\"#FFE03E\",\"secondColor\":\"#F98E73\",\"reduceBrightness\":true},\"urn:rts:topic:tv:59220\":{\"firstColor\":\"#492b63\",\"secondColor\":\"#271633\"},\"urn:rts:topic:tv:67132\":{\"firstColor\":\"#415FAF\",\"secondColor\":\"#23376B\"},\"urn:srf:topic:tv:1d7d9cfb-6682-4d5b-9e36-322e8fa93c03\":{\"firstColor\":\"#00A4B3\",\"secondColor\":\"#006973\"},\"urn:srf:topic:tv:4acf86dd-7ff7-45d3-baf8-33375340d976\":{\"firstColor\":\"#3f4b70\",\"secondColor\":\"#131a2d\"},\"urn:srf:topic:tv:9a79b1de-cde8-4528-b304-d1ae1363f52f\":{\"firstColor\":\"#836fcd\",\"secondColor\":\"#36343f\"},\"urn:srf:topic:tv:63f937e4-859e-42c4-a430-bdb74dd09645\":{\"firstColor\":\"#4480a2\",\"secondColor\":\"#20182c\"},\"urn:srf:topic:tv:67f812fd-19a3-4c22-9e6b-ec36e65a4703\":{\"firstColor\":\"#bb3966\",\"secondColor\":\"#190406\"},\"urn:srf:topic:tv:593eb926-d892-41ba-8b1f-eccbcfd7f15f\":{\"firstColor\":\"#2bbf9b\",\"secondColor\":\"#02291e\"},\"urn:srf:topic:tv:649e36d7-ff57-41c8-9c1b-7892daf15e78\":{\"firstColor\":\"#FF0037\",\"secondColor\":\"#AF001E\"},\"urn:srf:topic:tv:882cb264-cf81-4a9c-b660-d42519b7ce28\":{\"firstColor\":\"#c91d7d\",\"secondColor\":\"#31041f\"},\"urn:srf:topic:tv:43741c59-317e-458b-ac38-c2b1c065c865\":{\"firstColor\":\"#0075ad\",\"secondColor\":\"#000022\"},\"urn:srf:topic:tv:516421f0-ec89-43ba-823b-1b5ceec262f3\":{\"firstColor\":\"#5FB281\",\"secondColor\":\"#154e60\"},\"urn:srf:topic:tv:641223fa-f112-4d98-8aec-cb22262a1182\":{\"firstColor\":\"#c55cee\",\"secondColor\":\"#0c1c68\"},\"urn:srf:topic:tv:a2d97206-0b85-4226-8afe-06e86ebd05b2\":{\"firstColor\":\"#9fc885\",\"secondColor\":\"#20281a\"},\"urn:srf:topic:tv:a709c610-b275-4c0c-a496-cba304c36712\":{\"firstColor\":\"#b3131d\",\"secondColor\":\"#3e0b14\"},\"urn:srf:topic:tv:b58dcf14-96ac-4046-8676-fd8a942c0e88\":{\"firstColor\":\"#7081b0\",\"secondColor\":\"#202020\"},\"urn:srf:topic:tv:bb7b21e0-1056-4e28-bac3-c610393b5b0f\":{\"firstColor\":\"#3c788e\",\"secondColor\":\"#1b3e48\"},\"urn:srf:topic:tv:e52080fc-f36b-481e-955f-071b6c8d6dc3\":{\"firstColor\":\"#ff6778\",\"secondColor\":\"#920a1a\",\"reduceBrightness\":true},\"urn:srf:topic:tv:fa793c13-bebc-41b9-9710-bf8a34192c15\":{\"firstColor\":\"#baead5\",\"secondColor\":\"#010b40\",\"reduceBrightness\":true}}",
"continuousPlaybackPlayerViewTransitionDuration": 10,
"continuousPlaybackForegroundTransitionDuration": 0,
"continuousPlaybackBackgroundTransitionDuration": 0,
diff --git a/Application/Resources/Apps/Play RTS/fr.lproj/Localizable.strings b/Application/Resources/Apps/Play RTS/fr.lproj/Localizable.strings
index 799a44181..631783823 100644
--- a/Application/Resources/Apps/Play RTS/fr.lproj/Localizable.strings
+++ b/Application/Resources/Apps/Play RTS/fr.lproj/Localizable.strings
@@ -738,7 +738,8 @@
Error message when a media cannot be opened via Handoff, deep linking or a push notification */
"The media cannot be opened." = "Le contenu ne peut être ouvert.";
-/* Error message when a page cannot be opened via Handoff, deep linking or a push notification
+/* Error message when a page cannot be opened from a page section title
+ Error message when a page cannot be opened via Handoff, deep linking or a push notification
Error message when a topic cannot be opened via Handoff, deep linking or a push notification */
"The page cannot be opened." = "La page ne peut être ouverte.";
diff --git a/Application/Resources/Apps/Play SRF/ApplicationConfiguration.json b/Application/Resources/Apps/Play SRF/ApplicationConfiguration.json
index 77fe672cf..51c611713 100755
--- a/Application/Resources/Apps/Play SRF/ApplicationConfiguration.json
+++ b/Application/Resources/Apps/Play SRF/ApplicationConfiguration.json
@@ -19,7 +19,8 @@
"whatsNewURL": "https://srgssr.github.io/playsrg-apple/releases/release_notes-ios-srf.html",
"radioChannels": "[{\"uid\":\"69e8ac16-4327-4af4-b873-fd5cd6e895a7\",\"name\":\"Radio SRF 1\",\"resourceUid\":\"srf1\",\"songsViewStyle\":\"collapsed\",\"color\":\"#F7A600\",\"secondColor\":\"#FFD651\",\"titleColor\":\"#161616\",\"hasDarkStatusBar\":true,\"numberOfLivePlaceholders\":8},{\"uid\":\"c8537421-c9c5-4461-9c9c-c15816458b46\",\"name\":\"Radio SRF 2 Kultur\",\"resourceUid\":\"srf2\",\"songsViewStyle\":\"collapsed\",\"color\":\"#CA3DAB\",\"secondColor\":\"#8C1D60\"},{\"uid\":\"dd0fa1ba-4ff6-4e1a-ab74-d7e49057d96f\",\"name\":\"Radio SRF 3\",\"resourceUid\":\"srf3\",\"songsViewStyle\":\"expanded\",\"color\":\"#464646\",\"secondColor\":\"#000000\"},{\"uid\":\"ee1fb348-2b6a-4958-9aac-ec6c87e190da\",\"name\":\"Radio SRF 4 News\",\"resourceUid\":\"srf4\",\"color\":\"#E31F2B\",\"secondColor\":\"#6A0B0C\"},{\"uid\":\"a9c5c070-8899-46c7-ac27-f04f1be902fd\",\"name\":\"Radio SRF Musikwelle\",\"resourceUid\":\"srf_musikwelle\",\"songsViewStyle\":\"expanded\",\"color\":\"#42A3F1\",\"secondColor\":\"#0066B0\"},{\"uid\":\"66815fe2-9008-4853-80a5-f9caaffdf3a9\",\"name\":\"Radio SRF Virus\",\"resourceUid\":\"virus\",\"songsViewStyle\":\"expanded\",\"color\":\"#A5FF00\",\"secondColor\":\"#BDFF44\",\"titleColor\":\"#161616\",\"hasDarkStatusBar\":true,\"homepageHidden\":true}]",
"tvChannels": "[{\"uid\":\"la1\",\"name\":\"LA 1\",\"resourceUid\":\"la1\",\"color\":\"#FF9120\",\"secondColor\":\"#E15100\"},{\"uid\":\"la2\",\"name\":\"LA 2\",\"resourceUid\":\"la2\",\"color\":\"#FFCF2F\",\"secondColor\":\"#F38A0D\"},{\"uid\":\"143932a79bb5a123a646b68b1d1188d7ae493e5b\",\"name\":\"RTS 1\",\"resourceUid\":\"rts_un\",\"color\":\"#00D6F3\",\"secondColor\":\"#00B6F0\",\"titleColor\":\"#161616\"},{\"uid\":\"d7dfff28deee44e1d3c49a3d37d36d492b29671b\",\"name\":\"RTS 2\",\"resourceUid\":\"rts_deux\",\"color\":\"#BB66FF\",\"secondColor\":\"#782EB5\"},{\"uid\":\"5d332a26e06d08eec8ad385d566187df72955623\",\"name\":\"RTS Info\",\"resourceUid\":\"rts_info\",\"color\":\"#3787FF\",\"secondColor\":\"#153567\"},{\"uid\":\"23FFBE1B-65CE-4188-ADD2-C724186C2C9F\",\"name\":\"SRF 1\",\"resourceUid\":\"tv_srf1\",\"color\":\"#C91024\",\"secondColor\":\"#8D0614\"},{\"uid\":\"E4D5AD08-C1E8-46A3-BB58-4875051D60D2\",\"name\":\"SRF zwei\",\"resourceUid\":\"tv_srf2\",\"color\":\"#FFB600\",\"secondColor\":\"#ED7004\",\"titleColor\":\"#161616\",\"hasDarkStatusBar\":true},{\"uid\":\"34c2819e-e715-43d7-9026-40a443152a97\",\"name\":\"SRF info\",\"resourceUid\":\"tv_srf_info\",\"color\":\"#AF001E\",\"secondColor\":\"#830512\"}]",
- "satelliteRadioChannels": "[{\"uid\":\"rsp\",\"name\":\"Radio Swiss Pop\",\"resourceUid\":\"rsp\",\"songsViewStyle\":\"expanded\",\"color\":\"#F01F73\",\"secondColor\":\"#D31A3C\",\"homepageHidden\":true},{\"uid\":\"rsc-de\",\"name\":\"Radio Swiss Classic\",\"resourceUid\":\"rsc\",\"songsViewStyle\":\"expanded\",\"color\":\"#09A1DE\",\"secondColor\":\"#036E99\",\"homepageHidden\":true},{\"uid\":\"rsj\",\"name\":\"Radio Swiss Jazz\",\"resourceUid\":\"rsj\",\"songsViewStyle\":\"expanded\",\"color\":\"#F7B222\",\"secondColor\":\"#CC7A00\",\"homepageHidden\":true}]",
+ "satelliteRadioChannels": "[{\"uid\":\"rsp\",\"name\":\"Radio Swiss Pop\",\"resourceUid\":\"rsp\",\"songsViewStyle\":\"expanded\",\"color\":\"#F01F73\",\"secondColor\":\"#D31A3C\",\"homepageHidden\":true, \"shareURL\":\"https://www.radioswisspop.ch/de\"},{\"uid\":\"rsc-de\",\"name\":\"Radio Swiss Classic\",\"resourceUid\":\"rsc\",\"songsViewStyle\":\"expanded\",\"color\":\"#09A1DE\",\"secondColor\":\"#036E99\",\"homepageHidden\":true, \"shareURL\":\"https://www.radioswissclassic.ch/de\"},{\"uid\":\"rsj\",\"name\":\"Radio Swiss Jazz\",\"resourceUid\":\"rsj\",\"songsViewStyle\":\"expanded\",\"color\":\"#F7B222\",\"secondColor\":\"#CC7A00\",\"homepageHidden\":true, \"shareURL\":\"https://www.radioswissjazz.ch/de\"}]",
+ "topicColors": "{\"urn:rsi:topic:tv:1\":{\"firstColor\":\"#c01232\",\"secondColor\":\"#480010\"},\"urn:rsi:topic:tv:4\":{\"firstColor\":\"#d7b447\",\"secondColor\":\"#b62019\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:7\":{\"firstColor\":\"#da2146\",\"secondColor\":\"#2d38c0\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:8\":{\"firstColor\":\"#cd4023\",\"secondColor\":\"#90062e\"},\"urn:rsi:topic:tv:11\":{\"firstColor\":\"#dea706\",\"secondColor\":\"#bd2e5e\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:40\":{\"firstColor\":\"#44bda8\",\"secondColor\":\"#00324e\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:80\":{\"firstColor\":\"#1f509d\",\"secondColor\":\"#121a37\"},\"urn:rsi:topic:tv:90\":{\"firstColor\":\"#738dae\",\"secondColor\":\"#3a465e\"},\"urn:rsi:topic:tv:100\":{\"firstColor\":\"#d75959\",\"secondColor\":\"#29336c\",\"reduceBrightness\":true},\"urn:rsi:topic:tv:600\":{\"firstColor\":\"#27DCF9\",\"secondColor\":\"#932387\"},\"urn:rsi:topic:tv:6000\":{\"firstColor\":\"#02cde9\",\"secondColor\":\"#011844\"},\"urn:rtr:topic:tv:2d48ba80-566c-4359-9e8d-8d9b2d570e0a\":{\"firstColor\":\"#00A1A1\",\"secondColor\":\"#04575B\"},\"urn:rtr:topic:tv:7d7f21be-6727-4939-9126-5bca25eb3a49\":{\"firstColor\":\"#80D2E3\",\"secondColor\":\"#003D58\"},\"urn:rtr:topic:tv:20e7478f-1ea1-49c3-81c2-5f157d6ff092\":{\"firstColor\":\"#340101\",\"secondColor\":\"#8F0E0F\"},\"urn:rtr:topic:tv:50bb90d6-41af-4bbd-b92c-6ef5db16a9b3\":{\"firstColor\":\"#8A0533\",\"secondColor\":\"#812626\"},\"urn:rtr:topic:tv:c50140e7-5740-4c44-abd0-0f7d9ea68da7\":{\"firstColor\":\"#A6A6A7\",\"secondColor\":\"#2C2B2D\"},\"urn:rtr:topic:tv:dfb7ae6d-cb73-431b-a817-b1663ec2f58a\":{\"firstColor\":\"#00F8CC\",\"secondColor\":\"#018864\"},\"urn:rts:topic:tv:623\":{\"firstColor\":\"#5C845B\",\"secondColor\":\"#16280F\"},\"urn:rts:topic:tv:665\":{\"firstColor\":\"#3787FF\",\"secondColor\":\"#0A1C33\"},\"urn:rts:topic:tv:1095\":{\"firstColor\":\"#F5F500\",\"secondColor\":\"#BEB405\",\"reduceBrightness\":true},\"urn:rts:topic:tv:1353\":{\"firstColor\":\"#084165\",\"secondColor\":\"#140953\"},\"urn:rts:topic:tv:2743\":{\"firstColor\":\"#BCF6FF\",\"secondColor\":\"#00D0EF\",\"reduceBrightness\":true},\"urn:rts:topic:tv:10193\":{\"firstColor\":\"#EB2350\",\"secondColor\":\"#A61637\"},\"urn:rts:topic:tv:54537\":{\"firstColor\":\"#FFE03E\",\"secondColor\":\"#F98E73\",\"reduceBrightness\":true},\"urn:rts:topic:tv:59220\":{\"firstColor\":\"#492b63\",\"secondColor\":\"#271633\"},\"urn:rts:topic:tv:67132\":{\"firstColor\":\"#415FAF\",\"secondColor\":\"#23376B\"},\"urn:srf:topic:tv:1d7d9cfb-6682-4d5b-9e36-322e8fa93c03\":{\"firstColor\":\"#00A4B3\",\"secondColor\":\"#006973\"},\"urn:srf:topic:tv:4acf86dd-7ff7-45d3-baf8-33375340d976\":{\"firstColor\":\"#3f4b70\",\"secondColor\":\"#131a2d\"},\"urn:srf:topic:tv:9a79b1de-cde8-4528-b304-d1ae1363f52f\":{\"firstColor\":\"#836fcd\",\"secondColor\":\"#36343f\"},\"urn:srf:topic:tv:63f937e4-859e-42c4-a430-bdb74dd09645\":{\"firstColor\":\"#4480a2\",\"secondColor\":\"#20182c\"},\"urn:srf:topic:tv:67f812fd-19a3-4c22-9e6b-ec36e65a4703\":{\"firstColor\":\"#bb3966\",\"secondColor\":\"#190406\"},\"urn:srf:topic:tv:593eb926-d892-41ba-8b1f-eccbcfd7f15f\":{\"firstColor\":\"#2bbf9b\",\"secondColor\":\"#02291e\"},\"urn:srf:topic:tv:649e36d7-ff57-41c8-9c1b-7892daf15e78\":{\"firstColor\":\"#FF0037\",\"secondColor\":\"#AF001E\"},\"urn:srf:topic:tv:882cb264-cf81-4a9c-b660-d42519b7ce28\":{\"firstColor\":\"#c91d7d\",\"secondColor\":\"#31041f\"},\"urn:srf:topic:tv:43741c59-317e-458b-ac38-c2b1c065c865\":{\"firstColor\":\"#0075ad\",\"secondColor\":\"#000022\"},\"urn:srf:topic:tv:516421f0-ec89-43ba-823b-1b5ceec262f3\":{\"firstColor\":\"#5FB281\",\"secondColor\":\"#154e60\"},\"urn:srf:topic:tv:641223fa-f112-4d98-8aec-cb22262a1182\":{\"firstColor\":\"#c55cee\",\"secondColor\":\"#0c1c68\"},\"urn:srf:topic:tv:a2d97206-0b85-4226-8afe-06e86ebd05b2\":{\"firstColor\":\"#9fc885\",\"secondColor\":\"#20281a\"},\"urn:srf:topic:tv:a709c610-b275-4c0c-a496-cba304c36712\":{\"firstColor\":\"#b3131d\",\"secondColor\":\"#3e0b14\"},\"urn:srf:topic:tv:b58dcf14-96ac-4046-8676-fd8a942c0e88\":{\"firstColor\":\"#7081b0\",\"secondColor\":\"#202020\"},\"urn:srf:topic:tv:bb7b21e0-1056-4e28-bac3-c610393b5b0f\":{\"firstColor\":\"#3c788e\",\"secondColor\":\"#1b3e48\"},\"urn:srf:topic:tv:e52080fc-f36b-481e-955f-071b6c8d6dc3\":{\"firstColor\":\"#ff6778\",\"secondColor\":\"#920a1a\",\"reduceBrightness\":true},\"urn:srf:topic:tv:fa793c13-bebc-41b9-9710-bf8a34192c15\":{\"firstColor\":\"#baead5\",\"secondColor\":\"#010b40\",\"reduceBrightness\":true}}",
"continuousPlaybackPlayerViewTransitionDuration": 10,
"continuousPlaybackForegroundTransitionDuration": 0,
"continuousPlaybackBackgroundTransitionDuration": 0,
diff --git a/Application/Resources/Apps/Play SRF/de.lproj/Localizable.strings b/Application/Resources/Apps/Play SRF/de.lproj/Localizable.strings
index d22ac0755..5e929500e 100755
--- a/Application/Resources/Apps/Play SRF/de.lproj/Localizable.strings
+++ b/Application/Resources/Apps/Play SRF/de.lproj/Localizable.strings
@@ -738,7 +738,8 @@
Error message when a media cannot be opened via Handoff, deep linking or a push notification */
"The media cannot be opened." = "Der Inhalt kann nicht geöffnet werden.";
-/* Error message when a page cannot be opened via Handoff, deep linking or a push notification
+/* Error message when a page cannot be opened from a page section title
+ Error message when a page cannot be opened via Handoff, deep linking or a push notification
Error message when a topic cannot be opened via Handoff, deep linking or a push notification */
"The page cannot be opened." = "Seite konnte nicht geöffnet werden.";
diff --git a/Application/Resources/Apps/Play SWI/en.lproj/Localizable.strings b/Application/Resources/Apps/Play SWI/en.lproj/Localizable.strings
index 03f4f6593..fa67e5be8 100755
--- a/Application/Resources/Apps/Play SWI/en.lproj/Localizable.strings
+++ b/Application/Resources/Apps/Play SWI/en.lproj/Localizable.strings
@@ -738,7 +738,8 @@
Error message when a media cannot be opened via Handoff, deep linking or a push notification */
"The media cannot be opened." = "The media cannot be opened.";
-/* Error message when a page cannot be opened via Handoff, deep linking or a push notification
+/* Error message when a page cannot be opened from a page section title
+ Error message when a page cannot be opened via Handoff, deep linking or a push notification
Error message when a topic cannot be opened via Handoff, deep linking or a push notification */
"The page cannot be opened." = "The page cannot be opened.";
diff --git a/Application/Resources/Settings.bundle/com.mono0926.LicensePlist.latest_result.txt b/Application/Resources/Settings.bundle/com.mono0926.LicensePlist.latest_result.txt
index a8eac14bb..d936d543d 100755
--- a/Application/Resources/Settings.bundle/com.mono0926.LicensePlist.latest_result.txt
+++ b/Application/Resources/Settings.bundle/com.mono0926.LicensePlist.latest_result.txt
@@ -163,11 +163,11 @@ name: promises, nameSpecified: Promises, owner: google, version: 2.4.0, source:
name: srganalytics-apple, nameSpecified: SRGAnalytics, owner: SRGSSR, version: 9.1.0, source: https://github.com/SRGSSR/srganalytics-apple
-name: srgappearance-apple, nameSpecified: SRGAppearance, owner: SRGSSR, version: 5.2.1, source: https://github.com/SRGSSR/srgappearance-apple
+name: srgappearance-apple, nameSpecified: SRGAppearance, owner: SRGSSR, version: 5.2.2, source: https://github.com/SRGSSR/srgappearance-apple
name: srgcontentprotection-apple, nameSpecified: SRGContentProtection, owner: SRGSSR, version: 3.1.0, source: https://github.com/SRGSSR/srgcontentprotection-apple
-name: srgdataprovider-apple, nameSpecified: SRGDataProvider, owner: SRGSSR, version: 19.0.2, source: https://github.com/SRGSSR/srgdataprovider-apple
+name: srgdataprovider-apple, nameSpecified: SRGDataProvider, owner: SRGSSR, version: 19.0.3, source: https://github.com/SRGSSR/srgdataprovider-apple
name: srgdiagnostics-apple, nameSpecified: SRGDiagnostics, owner: SRGSSR, version: 3.1.0, source: https://github.com/SRGSSR/srgdiagnostics-apple
@@ -197,4 +197,4 @@ name: YYWebImage, nameSpecified: YYWebImage, owner: SRGSSR, version: 1.0.5-srg3,
add-version-numbers: true
-LicensePlist Version: 3.24.10
\ No newline at end of file
+LicensePlist Version: 3.25.1
\ No newline at end of file
diff --git a/Application/Resources/Settings.bundle/com.mono0926.LicensePlist.plist b/Application/Resources/Settings.bundle/com.mono0926.LicensePlist.plist
index 371bf53a3..c3e5a1ec9 100755
--- a/Application/Resources/Settings.bundle/com.mono0926.LicensePlist.plist
+++ b/Application/Resources/Settings.bundle/com.mono0926.LicensePlist.plist
@@ -286,7 +286,7 @@
File
com.mono0926.LicensePlist/srgappearance-apple
Title
- SRGAppearance (5.2.1)
+ SRGAppearance (5.2.2)
Type
PSChildPaneSpecifier
@@ -302,7 +302,7 @@
File
com.mono0926.LicensePlist/srgdataprovider-apple
Title
- SRGDataProvider (19.0.2)
+ SRGDataProvider (19.0.3)
Type
PSChildPaneSpecifier
diff --git a/Application/Resources/Settings.bundle/com.mono0926.LicensePlist/ios-library.plist b/Application/Resources/Settings.bundle/com.mono0926.LicensePlist/ios-library.plist
index 436740324..43f905af2 100644
--- a/Application/Resources/Settings.bundle/com.mono0926.LicensePlist/ios-library.plist
+++ b/Application/Resources/Settings.bundle/com.mono0926.LicensePlist/ios-library.plist
@@ -6,22 +6,210 @@
FooterText
- Copyright Airship and Contributors
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
+ 1. Definitions.
- http://www.apache.org/licenses/LICENSE-2.0
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
License
- unknown
+ Apache-2.0
Type
PSGroupSpecifier
diff --git a/Application/Sources/Application/Navigation.swift b/Application/Sources/Application/Navigation.swift
index 606676166..9d6331377 100644
--- a/Application/Sources/Application/Navigation.swift
+++ b/Application/Sources/Application/Navigation.swift
@@ -166,6 +166,14 @@ func navigateToPage(_ page: SRGContentPage, animated: Bool = true) {
}
func navigateToSection(_ section: Content.Section, filter: SectionFiltering?, animated: Bool = true) {
+ if let microPageId = section.properties.openContentPageId {
+ openContentPage(id: microPageId, animated: animated)
+ } else {
+ openSectionPage(section: section, filter: filter, animated: animated)
+ }
+}
+
+private func openSectionPage(section: Content.Section, filter: SectionFiltering?, animated: Bool) {
guard !isPresenting, let topViewController = UIApplication.shared.mainTopViewController else { return }
isPresenting = true
topViewController.navigateToSection(section, filter: filter, animated: animated) {
@@ -173,6 +181,23 @@ func navigateToSection(_ section: Content.Section, filter: SectionFiltering?, an
}
}
+private func openContentPage(id: String, animated: Bool) {
+ guard !isPresenting, let topViewController = UIApplication.shared.mainTopViewController else { return }
+ isPresenting = true
+
+ SRGDataProvider.current!.contentPage(for: ApplicationConfiguration.shared.vendor, uid: id)
+ .receive(on: DispatchQueue.main)
+ .sink { _ in
+ // No error banners displayed on tvOS yet
+ isPresenting = false
+ } receiveValue: { contentPage in
+ topViewController.navigateToPage(contentPage, animated: animated) {
+ isPresenting = false
+ }
+ }
+ .store(in: &cancellables)
+}
+
func navigateToTopic(_ topic: SRGTopic, animated: Bool = true) {
guard !isPresenting, let topViewController = UIApplication.shared.mainTopViewController else { return }
isPresenting = true
diff --git a/Application/Sources/Browser/WebViewController.m b/Application/Sources/Browser/WebViewController.m
index c8fc391e2..22a0c6a79 100755
--- a/Application/Sources/Browser/WebViewController.m
+++ b/Application/Sources/Browser/WebViewController.m
@@ -91,7 +91,7 @@ - (void)loadView
self.customizationBlock(webView);
}
- UIImageView *loadingImageView = [UIImageView play_largeLoadingImageViewWithTintColor:UIColor.srg_grayC7Color];
+ UIImageView *loadingImageView = [UIImageView play_largeLoadingImageViewWithTintColor:UIColor.srg_grayD2Color];
loadingImageView.hidden = YES;
[view insertSubview:loadingImageView atIndex:0];
self.loadingImageView = loadingImageView;
diff --git a/Application/Sources/Calendar/CalendarViewController.m b/Application/Sources/Calendar/CalendarViewController.m
index c372f9b6b..5285854ec 100755
--- a/Application/Sources/Calendar/CalendarViewController.m
+++ b/Application/Sources/Calendar/CalendarViewController.m
@@ -325,7 +325,7 @@ - (UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)
NSDate *startDate = [self minimumDateForCalendar:calendar];
NSDate *endDate = [self maximumDateForCalendar:calendar];
NSDateInterval *dateInterval = [[NSDateInterval alloc] initWithStartDate:startDate endDate:endDate];
- return [dateInterval containsDate:date] ? UIColor.srg_grayC7Color : [UIColor.srg_grayC7Color colorWithAlphaComponent:0.4f];
+ return [dateInterval containsDate:date] ? UIColor.srg_grayD2Color : [UIColor.srg_grayD2Color colorWithAlphaComponent:0.4f];
}
#pragma mark ContainerContentInsets protocol
diff --git a/Application/Sources/Configuration/ApplicationConfiguration.h b/Application/Sources/Configuration/ApplicationConfiguration.h
index 6347ad0d4..55d29d076 100755
--- a/Application/Sources/Configuration/ApplicationConfiguration.h
+++ b/Application/Sources/Configuration/ApplicationConfiguration.h
@@ -76,6 +76,8 @@ OBJC_EXPORT NSString * const ApplicationConfigurationDidChangeNotification;
@property (nonatomic, readonly) NSArray *tvChannels;
@property (nonatomic, readonly) NSArray *satelliteRadioChannels;
+@property (nonatomic, readonly) NSDictionary *> *topicColors;
+
@property (nonatomic, readonly) NSArray *tvGuideOtherBouquetsObjc;
@property (nonatomic, readonly) NSUInteger pageSize; // page size to be used in general throughout the app
diff --git a/Application/Sources/Configuration/ApplicationConfiguration.m b/Application/Sources/Configuration/ApplicationConfiguration.m
index 48a61df69..e9647e683 100755
--- a/Application/Sources/Configuration/ApplicationConfiguration.m
+++ b/Application/Sources/Configuration/ApplicationConfiguration.m
@@ -163,6 +163,8 @@ @interface ApplicationConfiguration ()
@property (nonatomic) NSArray *satelliteRadioChannels;
+@property (nonatomic) NSDictionary *> *topicColors;
+
@property (nonatomic) NSArray *tvGuideOtherBouquetsObjc;
@property (nonatomic) NSUInteger pageSize;
@@ -458,6 +460,8 @@ - (BOOL)synchronizeWithFirebaseConfiguration:(PlayFirebaseConfiguration *)fireba
self.tvChannels = [firebaseConfiguration tvChannelsForKey:@"tvChannels"];
self.satelliteRadioChannels = [firebaseConfiguration radioChannelsForKey:@"satelliteRadioChannels" defaultHomeSections:nil];
+ self.topicColors = [firebaseConfiguration topicColorsForKey:@"topicColors"];
+
self.tvGuideOtherBouquetsObjc = [firebaseConfiguration tvGuideOtherBouquetsForKey:@"tvGuideOtherBouquets" vendor:vendor];
NSNumber *pageSize = [firebaseConfiguration numberForKey:@"pageSize"];
@@ -568,10 +572,7 @@ - (NSURL *)sharingURLForMedia:(SRGMedia *)media atTime:(CMTime)time
return URLComponents.URL;
}
else if (media.channel.vendor == SRGVendorSSATR) {
- NSURLComponents *URLComponents = [NSURLComponents componentsWithURL:[self playURLForVendor:media.vendor] resolvingAgainstBaseURL:NO];
- URLComponents.path = [URLComponents.path stringByAppendingPathComponent:@"embed"];
- URLComponents.queryItems = @[ [NSURLQueryItem queryItemWithName:@"urn" value:media.URN] ];
- return URLComponents.URL;
+ return [[self channelForUid:media.uid] shareURL];
}
else {
static NSDictionary *s_mediaTypeNames;
diff --git a/Application/Sources/Configuration/ApplicationConfiguration.swift b/Application/Sources/Configuration/ApplicationConfiguration.swift
index d21d17194..9964edd81 100644
--- a/Application/Sources/Configuration/ApplicationConfiguration.swift
+++ b/Application/Sources/Configuration/ApplicationConfiguration.swift
@@ -4,6 +4,8 @@
// License information is available from the LICENSE file.
//
+import SwiftUI
+
extension ApplicationConfiguration {
private static func configuredSection(from homeSection: HomeSection) -> ConfiguredSection? {
switch homeSection {
@@ -49,6 +51,13 @@ extension ApplicationConfiguration {
return URL(string: "api/v2/playlist/recommendation/relatedContent/\(media.urn)", relativeTo: self.middlewareURL)!
}
+ func topicColors(for topic: SRGTopic) -> (Color, Color)? {
+ guard let topicColorsArray = self.topicColors[topic.urn], topicColorsArray.count == 2 else { return nil }
+
+ let colors = topicColorsArray.map { Color($0) }
+ return (colors.first!, colors.last!)
+ }
+
private static var version: String {
return Bundle.main.play_friendlyVersionNumber
}
diff --git a/Application/Sources/Configuration/Channel.h b/Application/Sources/Configuration/Channel.h
index ec38b99f7..cbc415b65 100644
--- a/Application/Sources/Configuration/Channel.h
+++ b/Application/Sources/Configuration/Channel.h
@@ -48,6 +48,11 @@ typedef NS_ENUM(NSInteger, SongsViewStyle) {
*/
@property (nonatomic, readonly, copy) NSString *name;
+/**
+ * The URL used to share the channel website.
+ */
+@property (nonatomic, readonly, copy, nullable) NSURL *shareURL;
+
/**
* The channel primary color.
*/
diff --git a/Application/Sources/Configuration/Channel.m b/Application/Sources/Configuration/Channel.m
index 5d3c8079c..c87784aa4 100644
--- a/Application/Sources/Configuration/Channel.m
+++ b/Application/Sources/Configuration/Channel.m
@@ -25,6 +25,7 @@ @interface Channel ()
@property (nonatomic, copy) NSString *uid;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *resourceUid;
+@property (nonatomic, copy) NSURL *shareURL;
@property (nonatomic) UIColor *color;
@property (nonatomic) UIColor *secondColor;
@property (nonatomic) UIColor *titleColor;
@@ -55,6 +56,8 @@ - (instancetype)initWithDictionary:(NSDictionary *)dictionary
return nil;
}
+ self.shareURL = [NSURL URLWithString:dictionary[@"shareURL"]];
+
id colorValue = dictionary[@"color"];
if ([colorValue isKindOfClass:NSString.class]) {
self.color = [UIColor srg_colorFromHexadecimalString:colorValue] ?: UIColor.grayColor;
diff --git a/Application/Sources/Configuration/PlayFirebaseConfiguration.h b/Application/Sources/Configuration/PlayFirebaseConfiguration.h
index 8fe0f7159..407c000d8 100644
--- a/Application/Sources/Configuration/PlayFirebaseConfiguration.h
+++ b/Application/Sources/Configuration/PlayFirebaseConfiguration.h
@@ -57,6 +57,11 @@ OBJC_EXPORT NSArray * _Nullable FirebaseConfigurat
- (NSArray *)radioChannelsForKey:(NSString *)key defaultHomeSections:(nullable NSArray *)defaultHomeSections;
- (NSArray *)tvChannelsForKey:(NSString *)key;
+/**
+ * Topic colors accessors. Return an empty dictionnary if no valid data is found under the specified key.
+ */
+- (NSDictionary *> *)topicColorsForKey:(NSString *)key;
+
/**
* TV guide other bouquets accessor, main bouquet excluded. Return an empty array if no valid data is found under the specified key.
*/
diff --git a/Application/Sources/Configuration/PlayFirebaseConfiguration.m b/Application/Sources/Configuration/PlayFirebaseConfiguration.m
index ae8847196..8de93143c 100644
--- a/Application/Sources/Configuration/PlayFirebaseConfiguration.m
+++ b/Application/Sources/Configuration/PlayFirebaseConfiguration.m
@@ -9,6 +9,7 @@
#import "PlayLogger.h"
@import Firebase;
+@import SRGAppearance;
@import UIKit;
static HomeSection HomeSectionWithString(NSString *string)
@@ -299,6 +300,33 @@ - (NSDictionary *)JSONDictionaryForKey:(NSString *)key
return tvChannels.copy;
}
+- (NSDictionary *> *)topicColorsForKey:(NSString *)key
+{
+ NSMutableDictionary *topicColors = [NSMutableDictionary dictionary];
+
+ NSDictionary *topicColorsDictionary = [self JSONDictionaryForKey:key];
+ for (NSString *topicUrn in topicColorsDictionary) {
+ NSDictionary *colors = topicColorsDictionary[topicUrn];
+ if ([colors isKindOfClass:NSDictionary.class]) {
+ UIColor *firstColor = [UIColor srg_colorFromHexadecimalString:colors[@"firstColor"]];
+ UIColor *secondColor = [UIColor srg_colorFromHexadecimalString:colors[@"secondColor"]];
+ BOOL reduceBrightness = [colors[@"reduceBrightness"] boolValue];
+ if (firstColor && secondColor) {
+ CGFloat alpha = reduceBrightness ? 0.65 : 1.;
+ topicColors[topicUrn] = @[[firstColor colorWithAlphaComponent:alpha], [secondColor colorWithAlphaComponent:alpha]];
+ }
+ else {
+ PlayLogWarning(@"configuration", @"Topic colors dictionnary is missing valid colors. The content of %@ is not valid.", topicUrn);
+ }
+ }
+ else {
+ PlayLogWarning(@"configuration", @"Topic colors dictionnary is not valid. The content of %@ is not valid.", topicUrn);
+ }
+ }
+
+ return topicColors.copy;
+}
+
- (NSArray *)tvGuideOtherBouquetsForKey:(NSString *)key vendor:(SRGVendor)vendor
{
NSString *tvGuideBouquetsString = [self stringForKey:key];
diff --git a/Application/Sources/Content/Content.swift b/Application/Sources/Content/Content.swift
index c6e550927..7d61943b0 100644
--- a/Application/Sources/Content/Content.swift
+++ b/Application/Sources/Content/Content.swift
@@ -157,6 +157,7 @@ protocol SectionProperties {
var rowHighlight: Highlight? { get }
var placeholderRowItems: [Content.Item] { get }
var displaysRowHeader: Bool { get }
+ var openContentPageId: String? { get }
/// Publisher providing content for the section. A single result must be delivered upon subscription. Further
/// results can be retrieved (if any) using a paginator, one page at a time.
@@ -391,13 +392,21 @@ private extension Content {
return contentSection.presentation.type != .highlight && contentSection.presentation.type != .showPromotion
}
+ var openContentPageId: String? {
+ guard let link = contentSection.presentation.contentLink, link.type == .microPage, let id = link.target else {
+ return nil
+ }
+
+ return id
+ }
+
func publisher(pageSize: UInt, paginatedBy paginator: Trigger.Signal?, filter: SectionFiltering?) -> AnyPublisher<[Content.Item], Error> {
let dataProvider = SRGDataProvider.current!
switch contentSection.type {
case .medias:
return dataProvider.medias(for: contentSection.vendor, contentSectionUid: contentSection.uid, pageSize: pageSize, paginatedBy: paginator)
- .map { self.filterItems($0).map { .media($0) } }
+ .map { filterItems($0).map { .media($0) } }
.eraseToAnyPublisher()
case .showAndMedias:
return dataProvider.showAndMedias(for: contentSection.vendor, contentSectionUid: contentSection.uid, pageSize: pageSize, paginatedBy: paginator)
@@ -412,7 +421,7 @@ private extension Content {
.eraseToAnyPublisher()
case .shows:
return dataProvider.shows(for: contentSection.vendor, contentSectionUid: contentSection.uid, pageSize: pageSize, paginatedBy: paginator)
- .map { self.filterItems($0).map { .show($0) } }
+ .map { filterItems($0).map { .show($0) } }
.eraseToAnyPublisher()
case .predefined:
switch presentation.type {
@@ -818,6 +827,10 @@ private extension Content {
return true
}
+ var openContentPageId: String? {
+ nil
+ }
+
func publisher(pageSize: UInt, paginatedBy paginator: Trigger.Signal?, filter: SectionFiltering?) -> AnyPublisher<[Content.Item], Error> {
let dataProvider = SRGDataProvider.current!
diff --git a/Application/Sources/Content/PageViewController.swift b/Application/Sources/Content/PageViewController.swift
index bf5c932e3..d83fbaf1b 100644
--- a/Application/Sources/Content/PageViewController.swift
+++ b/Application/Sources/Content/PageViewController.swift
@@ -25,13 +25,17 @@ final class PageViewController: UIViewController {
private weak var collectionView: UICollectionView!
private weak var emptyContentView: HostView!
+ private weak var topicGradientView: HostView!
+ private weak var topicGradientViewFixTopAnchor: NSLayoutConstraint!
+ private weak var topicGradientViewStickyTopAnchor: NSLayoutConstraint!
+ private weak var topicGradientViewHeightAnchor: NSLayoutConstraint!
#if os(iOS)
private weak var refreshControl: UIRefreshControl!
private weak var googleCastButton: GoogleCastFloatingButton?
private var isNavigationBarHidden: Bool {
- return model.id.isNavigationBarHidden && !UIAccessibility.isVoiceOverRunning
+ return model.isNavigationBarHidden && !UIAccessibility.isVoiceOverRunning
}
private var refreshTriggered = false
@@ -76,8 +80,8 @@ final class PageViewController: UIViewController {
fatalError("init(coder:) has not been implemented")
}
- var id: PageViewModel.Id {
- return model.id
+ var displayedShow: SRGShow? {
+ return model.displayedShow
}
@objc var radioChannel: RadioChannel? {
@@ -111,14 +115,56 @@ final class PageViewController: UIViewController {
if #available(iOS 17.0, *) {
collectionView.registerForTraitChanges([UITraitHorizontalSizeClass.self]) { (collectionView: CollectionView, _) in
collectionView.collectionViewLayout.invalidateLayout()
+
+ self.updateLayoutConfiguration()
+ self.updateTopicGradientLayout()
}
}
#endif
+ let backgroundView = UIView(frame: .zero)
+ collectionView.backgroundView = backgroundView
+
+ backgroundView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ backgroundView.topAnchor.constraint(equalTo: view.topAnchor),
+ backgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ backgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ backgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
+ ])
+
+ let topicGradientView = HostView(frame: .zero)
+ backgroundView.addSubview(topicGradientView)
+ self.topicGradientView = topicGradientView
+
+ topicGradientView.translatesAutoresizingMaskIntoConstraints = false
+ let topicGradientViewFixTopAnchor = topicGradientView.topAnchor.constraint(equalTo: backgroundView.topAnchor, constant: 0 /* set in updateTopicGradientLayout() */)
+ topicGradientViewFixTopAnchor.priority = .defaultHigh
+ let topicGradientViewStickyTopAnchor = topicGradientView.topAnchor.constraint(equalTo: collectionView.topAnchor, constant: 0 /* set in updateTopicGradientLayout() */)
+ topicGradientViewStickyTopAnchor.priority = .defaultLow
+ let topicGradientViewHeightAnchor = topicGradientView.heightAnchor.constraint(equalToConstant: 0 /* set in updateTopicGradientLayout() */)
+ NSLayoutConstraint.activate([
+ topicGradientViewFixTopAnchor,
+ topicGradientViewStickyTopAnchor,
+ topicGradientView.leftAnchor.constraint(equalTo: backgroundView.leftAnchor),
+ topicGradientView.widthAnchor.constraint(equalTo: backgroundView.widthAnchor),
+ topicGradientViewHeightAnchor
+ ])
+ self.topicGradientViewFixTopAnchor = topicGradientViewFixTopAnchor
+ self.topicGradientViewStickyTopAnchor = topicGradientViewStickyTopAnchor
+ self.topicGradientViewHeightAnchor = topicGradientViewHeightAnchor
let emptyContentView = HostView(frame: .zero)
- collectionView.backgroundView = emptyContentView
+ backgroundView.addSubview(emptyContentView)
self.emptyContentView = emptyContentView
+ emptyContentView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ emptyContentView.topAnchor.constraint(equalTo: backgroundView.topAnchor),
+ emptyContentView.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor),
+ emptyContentView.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor),
+ emptyContentView.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor)
+ ])
+
#if os(tvOS)
tabBarObservedScrollView = collectionView
#else
@@ -134,48 +180,40 @@ final class PageViewController: UIViewController {
super.viewDidLoad()
#if os(iOS)
- navigationItem.largeTitleDisplayMode = model.id.isLargeTitleDisplayMode ? .always : .never
- headerWithTitleVisible = model.id.isHeaderWithTitle
+ navigationItem.largeTitleDisplayMode = model.isLargeTitleDisplayMode ? .always : .never
+ headerWithTitleVisible = model.isHeaderWithTitle
#endif
let cellRegistration = UICollectionView.CellRegistration, PageViewModel.Item> { [model] cell, _, item in
- cell.content = ItemCell(item: item, id: model.id)
+ cell.content = ItemCell(item: item, id: model.id, primaryColor: model.primaryColor, secondaryColor: model.secondaryColor)
}
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
}
- let globalHeaderViewRegistration = UICollectionView.SupplementaryRegistration>(elementKind: Header.global.rawValue) { [weak self] view, _, _ in
- guard let self else { return }
- view.content = TitleView(text: model.id.displayedGlobalTitle)
- }
-
- let pageHeaderViewRegistration = UICollectionView.SupplementaryRegistration>(elementKind: Header.pageHeader.rawValue) { [weak self] view, _, _ in
+ let titleHeaderViewRegistration = UICollectionView.SupplementaryRegistration>(elementKind: Header.titleHeader.rawValue) { [weak self] view, _, _ in
guard let self else { return }
- view.content = PageHeaderView(page: model.id.displayedPage)
+ view.content = TitleHeaderView(model.displayedTitle, description: model.displayedTitleDescription, titleTextAlignment: model.displayedTitleTextAlignment, topPadding: Self.layoutDisplayedTitleTopPadding(model.displayedTitleNeedsTopPadding)).primaryColor(model.primaryColor)
}
let showHeaderViewRegistration = UICollectionView.SupplementaryRegistration>(elementKind: Header.showHeader.rawValue) { [weak self] view, _, _ in
guard let self else { return }
- view.content = ShowHeaderView(show: model.id.displayedShow, horizontalPadding: Self.layoutHorizontalMargin)
+ view.content = ShowHeaderView(model.displayedShow, horizontalPadding: Self.layoutHorizontalMargin).primaryColor(model.primaryColor)
}
let sectionHeaderViewRegistration = UICollectionView.SupplementaryRegistration>(elementKind: UICollectionView.elementKindSectionHeader) { [weak self] view, _, indexPath in
guard let self else { return }
let snapshot = dataSource.snapshot()
let section = snapshot.sectionIdentifiers[indexPath.section]
- view.content = SectionHeaderView(section: section, pageId: model.id)
+ view.content = SectionHeaderView(section: section, pageId: model.id).primaryColor(model.primaryColor)
}
dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in
- if kind == Header.global.rawValue {
- return collectionView.dequeueConfiguredReusableSupplementary(using: globalHeaderViewRegistration, for: indexPath)
- }
- if kind == Header.pageHeader.rawValue {
- return collectionView.dequeueConfiguredReusableSupplementary(using: pageHeaderViewRegistration, for: indexPath)
+ if kind == Header.titleHeader.rawValue {
+ return collectionView.dequeueConfiguredReusableSupplementary(using: titleHeaderViewRegistration, for: indexPath)
}
- if kind == Header.showHeader.rawValue {
+ else if kind == Header.showHeader.rawValue {
return collectionView.dequeueConfiguredReusableSupplementary(using: showHeaderViewRegistration, for: indexPath)
}
else {
@@ -190,6 +228,19 @@ final class PageViewController: UIViewController {
}
.store(in: &cancellables)
+ model.$displayedShow
+ .dropFirst()
+ .sink { [weak self] _ in
+ if let self, isViewLoaded {
+ // Dispatch on next main thread loop to have new model.displayedShow for ShowHeaderView supplementary view
+ DispatchQueue.main.asyncAfter(deadline: .now() + DispatchTimeInterval.seconds(1)) {
+ self.updateLayoutConfiguration()
+ self.updateTopicGradientLayout()
+ }
+ }
+ }
+ .store(in: &cancellables)
+
#if os(iOS)
model.$serviceMessage
.sink { serviceMessage in
@@ -211,6 +262,7 @@ final class PageViewController: UIViewController {
super.viewWillAppear(animated)
updateLayoutConfiguration()
+ updateTopicGradientLayout()
model.reload()
deselectItems(in: collectionView, animated: animated)
#if os(iOS)
@@ -228,6 +280,7 @@ final class PageViewController: UIViewController {
super.viewDidLayoutSubviews()
updateLayoutConfiguration()
+ updateTopicGradientLayout()
}
private func updateLayoutConfiguration() {
@@ -254,6 +307,13 @@ final class PageViewController: UIViewController {
emptyContentView.content = rows.isEmpty ? EmptyContentView(state: .empty(type: .generic), insets: emptyViewEdgeInsets()) : nil
}
+ if let topic = model.displayedGradientTopic, let style = model.displayedGradientTopicStyle {
+ self.topicGradientView.content = TopicGradientView(topic, style: style)
+ }
+ else {
+ self.topicGradientView.content = nil
+ }
+
DispatchQueue.global(qos: .userInteractive).async {
// Can be triggered on a background thread. Layout is updated on the main thread.
self.dataSource.apply(Self.snapshot(from: state)) {
@@ -350,8 +410,7 @@ final class PageViewController: UIViewController {
private extension PageViewController {
enum Header: String {
- case global
- case pageHeader
+ case titleHeader
case showHeader
}
@@ -399,7 +458,7 @@ extension PageViewController: ContentInsets {
var play_paddingContentInsets: UIEdgeInsets {
#if os(iOS)
- let top = (isNavigationBarHidden || model.id.isHeaderWithTitle) ? 0 : Self.layoutVerticalMargin
+ let top = (isNavigationBarHidden || model.isHeaderWithTitle) ? 0 : Self.layoutVerticalMargin
#else
let top = Self.layoutVerticalMargin
#endif
@@ -489,7 +548,7 @@ extension PageViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) {
switch elementKind {
- case Header.showHeader.rawValue, Header.pageHeader.rawValue:
+ case Header.showHeader.rawValue, Header.titleHeader.rawValue:
headerWithTitleVisible = true
updateNavigationBar(animated: true)
default:
@@ -499,9 +558,9 @@ extension PageViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, at indexPath: IndexPath) {
switch elementKind {
- case Header.showHeader.rawValue, Header.pageHeader.rawValue:
- headerWithTitleVisible = false
- updateNavigationBar(animated: true)
+ case Header.showHeader.rawValue, Header.titleHeader.rawValue:
+ headerWithTitleVisible = false
+ updateNavigationBar(animated: true)
default:
break
}
@@ -546,6 +605,12 @@ extension PageViewController: UIScrollViewDelegate {
if scrollView.contentOffset.y > scrollView.contentSize.height - CGFloat(numberOfScreens) * scrollView.frame.height {
model.loadMore()
}
+
+#if os(iOS)
+ if isShowHeaderVerticalLayout {
+ topicGradientViewStickyTopAnchor.constant = showPageStickyTopAnchorConstant
+ }
+#endif
}
}
@@ -599,11 +664,43 @@ extension PageViewController: ShowAccessCellActions {
extension PageViewController: SectionHeaderViewAction {
fileprivate func openSection(sender: Any?, event: OpenSectionEvent?) {
- if let event, let navigationController {
- let sectionViewController = SectionViewController(section: event.section.wrappedValue, filter: model.id)
- navigationController.pushViewController(sectionViewController, animated: true)
+ if let event {
+ if let microPageId = event.section.wrappedValue.properties.openContentPageId {
+ openContentPage(id: microPageId)
+ } else {
+ openSectionPage(section: event.section, filter: model.id)
+ }
}
}
+
+ private func openSectionPage(section: PageViewModel.Section, filter: PageViewModel.Id) {
+ guard let navigationController else { return }
+
+ let sectionViewController = SectionViewController(section: section.wrappedValue, filter: filter)
+ navigationController.pushViewController(sectionViewController, animated: true)
+ }
+
+ private func openContentPage(id: String) {
+ guard let navigationController else { return }
+
+ SRGDataProvider.current!.contentPage(for: ApplicationConfiguration.shared.vendor, uid: id)
+ .receive(on: DispatchQueue.main)
+ .sink { result in
+ if case .failure = result {
+ let error = NSError(
+ domain: PlayErrorDomain,
+ code: PlayErrorCode.notFound.rawValue,
+ userInfo: [
+ NSLocalizedDescriptionKey: NSLocalizedString("The page cannot be opened.", comment: "Error message when a page cannot be opened from a page section title")
+ ])
+ Banner.showError(error)
+ }
+ } receiveValue: { contentPage in
+ let pageViewController = PageViewController.pageViewController(for: contentPage)
+ navigationController.pushViewController(pageViewController, animated: true)
+ }
+ .store(in: &cancellables)
+ }
}
extension PageViewController: TabBarActionable {
@@ -639,24 +736,26 @@ private extension PageViewController {
private static let layoutHorizontalMargin: CGFloat = constant(iOS: 16, tvOS: 0)
private static let layoutVerticalMargin: CGFloat = constant(iOS: 8, tvOS: 0)
private static let layoutHorizontalConfigurationViewMargin: CGFloat = constant(iOS: 0, tvOS: 8)
+ private static let layoutTopicGradientViewHeight: CGFloat = 572
+
+ private static func layoutDisplayedTitleTopPadding(_ required: Bool) -> CGFloat {
+ return required ? layoutVerticalMargin * 2 : 0
+ }
private static func layoutConfiguration(model: PageViewModel, layoutWidth: CGFloat, horizontalSizeClass: UIUserInterfaceSizeClass, offsetX: CGFloat) -> UICollectionViewCompositionalLayoutConfiguration {
let configuration = UICollectionViewCompositionalLayoutConfiguration()
configuration.interSectionSpacing = constant(iOS: 35, tvOS: 70)
configuration.contentInsetsReference = constant(iOS: .automatic, tvOS: .layoutMargins)
- if let show = model.id.displayedShow {
+ if let title = model.displayedTitle {
+ let titleHeaderSize = TitleHeaderViewSize.recommended(for: title, description: model.displayedTitleDescription,
+ topPadding: layoutDisplayedTitleTopPadding(model.displayedTitleNeedsTopPadding), layoutWidth: layoutWidth - layoutHorizontalConfigurationViewMargin * 2, horizontalSizeClass: horizontalSizeClass)
+ configuration.boundarySupplementaryItems = [ NSCollectionLayoutBoundarySupplementaryItem(layoutSize: titleHeaderSize, elementKind: Header.titleHeader.rawValue, alignment: .topLeading, absoluteOffset: CGPoint(x: offsetX, y: 0)) ]
+ }
+ else if let show = model.displayedShow {
let showHeaderSize = ShowHeaderViewSize.recommended(for: show, horizontalPadding: layoutHorizontalMargin, layoutWidth: layoutWidth - layoutHorizontalConfigurationViewMargin * 2, horizontalSizeClass: horizontalSizeClass)
configuration.boundarySupplementaryItems = [ NSCollectionLayoutBoundarySupplementaryItem(layoutSize: showHeaderSize, elementKind: Header.showHeader.rawValue, alignment: .topLeading, absoluteOffset: CGPoint(x: offsetX + layoutHorizontalConfigurationViewMargin, y: 0)) ]
}
- else if let globalTitle = model.id.displayedGlobalTitle {
- let globalHeaderSize = TitleViewSize.recommended(forText: globalTitle)
- configuration.boundarySupplementaryItems = [ NSCollectionLayoutBoundarySupplementaryItem(layoutSize: globalHeaderSize, elementKind: Header.global.rawValue, alignment: .topLeading, absoluteOffset: CGPoint(x: offsetX, y: 0)) ]
- }
- else if let page = model.id.displayedPage {
- let globalHeaderSize = PageHeaderViewSize.recommended(for: page, layoutWidth: layoutWidth - layoutHorizontalConfigurationViewMargin * 2, horizontalSizeClass: horizontalSizeClass)
- configuration.boundarySupplementaryItems = [ NSCollectionLayoutBoundarySupplementaryItem(layoutSize: globalHeaderSize, elementKind: Header.pageHeader.rawValue, alignment: .topLeading, absoluteOffset: CGPoint(x: offsetX, y: 0)) ]
- }
return configuration
}
@@ -785,6 +884,56 @@ private extension PageViewController {
return layoutSection
}, configuration: Self.layoutConfiguration(model: model, layoutWidth: 0, horizontalSizeClass: .unspecified, offsetX: 0))
}
+
+ private func updateTopicGradientLayout() {
+ let topScreenOffset = constant(iOS: collectionView.safeAreaInsets.top, tvOS: 0)
+
+ if case .show = model.id {
+ let configuration = Self.layoutConfiguration(model: model, layoutWidth: view.safeAreaLayoutGuide.layoutFrame.width, horizontalSizeClass: view.traitCollection.horizontalSizeClass, offsetX: view.safeAreaLayoutGuide.layoutFrame.minX)
+ let supplementaryItemsHeight = configuration.boundarySupplementaryItems.map { $0.layoutSize.heightDimension.dimension }.reduce(0, +)
+ let mediaCellHeight = MediaCellSize.height(horizontalSizeClass: traitCollection.horizontalSizeClass)
+
+ // Move the gradient view below the show image when displayed in compact horizontal size class
+ if isShowHeaderVerticalLayout {
+ topicGradientViewFixTopAnchor.priority = .defaultLow
+ topicGradientViewStickyTopAnchor.priority = .defaultHigh
+
+ topicGradientViewStickyTopAnchor.constant = showPageStickyTopAnchorConstant
+ let showImageOffset = view.safeAreaLayoutGuide.layoutFrame.width / ShowHeaderView.imageAspectRatio
+ topicGradientViewHeightAnchor.constant = supplementaryItemsHeight - showImageOffset + mediaCellHeight
+ }
+ else {
+ topicGradientViewStickyTopAnchor.priority = .defaultLow
+ topicGradientViewFixTopAnchor.priority = .defaultHigh
+
+ topicGradientViewFixTopAnchor.constant = topScreenOffset
+ topicGradientViewHeightAnchor.constant = supplementaryItemsHeight + mediaCellHeight
+ }
+ }
+ else {
+ topicGradientViewStickyTopAnchor.priority = .defaultLow
+ topicGradientViewFixTopAnchor.priority = .defaultHigh
+
+ topicGradientViewFixTopAnchor.constant = topScreenOffset
+ topicGradientViewHeightAnchor.constant = Self.layoutTopicGradientViewHeight
+ }
+ }
+
+ private var showPageStickyTopAnchorConstant: CGFloat {
+ let showImageOffset = view.safeAreaLayoutGuide.layoutFrame.width / ShowHeaderView.imageAspectRatio
+ let topScreenOffset = constant(iOS: collectionView.safeAreaInsets.top, tvOS: 0)
+ let offset = topScreenOffset + collectionView.contentOffset.y
+ return (offset < showImageOffset) ? showImageOffset : offset
+ }
+
+ private var isShowHeaderVerticalLayout: Bool {
+ guard case .show = model.id else { return false }
+
+ return ShowHeaderView.isVerticalLayout(
+ horizontalSizeClass: traitCollection.horizontalSizeClass,
+ isLandscape: UIApplication.shared.mainWindow?.isLandscape ?? false
+ )
+ }
}
// MARK: Cells
@@ -793,23 +942,25 @@ private extension PageViewController {
struct MediaCell: View {
let media: SRGMedia?
let section: PageViewModel.Section
+ let primaryColor: Color
+ let secondaryColor: Color
var body: some View {
switch section.viewModelProperties.layout {
case .heroStage:
HeroMediaCell(media: media, label: section.properties.label)
case .headline:
- FeaturedContentCell(media: media, style: haveSameShow(media: media, in: section) ? .date : .show, label: section.properties.label, layout: .headline)
+ FeaturedContentCell(media: media, style: haveSameShow(media: media, in: section) ? .date : .show, label: section.properties.label, layout: .headline).primaryColor(primaryColor).secondaryColor(secondaryColor)
case .element, .elementSwimlane:
- FeaturedContentCell(media: media, style: haveSameShow(media: media, in: section) ? .date : .show, label: section.properties.label, layout: .element)
+ FeaturedContentCell(media: media, style: haveSameShow(media: media, in: section) ? .date : .show, label: section.properties.label, layout: .element).primaryColor(primaryColor).secondaryColor(secondaryColor)
case .liveMediaSwimlane, .liveMediaGrid:
LiveMediaCell(media: media)
case .mediaGrid:
- PlaySRG.MediaCell(media: media, style: haveSameShow(media: media, in: section) ? .date : .show)
+ PlaySRG.MediaCell(media: media, style: haveSameShow(media: media, in: section) ? .date : .show).primaryColor(primaryColor).secondaryColor(secondaryColor)
case .mediaList:
- PlaySRG.MediaCell(media: media, style: .dateAndSummary, layout: .horizontal)
+ PlaySRG.MediaCell(media: media, style: .dateAndSummary, layout: .horizontal).primaryColor(primaryColor).secondaryColor(secondaryColor)
default:
- PlaySRG.MediaCell(media: media, style: haveSameShow(media: media, in: section) ? .date : .show, layout: .vertical)
+ PlaySRG.MediaCell(media: media, style: haveSameShow(media: media, in: section) ? .date : .show, layout: .vertical).primaryColor(primaryColor).secondaryColor(secondaryColor)
}
}
@@ -823,15 +974,17 @@ private extension PageViewController {
struct ShowCell: View {
let show: SRGShow?
let section: PageViewModel.Section
+ let primaryColor: Color
+ let secondaryColor: Color
var body: some View {
switch section.viewModelProperties.layout {
case .heroStage, .headline:
- FeaturedContentCell(show: show, label: section.properties.label, layout: .headline)
+ FeaturedContentCell(show: show, label: section.properties.label, layout: .headline).primaryColor(primaryColor).secondaryColor(secondaryColor)
case .element:
- FeaturedContentCell(show: show, label: section.properties.label, layout: .element)
+ FeaturedContentCell(show: show, label: section.properties.label, layout: .element).primaryColor(primaryColor).secondaryColor(secondaryColor)
default:
- PlaySRG.ShowCell(show: show, style: .standard, imageVariant: section.properties.imageVariant)
+ PlaySRG.ShowCell(show: show, style: .standard, imageVariant: section.properties.imageVariant).primaryColor(primaryColor)
}
}
}
@@ -839,19 +992,21 @@ private extension PageViewController {
struct ItemCell: View {
let item: PageViewModel.Item
let id: PageViewModel.Id
+ let primaryColor: Color
+ let secondaryColor: Color
var body: some View {
switch item.wrappedValue {
case let .item(wrappedItem):
switch wrappedItem {
case .mediaPlaceholder:
- MediaCell(media: nil, section: item.section)
+ MediaCell(media: nil, section: item.section, primaryColor: primaryColor, secondaryColor: secondaryColor)
case let .media(media):
- MediaCell(media: media, section: item.section)
+ MediaCell(media: media, section: item.section, primaryColor: primaryColor, secondaryColor: secondaryColor)
case .showPlaceholder:
- ShowCell(show: nil, section: item.section)
+ ShowCell(show: nil, section: item.section, primaryColor: primaryColor, secondaryColor: secondaryColor)
case let .show(show):
- ShowCell(show: show, section: item.section)
+ ShowCell(show: show, section: item.section, primaryColor: primaryColor, secondaryColor: secondaryColor)
case .topicPlaceholder:
TopicCell(topic: nil)
case let .topic(topic):
@@ -865,9 +1020,9 @@ private extension PageViewController {
switch id {
case .video:
let style: ShowAccessCell.Style = !ApplicationConfiguration.shared.isTvGuideUnavailable ? .programGuide : .calendar
- ShowAccessCell(style: style)
+ ShowAccessCell(style: style).primaryColor(primaryColor)
default:
- ShowAccessCell(style: .calendar)
+ ShowAccessCell(style: .calendar).primaryColor(primaryColor)
}
#endif
case .highlightPlaceholder:
@@ -904,10 +1059,12 @@ private class OpenSectionEvent: UIEvent {
}
private extension PageViewController {
- private struct SectionHeaderView: View {
+ private struct SectionHeaderView: View, PrimaryColorSettable {
let section: PageViewModel.Section
let pageId: PageViewModel.Id
+ internal var primaryColor: Color = .srgGrayD2
+
@FirstResponder private var firstResponder
@AppStorage(PlaySRGSettingSectionWideSupportEnabled) var isSectionWideSupportEnabled = false
@@ -920,7 +1077,7 @@ private extension PageViewController {
}
private var hasDetailDisclosure: Bool {
- return section.viewModelProperties.canOpenDetailPage || isSectionWideSupportEnabled
+ return section.viewModelProperties.canOpenPage || isSectionWideSupportEnabled
}
var accessibilityLabel: String? {
@@ -934,12 +1091,12 @@ private extension PageViewController {
var body: some View {
if section.properties.displaysRowHeader, let title = Self.title(for: section) {
#if os(tvOS)
- HeaderView(title: title, subtitle: Self.subtitle(for: section), hasDetailDisclosure: false)
+ HeaderView(title: title, subtitle: Self.subtitle(for: section), hasDetailDisclosure: false, primaryColor: primaryColor)
#else
Button {
firstResponder.sendAction(#selector(SectionHeaderViewAction.openSection(sender:event:)), for: OpenSectionEvent(section: section))
} label: {
- HeaderView(title: title, subtitle: Self.subtitle(for: section), hasDetailDisclosure: hasDetailDisclosure)
+ HeaderView(title: title, subtitle: Self.subtitle(for: section), hasDetailDisclosure: hasDetailDisclosure, primaryColor: primaryColor)
}
.disabled(!hasDetailDisclosure)
.responderChain(from: firstResponder)
diff --git a/Application/Sources/Content/PageViewModel.swift b/Application/Sources/Content/PageViewModel.swift
index 99cf95c42..06cce8632 100644
--- a/Application/Sources/Content/PageViewModel.swift
+++ b/Application/Sources/Content/PageViewModel.swift
@@ -5,6 +5,7 @@
//
import SRGDataProviderCombine
+import SwiftUI
// MARK: View model
@@ -14,6 +15,8 @@ final class PageViewModel: Identifiable, ObservableObject {
@Published private(set) var state: State = .loading
@Published private(set) var serviceMessage: ServiceMessage?
+ @Published private(set) var displayedShow: SRGShow?
+
private let trigger = Trigger()
init(id: Id) {
@@ -58,6 +61,17 @@ final class PageViewModel: Identifiable, ObservableObject {
.removeDuplicates()
.receive(on: DispatchQueue.main)
.assign(to: &$serviceMessage)
+
+ if case let .show(show) = id {
+ self.displayedShow = show
+
+ // The show page needs `topics` which could be available only in the show request.
+ SRGDataProvider.current!.show(withUrn: show.urn)
+ .map { $0 }
+ .replaceError(with: show)
+ .receive(on: DispatchQueue.main)
+ .assign(to: &$displayedShow)
+ }
}
func loadMore() {
@@ -153,31 +167,6 @@ extension PageViewModel {
case page(_ page: SRGContentPage)
#if os(iOS)
- var isHeaderWithTitle: Bool {
- return hasShowHeaderView || hasPageHeaderView
- }
-
- var isLargeTitleDisplayMode: Bool {
- if isHeaderWithTitle {
- return false
- }
- else {
- // Avoid iOS automatic scroll insets / offset bugs occurring if large titles are desired by a view controller
- // but the navigation bar is hidden. The scroll insets are incorrect and sometimes the scroll offset might
- // be incorrect at the top.
- return !isNavigationBarHidden
- }
- }
-
- var isNavigationBarHidden: Bool {
- switch self {
- case .video:
- return true
- default:
- return false
- }
- }
-
var sharingItem: SharingItem? {
switch self {
case let .show(show):
@@ -225,45 +214,6 @@ extension PageViewModel {
}
}
- var displayedShow: SRGShow? {
- if case let .show(show) = self {
- return show
- }
- else {
- return nil
- }
- }
-
- var hasShowHeaderView: Bool {
- return displayedShow != nil
- }
-
- var displayedGlobalTitle: String? {
- if case .topic = self {
-#if os(tvOS)
- return title
-#else
- return nil
-#endif
- }
- else {
- return nil
- }
- }
-
- var displayedPage: SRGContentPage? {
- if case let .page(page) = self {
- return page
- }
- else {
- return nil
- }
- }
-
- var hasPageHeaderView: Bool {
- return displayedPage != nil
- }
-
var analyticsPageViewTitle: String {
switch self {
case .video, .audio, .live:
@@ -447,6 +397,114 @@ extension PageViewModel {
}
}
+// MARK: Header and navigation
+
+extension PageViewModel {
+#if os(iOS)
+ var isHeaderWithTitle: Bool {
+ return displayedTitle != nil || displayedShow != nil
+ }
+
+ var isLargeTitleDisplayMode: Bool {
+ if isHeaderWithTitle {
+ return false
+ }
+ else {
+ // Avoid iOS automatic scroll insets / offset bugs occurring if large titles are desired by a view controller
+ // but the navigation bar is hidden. The scroll insets are incorrect and sometimes the scroll offset might
+ // be incorrect at the top.
+ return !isNavigationBarHidden
+ }
+ }
+
+ var isNavigationBarHidden: Bool {
+ switch id {
+ case .video:
+ return true
+ default:
+ return false
+ }
+ }
+#endif
+
+ var primaryColor: Color {
+ switch id {
+ case let .topic(topic):
+ return ApplicationConfiguration.shared.topicColors(for: topic) != nil ? .white : .srgGrayD2
+ case .show:
+ guard let topic = displayedShow?.topics?.first else { return .srgGrayD2 }
+ return ApplicationConfiguration.shared.topicColors(for: topic) != nil ? .white : .srgGrayD2
+ default:
+ return .srgGrayD2
+ }
+ }
+
+ var secondaryColor: Color {
+ return .srgGray96
+ }
+
+ var displayedTitle: String? {
+ switch id {
+ case let .page(page):
+ return page.title
+ case let .topic(topic):
+ return topic.title
+ default:
+ return nil
+ }
+ }
+
+ var displayedTitleDescription: String? {
+ if case let .page(page) = id {
+ return page.summary
+ }
+ else {
+ return nil
+ }
+ }
+
+ var displayedTitleTextAlignment: TextAlignment {
+ if case .topic = id {
+ return constant(iOS: .leading, tvOS: .center)
+ }
+ else {
+ return .leading
+ }
+ }
+
+ var displayedTitleNeedsTopPadding: Bool {
+ if case let .topic(topic) = id, ApplicationConfiguration.shared.topicColors(for: topic) != nil {
+ return constant(iOS: true, tvOS: false)
+ }
+ else {
+ return false
+ }
+ }
+
+ var displayedGradientTopic: SRGTopic? {
+ switch id {
+ case let .topic(topic):
+ return topic
+ case .show:
+ guard let topic = displayedShow?.topics?.first else { return nil }
+ return topic
+ default:
+ return nil
+ }
+ }
+
+ var displayedGradientTopicStyle: TopicGradientView.Style? {
+ switch id {
+ case .topic:
+ return .topicPage
+ case .show:
+ return .showPage
+ default:
+ return nil
+ }
+ }
+}
+
// MARK: User activity
extension PageViewModel {
@@ -554,7 +612,7 @@ private extension PageViewModel {
var rowItems = items.map { Item(.item($0), in: section) }
#if os(tvOS)
if !rowItems.isEmpty
- && (section.viewModelProperties.canOpenDetailPage || ApplicationSettingSectionWideSupportEnabled())
+ && (section.viewModelProperties.canOpenPage || ApplicationSettingSectionWideSupportEnabled())
&& section.viewModelProperties.hasMoreRowItem {
rowItems.append(Item(.more, in: section))
}
@@ -567,7 +625,7 @@ private extension PageViewModel {
protocol PageViewModelProperties {
var layout: PageViewModel.SectionLayout { get }
- var canOpenDetailPage: Bool { get }
+ var canOpenPage: Bool { get }
}
extension PageViewModelProperties {
@@ -637,12 +695,16 @@ private extension PageViewModel {
}
}
- var canOpenDetailPage: Bool {
+ var canOpenPage: Bool {
switch presentation.type {
case .favoriteShows, .myProgram, .continueWatching, .topicSelector, .watchLater:
return true
default:
- return presentation.hasDetailPage
+ if presentation.contentLink != nil {
+ return true
+ } else {
+ return false
+ }
}
}
}
@@ -680,7 +742,7 @@ private extension PageViewModel {
}
}
- var canOpenDetailPage: Bool {
+ var canOpenPage: Bool {
return layout == .mediaSwimlane || layout == .showSwimlane
}
}
diff --git a/Application/Sources/Content/SectionShowHeaderView.swift b/Application/Sources/Content/SectionShowHeaderView.swift
index d1cb735ca..b5b08c986 100644
--- a/Application/Sources/Content/SectionShowHeaderView.swift
+++ b/Application/Sources/Content/SectionShowHeaderView.swift
@@ -46,13 +46,9 @@ struct SectionShowHeaderView: View {
return (horizontalSizeClass == .compact) ? .center : .leading
}
- private var imageUrl: URL? {
- return url(for: show.image, size: .medium)
- }
-
var body: some View {
Stack(direction: direction, alignment: alignment, spacing: 0) {
- ImageView(source: imageUrl)
+ ShowVisualView(show: show, size: .medium)
.aspectRatio(16 / 9, contentMode: .fit)
.overlay(ImageOverlay(horizontalSizeClass: horizontalSizeClass))
.adaptiveMainFrame(for: horizontalSizeClass)
@@ -95,7 +91,7 @@ struct SectionShowHeaderView: View {
// all lines it could.
.fixedSize(horizontal: false, vertical: true)
.multilineTextAlignment(.center)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
}
if let summary = section.properties.summary {
Text(summary)
diff --git a/Application/Sources/Content/SectionViewController.swift b/Application/Sources/Content/SectionViewController.swift
index 730abe079..a694ad92e 100644
--- a/Application/Sources/Content/SectionViewController.swift
+++ b/Application/Sources/Content/SectionViewController.swift
@@ -37,7 +37,7 @@ final class SectionViewController: UIViewController {
private var contentInsets: UIEdgeInsets
private var leftBarButtonItem: UIBarButtonItem?
- private var globalHeaderTitle: String? {
+ private var headerTitle: String? {
#if os(tvOS)
return (tabBarController == nil && model.displaysTitle) ? model.title : nil
#else
@@ -137,9 +137,9 @@ final class SectionViewController: UIViewController {
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
}
- let globalHeaderViewRegistration = UICollectionView.SupplementaryRegistration>(elementKind: Header.global.rawValue) { [weak self] view, _, _ in
+ let titleHeaderViewRegistration = UICollectionView.SupplementaryRegistration>(elementKind: Header.titleHeader.rawValue) { [weak self] view, _, _ in
guard let self else { return }
- view.content = TitleView(text: globalHeaderTitle)
+ view.content = TitleHeaderView(headerTitle, titleTextAlignment: constant(iOS: .leading, tvOS: .center))
if let hostController = view.hostController {
addChild(hostController)
}
@@ -167,8 +167,8 @@ final class SectionViewController: UIViewController {
dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in
switch kind {
- case Header.global.rawValue:
- return collectionView.dequeueConfiguredReusableSupplementary(using: globalHeaderViewRegistration, for: indexPath)
+ case Header.titleHeader.rawValue:
+ return collectionView.dequeueConfiguredReusableSupplementary(using: titleHeaderViewRegistration, for: indexPath)
case UICollectionView.elementKindSectionHeader:
return collectionView.dequeueConfiguredReusableSupplementary(using: sectionHeaderViewRegistration, for: indexPath)
case UICollectionView.elementKindSectionFooter:
@@ -187,12 +187,21 @@ final class SectionViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
+
+ updateLayoutConfiguration()
+
model.resetApplicationBadgeIfNeeded()
model.reload()
deselectItems(in: collectionView, animated: animated)
navigationController?.setNavigationBarHidden(false, animated: animated)
}
+ private func updateLayoutConfiguration() {
+ if let collectionViewLayout = self.collectionView.collectionViewLayout as? UICollectionViewCompositionalLayout {
+ collectionViewLayout.configuration = Self.layoutConfiguration(title: headerTitle, layoutWidth: view.safeAreaLayoutGuide.layoutFrame.width, horizontalSizeClass: view.traitCollection.horizontalSizeClass)
+ }
+ }
+
#if os(iOS)
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
@@ -367,7 +376,7 @@ final class SectionViewController: UIViewController {
private extension SectionViewController {
enum Header: String {
- case global
+ case titleHeader
}
}
@@ -609,14 +618,13 @@ extension SectionViewController: TabBarActionable {
// MARK: Layout
private extension SectionViewController {
- private func layoutConfiguration() -> UICollectionViewCompositionalLayoutConfiguration {
+ private static func layoutConfiguration(title: String?, layoutWidth: CGFloat, horizontalSizeClass: UIUserInterfaceSizeClass) -> UICollectionViewCompositionalLayoutConfiguration {
let configuration = UICollectionViewCompositionalLayoutConfiguration()
configuration.contentInsetsReference = constant(iOS: .automatic, tvOS: .layoutMargins)
configuration.interSectionSpacing = constant(iOS: 15, tvOS: 100)
- let headerSize = TitleViewSize.recommended(forText: globalHeaderTitle)
- let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: Header.global.rawValue, alignment: .topLeading)
- configuration.boundarySupplementaryItems = [header]
+ let titleHeaderSize = TitleHeaderViewSize.recommended(for: title, layoutWidth: layoutWidth, horizontalSizeClass: horizontalSizeClass)
+ configuration.boundarySupplementaryItems = [ NSCollectionLayoutBoundarySupplementaryItem(layoutSize: titleHeaderSize, elementKind: Header.titleHeader.rawValue, alignment: .topLeading) ]
return configuration
}
@@ -707,7 +715,7 @@ private extension SectionViewController {
layoutSection.boundarySupplementaryItems = sectionSupplementaryItems(for: section, configuration: configuration, layoutEnvironment: layoutEnvironment)
layoutSection.supplementariesFollowContentInsets = false
return layoutSection
- }, configuration: layoutConfiguration())
+ }, configuration: Self.layoutConfiguration(title: headerTitle, layoutWidth: 0, horizontalSizeClass: .unspecified))
}
}
diff --git a/Application/Sources/Content/ShowHeaderView.swift b/Application/Sources/Content/ShowHeaderView.swift
index 90f39c343..11ad469a3 100644
--- a/Application/Sources/Content/ShowHeaderView.swift
+++ b/Application/Sources/Content/ShowHeaderView.swift
@@ -29,21 +29,30 @@ class ShowMoreEvent: UIEvent {
// MARK: View
/// Behavior: h-hug, v-hug
-struct ShowHeaderView: View {
+struct ShowHeaderView: View, PrimaryColorSettable {
@Binding private(set) var show: SRGShow?
let horizontalPadding: CGFloat
+ internal var primaryColor: Color = .srgGrayD2
+
+ static let imageAspectRatio: CGFloat = 16 / 9
+
+ static func isVerticalLayout(horizontalSizeClass: UIUserInterfaceSizeClass, isLandscape: Bool) -> Bool {
+ return horizontalSizeClass == .compact || !isLandscape
+ }
+
@StateObject private var model = ShowHeaderViewModel()
fileprivate static let verticalSpacing: CGFloat = 24
- init(show: SRGShow?, horizontalPadding: CGFloat) {
+ init(_ show: SRGShow?, horizontalPadding: CGFloat) {
_show = .constant(show)
self.horizontalPadding = horizontalPadding
}
var body: some View {
MainView(model: model, horizontalPadding: horizontalPadding)
+ .primaryColor(primaryColor)
.onAppear {
model.show = show
}
@@ -53,14 +62,14 @@ struct ShowHeaderView: View {
}
/// Behavior: h-hug, v-hug.
- fileprivate struct MainView: View {
+ fileprivate struct MainView: View, PrimaryColorSettable {
@ObservedObject var model: ShowHeaderViewModel
let horizontalPadding: CGFloat
@Environment(\.uiHorizontalSizeClass) private var horizontalSizeClass
@State private var isLandscape: Bool
- private let compactDescriptionOffet: CGFloat = -12
+ internal var primaryColor: Color = .srgGrayD2
init(model: ShowHeaderViewModel, horizontalPadding: CGFloat) {
self.model = model
@@ -68,36 +77,35 @@ struct ShowHeaderView: View {
self.isLandscape = (UIApplication.shared.mainWindow?.isLandscape ?? false)
}
- private var descriptionHorizontalPadding: CGFloat {
+ private var padding: CGFloat {
return horizontalSizeClass == .compact ? horizontalPadding : horizontalPadding * 2
}
var body: some View {
Group {
- if horizontalSizeClass == .compact || !isLandscape {
- VStack(alignment: .center, spacing: 0) {
- ImageView(source: model.imageUrl)
- .aspectRatio(16 / 9, contentMode: .fit)
- .overlay(ImageOverlay(isHorizontal: false))
+ if isVerticalLayout(horizontalSizeClass: horizontalSizeClass, isLandscape: isLandscape) {
+ VStack(alignment: .leading, spacing: 0) {
+ ShowVisualView(show: model.show, size: .large)
+ .aspectRatio(ShowHeaderView.imageAspectRatio, contentMode: .fit)
.layoutPriority(1)
- DescriptionView(model: model, centerLayout: horizontalSizeClass == .compact)
- .padding(.horizontal, descriptionHorizontalPadding)
- .offset(y: compactDescriptionOffet)
+ DescriptionView(model: model, compactLayout: horizontalSizeClass == .compact)
+ .primaryColor(primaryColor)
+ .padding(.top, padding)
+ .padding(.horizontal, padding)
}
- .padding(.bottom, 24 + compactDescriptionOffet)
- .focusable()
+ .padding(.bottom, 24)
}
else {
- HStack(spacing: 0) {
- DescriptionView(model: model, centerLayout: false)
- .padding(.leading, descriptionHorizontalPadding)
- .padding(.trailing, 16)
- ImageView(source: model.imageUrl)
- .aspectRatio(16 / 9, contentMode: .fit)
- .overlay(ImageOverlay(isHorizontal: true))
+ HStack(spacing: constant(iOS: padding, tvOS: 50)) {
+ DescriptionView(model: model, compactLayout: false)
+ .primaryColor(primaryColor)
+ ShowVisualView(show: model.show, size: .large)
+ .aspectRatio(ShowHeaderView.imageAspectRatio, contentMode: .fit)
+ .frame(width: UIScreen.main.bounds.width * 0.35)
}
+ .padding(.top, padding)
+ .padding(.horizontal, padding)
.padding(.bottom, constant(iOS: 40, tvOS: 50))
- .focusable()
}
}
.readSize { _ in
@@ -106,38 +114,15 @@ struct ShowHeaderView: View {
}
}
- /// Behavior: h-exp, v-exp
- private struct ImageOverlay: View {
- let isHorizontal: Bool
-
- var body: some View {
- if isHorizontal {
- Group {
- LinearGradient(colors: [.clear, .srgGray16], startPoint: UnitPoint(x: 0.1, y: 0.5), endPoint: .leading)
- LinearGradient(colors: [.clear, .srgGray16], startPoint: UnitPoint(x: 0.5, y: 0.95), endPoint: .bottom)
- }
- }
- else {
- LinearGradient(colors: [.clear, .srgGray16], startPoint: UnitPoint(x: 0.5, y: 0.9), endPoint: .bottom)
- }
- }
- }
-
/// Behavior: h-hug, v-hug
- private struct DescriptionView: View {
+ private struct DescriptionView: View, PrimaryColorSettable {
@ObservedObject var model: ShowHeaderViewModel
- let centerLayout: Bool
+ let compactLayout: Bool
- private var stackAlignment: HorizontalAlignment {
- return centerLayout ? .center : .leading
- }
-
- private var titleAlignment: TextAlignment {
- return centerLayout ? .center : .leading
- }
+ internal var primaryColor: Color = .srgGrayD2
var body: some View {
- VStack(alignment: stackAlignment, spacing: ShowHeaderView.verticalSpacing) {
+ VStack(alignment: .leading, spacing: ShowHeaderView.verticalSpacing) {
Text(model.title ?? "")
.srgFont(.H2)
.lineLimit(2)
@@ -145,14 +130,60 @@ struct ShowHeaderView: View {
// when calculated with a `UIHostingController`, but without this the text does not occupy
// all lines it could.
.fixedSize(horizontal: false, vertical: true)
- .multilineTextAlignment(titleAlignment)
- .foregroundColor(.white)
+ .multilineTextAlignment(.leading)
+ .foregroundColor(primaryColor)
+ if let broadcastInformation = model.broadcastInformation {
+ Badge(text: broadcastInformation, color: Color(.srgDarkRed))
+ }
+ if let summary = model.show?.play_summary {
+ SummaryView(summary)
+ .primaryColor(primaryColor)
+ // See above
+ .fixedSize(horizontal: false, vertical: true)
+ }
+ ActionsView(model: model, compactLayout: compactLayout)
+ .primaryColor(primaryColor)
+ .frame(height: constant(iOS: 40, tvOS: 70))
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .focusable()
+ }
+
+ /// Behavior: h-exp, v-hug
+ private struct SummaryView: View, PrimaryColorSettable {
+ let content: String
+
+ internal var primaryColor: Color = .srgGrayD2
+
+ @FirstResponder private var firstResponder
+
+ init(_ content: String) {
+ self.content = content
+ }
+
+ var body: some View {
+ TruncatableTextView(content: content, lineLimit: 3) {
+ firstResponder.sendAction(#selector(ShowHeaderViewAction.showMore(sender:event:)), for: ShowMoreEvent(content: content))
+ }
+ .primaryColor(primaryColor)
+ .responderChain(from: firstResponder)
+ }
+ }
+
+ private struct ActionsView: View, PrimaryColorSettable {
+ @ObservedObject var model: ShowHeaderViewModel
+ let compactLayout: Bool
+
+ internal var primaryColor: Color = .srgGrayD2
+
+ var body: some View {
HStack(spacing: 8) {
- if centerLayout {
+ if compactLayout {
ExpandingButton(icon: model.favoriteIcon,
label: model.favoriteLabel,
accessibilityLabel: model.favoriteAccessibilityLabel,
action: favoriteAction)
+ .primaryColor(primaryColor)
.alert(isPresented: $model.isFavoriteRemovalAlertDisplayed, content: favoriteRemovalAlert)
#if os(iOS)
if model.isSubscriptionPossible {
@@ -160,6 +191,7 @@ struct ShowHeaderView: View {
label: model.subscriptionLabel,
accessibilityLabel: model.subscriptionAccessibilityLabel,
action: subscriptionAction)
+ .primaryColor(primaryColor)
}
#endif
}
@@ -169,72 +201,46 @@ struct ShowHeaderView: View {
labelMinimumScaleFactor: 1,
accessibilityLabel: model.favoriteAccessibilityLabel,
action: favoriteAction)
+ .primaryColor(primaryColor)
#if os(iOS)
if model.isSubscriptionPossible {
SimpleButton(icon: model.subscriptionIcon,
label: model.subscriptionLabel,
accessibilityLabel: model.subscriptionAccessibilityLabel,
action: subscriptionAction)
+ .primaryColor(primaryColor)
}
#endif
}
}
- .frame(height: constant(iOS: 40, tvOS: 70))
.alert(isPresented: $model.isFavoriteRemovalAlertDisplayed, content: favoriteRemovalAlert)
- if let summary = model.show?.play_summary {
- SummaryView(summary)
- // See above
- .fixedSize(horizontal: false, vertical: true)
+ }
+
+ private func favoriteAction() {
+ if model.shouldDisplayFavoriteRemovalAlert {
+ model.isFavoriteRemovalAlertDisplayed = true
}
- if let broadcastInformation = model.broadcastInformation {
- Badge(text: broadcastInformation, color: Color(.srgGray96), textColor: Color(.srgGray16))
+ else {
+ model.toggleFavorite()
}
}
- .frame(maxWidth: .infinity, alignment: .leading)
- }
-
- private func favoriteAction() {
- if model.shouldDisplayFavoriteRemovalAlert {
- model.isFavoriteRemovalAlertDisplayed = true
- }
- else {
- model.toggleFavorite()
- }
- }
-
- private func favoriteRemovalAlert() -> Alert {
- let primaryButton = Alert.Button.cancel(Text(NSLocalizedString("Cancel", comment: "Title of a cancel button"))) {}
- let secondaryButton = Alert.Button.destructive(Text(NSLocalizedString("Delete", comment: "Title of a delete button"))) {
- model.toggleFavorite()
- }
- return Alert(title: Text(NSLocalizedString("Delete from favorites", comment: "Title of the confirmation pop-up displayed when the user is about to delete a favorite")),
- message: Text(NSLocalizedString("The favorite and notification subscription will be deleted on all devices connected to your account.", comment: "Confirmation message displayed when a logged in user is about to delete a favorite")),
- primaryButton: primaryButton,
- secondaryButton: secondaryButton)
- }
-
-#if os(iOS)
- private func subscriptionAction() {
- model.toggleSubscription()
- }
-#endif
-
- /// Behavior: h-exp, v-hug
- private struct SummaryView: View {
- let content: String
- @FirstResponder private var firstResponder
-
- var body: some View {
- TruncatableTextView(content: content, lineLimit: 3) {
- firstResponder.sendAction(#selector(ShowHeaderViewAction.showMore(sender:event:)), for: ShowMoreEvent(content: content))
+ private func favoriteRemovalAlert() -> Alert {
+ let primaryButton = Alert.Button.cancel(Text(NSLocalizedString("Cancel", comment: "Title of a cancel button"))) {}
+ let secondaryButton = Alert.Button.destructive(Text(NSLocalizedString("Delete", comment: "Title of a delete button"))) {
+ model.toggleFavorite()
}
- .responderChain(from: firstResponder)
+ return Alert(title: Text(NSLocalizedString("Delete from favorites", comment: "Title of the confirmation pop-up displayed when the user is about to delete a favorite")),
+ message: Text(NSLocalizedString("The favorite and notification subscription will be deleted on all devices connected to your account.", comment: "Confirmation message displayed when a logged in user is about to delete a favorite")),
+ primaryButton: primaryButton,
+ secondaryButton: secondaryButton)
}
- init(_ content: String) {
- self.content = content
+#if os(iOS)
+ private func subscriptionAction() {
+ model.toggleSubscription()
}
+#endif
}
}
}
diff --git a/Application/Sources/Content/ShowHeaderViewModel.swift b/Application/Sources/Content/ShowHeaderViewModel.swift
index ddbeb6fff..79b5191c2 100644
--- a/Application/Sources/Content/ShowHeaderViewModel.swift
+++ b/Application/Sources/Content/ShowHeaderViewModel.swift
@@ -75,10 +75,6 @@ final class ShowHeaderViewModel: ObservableObject {
return show?.broadcastInformation?.message
}
- var imageUrl: URL? {
- return url(for: show?.image, size: .large)
- }
-
var favoriteIcon: ImageResource {
return isFavorite ? .favoriteFull : .favorite
}
diff --git a/Application/Sources/Helpers/Colors.swift b/Application/Sources/Helpers/Colors.swift
index 860e87232..334a3d3e2 100644
--- a/Application/Sources/Helpers/Colors.swift
+++ b/Application/Sources/Helpers/Colors.swift
@@ -9,6 +9,7 @@ import SwiftUI
extension Color {
static let darkGray = Color(.darkGray)
static let placeholder = Color(.placeholder)
+ static let thumbnailBackground = Color(.thumbnailBackground)
}
extension UIColor {
@@ -19,10 +20,11 @@ extension UIColor {
}
}
- public static var placeholder = UIColor(white: 1, alpha: 0.1)
+ static var placeholder = UIColor(white: 1, alpha: 0.1)
+ @objc static var thumbnailBackground = UIColor.black
#if DEBUG
- public static func random(alpha: CGFloat = 1) -> UIColor {
+ static func random(alpha: CGFloat = 1) -> UIColor {
return UIColor(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1), alpha: alpha)
}
#endif
diff --git a/Application/Sources/Helpers/Extensions/UIColor+PlaySRG.swift b/Application/Sources/Helpers/Extensions/UIColor+PlaySRG.swift
index b36c322c2..640d10e62 100644
--- a/Application/Sources/Helpers/Extensions/UIColor+PlaySRG.swift
+++ b/Application/Sources/Helpers/Extensions/UIColor+PlaySRG.swift
@@ -27,10 +27,6 @@ extension UIColor {
}
}
- @objc static var play_grayThumbnailImageViewBackground: UIColor {
- return play_hexadecimal("#202020")
- }
-
@objc static var play_blackDurationLabelBackground: UIColor {
return UIColor(white: 0.0, alpha: 0.5)
}
diff --git a/Application/Sources/Helpers/UserConsentHelper.swift b/Application/Sources/Helpers/UserConsentHelper.swift
index c05b203f5..8f36be04e 100644
--- a/Application/Sources/Helpers/UserConsentHelper.swift
+++ b/Application/Sources/Helpers/UserConsentHelper.swift
@@ -189,7 +189,7 @@ enum UCService: Hashable, CaseIterable {
#if os(iOS)
let backgroundColor = UIColor.srgGray23
let foregroundColor = UIColor.white
- let textColor = UIColor.srgGrayC7
+ let textColor = UIColor.srgGrayD2
var settings = GeneralStyleSettings()
@@ -230,7 +230,7 @@ enum UCService: Hashable, CaseIterable {
#if os(iOS)
private static func firstLayerButtonSettings(cmpData: UsercentricsCMPData) -> [ButtonSettings] {
- var buttons: [ButtonSettings] = [ButtonSettings]()
+ var buttons = [ButtonSettings]()
buttons.append(button(type: .acceptAll, isPrimary: true))
if !(cmpData.settings.firstLayer?.hideButtonDeny?.boolValue ?? false) {
buttons.append(button(type: .denyAll, isPrimary: true))
@@ -240,7 +240,7 @@ enum UCService: Hashable, CaseIterable {
}
private static func secondLayerButtonSettings(cmpData: UsercentricsCMPData) -> [ButtonSettings] {
- var buttons: [ButtonSettings] = [ButtonSettings]()
+ var buttons = [ButtonSettings]()
buttons.append(button(type: .acceptAll, isPrimary: false))
if !(cmpData.settings.secondLayer.hideButtonDeny?.boolValue ?? false) {
buttons.append(button(type: .denyAll, isPrimary: false))
@@ -252,7 +252,7 @@ enum UCService: Hashable, CaseIterable {
private static func button(type: UsercentricsUI.ButtonType, isPrimary: Bool) -> ButtonSettings {
return ButtonSettings(type: type,
textColor: isPrimary ? .white : .srgGray23,
- backgroundColor: isPrimary ? .srgRed : .srgGrayC7,
+ backgroundColor: isPrimary ? .srgRed : .srgGrayD2,
cornerRadius: 8)
}
#endif
diff --git a/Application/Sources/Model/FeaturedContent.swift b/Application/Sources/Model/FeaturedContent.swift
index 1b068fcdc..0b15bea1a 100644
--- a/Application/Sources/Model/FeaturedContent.swift
+++ b/Application/Sources/Model/FeaturedContent.swift
@@ -111,7 +111,7 @@ struct FeaturedShowContent: FeaturedContent {
}
func visualView() -> some View {
- return ImageView(source: url(for: show?.image, size: .medium))
+ return ShowVisualView(show: show, size: .medium)
}
#if os(tvOS)
diff --git a/Application/Sources/Player/MediaPlayerViewController+SongPanel.swift b/Application/Sources/Player/MediaPlayerViewController+SongPanel.swift
index f8678fe9f..5a8dacc67 100644
--- a/Application/Sources/Player/MediaPlayerViewController+SongPanel.swift
+++ b/Application/Sources/Player/MediaPlayerViewController+SongPanel.swift
@@ -161,7 +161,7 @@ private extension MediaPlayerViewController {
configuration.supportedModes = [.compact, .expanded, .fullHeight]
configuration.mode = mode
- configuration.appearance.resizeHandle = .visible(foregroundColor: .srgGrayC7, backgroundColor: .srgGray23)
+ configuration.appearance.resizeHandle = .visible(foregroundColor: .srgGrayD2, backgroundColor: .srgGray23)
configuration.appearance.separatorColor = .clear
configuration.appearance.borderColor = .clear
diff --git a/Application/Sources/Player/MediaPlayerViewController.m b/Application/Sources/Player/MediaPlayerViewController.m
index adf3366c4..ecc8afc70 100755
--- a/Application/Sources/Player/MediaPlayerViewController.m
+++ b/Application/Sources/Player/MediaPlayerViewController.m
@@ -369,9 +369,9 @@ - (void)viewDidLoad
self.showWrapperView.layer.cornerRadius = LayoutStandardViewCornerRadius;
self.showWrapperView.layer.masksToBounds = YES;
- self.showThumbnailImageView.backgroundColor = UIColor.play_grayThumbnailImageViewBackground;
+ self.showThumbnailImageView.backgroundColor = UIColor.thumbnailBackground;
- self.moreEpisodesLabel.textColor = UIColor.srg_grayC7Color;
+ self.moreEpisodesLabel.textColor = UIColor.srg_grayD2Color;
self.pullDownGestureRecognizer.delegate = self;
diff --git a/Application/Sources/Player/ProgramTableViewCell.m b/Application/Sources/Player/ProgramTableViewCell.m
index b49d942c6..c90bb6bda 100644
--- a/Application/Sources/Player/ProgramTableViewCell.m
+++ b/Application/Sources/Player/ProgramTableViewCell.m
@@ -40,7 +40,7 @@ - (void)awakeFromNib
self.backgroundColor = UIColor.clearColor;
self.selectionStyle = UITableViewCellSelectionStyleNone;
- self.thumbnailWrapperView.backgroundColor = UIColor.play_grayThumbnailImageViewBackground;
+ self.thumbnailWrapperView.backgroundColor = UIColor.thumbnailBackground;
self.thumbnailWrapperView.layer.cornerRadius = LayoutStandardViewCornerRadius;
self.thumbnailWrapperView.layer.masksToBounds = YES;
diff --git a/Application/Sources/Player/RelatedContentView.m b/Application/Sources/Player/RelatedContentView.m
index e7941e9f8..ad2b94150 100755
--- a/Application/Sources/Player/RelatedContentView.m
+++ b/Application/Sources/Player/RelatedContentView.m
@@ -52,7 +52,7 @@ - (void)setRelatedContent:(SRGRelatedContent *)relatedContent
// Unbreakable spaces before / after the separator
[attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" - %@", text]
attributes:@{ NSFontAttributeName : [SRGFont fontWithStyle:SRGFontStyleSubtitle1],
- NSForegroundColorAttributeName : UIColor.srg_grayC7Color }]];
+ NSForegroundColorAttributeName : UIColor.srg_grayD2Color }]];
}
self.textLabel.attributedText = attributedText.copy;
diff --git a/Application/Sources/Profile/ProfileAccountHeaderView.swift b/Application/Sources/Profile/ProfileAccountHeaderView.swift
index 54b2c76de..852ab815c 100644
--- a/Application/Sources/Profile/ProfileAccountHeaderView.swift
+++ b/Application/Sources/Profile/ProfileAccountHeaderView.swift
@@ -58,7 +58,7 @@ struct ProfileAccountHeaderView: View {
)
.overlay(
Circle()
- .stroke(Color.srgGrayC7, lineWidth: lineWidth)
+ .stroke(Color.srgGrayD2, lineWidth: lineWidth)
.frame(maxWidth: iconHeight - lineWidth, maxHeight: iconHeight - lineWidth)
)
.opacity(1)
@@ -88,7 +88,7 @@ struct ProfileAccountHeaderView: View {
.aspectRatio(contentMode: .fit)
.frame(height: 16)
}
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
.padding(.horizontal, 16)
.padding(.vertical, 4)
.frame(maxHeight: .infinity)
diff --git a/Application/Sources/Profile/ProfileCell.swift b/Application/Sources/Profile/ProfileCell.swift
index b583ef871..7ba3fd4c6 100644
--- a/Application/Sources/Profile/ProfileCell.swift
+++ b/Application/Sources/Profile/ProfileCell.swift
@@ -76,7 +76,7 @@ struct ProfileCell: View {
.frame(height: 16)
}
}
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
.padding(.horizontal, 16)
.padding(.vertical, 4)
.frame(maxHeight: .infinity)
diff --git a/Application/Sources/Profile/ProfileHelp.swift b/Application/Sources/Profile/ProfileHelp.swift
index f0df3371f..0fddb5a3c 100644
--- a/Application/Sources/Profile/ProfileHelp.swift
+++ b/Application/Sources/Profile/ProfileHelp.swift
@@ -59,7 +59,7 @@ import SwiftUI
let safariViewController = SFSafariViewController(url: url)
safariViewController.preferredBarTintColor = UIColor.srgGray16
- safariViewController.preferredControlTintColor = UIColor.srgGrayC7
+ safariViewController.preferredControlTintColor = UIColor.srgGrayD2
safariViewController.modalPresentationStyle = .pageSheet
tabBarController.play_top.present(safariViewController, animated: true, completion: completion)
diff --git a/Application/Sources/Profile/ProfileSectionHeaderView.swift b/Application/Sources/Profile/ProfileSectionHeaderView.swift
index 56df2c9cd..8e46b3807 100644
--- a/Application/Sources/Profile/ProfileSectionHeaderView.swift
+++ b/Application/Sources/Profile/ProfileSectionHeaderView.swift
@@ -17,7 +17,7 @@ struct ProfileSectionHeaderView: View {
.lineLimit(1)
}
.frame(maxWidth: .infinity, alignment: .leading)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
.padding(.all, 16)
}
}
diff --git a/Application/Sources/ProgramGuide/ProgramCell.swift b/Application/Sources/ProgramGuide/ProgramCell.swift
index 49cf241c4..57a358c15 100644
--- a/Application/Sources/ProgramGuide/ProgramCell.swift
+++ b/Application/Sources/ProgramGuide/ProgramCell.swift
@@ -128,14 +128,14 @@ struct ProgramCell: View {
HStack(spacing: 10) {
if !compact && model.canPlay {
Image(.playCircle)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
.frame(height: canPlayHeight)
}
if let title = model.data?.program.title {
Text(title)
.srgFont(.body)
.lineLimit(1)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
}
}
.frame(minHeight: canPlayHeight)
diff --git a/Application/Sources/ProgramGuide/ProgramGuideHeaderView.swift b/Application/Sources/ProgramGuide/ProgramGuideHeaderView.swift
index d1e9c38e5..2a4f06d9e 100644
--- a/Application/Sources/ProgramGuide/ProgramGuideHeaderView.swift
+++ b/Application/Sources/ProgramGuide/ProgramGuideHeaderView.swift
@@ -146,7 +146,7 @@ struct ProgramGuideHeaderView: View {
Text(model.dateString)
.srgFont(.H2)
.minimumScaleFactor(0.8)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
.frame(maxWidth: .infinity)
}
}
diff --git a/Application/Sources/ProgramGuide/ProgramPreview.swift b/Application/Sources/ProgramGuide/ProgramPreview.swift
index fd43ba310..80ea8371d 100644
--- a/Application/Sources/ProgramGuide/ProgramPreview.swift
+++ b/Application/Sources/ProgramGuide/ProgramPreview.swift
@@ -64,7 +64,7 @@ struct ProgramPreview: View {
Text(model.title)
.srgFont(.H2)
.lineLimit(2)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
Text(model.timeInformation)
.srgFont(.H4)
.lineLimit(1)
diff --git a/Application/Sources/ProgramGuide/ProgramView.swift b/Application/Sources/ProgramGuide/ProgramView.swift
index db3105def..bf6660812 100644
--- a/Application/Sources/ProgramGuide/ProgramView.swift
+++ b/Application/Sources/ProgramGuide/ProgramView.swift
@@ -92,6 +92,7 @@ struct ProgramView: View {
var body: some View {
ZStack {
ImageView(source: model.imageUrl)
+ .background(Color.thumbnailBackground)
BlockingOverlay(media: model.currentMedia, messageDisplayed: true)
if let progress = model.progress {
@@ -261,7 +262,7 @@ struct ProgramView: View {
.srgFont(.H2)
.lineLimit(2)
.multilineTextAlignment(.center)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
}
if let subtitle = model.subtitle {
Text(subtitle)
diff --git a/Application/Sources/Search/SearchViewController.swift b/Application/Sources/Search/SearchViewController.swift
index 69806573a..83881a820 100644
--- a/Application/Sources/Search/SearchViewController.swift
+++ b/Application/Sources/Search/SearchViewController.swift
@@ -233,7 +233,7 @@ final class SearchViewController: UIViewController {
titleLabel.lineBreakMode = .byClipping
}
filtersButton.setTitle(NSLocalizedString("Filters", comment: "Filters button title"), for: .normal)
- filtersButton.setTitleColor(.srgGrayC7, for: .normal)
+ filtersButton.setTitleColor(.srgGrayD2, for: .normal)
filtersButton.setTitleColor(.gray, for: .highlighted)
// See https://stackoverflow.com/a/25559946/760435
diff --git a/Application/Sources/UI/Controllers/NavigationController.m b/Application/Sources/UI/Controllers/NavigationController.m
index 23d4a5c8f..77e1a54c5 100755
--- a/Application/Sources/UI/Controllers/NavigationController.m
+++ b/Application/Sources/UI/Controllers/NavigationController.m
@@ -101,7 +101,7 @@ - (void)updateWithTintColor:(UIColor *)tintColor backgroundColor:(UIColor *)back
// Remove the separator (looks nicer)
appearance.shadowColor = UIColor.clearColor;
- UIColor *foregroundColor = tintColor ?: UIColor.srg_grayC7Color;
+ UIColor *foregroundColor = tintColor ?: UIColor.srg_grayD2Color;
appearance.titleTextAttributes = @{
NSFontAttributeName : [SRGFont fontWithFamily:SRGFontFamilyText weight:UIFontWeightSemibold fixedSize:17.f],
NSForegroundColorAttributeName : foregroundColor
diff --git a/Application/Sources/UI/Controllers/TableRequestViewController.m b/Application/Sources/UI/Controllers/TableRequestViewController.m
index d8e7d9319..7bba792ce 100755
--- a/Application/Sources/UI/Controllers/TableRequestViewController.m
+++ b/Application/Sources/UI/Controllers/TableRequestViewController.m
@@ -69,7 +69,7 @@ - (void)viewDidLoad
// DZNEmptyDataSet stretches custom views horizontally. Ensure the image stays centered and does not get
// stretched
- self.loadingImageView = [UIImageView play_largeLoadingImageViewWithTintColor:UIColor.srg_grayC7Color];
+ self.loadingImageView = [UIImageView play_largeLoadingImageViewWithTintColor:UIColor.srg_grayD2Color];
self.loadingImageView.contentMode = UIViewContentModeCenter;
}
@@ -234,7 +234,7 @@ - (NSAttributedString *)titleForEmptyDataSet:(UIScrollView *)scrollView
{
// Remark: No test for self.loading since a custom view is used in such cases
NSDictionary *attributes = @{ NSFontAttributeName : [SRGFont fontWithStyle:SRGFontStyleH2],
- NSForegroundColorAttributeName : UIColor.srg_grayC7Color };
+ NSForegroundColorAttributeName : UIColor.srg_grayD2Color };
if (self.lastRequestError) {
// Multiple errors. Pick the first ones
@@ -256,7 +256,7 @@ - (NSAttributedString *)descriptionForEmptyDataSet:(UIScrollView *)scrollView
if (description) {
return [[NSAttributedString alloc] initWithString:description
attributes:@{ NSFontAttributeName : [SRGFont fontWithStyle:SRGFontStyleH4],
- NSForegroundColorAttributeName : UIColor.srg_grayC7Color }];
+ NSForegroundColorAttributeName : UIColor.srg_grayD2Color }];
}
else {
return nil;
@@ -277,7 +277,7 @@ - (UIImage *)imageForEmptyDataSet:(UIScrollView *)scrollView
- (UIColor *)imageTintColorForEmptyDataSet:(UIScrollView *)scrollView
{
- return UIColor.srg_grayC7Color;
+ return UIColor.srg_grayD2Color;
}
- (BOOL)emptyDataSetShouldAllowScroll:(UIScrollView *)scrollView
diff --git a/Application/Sources/UI/Helpers/ColorsSettable.swift b/Application/Sources/UI/Helpers/ColorsSettable.swift
new file mode 100644
index 000000000..17d4715d9
--- /dev/null
+++ b/Application/Sources/UI/Helpers/ColorsSettable.swift
@@ -0,0 +1,46 @@
+//
+// Copyright (c) SRG SSR. All rights reserved.
+//
+// License information is available from the LICENSE file.
+//
+
+import SwiftUI
+
+protocol PrimaryColorSettable: View {
+ var primaryColor: Color { get set }
+ func primaryColor(_ color: Color) -> Self
+}
+
+extension PrimaryColorSettable {
+ func primaryColor(_ color: Color) -> Self {
+ var view = self
+ view.primaryColor = color
+ return view
+ }
+}
+
+protocol PrimaryFocusedColorSettable: View {
+ var primaryFocusedColor: Color { get set }
+ func primaryFocusedColor(_ color: Color) -> Self
+}
+
+extension PrimaryFocusedColorSettable {
+ func primaryFocusedColor(_ color: Color) -> Self {
+ var view = self
+ view.primaryFocusedColor = color
+ return view
+ }
+}
+
+protocol SecondaryColorSettable: View {
+ var secondaryColor: Color { get set }
+ func secondaryColor(_ color: Color) -> Self
+}
+
+extension SecondaryColorSettable {
+ func secondaryColor(_ color: Color) -> Self {
+ var view = self
+ view.secondaryColor = color
+ return view
+ }
+}
diff --git a/Application/Sources/UI/Helpers/ContextMenu.swift b/Application/Sources/UI/Helpers/ContextMenu.swift
index aae9b22f0..44eb866fe 100644
--- a/Application/Sources/UI/Helpers/ContextMenu.swift
+++ b/Application/Sources/UI/Helpers/ContextMenu.swift
@@ -218,7 +218,7 @@ extension ContextMenu {
let show = media.show,
let navigationController = viewController.navigationController else { return nil }
if let pageViewController = viewController as? PageViewController,
- let displayedShow = pageViewController.id.displayedShow {
+ let displayedShow = pageViewController.displayedShow {
guard !show.isEqual(displayedShow) else { return nil }
}
return UIAction(title: NSLocalizedString("More episodes", comment: "Context menu action to open more episodes associated with a media"),
diff --git a/Application/Sources/UI/Views/ActivityIndicator.swift b/Application/Sources/UI/Views/ActivityIndicator.swift
index 5e4ee769a..5260fe5f9 100644
--- a/Application/Sources/UI/Views/ActivityIndicator.swift
+++ b/Application/Sources/UI/Views/ActivityIndicator.swift
@@ -18,7 +18,7 @@ struct ActivityIndicator: View {
private struct LoadingImageView: UIViewRepresentable {
func makeUIView(context: Context) -> UIImageView {
- return UIImageView.play_largeLoadingImageView(withTintColor: .srgGrayC7)
+ return UIImageView.play_largeLoadingImageView(withTintColor: .srgGrayD2)
}
func updateUIView(_ uiView: UIImageView, context: Context) {
diff --git a/Application/Sources/UI/Views/ChannelButton.swift b/Application/Sources/UI/Views/ChannelButton.swift
index bfca7e44c..b1dd91e85 100644
--- a/Application/Sources/UI/Views/ChannelButton.swift
+++ b/Application/Sources/UI/Views/ChannelButton.swift
@@ -42,7 +42,7 @@ struct ChannelButton: View {
.fixedSize(horizontal: true, vertical: false)
.padding(.horizontal, 18)
.padding(.vertical, 12)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
.background(isSelected ? Color.srgGray4A : Color.srgGray23)
.cornerRadius(100)
.accessibilityElement(label: accessibilityLabel, hint: accessibilityHint, traits: .isButton)
diff --git a/Application/Sources/UI/Views/DiskInfoFooterView.swift b/Application/Sources/UI/Views/DiskInfoFooterView.swift
index 3b5eda436..8cc3cdf87 100644
--- a/Application/Sources/UI/Views/DiskInfoFooterView.swift
+++ b/Application/Sources/UI/Views/DiskInfoFooterView.swift
@@ -15,7 +15,7 @@ struct DiskInfoFooterView: View {
var body: some View {
Text(model.formattedFreeSpace)
.srgFont(.caption)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
}
}
diff --git a/Application/Sources/UI/Views/DownloadCell.swift b/Application/Sources/UI/Views/DownloadCell.swift
index f1d5c833c..56191cb3b 100644
--- a/Application/Sources/UI/Views/DownloadCell.swift
+++ b/Application/Sources/UI/Views/DownloadCell.swift
@@ -111,7 +111,7 @@ struct DownloadCell: View {
Text(subtitle)
.srgFont(.subtitle1)
.lineLimit(1)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
.layoutPriority(1)
}
}
@@ -147,7 +147,7 @@ struct DownloadCell: View {
private struct DownloadImageView: UIViewRepresentable {
func makeUIView(context: Context) -> UIImageView {
- return UIImageView.play_smallDownloadingImageView(withTintColor: .srgGrayC7)
+ return UIImageView.play_smallDownloadingImageView(withTintColor: .srgGrayD2)
}
func updateUIView(_ uiView: UIImageView, context: Context) {
diff --git a/Application/Sources/UI/Views/EmptyContentView.swift b/Application/Sources/UI/Views/EmptyContentView.swift
index f19e0dfd8..b7d110639 100644
--- a/Application/Sources/UI/Views/EmptyContentView.swift
+++ b/Application/Sources/UI/Views/EmptyContentView.swift
@@ -89,7 +89,7 @@ struct EmptyContentView: View {
}
.multilineTextAlignment(.center)
.lineLimit(3)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
.padding()
.padding(insets)
}
diff --git a/Application/Sources/UI/Views/ExpandingButton.swift b/Application/Sources/UI/Views/ExpandingButton.swift
index ac329eba5..2b54f0472 100644
--- a/Application/Sources/UI/Views/ExpandingButton.swift
+++ b/Application/Sources/UI/Views/ExpandingButton.swift
@@ -10,13 +10,16 @@ import SwiftUI
// MARK: View
/// Behavior: h-exp, v-exp
-struct ExpandingButton: View {
+struct ExpandingButton: View, PrimaryColorSettable, PrimaryFocusedColorSettable {
private let icon: ImageResource?
private let label: String?
private let accessibilityLabel: String
private let accessibilityHint: String?
private let action: () -> Void
+ internal var primaryColor: Color = .srgGrayD2
+ internal var primaryFocusedColor: Color = .srgGray16
+
@State private var isFocused = false
init(icon: ImageResource, label: String, accessibilityLabel: String? = nil, accessibilityHint: String? = nil, action: @escaping () -> Void) {
@@ -58,7 +61,7 @@ struct ExpandingButton: View {
}
.onParentFocusChange { isFocused = $0 }
.frame(maxWidth: .infinity, maxHeight: .infinity)
- .foregroundColor(isFocused ? .srgGray16 : .srgGrayC7)
+ .foregroundColor(isFocused ? primaryFocusedColor : primaryColor)
}
.buttonStyle(FlatButtonStyle(focused: isFocused))
.accessibilityElement(label: accessibilityLabel, hint: accessibilityHint, traits: .isButton)
@@ -82,6 +85,10 @@ struct ExpandingButton_Previews: PreviewProvider {
ExpandingButton(icon: .watchLater, action: {})
.padding()
.previewLayout(.fixed(width: 120, height: 120))
+ ExpandingButton(label: "White foreground", action: {})
+ .primaryColor(.white)
+ .padding()
+ .previewLayout(.fixed(width: 240, height: 120))
}
}
}
diff --git a/Application/Sources/UI/Views/FeaturedContentCell.swift b/Application/Sources/UI/Views/FeaturedContentCell.swift
index 4cd997dc4..11c487c09 100644
--- a/Application/Sources/UI/Views/FeaturedContentCell.swift
+++ b/Application/Sources/UI/Views/FeaturedContentCell.swift
@@ -9,7 +9,7 @@ import SwiftUI
// MARK: View
-struct FeaturedContentCell: View {
+struct FeaturedContentCell: View, PrimaryColorSettable, SecondaryColorSettable {
public enum Layout {
case headline
case element
@@ -26,6 +26,9 @@ struct FeaturedContentCell: View {
let layout: Layout
let style: Style
+ internal var primaryColor: Color = .srgGrayD2
+ internal var secondaryColor: Color = .srgGray96
+
@Environment(\.isSelected) private var isSelected
@Environment(\.uiHorizontalSizeClass) private var horizontalSizeClass
@@ -63,6 +66,8 @@ struct FeaturedContentCell: View {
.aspectRatio(FeaturedContentCellSize.aspectRatio, contentMode: .fit)
.layoutPriority(1)
FeaturedDescriptionView(content: content, alignment: descriptionAlignment, detailed: detailed)
+ .primaryColor(primaryColor)
+ .secondaryColor(secondaryColor)
.padding(.horizontal, horizontalPadding)
.padding(.vertical, verticalPadding)
}
@@ -77,6 +82,8 @@ struct FeaturedContentCell: View {
.aspectRatio(FeaturedContentCellSize.aspectRatio, contentMode: .fit)
.layoutPriority(1)
FeaturedDescriptionView(content: content, alignment: descriptionAlignment, detailed: detailed)
+ .primaryColor(primaryColor)
+ .secondaryColor(secondaryColor)
.padding(.horizontal, horizontalPadding)
.padding(.vertical, verticalPadding)
}
diff --git a/Application/Sources/UI/Views/FeaturedDescriptionView.swift b/Application/Sources/UI/Views/FeaturedDescriptionView.swift
index a396fc91b..4a86b7353 100644
--- a/Application/Sources/UI/Views/FeaturedDescriptionView.swift
+++ b/Application/Sources/UI/Views/FeaturedDescriptionView.swift
@@ -9,7 +9,7 @@ import SwiftUI
// MARK: View
/// Behavior: h-exp, v-exp
-struct FeaturedDescriptionView: View {
+struct FeaturedDescriptionView: View, PrimaryColorSettable, SecondaryColorSettable {
enum Alignment {
case leading
case topLeading
@@ -20,6 +20,9 @@ struct FeaturedDescriptionView: View {
let alignment: Alignment
let detailed: Bool
+ internal var primaryColor: Color = .srgGrayD2
+ internal var secondaryColor: Color = .srgGray96
+
private var stackAlignment: HorizontalAlignment {
return alignment == .center ? .center : .leading
}
@@ -49,7 +52,7 @@ struct FeaturedDescriptionView: View {
Text(introduction)
.srgFont(.subtitle1)
.lineLimit(1)
- .foregroundColor(.srgGray96)
+ .foregroundColor(secondaryColor)
}
}
@@ -57,12 +60,12 @@ struct FeaturedDescriptionView: View {
Text(content.title ?? "")
.srgFont(.H3)
.lineLimit(2)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(primaryColor)
if detailed, let summary = content.summary {
Text(summary)
.srgFont(.body)
.lineLimit(3)
- .foregroundColor(.srgGray96)
+ .foregroundColor(secondaryColor)
}
}
.multilineTextAlignment(textAlignment)
diff --git a/Application/Sources/UI/Views/Handle.swift b/Application/Sources/UI/Views/Handle.swift
index a8c59f64d..408c340df 100644
--- a/Application/Sources/UI/Views/Handle.swift
+++ b/Application/Sources/UI/Views/Handle.swift
@@ -35,7 +35,7 @@ struct Handle: View {
var body: some View {
RoundedRectangle(cornerRadius: grabberHeight / 2)
.frame(width: grabberWidth, height: grabberHeight)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
}
}
}
diff --git a/Application/Sources/UI/Views/HeaderView.swift b/Application/Sources/UI/Views/HeaderView.swift
index c60836cc5..0e1ada922 100644
--- a/Application/Sources/UI/Views/HeaderView.swift
+++ b/Application/Sources/UI/Views/HeaderView.swift
@@ -14,10 +14,18 @@ struct HeaderView: View {
let title: String
let subtitle: String?
let hasDetailDisclosure: Bool
+ let primaryColor: Color
@Environment(\.uiHorizontalSizeClass) private var horizontalSizeClass
@Environment(\.sizeCategory) private var sizeCategory
+ init(title: String, subtitle: String?, hasDetailDisclosure: Bool, primaryColor: Color = .srgGrayD2) {
+ self.title = title
+ self.subtitle = subtitle
+ self.hasDetailDisclosure = hasDetailDisclosure
+ self.primaryColor = primaryColor
+ }
+
private var displayableSubtitle: String? {
if horizontalSizeClass == .regular, let subtitle, !subtitle.isEmpty {
return subtitle
@@ -47,7 +55,7 @@ struct HeaderView: View {
.lineLimit(1)
}
}
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(primaryColor)
.padding(.vertical, constant(iOS: 3, tvOS: 15))
.frame(maxWidth: .infinity, alignment: .leading)
.accessibilityElement(label: title, traits: .isHeader)
@@ -76,10 +84,13 @@ struct HeaderView_Previews: PreviewProvider {
Group {
HeaderView(title: "Title", subtitle: nil, hasDetailDisclosure: false)
HeaderView(title: "Title", subtitle: nil, hasDetailDisclosure: true)
+ HeaderView(title: "Title", subtitle: nil, hasDetailDisclosure: true, primaryColor: .white)
HeaderView(title: "Title", subtitle: "Subtitle", hasDetailDisclosure: false)
HeaderView(title: "Title", subtitle: "Subtitle", hasDetailDisclosure: true)
+ HeaderView(title: "Title", subtitle: "Subtitle", hasDetailDisclosure: true, primaryColor: .white)
HeaderView(title: .loremIpsum, subtitle: .loremIpsum, hasDetailDisclosure: false)
HeaderView(title: .loremIpsum, subtitle: .loremIpsum, hasDetailDisclosure: true)
+ HeaderView(title: .loremIpsum, subtitle: .loremIpsum, hasDetailDisclosure: true, primaryColor: .white)
}
.frame(width: 800)
.previewLayout(.sizeThatFits)
diff --git a/Application/Sources/UI/Views/MediaCell.swift b/Application/Sources/UI/Views/MediaCell.swift
index cec51615a..a07475883 100644
--- a/Application/Sources/UI/Views/MediaCell.swift
+++ b/Application/Sources/UI/Views/MediaCell.swift
@@ -9,7 +9,7 @@ import SwiftUI
// MARK: View
-struct MediaCell: View {
+struct MediaCell: View, PrimaryColorSettable, SecondaryColorSettable {
enum Layout {
case vertical
case horizontal
@@ -32,6 +32,9 @@ struct MediaCell: View {
let layout: Layout
let action: (() -> Void)?
+ internal var primaryColor: Color = .srgGrayD2
+ internal var secondaryColor: Color = .srgGray96
+
fileprivate var onFocusAction: ((Bool) -> Void)?
@State private var isFocused = false
@@ -78,6 +81,8 @@ struct MediaCell: View {
.accessibilityElement(label: accessibilityLabel, hint: accessibilityHint, traits: accessibilityTraits)
} label: {
DescriptionView(media: media, style: style)
+ .primaryColor(primaryColor)
+ .secondaryColor(secondaryColor)
.padding(.top, verticalPadding)
}
#else
@@ -90,6 +95,8 @@ struct MediaCell: View {
.redactable()
.layoutPriority(1)
DescriptionView(media: media, style: style, embeddedDirection: direction)
+ .primaryColor(primaryColor)
+ .secondaryColor(secondaryColor)
.selectionAppearance(.transluscent, when: hasSelectionAppearance, while: isEditing)
.padding(.leading, horizontalPadding)
.padding(.top, verticalPadding)
@@ -121,11 +128,14 @@ struct MediaCell: View {
#endif
/// Behavior: h-exp, v-exp
- private struct DescriptionView: View {
+ private struct DescriptionView: View, PrimaryColorSettable, SecondaryColorSettable {
let media: SRGMedia?
let style: MediaCell.Style
let embeddedDirection: StackDirection
+ internal var primaryColor: Color = .srgGrayD2
+ internal var secondaryColor: Color = .srgGray96
+
init(
media: SRGMedia?,
style: MediaCell.Style,
@@ -195,20 +205,20 @@ struct MediaCell: View {
Text(subtitle)
.srgFont(.subtitle1)
.lineLimit(2)
- .foregroundColor(.srgGray96)
+ .foregroundColor(secondaryColor)
}
if let title {
Text(title)
.srgFont(.H4)
.lineLimit(titleLineLimit)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(primaryColor)
.layoutPriority(1)
}
if let summary {
Text(summary)
.srgFont(.body)
.lineLimit(2)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(primaryColor)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
@@ -265,9 +275,13 @@ final class MediaCellSize: NSObject {
}
static func fullWidth(horizontalSizeClass: UIUserInterfaceSizeClass = .compact) -> NSCollectionLayoutSize {
- let height = horizontalSizeClass == .compact ? constant(iOS: 84, tvOS: 120) : constant(iOS: 104, tvOS: 120)
+ let height = height(horizontalSizeClass: horizontalSizeClass)
return NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(CGFloat(height)))
}
+
+ static func height(horizontalSizeClass: UIUserInterfaceSizeClass) -> CGFloat {
+ return horizontalSizeClass == .compact ? constant(iOS: 84, tvOS: 120) : constant(iOS: 104, tvOS: 120)
+ }
}
// MARK: Preview
diff --git a/Application/Sources/UI/Views/MediaMoreButton.swift b/Application/Sources/UI/Views/MediaMoreButton.swift
index 72cf2818b..b2dba5a9a 100644
--- a/Application/Sources/UI/Views/MediaMoreButton.swift
+++ b/Application/Sources/UI/Views/MediaMoreButton.swift
@@ -14,7 +14,7 @@ struct MediaMoreButton: UIViewRepresentable {
func makeUIView(context: Context) -> UIButton {
let button = UIButton(type: .custom)
button.setImage(UIImage(resource: .ellipsis), for: .normal)
- button.tintColor = .srgGrayC7
+ button.tintColor = .srgGrayD2
button.showsMenuAsPrimaryAction = true
button.menu = UIMenu()
diff --git a/Application/Sources/UI/Views/MediaVisualView.swift b/Application/Sources/UI/Views/MediaVisualView.swift
index f693ea2fd..b06b89ce9 100644
--- a/Application/Sources/UI/Views/MediaVisualView.swift
+++ b/Application/Sources/UI/Views/MediaVisualView.swift
@@ -4,9 +4,6 @@
// License information is available from the LICENSE file.
//
-import NukeUI
-import SRGDataProviderModel
-import SRGUserData
import SwiftUI
// MARK: View
@@ -41,6 +38,7 @@ struct MediaVisualView: View {
var body: some View {
ZStack {
ImageView(source: model.imageUrl(for: size), contentMode: contentMode)
+ .background(Color.thumbnailBackground)
content(media)
BlockingOverlay(media: media)
diff --git a/Application/Sources/UI/Views/MoreCell.swift b/Application/Sources/UI/Views/MoreCell.swift
index 8aafdaca3..f7eacd974 100644
--- a/Application/Sources/UI/Views/MoreCell.swift
+++ b/Application/Sources/UI/Views/MoreCell.swift
@@ -26,7 +26,7 @@ struct MoreCell: View {
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: Self.iconHeight)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
.opacity(0.8)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.srgGray33)
@@ -39,7 +39,7 @@ struct MoreCell: View {
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: Self.iconHeight)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
.opacity(0.8)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.aspectRatio(Self.aspectRatio(for: imageVariant), contentMode: .fit)
diff --git a/Application/Sources/UI/Views/NotificationCell.swift b/Application/Sources/UI/Views/NotificationCell.swift
index 51bef09d1..f868f1989 100644
--- a/Application/Sources/UI/Views/NotificationCell.swift
+++ b/Application/Sources/UI/Views/NotificationCell.swift
@@ -65,7 +65,7 @@ struct NotificationCell: View {
Text(notification.body)
.srgFont(.H4)
.lineLimit(2)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
.layoutPriority(1)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
diff --git a/Application/Sources/UI/Views/PageHeaderView.swift b/Application/Sources/UI/Views/PageHeaderView.swift
deleted file mode 100644
index 6c6b201a1..000000000
--- a/Application/Sources/UI/Views/PageHeaderView.swift
+++ /dev/null
@@ -1,86 +0,0 @@
-//
-// Copyright (c) SRG SSR. All rights reserved.
-//
-// License information is available from the LICENSE file.
-//
-
-import SwiftUI
-
-// MARK: View
-
-/// Behavior: h-hug, v-hug
-struct PageHeaderView: View {
- let page: SRGContentPage?
-
- var body: some View {
- VStack(alignment: .leading, spacing: 16) {
- if let title = page?.title {
- HStack(spacing: 0) {
- Text(title)
- .srgFont(.H1)
- .foregroundColor(.white)
- // Fix sizing issue, see https://swiftui-lab.com/bug-linelimit-ignored/. The size is correct
- // when calculated with a `UIHostingController`, but without this the text does not occupy
- // all lines it could.
- .fixedSize(horizontal: false, vertical: true)
- .multilineTextAlignment(.leading)
- Spacer()
- }
- }
- if let description = page?.summary {
- Text(description)
- .srgFont(.body)
- .foregroundColor(.white)
- // Fix sizing issue, see https://swiftui-lab.com/bug-linelimit-ignored/. The size is correct
- // when calculated with a `UIHostingController`, but without this the text does not occupy
- // all lines it could.
- .fixedSize(horizontal: false, vertical: true)
- .multilineTextAlignment(.leading)
- }
- }
- .frame(maxWidth: .infinity, alignment: .leading)
- .padding(.horizontal, constant(iOS: 16, tvOS: 0))
- .padding(.bottom, constant(iOS: 12, tvOS: 80))
- }
-}
-
-// MARK: Size
-
-enum PageHeaderViewSize {
- static func recommended(for page: SRGContentPage?, layoutWidth: CGFloat, horizontalSizeClass: UIUserInterfaceSizeClass) -> NSCollectionLayoutSize {
- if let page {
- let fittingSize = CGSize(width: layoutWidth, height: UIView.layoutFittingExpandedSize.height)
- let size = PageHeaderView(page: page).adaptiveSizeThatFits(in: fittingSize, for: horizontalSizeClass)
- return NSCollectionLayoutSize(widthDimension: .absolute(size.width), heightDimension: .absolute(size.height))
- }
- else {
- return NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(LayoutHeaderHeightZero))
- }
- }
-}
-
-// MARK: Preview
-
-struct PageHeaderView_Previews: PreviewProvider {
- static var previews: some View {
- Group {
- PageHeaderView(page: Mock.page())
- PageHeaderView(page: Mock.page(.short))
- PageHeaderView(page: Mock.page(.overflow))
- PageHeaderView(page: nil)
- }
- .previewLayout(.sizeThatFits)
- .frame(width: 1000)
- .environment(\.horizontalSizeClass, .regular)
-
- Group {
- PageHeaderView(page: Mock.page())
- PageHeaderView(page: Mock.page(.short))
- PageHeaderView(page: Mock.page(.overflow))
- PageHeaderView(page: nil)
- }
- .frame(width: 375)
- .previewLayout(.sizeThatFits)
- .environment(\.horizontalSizeClass, .compact)
- }
-}
diff --git a/Application/Sources/UI/Views/SheetTextView.swift b/Application/Sources/UI/Views/SheetTextView.swift
index c421e53c7..840aa2abd 100644
--- a/Application/Sources/UI/Views/SheetTextView.swift
+++ b/Application/Sources/UI/Views/SheetTextView.swift
@@ -23,7 +23,7 @@ struct SheetTextView: View {
HStack(spacing: 0) {
Text(content)
.srgFont(.body)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
.multilineTextAlignment(.leading)
.padding(.horizontal, 28)
Spacer(minLength: 0)
diff --git a/Application/Sources/UI/Views/ShowAccessCell.swift b/Application/Sources/UI/Views/ShowAccessCell.swift
index b5cff7f3d..b06ed348c 100644
--- a/Application/Sources/UI/Views/ShowAccessCell.swift
+++ b/Application/Sources/UI/Views/ShowAccessCell.swift
@@ -17,9 +17,11 @@ import SwiftUI
// MARK: View
/// Behavior: h-exp, v-exp
-struct ShowAccessCell: View {
+struct ShowAccessCell: View, PrimaryColorSettable {
let style: Style
+ internal var primaryColor: Color = .srgGrayD2
+
@FirstResponder private var firstResponder
private var showAZButtonProperties: ButtonProperties {
@@ -51,9 +53,11 @@ struct ShowAccessCell: View {
ExpandingButton(icon: showAZButtonProperties.icon, label: showAZButtonProperties.label, accessibilityLabel: showAZButtonProperties.accessibilityLabel) {
firstResponder.sendAction(#selector(ShowAccessCellActions.openShowAZ))
}
+ .primaryColor(primaryColor)
ExpandingButton(icon: showByDateButtonProperties.icon, label: showByDateButtonProperties.label, accessibilityLabel: showByDateButtonProperties.accessibilityLabel) {
firstResponder.sendAction(#selector(ShowAccessCellActions.openShowByDate))
}
+ .primaryColor(primaryColor)
}
.responderChain(from: firstResponder)
}
diff --git a/Application/Sources/UI/Views/ShowButton.swift b/Application/Sources/UI/Views/ShowButton.swift
index 03786511e..a78d2c332 100644
--- a/Application/Sources/UI/Views/ShowButton.swift
+++ b/Application/Sources/UI/Views/ShowButton.swift
@@ -23,10 +23,6 @@ struct ShowButton: View {
self.action = action
}
- private var imageUrl: URL? {
- return url(for: show.image, size: .small)
- }
-
private var favoriteIcon: ImageResource {
return isFavorite ? .favoriteFull : .favorite
}
@@ -38,7 +34,7 @@ struct ShowButton: View {
var body: some View {
Button(action: action) {
HStack(spacing: 8) {
- ImageView(source: imageUrl)
+ ShowVisualView(show: show, size: .small)
.aspectRatio(16 / 9, contentMode: .fit)
VStack(alignment: .leading, spacing: 2) {
Text(show.title)
@@ -46,7 +42,7 @@ struct ShowButton: View {
.lineLimit(2)
Text(NSLocalizedString("More episodes", comment: "Button to access more episodes"))
.srgFont(.subtitle1)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
Spacer()
}
.padding(.vertical, 2)
diff --git a/Application/Sources/UI/Views/ShowCell.swift b/Application/Sources/UI/Views/ShowCell.swift
index 54c887e3c..06f497807 100644
--- a/Application/Sources/UI/Views/ShowCell.swift
+++ b/Application/Sources/UI/Views/ShowCell.swift
@@ -10,7 +10,7 @@ import SwiftUI
// MARK: View
-struct ShowCell: View {
+struct ShowCell: View, PrimaryColorSettable {
enum Style {
case standard
case favorite
@@ -21,6 +21,8 @@ struct ShowCell: View {
let style: Style
let imageVariant: SRGImageVariant
+ internal var primaryColor: Color = .srgGrayD2
+
@StateObject private var model = ShowCellViewModel()
@Environment(\.isEditing) private var isEditing
@@ -36,22 +38,24 @@ struct ShowCell: View {
Group {
#if os(tvOS)
LabeledCardButton(aspectRatio: ShowCellSize.aspectRatio(for: imageVariant), action: action) {
- ImageView(source: model.imageUrl(with: imageVariant))
+ ShowVisualView(show: model.show, size: .small, imageVariant: imageVariant)
.unredactable()
.accessibilityElement(label: accessibilityLabel, hint: accessibilityHint, traits: .isButton)
} label: {
if imageVariant != .poster {
DescriptionView(model: model, style: style)
+ .primaryColor(primaryColor)
.frame(maxHeight: .infinity, alignment: .top)
.padding(.top, ShowCellSize.verticalPadding)
}
}
#else
VStack(spacing: 0) {
- ImageView(source: model.imageUrl(with: imageVariant))
+ ShowVisualView(show: model.show, size: .small, imageVariant: imageVariant)
.aspectRatio(ShowCellSize.aspectRatio(for: imageVariant), contentMode: .fit)
if imageVariant != .poster {
DescriptionView(model: model, style: style)
+ .primaryColor(primaryColor)
.padding(.horizontal, ShowCellSize.horizontalPadding)
.padding(.vertical, ShowCellSize.verticalPadding)
}
@@ -82,10 +86,12 @@ struct ShowCell: View {
#endif
/// Behavior: h-exp, v-hug
- private struct DescriptionView: View {
+ private struct DescriptionView: View, PrimaryColorSettable {
@ObservedObject var model: ShowCellViewModel
let style: Style
+ internal var primaryColor: Color = .srgGrayD2
+
var body: some View {
HStack {
Text(model.title ?? "")
@@ -101,7 +107,7 @@ struct ShowCell: View {
}
#endif
}
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(primaryColor)
}
}
}
diff --git a/Application/Sources/UI/Views/ShowCellViewModel.swift b/Application/Sources/UI/Views/ShowCellViewModel.swift
index 20ceffdc1..d4511814b 100644
--- a/Application/Sources/UI/Views/ShowCellViewModel.swift
+++ b/Application/Sources/UI/Views/ShowCellViewModel.swift
@@ -38,8 +38,4 @@ extension ShowCellViewModel {
var title: String? {
return show?.title
}
-
- func imageUrl(with imageVariant: SRGImageVariant) -> URL? {
- return imageVariant == .poster ? url(for: show?.posterImage, size: .small) : url(for: show?.image, size: .small)
- }
}
diff --git a/Application/Sources/UI/Views/ShowVisualView.swift b/Application/Sources/UI/Views/ShowVisualView.swift
new file mode 100644
index 000000000..e7720bd60
--- /dev/null
+++ b/Application/Sources/UI/Views/ShowVisualView.swift
@@ -0,0 +1,53 @@
+//
+// Copyright (c) SRG SSR. All rights reserved.
+//
+// License information is available from the LICENSE file.
+//
+
+import SwiftUI
+
+// MARK: View
+
+/// Behavior: h-exp, v-exp
+struct ShowVisualView: View {
+ let show: SRGShow?
+ let size: SRGImageSize
+ let imageVariant: SRGImageVariant
+ let contentMode: ImageView.ContentMode
+
+ init(
+ show: SRGShow?,
+ size: SRGImageSize,
+ imageVariant: SRGImageVariant = .default,
+ contentMode: ImageView.ContentMode = .aspectFit
+ ) {
+ self.show = show
+ self.size = size
+ self.imageVariant = imageVariant
+ self.contentMode = contentMode
+ }
+
+ var body: some View {
+ ImageView(source: imageUrl, contentMode: contentMode)
+ .background(Color.thumbnailBackground)
+ }
+
+ private var imageUrl: URL? {
+ return imageVariant == .poster ? url(for: show?.posterImage, size: size) : url(for: show?.image, size: size)
+ }
+}
+
+// MARK: Preview
+
+struct ShowVisualView_Previews: PreviewProvider {
+ static var previews: some View {
+ Group {
+ ShowVisualView(show: Mock.show(.standard), size: .small)
+ ShowVisualView(show: Mock.show(.standard), size: .small, imageVariant: .poster)
+ ShowVisualView(show: Mock.show(.overflow), size: .small)
+ ShowVisualView(show: Mock.show(.short), size: .small)
+ }
+ .frame(width: 600, height: 500)
+ .previewLayout(.sizeThatFits)
+ }
+}
diff --git a/Application/Sources/UI/Views/SimpleButton.swift b/Application/Sources/UI/Views/SimpleButton.swift
index a5da64e14..0605b0fcd 100644
--- a/Application/Sources/UI/Views/SimpleButton.swift
+++ b/Application/Sources/UI/Views/SimpleButton.swift
@@ -10,7 +10,7 @@ import SwiftUI
// MARK: View
/// Behavior: h-hug, v-hug
-struct SimpleButton: View {
+struct SimpleButton: View, PrimaryColorSettable, PrimaryFocusedColorSettable {
private let icon: ImageResource
private let label: String?
private let labelMinimumScaleFactor: CGFloat?
@@ -18,6 +18,9 @@ struct SimpleButton: View {
private let accessibilityHint: String?
private let action: () -> Void
+ internal var primaryColor: Color = .srgGrayD2
+ internal var primaryFocusedColor: Color = .srgGray16
+
@State private var isFocused = false
init(icon: ImageResource, accessibilityLabel: String, accessibilityHint: String? = nil, action: @escaping () -> Void) {
@@ -50,7 +53,7 @@ struct SimpleButton: View {
}
}
.onParentFocusChange { isFocused = $0 }
- .foregroundColor(isFocused ? .srgGray16 : .srgGrayC7)
+ .foregroundColor(isFocused ? primaryFocusedColor : primaryColor)
}
.buttonStyle(FlatButtonStyle(focused: isFocused))
.accessibilityElement(label: accessibilityLabel, hint: accessibilityHint, traits: .isButton)
@@ -64,6 +67,7 @@ struct SimpleButton_Previews: PreviewProvider {
Group {
SimpleButton(icon: .favorite, label: "Add to favorites", action: {})
SimpleButton(icon: .favorite, accessibilityLabel: "Add to favorites", action: {})
+ SimpleButton(icon: .favorite, label: "White foreground", action: {}).primaryColor(.white)
}
.padding()
.previewLayout(.sizeThatFits)
diff --git a/Application/Sources/UI/Views/TableLoadMoreFooterView.swift b/Application/Sources/UI/Views/TableLoadMoreFooterView.swift
index 743286f29..a8720cb33 100644
--- a/Application/Sources/UI/Views/TableLoadMoreFooterView.swift
+++ b/Application/Sources/UI/Views/TableLoadMoreFooterView.swift
@@ -13,7 +13,7 @@ class TableLoadMoreFooterView: UIView {
backgroundColor = .clear
- let loadingImageView = UIImageView.play_loadingImageView(withTintColor: .srgGrayC7)
+ let loadingImageView = UIImageView.play_loadingImageView(withTintColor: .srgGrayD2)
addSubview(loadingImageView)
loadingImageView.translatesAutoresizingMaskIntoConstraints = false
diff --git a/Application/Sources/UI/Views/TitleHeaderView.swift b/Application/Sources/UI/Views/TitleHeaderView.swift
new file mode 100644
index 000000000..3e2a1fb46
--- /dev/null
+++ b/Application/Sources/UI/Views/TitleHeaderView.swift
@@ -0,0 +1,111 @@
+//
+// Copyright (c) SRG SSR. All rights reserved.
+//
+// License information is available from the LICENSE file.
+//
+
+import SwiftUI
+
+// MARK: View
+
+/// Behavior: h-hug, v-hug
+struct TitleHeaderView: View, PrimaryColorSettable {
+ let title: String?
+ let description: String?
+ let titleTextAlignment: TextAlignment
+ let topPadding: CGFloat
+
+ init(_ title: String?, description: String? = nil, titleTextAlignment: TextAlignment = .leading, topPadding: CGFloat = 0) {
+ self.title = title
+ self.description = description
+ self.titleTextAlignment = titleTextAlignment
+ self.topPadding = topPadding
+ }
+
+ internal var primaryColor: Color = .white
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 16) {
+ if let title {
+ HStack(spacing: 0) {
+ if titleTextAlignment != .leading {
+ Spacer(minLength: 0)
+ }
+ Text(title)
+ .srgFont(.H1)
+ .foregroundColor(primaryColor)
+ // Fix sizing issue, see https://swiftui-lab.com/bug-linelimit-ignored/. The size is correct
+ // when calculated with a `UIHostingController`, but without this the text does not occupy
+ // all lines it could.
+ .fixedSize(horizontal: false, vertical: true)
+ .multilineTextAlignment(titleTextAlignment)
+ if titleTextAlignment != .trailing {
+ Spacer(minLength: 0)
+ }
+ }
+ if let description {
+ Text(description)
+ .srgFont(.body)
+ .foregroundColor(primaryColor)
+ // Fix sizing issue, see https://swiftui-lab.com/bug-linelimit-ignored/. The size is correct
+ // when calculated with a `UIHostingController`, but without this the text does not occupy
+ // all lines it could.
+ .fixedSize(horizontal: false, vertical: true)
+ .multilineTextAlignment(.leading)
+ }
+ }
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding(.horizontal, constant(iOS: 16, tvOS: 0))
+ .padding(.top, topPadding)
+ .padding(.bottom, constant(iOS: 12, tvOS: 80))
+ }
+}
+
+// MARK: Size
+
+enum TitleHeaderViewSize {
+ static func recommended(for title: String?, description: String? = nil, topPadding: CGFloat = 0, layoutWidth: CGFloat, horizontalSizeClass: UIUserInterfaceSizeClass) -> NSCollectionLayoutSize {
+ if let title {
+ let fittingSize = CGSize(width: layoutWidth, height: UIView.layoutFittingExpandedSize.height)
+ let size = TitleHeaderView(title, description: description, topPadding: topPadding).adaptiveSizeThatFits(in: fittingSize, for: horizontalSizeClass)
+ return NSCollectionLayoutSize(widthDimension: .absolute(size.width), heightDimension: .absolute(size.height))
+ }
+ else {
+ return NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(LayoutHeaderHeightZero))
+ }
+ }
+}
+
+// MARK: Preview
+
+struct TitleHeaderView_Previews: PreviewProvider {
+ static var previews: some View {
+ Group {
+ TitleHeaderView("Title", description: "description")
+ TitleHeaderView(.loremIpsum, description: .loremIpsum)
+ TitleHeaderView("Title", description: "description", titleTextAlignment: .center)
+ TitleHeaderView("Title", description: nil, titleTextAlignment: .center)
+ TitleHeaderView("Title", description: "description", titleTextAlignment: .trailing)
+ TitleHeaderView("Title", description: nil, titleTextAlignment: .trailing)
+ TitleHeaderView(nil, description: nil)
+ }
+ .previewLayout(.sizeThatFits)
+ .frame(width: 1000)
+ .environment(\.horizontalSizeClass, .regular)
+
+ Group {
+ TitleHeaderView("Title", description: "description")
+ TitleHeaderView(.loremIpsum, description: .loremIpsum)
+ TitleHeaderView("Title", description: "description", titleTextAlignment: .center)
+ TitleHeaderView("Title", description: nil, titleTextAlignment: .center)
+ TitleHeaderView("Title", description: "description", titleTextAlignment: .trailing)
+ TitleHeaderView("Title", description: nil, titleTextAlignment: .trailing)
+ TitleHeaderView("Title", description: nil, titleTextAlignment: .leading, topPadding: 16)
+ TitleHeaderView(nil, description: nil)
+ }
+ .frame(width: 375)
+ .previewLayout(.sizeThatFits)
+ .environment(\.horizontalSizeClass, .compact)
+ }
+}
diff --git a/Application/Sources/UI/Views/TitleView.swift b/Application/Sources/UI/Views/TitleView.swift
deleted file mode 100644
index 4de55b4c1..000000000
--- a/Application/Sources/UI/Views/TitleView.swift
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-// Copyright (c) SRG SSR. All rights reserved.
-//
-// License information is available from the LICENSE file.
-//
-
-import SwiftUI
-
-// MARK: View
-
-/// Behavior: h-exp, v-exp
-struct TitleView: View {
- let text: String?
-
- var body: some View {
- if let text {
- Text(text)
- .srgFont(.H1)
- .foregroundColor(.srgGrayC7)
- .lineLimit(1)
- .frame(maxWidth: .infinity, maxHeight: .infinity)
- }
- }
-}
-
-// MARK: Size
-
-enum TitleViewSize {
- static func recommended(forText text: String?) -> NSCollectionLayoutSize {
- if let text, !text.isEmpty {
- return NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(constant(iOS: 60, tvOS: 100)))
- }
- else {
- return NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(LayoutHeaderHeightZero))
- }
- }
-}
-
-// MARK: Preview
-
-struct TitleView_Previews: PreviewProvider {
- static var previews: some View {
- Group {
- TitleView(text: "Title")
- TitleView(text: .loremIpsum)
- TitleView(text: nil)
- }
- .previewLayout(.fixed(width: 800, height: 200))
- }
-}
diff --git a/Application/Sources/UI/Views/TopicGradientView.swift b/Application/Sources/UI/Views/TopicGradientView.swift
new file mode 100644
index 000000000..5b414e699
--- /dev/null
+++ b/Application/Sources/UI/Views/TopicGradientView.swift
@@ -0,0 +1,123 @@
+//
+// Copyright (c) SRG SSR. All rights reserved.
+//
+// License information is available from the LICENSE file.
+//
+
+import SwiftUI
+
+// MARK: View
+
+/// Behavior: h-exp, v-exp
+struct TopicGradientView: View {
+ enum Style {
+ case topicPage
+ case showPage
+ }
+
+ let topic: SRGTopic
+ let style: Style
+
+ init(_ topic: SRGTopic, style: Style) {
+ self.topic = topic
+ self.style = style
+ }
+
+ var body: some View {
+ if let topicColors = ApplicationConfiguration.shared.topicColors(for: topic) {
+ ZStack {
+ RadialColorGradient(
+ topicColors: topicColors,
+ opacity: opacity
+ )
+ LinearGreyGradient()
+ }
+ } else {
+ Color.clear
+ }
+ }
+
+ /// Behavior: h-exp, v-exp
+ private struct RadialColorGradient: View {
+ let topicColors: (Color, Color)
+ let opacity: Double
+
+ var body: some View {
+ GeometryReader { geometry in
+ RadialGradient(
+ gradient: Gradient(stops: [
+ Gradient.Stop(color: topicColors.0.opacity(opacity), location: 0),
+ Gradient.Stop(color: topicColors.1.opacity(opacity), location: 0.8)
+ ]),
+ center: UnitPoint(x: 0.5, y: 0),
+ startRadius: 0,
+ endRadius: geometry.size.width
+ )
+ }
+ }
+ }
+
+ /// Behavior: h-exp, v-exp
+ private struct LinearGreyGradient: View {
+ var body: some View {
+ LinearGradient(
+ colors: [.clear, .srgGray16],
+ startPoint: UnitPoint(x: 0.5, y: 0),
+ endPoint: .bottom
+ )
+ }
+ }
+
+ private var opacity: Double {
+ switch style {
+ case .topicPage:
+ return 0.6
+ case .showPage:
+ return 0.4
+ }
+ }
+}
+
+// MARK: Preview
+
+struct TopicGradientView_Previews: PreviewProvider {
+ private struct PreviewView: View {
+ @ViewBuilder var content: () -> Content
+
+ var body: some View {
+ ZStack {
+ Rectangle()
+ .fill(Color.srgGray16)
+ content()
+ }
+ }
+ }
+
+ static var previews: some View {
+ Group {
+ PreviewView {
+ TopicGradientView(Mock.topic(), style: .topicPage)
+ }
+ PreviewView {
+ TopicGradientView(Mock.topic(), style: .showPage)
+ }
+ PreviewView {
+ TopicGradientView(Mock.topic(.overflow), style: .topicPage)
+ }
+ }
+ .previewLayout(.fixed(width: 400, height: 572))
+
+ Group {
+ PreviewView {
+ TopicGradientView(Mock.topic(), style: .topicPage)
+ }
+ PreviewView {
+ TopicGradientView(Mock.topic(), style: .showPage)
+ }
+ PreviewView {
+ TopicGradientView(Mock.topic(.overflow), style: .topicPage)
+ }
+ }
+ .previewLayout(.fixed(width: 1080, height: 572))
+ }
+}
diff --git a/Application/Sources/UI/Views/TransluscentHeaderView.swift b/Application/Sources/UI/Views/TransluscentHeaderView.swift
index e80b75b49..dc811eebf 100644
--- a/Application/Sources/UI/Views/TransluscentHeaderView.swift
+++ b/Application/Sources/UI/Views/TransluscentHeaderView.swift
@@ -17,7 +17,7 @@ struct TransluscentHeaderView: View {
Text(title)
.srgFont(.H3)
.lineLimit(1)
- .foregroundColor(.srgGrayC7)
+ .foregroundColor(.srgGrayD2)
.padding(.horizontal, horizontalPadding)
.padding(.vertical, constant(iOS: 3, tvOS: 15))
.frame(maxWidth: .infinity, alignment: .leading)
diff --git a/Application/Sources/UI/Views/TruncatableTextView.swift b/Application/Sources/UI/Views/TruncatableTextView.swift
index 6f505effc..5abb95328 100644
--- a/Application/Sources/UI/Views/TruncatableTextView.swift
+++ b/Application/Sources/UI/Views/TruncatableTextView.swift
@@ -16,14 +16,14 @@ import SwiftUI
*/
/// Behavior: h-exp, v-hug
-struct TruncatableTextView: View {
+struct TruncatableTextView: View, PrimaryColorSettable, SecondaryColorSettable {
let content: String
let lineLimit: Int?
let showMore: () -> Void
- var foregroundColor: Color = .srgGray96
- var secondaryColor: Color = .white
+ internal var primaryColor: Color = .srgGrayD2
+ internal var secondaryColor: Color = .white
@State private var isTruncated = false
@State private var isFocused = false
@@ -36,25 +36,11 @@ struct TruncatableTextView: View {
self.showMore = showMore
}
- func foregroundColor(_ color: Color) -> Self {
- var truncatableTextView = self
-
- truncatableTextView.foregroundColor = color
- return truncatableTextView
- }
-
- func secondaryColor(_ color: Color) -> Self {
- var truncatableTextView = self
-
- truncatableTextView.secondaryColor = color
- return truncatableTextView
- }
-
var body: some View {
Button {
showMore()
} label: {
- MainView(content: content, lineLimit: lineLimit, foregroundColor: foregroundColor, secondaryColor: secondaryColor, isTruncated: $isTruncated) {
+ MainView(content: content, lineLimit: lineLimit, primaryColor: primaryColor, secondaryColor: secondaryColor, isTruncated: $isTruncated) {
showMore()
}
.onParentFocusChange { isFocused = $0 }
@@ -69,7 +55,7 @@ struct TruncatableTextView: View {
fileprivate struct MainView: View {
let content: String
let lineLimit: Int?
- let foregroundColor: Color
+ let primaryColor: Color
let secondaryColor: Color
@Binding private(set) var isTruncated: Bool
@@ -86,7 +72,7 @@ struct TruncatableTextView: View {
return Text(content)
.srgFont(fontStyle)
.lineLimit(lineLimit)
- .foregroundColor(foregroundColor)
+ .foregroundColor(primaryColor)
.multilineTextAlignment(.leading)
}
@@ -173,7 +159,7 @@ struct TruncableTextView_Previews: PreviewProvider {
TruncatableTextView(content: "Short description.", lineLimit: 3) {}
TruncatableTextView(content: String.loremIpsum, lineLimit: 3) {}
TruncatableTextView(content: String.loremIpsum, lineLimit: 3) {}
- .foregroundColor(.white)
+ .primaryColor(.white)
.secondaryColor(.srgGray96)
TruncatableTextView(content: String.loremIpsumWithSpacesAndNewLine, lineLimit: 3) {}
}
diff --git a/PlaySRG.xcodeproj/project.pbxproj b/PlaySRG.xcodeproj/project.pbxproj
index 736e57edf..c2f8bd893 100644
--- a/PlaySRG.xcodeproj/project.pbxproj
+++ b/PlaySRG.xcodeproj/project.pbxproj
@@ -187,16 +187,16 @@
0456C4A82976EE460088508A /* AnalyticsEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0456C4A02976EE460088508A /* AnalyticsEvent.swift */; };
0456C4A92976EE460088508A /* AnalyticsEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0456C4A02976EE460088508A /* AnalyticsEvent.swift */; };
0456C4AA2976EE460088508A /* AnalyticsEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0456C4A02976EE460088508A /* AnalyticsEvent.swift */; };
- 0458A51B2B7AC10A0007BA10 /* PageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* PageHeaderView.swift */; };
- 0458A51C2B7AC10A0007BA10 /* PageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* PageHeaderView.swift */; };
- 0458A51D2B7AC10A0007BA10 /* PageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* PageHeaderView.swift */; };
- 0458A51E2B7AC10A0007BA10 /* PageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* PageHeaderView.swift */; };
- 0458A51F2B7AC10A0007BA10 /* PageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* PageHeaderView.swift */; };
- 0458A5202B7AC10A0007BA10 /* PageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* PageHeaderView.swift */; };
- 0458A5212B7AC10A0007BA10 /* PageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* PageHeaderView.swift */; };
- 0458A5222B7AC10A0007BA10 /* PageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* PageHeaderView.swift */; };
- 0458A5232B7AC10A0007BA10 /* PageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* PageHeaderView.swift */; };
- 0458A5242B7AC10A0007BA10 /* PageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* PageHeaderView.swift */; };
+ 0458A51B2B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* TitleHeaderView.swift */; };
+ 0458A51C2B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* TitleHeaderView.swift */; };
+ 0458A51D2B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* TitleHeaderView.swift */; };
+ 0458A51E2B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* TitleHeaderView.swift */; };
+ 0458A51F2B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* TitleHeaderView.swift */; };
+ 0458A5202B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* TitleHeaderView.swift */; };
+ 0458A5212B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* TitleHeaderView.swift */; };
+ 0458A5222B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* TitleHeaderView.swift */; };
+ 0458A5232B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* TitleHeaderView.swift */; };
+ 0458A5242B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0458A51A2B7AC1090007BA10 /* TitleHeaderView.swift */; };
045F8A042BA5A001005DDCEE /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 045F8A032BA5A001005DDCEE /* PrivacyInfo.xcprivacy */; };
045F8A052BA5A001005DDCEE /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 045F8A032BA5A001005DDCEE /* PrivacyInfo.xcprivacy */; };
045F8A062BA5A001005DDCEE /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 045F8A032BA5A001005DDCEE /* PrivacyInfo.xcprivacy */; };
@@ -217,6 +217,26 @@
0463DA172A73D8B000CD6556 /* ProgramAndChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0463DA0F2A73D8B000CD6556 /* ProgramAndChannel.swift */; };
0463DA182A73D8B000CD6556 /* ProgramAndChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0463DA0F2A73D8B000CD6556 /* ProgramAndChannel.swift */; };
0463DA192A73D8B000CD6556 /* ProgramAndChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0463DA0F2A73D8B000CD6556 /* ProgramAndChannel.swift */; };
+ 046845A62BF56A13003A0073 /* ColorsSettable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046845A52BF56A13003A0073 /* ColorsSettable.swift */; };
+ 046845A72BF56A13003A0073 /* ColorsSettable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046845A52BF56A13003A0073 /* ColorsSettable.swift */; };
+ 046845A82BF56A13003A0073 /* ColorsSettable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046845A52BF56A13003A0073 /* ColorsSettable.swift */; };
+ 046845A92BF56A13003A0073 /* ColorsSettable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046845A52BF56A13003A0073 /* ColorsSettable.swift */; };
+ 046845AA2BF56A13003A0073 /* ColorsSettable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046845A52BF56A13003A0073 /* ColorsSettable.swift */; };
+ 046845AB2BF56A13003A0073 /* ColorsSettable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046845A52BF56A13003A0073 /* ColorsSettable.swift */; };
+ 046845AC2BF56A13003A0073 /* ColorsSettable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046845A52BF56A13003A0073 /* ColorsSettable.swift */; };
+ 046845AD2BF56A13003A0073 /* ColorsSettable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046845A52BF56A13003A0073 /* ColorsSettable.swift */; };
+ 046845AE2BF56A13003A0073 /* ColorsSettable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046845A52BF56A13003A0073 /* ColorsSettable.swift */; };
+ 046845AF2BF56A13003A0073 /* ColorsSettable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046845A52BF56A13003A0073 /* ColorsSettable.swift */; };
+ 0468459B2BF513E2003A0073 /* ShowVisualView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468459A2BF513E2003A0073 /* ShowVisualView.swift */; };
+ 0468459C2BF513E2003A0073 /* ShowVisualView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468459A2BF513E2003A0073 /* ShowVisualView.swift */; };
+ 0468459D2BF513E2003A0073 /* ShowVisualView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468459A2BF513E2003A0073 /* ShowVisualView.swift */; };
+ 0468459E2BF513E2003A0073 /* ShowVisualView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468459A2BF513E2003A0073 /* ShowVisualView.swift */; };
+ 0468459F2BF513E2003A0073 /* ShowVisualView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468459A2BF513E2003A0073 /* ShowVisualView.swift */; };
+ 046845A02BF513E2003A0073 /* ShowVisualView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468459A2BF513E2003A0073 /* ShowVisualView.swift */; };
+ 046845A12BF513E2003A0073 /* ShowVisualView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468459A2BF513E2003A0073 /* ShowVisualView.swift */; };
+ 046845A22BF513E2003A0073 /* ShowVisualView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468459A2BF513E2003A0073 /* ShowVisualView.swift */; };
+ 046845A32BF513E2003A0073 /* ShowVisualView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468459A2BF513E2003A0073 /* ShowVisualView.swift */; };
+ 046845A42BF513E2003A0073 /* ShowVisualView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468459A2BF513E2003A0073 /* ShowVisualView.swift */; };
046F8DC02B778E5300A71091 /* RadioChannelsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046F8DBF2B778E5300A71091 /* RadioChannelsViewController.swift */; };
046F8DC12B778E5300A71091 /* RadioChannelsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046F8DBF2B778E5300A71091 /* RadioChannelsViewController.swift */; };
046F8DC22B778E5300A71091 /* RadioChannelsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046F8DBF2B778E5300A71091 /* RadioChannelsViewController.swift */; };
@@ -232,6 +252,16 @@
046F8DCE2B77D5F700A71091 /* UIVisualEffectView+PlaySRG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046F8DCB2B77D5F700A71091 /* UIVisualEffectView+PlaySRG.swift */; };
046F8DCF2B77D5F700A71091 /* UIVisualEffectView+PlaySRG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046F8DCB2B77D5F700A71091 /* UIVisualEffectView+PlaySRG.swift */; };
046F8DD02B77D5F700A71091 /* UIVisualEffectView+PlaySRG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046F8DCB2B77D5F700A71091 /* UIVisualEffectView+PlaySRG.swift */; };
+ 047030E72BBD51340032FA74 /* TopicGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047030E62BBD51340032FA74 /* TopicGradientView.swift */; };
+ 047030E82BBD51340032FA74 /* TopicGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047030E62BBD51340032FA74 /* TopicGradientView.swift */; };
+ 047030E92BBD51340032FA74 /* TopicGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047030E62BBD51340032FA74 /* TopicGradientView.swift */; };
+ 047030EA2BBD51340032FA74 /* TopicGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047030E62BBD51340032FA74 /* TopicGradientView.swift */; };
+ 047030EB2BBD51340032FA74 /* TopicGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047030E62BBD51340032FA74 /* TopicGradientView.swift */; };
+ 047030EC2BBD51340032FA74 /* TopicGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047030E62BBD51340032FA74 /* TopicGradientView.swift */; };
+ 047030ED2BBD51340032FA74 /* TopicGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047030E62BBD51340032FA74 /* TopicGradientView.swift */; };
+ 047030EE2BBD51340032FA74 /* TopicGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047030E62BBD51340032FA74 /* TopicGradientView.swift */; };
+ 047030EF2BBD51340032FA74 /* TopicGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047030E62BBD51340032FA74 /* TopicGradientView.swift */; };
+ 047030F02BBD51340032FA74 /* TopicGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047030E62BBD51340032FA74 /* TopicGradientView.swift */; };
04708C0F2B1CAF3E000D43C5 /* AccessibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04708C0E2B1CAF3E000D43C5 /* AccessibilityView.swift */; };
04708C102B1CAF3E000D43C5 /* AccessibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04708C0E2B1CAF3E000D43C5 /* AccessibilityView.swift */; };
04708C112B1CAF3E000D43C5 /* AccessibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04708C0E2B1CAF3E000D43C5 /* AccessibilityView.swift */; };
@@ -2234,16 +2264,6 @@
6FE14E67263EA83C004AD913 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE14E5F263EA83C004AD913 /* HeaderView.swift */; };
6FE14E68263EA83C004AD913 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE14E5F263EA83C004AD913 /* HeaderView.swift */; };
6FE14E69263EA83C004AD913 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE14E5F263EA83C004AD913 /* HeaderView.swift */; };
- 6FE14E6B263EA9B5004AD913 /* TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE14E6A263EA9B5004AD913 /* TitleView.swift */; };
- 6FE14E6C263EA9B5004AD913 /* TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE14E6A263EA9B5004AD913 /* TitleView.swift */; };
- 6FE14E6D263EA9B5004AD913 /* TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE14E6A263EA9B5004AD913 /* TitleView.swift */; };
- 6FE14E6E263EA9B5004AD913 /* TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE14E6A263EA9B5004AD913 /* TitleView.swift */; };
- 6FE14E6F263EA9B5004AD913 /* TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE14E6A263EA9B5004AD913 /* TitleView.swift */; };
- 6FE14E70263EA9B5004AD913 /* TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE14E6A263EA9B5004AD913 /* TitleView.swift */; };
- 6FE14E71263EA9B5004AD913 /* TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE14E6A263EA9B5004AD913 /* TitleView.swift */; };
- 6FE14E72263EA9B5004AD913 /* TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE14E6A263EA9B5004AD913 /* TitleView.swift */; };
- 6FE14E73263EA9B5004AD913 /* TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE14E6A263EA9B5004AD913 /* TitleView.swift */; };
- 6FE14E74263EA9B5004AD913 /* TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE14E6A263EA9B5004AD913 /* TitleView.swift */; };
6FE1B4971DCB84F00094D5BA /* AnalyticsConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 6FE1B4961DCB84F00094D5BA /* AnalyticsConstants.m */; };
6FE1B4981DCB84F00094D5BA /* AnalyticsConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 6FE1B4961DCB84F00094D5BA /* AnalyticsConstants.m */; };
6FE1B4991DCB84F00094D5BA /* AnalyticsConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 6FE1B4961DCB84F00094D5BA /* AnalyticsConstants.m */; };
@@ -2815,13 +2835,16 @@
0451D7ED2B1CEDAD005A2150 /* Banner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Banner.swift; sourceTree = ""; };
0451ECE228742D8000E89975 /* UIWindow+PlaySRG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIWindow+PlaySRG.swift"; sourceTree = ""; };
0456C4A02976EE460088508A /* AnalyticsEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnalyticsEvent.swift; sourceTree = ""; };
- 0458A51A2B7AC1090007BA10 /* PageHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageHeaderView.swift; sourceTree = ""; };
+ 0458A51A2B7AC1090007BA10 /* TitleHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleHeaderView.swift; sourceTree = ""; };
045F8A032BA5A001005DDCEE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; };
045F8A0E2BA5A8A5005DDCEE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; };
0463DA0F2A73D8B000CD6556 /* ProgramAndChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgramAndChannel.swift; sourceTree = ""; };
+ 046845A52BF56A13003A0073 /* ColorsSettable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorsSettable.swift; sourceTree = ""; };
+ 0468459A2BF513E2003A0073 /* ShowVisualView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowVisualView.swift; sourceTree = ""; };
046F8DBF2B778E5300A71091 /* RadioChannelsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioChannelsViewController.swift; sourceTree = ""; };
046F8DC52B779E9B00A71091 /* PageContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageContainerViewController.swift; sourceTree = ""; };
046F8DCB2B77D5F700A71091 /* UIVisualEffectView+PlaySRG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIVisualEffectView+PlaySRG.swift"; sourceTree = ""; };
+ 047030E62BBD51340032FA74 /* TopicGradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicGradientView.swift; sourceTree = ""; };
04708C0E2B1CAF3E000D43C5 /* AccessibilityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityView.swift; sourceTree = ""; };
0481D59829F41D5B00D174B3 /* SRGMedia+PlaySRG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SRGMedia+PlaySRG.swift"; sourceTree = ""; };
0481D5A329F44D4D00D174B3 /* SRGProgram+PlaySRG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SRGProgram+PlaySRG.swift"; sourceTree = ""; };
@@ -3360,7 +3383,6 @@
6FDF6FFF2682022C0004437E /* ApplicationSettings+Common.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ApplicationSettings+Common.m"; sourceTree = ""; };
6FDFD88B24E6BC9200F20382 /* TopicCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicCell.swift; sourceTree = ""; };
6FE14E5F263EA83C004AD913 /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = ""; };
- 6FE14E6A263EA9B5004AD913 /* TitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleView.swift; sourceTree = ""; };
6FE1B4951DCB84F00094D5BA /* AnalyticsConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnalyticsConstants.h; sourceTree = ""; };
6FE1B4961DCB84F00094D5BA /* AnalyticsConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnalyticsConstants.m; sourceTree = ""; };
6FE1B9171FAC34D600A58F3B /* ContentInsets.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContentInsets.h; sourceTree = ""; };
@@ -4766,6 +4788,7 @@
isa = PBXGroup;
children = (
6FF5D1FA2747A69900460F70 /* ButtonStyles.swift */,
+ 046845A52BF56A13003A0073 /* ColorsSettable.swift */,
6F0E374B2680B139008FC923 /* ContextMenu.swift */,
6F58903326AED4CD00553C24 /* Environment.swift */,
6FF8F155250609CF009A741F /* MediaDescription.swift */,
@@ -4826,7 +4849,6 @@
6FD9595326989FD900739FAE /* MediaVisualViewModel.swift */,
6FE8626B2657C5F30061D3F0 /* MoreCell.swift */,
6FBB7F9B2844B9FB00976FFB /* NotificationCell.swift */,
- 0458A51A2B7AC1090007BA10 /* PageHeaderView.swift */,
6FD1EF412861A3C500BCBF19 /* PlayNavigationView.swift */,
6FD1EF362861A39400BCBF19 /* PlaySection.swift */,
6F151E26256BF5CF009082F8 /* ProgressBar.swift */,
@@ -4840,12 +4862,14 @@
04F184CD28EC5EE500B1207C /* ShowButton.swift */,
6FB1ADF924EFEF2C00E80C1E /* ShowCell.swift */,
6FF0C84626B44F6A006B3C6A /* ShowCellViewModel.swift */,
+ 0468459A2BF513E2003A0073 /* ShowVisualView.swift */,
6FD4C2D5268B6CBB00F06F63 /* SimpleButton.swift */,
6FB899FE26335B090012F1B0 /* Stack.swift */,
04395F1A2B1BB72400F6A634 /* TableLoadMoreFooterView.swift */,
041DD17E2B1BA8B100C9368A /* TableView.swift */,
- 6FE14E6A263EA9B5004AD913 /* TitleView.swift */,
+ 0458A51A2B7AC1090007BA10 /* TitleHeaderView.swift */,
6FDFD88B24E6BC9200F20382 /* TopicCell.swift */,
+ 047030E62BBD51340032FA74 /* TopicGradientView.swift */,
6FB9B3D526CA88AD0065092F /* TransluscentHeaderView.swift */,
04D21DBB299BEB42009CEA15 /* TruncatableTextView.swift */,
6FDAAD93283283EB008E2806 /* WebView.swift */,
@@ -6961,7 +6985,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n exit 0\nfi\n\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n swiftlint --strict\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
+ shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n swiftlint\n else\n swiftlint --strict \n fi\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
08AD6C6E266BDC5000FAF1FA /* Swift Lint */ = {
isa = PBXShellScriptBuildPhase;
@@ -6980,7 +7004,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n exit 0\nfi\n\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n swiftlint --strict\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
+ shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n swiftlint\n else\n swiftlint --strict \n fi\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
08AD6C6F266BDC6D00FAF1FA /* Swift Lint */ = {
isa = PBXShellScriptBuildPhase;
@@ -6999,7 +7023,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n exit 0\nfi\n\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n swiftlint --strict\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
+ shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n swiftlint\n else\n swiftlint --strict \n fi\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
08AD6C70266BDC7B00FAF1FA /* Swift Lint */ = {
isa = PBXShellScriptBuildPhase;
@@ -7018,7 +7042,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n exit 0\nfi\n\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n swiftlint --strict\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
+ shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n swiftlint\n else\n swiftlint --strict \n fi\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
08AD6C71266BDC8D00FAF1FA /* Swift Lint */ = {
isa = PBXShellScriptBuildPhase;
@@ -7037,7 +7061,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n exit 0\nfi\n\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n swiftlint --strict\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
+ shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n swiftlint\n else\n swiftlint --strict \n fi\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
08AD6C72266BDCA100FAF1FA /* Swift Lint */ = {
isa = PBXShellScriptBuildPhase;
@@ -7056,7 +7080,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n exit 0\nfi\n\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n swiftlint --strict\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
+ shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n swiftlint\n else\n swiftlint --strict \n fi\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
08AD6C73266BDCBB00FAF1FA /* Swift Lint */ = {
isa = PBXShellScriptBuildPhase;
@@ -7075,7 +7099,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n exit 0\nfi\n\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n swiftlint --strict\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
+ shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n swiftlint\n else\n swiftlint --strict \n fi\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
08AD6C74266BDCC800FAF1FA /* Swift Lint */ = {
isa = PBXShellScriptBuildPhase;
@@ -7094,7 +7118,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n exit 0\nfi\n\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n swiftlint --strict\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
+ shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n swiftlint\n else\n swiftlint --strict \n fi\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
08AD6C75266BDCD400FAF1FA /* Swift Lint */ = {
isa = PBXShellScriptBuildPhase;
@@ -7113,7 +7137,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n exit 0\nfi\n\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n swiftlint --strict\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
+ shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n swiftlint\n else\n swiftlint --strict \n fi\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
08AD6C76266BDCDE00FAF1FA /* Swift Lint */ = {
isa = PBXShellScriptBuildPhase;
@@ -7132,7 +7156,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n exit 0\nfi\n\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n swiftlint --strict\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
+ shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n swiftlint\n else\n swiftlint --strict \n fi\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
08B4A22D2093577300474EBB /* Copy Urban Airship Configuration File */ = {
isa = PBXShellScriptBuildPhase;
@@ -8459,7 +8483,6 @@
0490B9F92A3789F500B6FB7B /* UserConsentHelper.swift in Sources */,
6F054FC626B98C44007A34F8 /* ProgramView.swift in Sources */,
6F676851281C0F7F00D61211 /* SupportInformation.swift in Sources */,
- 6FE14E6B263EA9B5004AD913 /* TitleView.swift in Sources */,
6F4760791EB37D60003021EA /* UIDevice+PlaySRG.m in Sources */,
6F573D0426D644A000757CD5 /* DeepLinkAction.m in Sources */,
6F475FE11EB37BC6003021EA /* ModalTransition.m in Sources */,
@@ -8537,6 +8560,7 @@
048FD2092A122BF100929AE5 /* ProfileAccountHeaderView+UIKit.swift in Sources */,
6F73BFB026563ABD0032D742 /* DampedCollectionView.swift in Sources */,
6FEF23722732C2D40098C639 /* TimelineView.swift in Sources */,
+ 046845A62BF56A13003A0073 /* ColorsSettable.swift in Sources */,
6F3B0221245AAE1B00C5A8D7 /* ProgramTableViewCell.m in Sources */,
6F74293D265BE52E0000538D /* Signals.swift in Sources */,
6FDF70002682022C0004437E /* ApplicationSettings+Common.m in Sources */,
@@ -8567,12 +8591,14 @@
6FC2F7DF2628F23600BF6B19 /* ResponderChain.swift in Sources */,
6F33E6122860AEFF00724E76 /* Navigation.swift in Sources */,
6F8A545A2655100400AE78FD /* SectionViewController.swift in Sources */,
- 0458A51B2B7AC10A0007BA10 /* PageHeaderView.swift in Sources */,
+ 0458A51B2B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */,
0451D7EE2B1CEDAD005A2150 /* Banner.swift in Sources */,
04395F272B1BC44200F6A634 /* StoreReview.swift in Sources */,
08209308208F522A00711DE4 /* PushService.m in Sources */,
+ 0468459B2BF513E2003A0073 /* ShowVisualView.swift in Sources */,
6F362A8A26A0706F00CBCC9D /* ProgramGuideDailyViewController.swift in Sources */,
6FE38A27270CF860004DD296 /* CarPlayTemplateListController.swift in Sources */,
+ 047030E72BBD51340032FA74 /* TopicGradientView.swift in Sources */,
042F6F6029E0AE6E003F46AA /* UIImage+PlaySRG.swift in Sources */,
08C68F8A1D38DEA100BB8AAA /* AppDelegate.m in Sources */,
041DD17F2B1BA8B100C9368A /* TableView.swift in Sources */,
@@ -8643,6 +8669,7 @@
6F80106A20443230009FE197 /* PlayApplication.m in Sources */,
6F4760801EB37DF1003021EA /* UIDevice+PlaySRG.m in Sources */,
04FB9CB42A0542BB00A9B69E /* ProfileSectionHeaderView.swift in Sources */,
+ 047030E82BBD51340032FA74 /* TopicGradientView.swift in Sources */,
044B405C2A01764900A10DB7 /* ProfileCell.swift in Sources */,
6FC2A21D265E3D2300EBC0F0 /* SectionShowHeaderView.swift in Sources */,
0809813B2622195900AA586B /* Badges.swift in Sources */,
@@ -8679,6 +8706,7 @@
6F0E374D2680B13A008FC923 /* ContextMenu.swift in Sources */,
6FCA5BD727D9DE0900916D0B /* DiskInfoFooterView.swift in Sources */,
6F6C7ACA2820576000BC3EA5 /* UserLocation.swift in Sources */,
+ 046845A72BF56A13003A0073 /* ColorsSettable.swift in Sources */,
6FDF08DD218B126700B2AF2C /* Recommendation.m in Sources */,
6FCE753626D3786F00667298 /* HeroMediaCell.swift in Sources */,
6FE9048726F3466800502077 /* UICollectionView+PlaySRG.m in Sources */,
@@ -8739,7 +8767,6 @@
6FA5D15E1F2077B10059E4E2 /* NSString+PlaySRG.m in Sources */,
6F566E9624EEC40A0024B4CA /* ApplicationSection.m in Sources */,
6F72E60826BA6B16001A890C /* SceneDelegate.m in Sources */,
- 6FE14E6C263EA9B5004AD913 /* TitleView.swift in Sources */,
04395F222B1BBA6600F6A634 /* RefreshControl.swift in Sources */,
6F0A7F0020AC0B9A00DF6723 /* OnboardingViewController.swift in Sources */,
085C0DC426132673008E07C8 /* ApplicationConfiguration.swift in Sources */,
@@ -8757,6 +8784,7 @@
042F6F2A29E0710C003F46AA /* UIStackView+PlaySRG.swift in Sources */,
6F3CCE9A26CAC7A2004039E2 /* Blur.swift in Sources */,
6F73BFB726563C830032D742 /* Content.swift in Sources */,
+ 0468459C2BF513E2003A0073 /* ShowVisualView.swift in Sources */,
6FD1EF432861A3C500BCBF19 /* PlayNavigationView.swift in Sources */,
6F8A545B2655100400AE78FD /* SectionViewController.swift in Sources */,
6F9122C01DC8708400725EEB /* PlayErrors.m in Sources */,
@@ -8861,7 +8889,7 @@
08669676273E63D1005AF2BA /* NowLineView.swift in Sources */,
6FFA68342637E99C00BCDA06 /* Mock.swift in Sources */,
6FCB65F126F4994C00A95C07 /* GoogleCastFloatingButton.swift in Sources */,
- 0458A51C2B7AC10A0007BA10 /* PageHeaderView.swift in Sources */,
+ 0458A51C2B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */,
6FF0C84826B44F6A006B3C6A /* ShowCellViewModel.swift in Sources */,
6F010E12286607450024A745 /* SearchSettingsBucketCell.swift in Sources */,
0463DA112A73D8B000CD6556 /* ProgramAndChannel.swift in Sources */,
@@ -8909,6 +8937,7 @@
6F80106B20443230009FE197 /* PlayApplication.m in Sources */,
6F4760871EB37DF1003021EA /* UIDevice+PlaySRG.m in Sources */,
04FB9CB52A0542BB00A9B69E /* ProfileSectionHeaderView.swift in Sources */,
+ 047030E92BBD51340032FA74 /* TopicGradientView.swift in Sources */,
044B405D2A01764900A10DB7 /* ProfileCell.swift in Sources */,
6FC2A21E265E3D2300EBC0F0 /* SectionShowHeaderView.swift in Sources */,
0809813C2622195900AA586B /* Badges.swift in Sources */,
@@ -8945,6 +8974,7 @@
6F0E374E2680B13A008FC923 /* ContextMenu.swift in Sources */,
6FCA5BD827D9DE0900916D0B /* DiskInfoFooterView.swift in Sources */,
6F6C7ACB2820576000BC3EA5 /* UserLocation.swift in Sources */,
+ 046845A82BF56A13003A0073 /* ColorsSettable.swift in Sources */,
6FE38A23270CBF63004DD296 /* CarPlay+Extensions.swift in Sources */,
6FDF08DE218B126700B2AF2C /* Recommendation.m in Sources */,
6FCE753726D3786F00667298 /* HeroMediaCell.swift in Sources */,
@@ -9007,7 +9037,6 @@
6F566E9724EEC40A0024B4CA /* ApplicationSection.m in Sources */,
6F72E60926BA6B16001A890C /* SceneDelegate.m in Sources */,
04395F232B1BBA6600F6A634 /* RefreshControl.swift in Sources */,
- 6FE14E6D263EA9B5004AD913 /* TitleView.swift in Sources */,
6F0A7F0120AC0B9A00DF6723 /* OnboardingViewController.swift in Sources */,
085C0DC526132673008E07C8 /* ApplicationConfiguration.swift in Sources */,
6F475FCF1EB37BC6003021EA /* NavigationController.m in Sources */,
@@ -9023,6 +9052,7 @@
042F6F2B29E0710C003F46AA /* UIStackView+PlaySRG.swift in Sources */,
6F3CCE9B26CAC7A2004039E2 /* Blur.swift in Sources */,
6F73BFB826563C830032D742 /* Content.swift in Sources */,
+ 0468459D2BF513E2003A0073 /* ShowVisualView.swift in Sources */,
6FD1EF442861A3C500BCBF19 /* PlayNavigationView.swift in Sources */,
6F8A545C2655100400AE78FD /* SectionViewController.swift in Sources */,
6F9122C11DC8708400725EEB /* PlayErrors.m in Sources */,
@@ -9127,7 +9157,7 @@
08669677273E63D1005AF2BA /* NowLineView.swift in Sources */,
6FFA68352637E99C00BCDA06 /* Mock.swift in Sources */,
6FCB65F226F4994C00A95C07 /* GoogleCastFloatingButton.swift in Sources */,
- 0458A51D2B7AC10A0007BA10 /* PageHeaderView.swift in Sources */,
+ 0458A51D2B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */,
6FF0C84926B44F6A006B3C6A /* ShowCellViewModel.swift in Sources */,
6F010E13286607450024A745 /* SearchSettingsBucketCell.swift in Sources */,
0463DA122A73D8B000CD6556 /* ProgramAndChannel.swift in Sources */,
@@ -9175,6 +9205,7 @@
6F80106C20443230009FE197 /* PlayApplication.m in Sources */,
6F47608E1EB37DF1003021EA /* UIDevice+PlaySRG.m in Sources */,
04FB9CB62A0542BB00A9B69E /* ProfileSectionHeaderView.swift in Sources */,
+ 047030EA2BBD51340032FA74 /* TopicGradientView.swift in Sources */,
044B405E2A01764900A10DB7 /* ProfileCell.swift in Sources */,
6FC2A21F265E3D2300EBC0F0 /* SectionShowHeaderView.swift in Sources */,
0809813D2622195900AA586B /* Badges.swift in Sources */,
@@ -9211,6 +9242,7 @@
6F0E374F2680B13A008FC923 /* ContextMenu.swift in Sources */,
6FCA5BD927D9DE0900916D0B /* DiskInfoFooterView.swift in Sources */,
6F6C7ACC2820576000BC3EA5 /* UserLocation.swift in Sources */,
+ 046845A92BF56A13003A0073 /* ColorsSettable.swift in Sources */,
6FE38A24270CBF63004DD296 /* CarPlay+Extensions.swift in Sources */,
6FDF08DF218B126700B2AF2C /* Recommendation.m in Sources */,
6FCE753826D3786F00667298 /* HeroMediaCell.swift in Sources */,
@@ -9273,7 +9305,6 @@
6F566E9824EEC40A0024B4CA /* ApplicationSection.m in Sources */,
6F72E60A26BA6B16001A890C /* SceneDelegate.m in Sources */,
04395F242B1BBA6600F6A634 /* RefreshControl.swift in Sources */,
- 6FE14E6E263EA9B5004AD913 /* TitleView.swift in Sources */,
6F0A7F0220AC0B9A00DF6723 /* OnboardingViewController.swift in Sources */,
085C0DC626132673008E07C8 /* ApplicationConfiguration.swift in Sources */,
6F475FD01EB37BC6003021EA /* NavigationController.m in Sources */,
@@ -9289,6 +9320,7 @@
042F6F2C29E0710C003F46AA /* UIStackView+PlaySRG.swift in Sources */,
6F3CCE9C26CAC7A2004039E2 /* Blur.swift in Sources */,
6F73BFB926563C830032D742 /* Content.swift in Sources */,
+ 0468459E2BF513E2003A0073 /* ShowVisualView.swift in Sources */,
6FD1EF452861A3C500BCBF19 /* PlayNavigationView.swift in Sources */,
6F8A545D2655100400AE78FD /* SectionViewController.swift in Sources */,
6F9122C21DC8708400725EEB /* PlayErrors.m in Sources */,
@@ -9393,7 +9425,7 @@
08669678273E63D1005AF2BA /* NowLineView.swift in Sources */,
6FFA68362637E99C00BCDA06 /* Mock.swift in Sources */,
6FCB65F326F4994C00A95C07 /* GoogleCastFloatingButton.swift in Sources */,
- 0458A51E2B7AC10A0007BA10 /* PageHeaderView.swift in Sources */,
+ 0458A51E2B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */,
6FF0C84A26B44F6A006B3C6A /* ShowCellViewModel.swift in Sources */,
6F010E14286607450024A745 /* SearchSettingsBucketCell.swift in Sources */,
0463DA132A73D8B000CD6556 /* ProgramAndChannel.swift in Sources */,
@@ -9506,6 +9538,7 @@
6F2E160D26A84ED200F3DC89 /* ProgramCell.swift in Sources */,
6FB79BF2287E81790091D157 /* Orientation.m in Sources */,
081220C51DD0ADAC00BF8326 /* DownloadSession.m in Sources */,
+ 047030EB2BBD51340032FA74 /* TopicGradientView.swift in Sources */,
6F3B0225245AAE1B00C5A8D7 /* ProgramTableViewCell.m in Sources */,
6FD686232460670600B8018A /* Channel.m in Sources */,
6F475FE01EB37BC6003021EA /* RequestViewController.m in Sources */,
@@ -9537,7 +9570,6 @@
0456C4A52976EE460088508A /* AnalyticsEvent.swift in Sources */,
6F72E60B26BA6B16001A890C /* SceneDelegate.m in Sources */,
6F11BADF27D60525003E59B2 /* DownloadCellViewModel.swift in Sources */,
- 6FE14E6F263EA9B5004AD913 /* TitleView.swift in Sources */,
6F0A7F0320AC0B9A00DF6723 /* OnboardingViewController.swift in Sources */,
0490B9FD2A3789F500B6FB7B /* UserConsentHelper.swift in Sources */,
085C0DC726132673008E07C8 /* ApplicationConfiguration.swift in Sources */,
@@ -9607,6 +9639,7 @@
6FB89A0326335B090012F1B0 /* Stack.swift in Sources */,
08AF9484217D27E40028B082 /* SharingItem.m in Sources */,
04708C132B1CAF3E000D43C5 /* AccessibilityView.swift in Sources */,
+ 046845AA2BF56A13003A0073 /* ColorsSettable.swift in Sources */,
6F8D54462639ABFE00EF5FE8 /* FeaturedContent.swift in Sources */,
086BDE1D1EA63D5B00965F45 /* PlayMiniPlayerView.m in Sources */,
0820930C208F522B00711DE4 /* PushService.m in Sources */,
@@ -9640,6 +9673,7 @@
6F4CF73A281341B7006AFE6D /* ImageView.swift in Sources */,
048FD2132A124CB300929AE5 /* ProfileAccountHeaderViewModel.swift in Sources */,
6F0506EB245468EE0053253E /* SplitViewController.m in Sources */,
+ 0468459F2BF513E2003A0073 /* ShowVisualView.swift in Sources */,
041DD1832B1BA8B100C9368A /* TableView.swift in Sources */,
04395F1F2B1BB72400F6A634 /* TableLoadMoreFooterView.swift in Sources */,
6F4760971EB37DF2003021EA /* UIImageView+PlaySRG.m in Sources */,
@@ -9653,7 +9687,7 @@
6FFA68372637E99C00BCDA06 /* Mock.swift in Sources */,
0481D59D29F41D5B00D174B3 /* SRGMedia+PlaySRG.swift in Sources */,
6FCB65F426F4994C00A95C07 /* GoogleCastFloatingButton.swift in Sources */,
- 0458A51F2B7AC10A0007BA10 /* PageHeaderView.swift in Sources */,
+ 0458A51F2B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */,
6FF0C84B26B44F6A006B3C6A /* ShowCellViewModel.swift in Sources */,
6F0850F826256A7700B4E410 /* Reachability.m in Sources */,
6FB89E2B283DEA12006F9C6C /* HighlightCell.swift in Sources */,
@@ -9839,6 +9873,7 @@
6FEC89EF261F19A000FF9762 /* ContentInsets.m in Sources */,
08F5DB15262DC7F700F717D0 /* Logger.swift in Sources */,
6F8125E72638C37900EB029E /* LabeledCardButton.swift in Sources */,
+ 046845A02BF513E2003A0073 /* ShowVisualView.swift in Sources */,
6FC24A8326395F3E00CACC20 /* FeaturedDescriptionView.swift in Sources */,
6F151E27256BF5CF009082F8 /* ProgressBar.swift in Sources */,
6FB89A0426335B090012F1B0 /* Stack.swift in Sources */,
@@ -9904,10 +9939,11 @@
6F8A545F2655100400AE78FD /* SectionViewController.swift in Sources */,
0463DA152A73D8B000CD6556 /* ProgramAndChannel.swift in Sources */,
6FC44B4A25DE552300DE6E6F /* Recommendation.m in Sources */,
+ 047030EC2BBD51340032FA74 /* TopicGradientView.swift in Sources */,
6FF129DD256C0D370042F446 /* PlayDurationFormatter.m in Sources */,
6F7625CB2721786B00C134AA /* DeepLinkAction.m in Sources */,
6F6C7AD92820578900BC3EA5 /* PosterImages.swift in Sources */,
- 0458A5202B7AC10A0007BA10 /* PageHeaderView.swift in Sources */,
+ 0458A5202B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */,
6F85F7822567ED2000AC8286 /* ForegroundTimer.m in Sources */,
6FE86F7B2719C5CF0082CAE9 /* PlayAccessibilityFormatter.m in Sources */,
6FF5D2002747A69900460F70 /* ButtonStyles.swift in Sources */,
@@ -9937,8 +9973,8 @@
085C0DD3261326C8008E07C8 /* RadioChannel.swift in Sources */,
0858CA6A2710927B00EE36EA /* ProgramGuideViewModel.swift in Sources */,
6F3F1ABA25060496000FF4DD /* MediaVisualView.swift in Sources */,
+ 046845AB2BF56A13003A0073 /* ColorsSettable.swift in Sources */,
6FDF70052682022C0004437E /* ApplicationSettings+Common.m in Sources */,
- 6FE14E70263EA9B5004AD913 /* TitleView.swift in Sources */,
084EF77D26035BB10058A567 /* PageViewModel.swift in Sources */,
6F9FFB4E261662D900CDDC26 /* CollectionRow.swift in Sources */,
6FAE562126C19D6F00EBFCD6 /* UICollectionView+Index.swift in Sources */,
@@ -9994,6 +10030,7 @@
6FEC89F0261F19A100FF9762 /* ContentInsets.m in Sources */,
08F5DB16262DC7F700F717D0 /* Logger.swift in Sources */,
6F8125E82638C37900EB029E /* LabeledCardButton.swift in Sources */,
+ 046845A12BF513E2003A0073 /* ShowVisualView.swift in Sources */,
6FC24A8426395F3E00CACC20 /* FeaturedDescriptionView.swift in Sources */,
6F151E28256BF5CF009082F8 /* ProgressBar.swift in Sources */,
6FB89A0526335B090012F1B0 /* Stack.swift in Sources */,
@@ -10059,10 +10096,11 @@
6F8A54602655100400AE78FD /* SectionViewController.swift in Sources */,
0463DA162A73D8B000CD6556 /* ProgramAndChannel.swift in Sources */,
6FC44B4B25DE552400DE6E6F /* Recommendation.m in Sources */,
+ 047030ED2BBD51340032FA74 /* TopicGradientView.swift in Sources */,
6FF129F2256C0D370042F446 /* PlayDurationFormatter.m in Sources */,
6F7625CC2721786D00C134AA /* DeepLinkAction.m in Sources */,
6F6C7ADA2820578900BC3EA5 /* PosterImages.swift in Sources */,
- 0458A5212B7AC10A0007BA10 /* PageHeaderView.swift in Sources */,
+ 0458A5212B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */,
6F85F7972567ED2000AC8286 /* ForegroundTimer.m in Sources */,
6FE86F7C2719C5D00082CAE9 /* PlayAccessibilityFormatter.m in Sources */,
6FF5D2012747A69900460F70 /* ButtonStyles.swift in Sources */,
@@ -10092,8 +10130,8 @@
085C0DD4261326C8008E07C8 /* RadioChannel.swift in Sources */,
0858CA6B2710927C00EE36EA /* ProgramGuideViewModel.swift in Sources */,
6F3F1ABB25060496000FF4DD /* MediaVisualView.swift in Sources */,
+ 046845AC2BF56A13003A0073 /* ColorsSettable.swift in Sources */,
6FDF70062682022C0004437E /* ApplicationSettings+Common.m in Sources */,
- 6FE14E71263EA9B5004AD913 /* TitleView.swift in Sources */,
084EF77E26035BB10058A567 /* PageViewModel.swift in Sources */,
6F9FFB4F261662D900CDDC26 /* CollectionRow.swift in Sources */,
6FAE562226C19D6F00EBFCD6 /* UICollectionView+Index.swift in Sources */,
@@ -10149,6 +10187,7 @@
6FEC89F1261F19A100FF9762 /* ContentInsets.m in Sources */,
08F5DB17262DC7F700F717D0 /* Logger.swift in Sources */,
6F8125E92638C37900EB029E /* LabeledCardButton.swift in Sources */,
+ 046845A22BF513E2003A0073 /* ShowVisualView.swift in Sources */,
6FC24A8526395F3E00CACC20 /* FeaturedDescriptionView.swift in Sources */,
6F151E29256BF5CF009082F8 /* ProgressBar.swift in Sources */,
6FB89A0626335B090012F1B0 /* Stack.swift in Sources */,
@@ -10214,10 +10253,11 @@
6F8A54612655100500AE78FD /* SectionViewController.swift in Sources */,
0463DA172A73D8B000CD6556 /* ProgramAndChannel.swift in Sources */,
6FC44B4C25DE552400DE6E6F /* Recommendation.m in Sources */,
+ 047030EE2BBD51340032FA74 /* TopicGradientView.swift in Sources */,
6FF129F3256C0D380042F446 /* PlayDurationFormatter.m in Sources */,
6F7625CD2721786D00C134AA /* DeepLinkAction.m in Sources */,
6F6C7ADB2820578900BC3EA5 /* PosterImages.swift in Sources */,
- 0458A5222B7AC10A0007BA10 /* PageHeaderView.swift in Sources */,
+ 0458A5222B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */,
6F85F7AC2567ED2100AC8286 /* ForegroundTimer.m in Sources */,
6FE86F7D2719C5D10082CAE9 /* PlayAccessibilityFormatter.m in Sources */,
6FF5D2022747A69900460F70 /* ButtonStyles.swift in Sources */,
@@ -10247,8 +10287,8 @@
085C0DD5261326C8008E07C8 /* RadioChannel.swift in Sources */,
0858CA6C2710927E00EE36EA /* ProgramGuideViewModel.swift in Sources */,
6F3F1ABC25060496000FF4DD /* MediaVisualView.swift in Sources */,
+ 046845AD2BF56A13003A0073 /* ColorsSettable.swift in Sources */,
6FDF70072682022C0004437E /* ApplicationSettings+Common.m in Sources */,
- 6FE14E72263EA9B5004AD913 /* TitleView.swift in Sources */,
084EF77F26035BB30058A567 /* PageViewModel.swift in Sources */,
6F9FFB50261662D900CDDC26 /* CollectionRow.swift in Sources */,
6FAE562326C19D6F00EBFCD6 /* UICollectionView+Index.swift in Sources */,
@@ -10304,6 +10344,7 @@
6FEC8A0B261F19A300FF9762 /* ContentInsets.m in Sources */,
08F5DB18262DC7F700F717D0 /* Logger.swift in Sources */,
6F8125EA2638C37900EB029E /* LabeledCardButton.swift in Sources */,
+ 046845A32BF513E2003A0073 /* ShowVisualView.swift in Sources */,
6FC24A8626395F3E00CACC20 /* FeaturedDescriptionView.swift in Sources */,
6F151E2A256BF5CF009082F8 /* ProgressBar.swift in Sources */,
6FB89A0726335B090012F1B0 /* Stack.swift in Sources */,
@@ -10369,10 +10410,11 @@
6F8A54622655100500AE78FD /* SectionViewController.swift in Sources */,
0463DA182A73D8B000CD6556 /* ProgramAndChannel.swift in Sources */,
6FC44B4D25DE552400DE6E6F /* Recommendation.m in Sources */,
+ 047030EF2BBD51340032FA74 /* TopicGradientView.swift in Sources */,
6FF12A08256C0D390042F446 /* PlayDurationFormatter.m in Sources */,
6F7625CE2721786E00C134AA /* DeepLinkAction.m in Sources */,
6F6C7ADC2820578900BC3EA5 /* PosterImages.swift in Sources */,
- 0458A5232B7AC10A0007BA10 /* PageHeaderView.swift in Sources */,
+ 0458A5232B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */,
6F85F7AD2567ED2100AC8286 /* ForegroundTimer.m in Sources */,
6FE86F7E2719C5D20082CAE9 /* PlayAccessibilityFormatter.m in Sources */,
6FF5D2032747A69900460F70 /* ButtonStyles.swift in Sources */,
@@ -10402,8 +10444,8 @@
085C0DD6261326C8008E07C8 /* RadioChannel.swift in Sources */,
0858CA6D2710927F00EE36EA /* ProgramGuideViewModel.swift in Sources */,
6F3F1ABD25060496000FF4DD /* MediaVisualView.swift in Sources */,
+ 046845AE2BF56A13003A0073 /* ColorsSettable.swift in Sources */,
6FDF70082682022C0004437E /* ApplicationSettings+Common.m in Sources */,
- 6FE14E73263EA9B5004AD913 /* TitleView.swift in Sources */,
084EF78026035BB40058A567 /* PageViewModel.swift in Sources */,
6F9FFB51261662D900CDDC26 /* CollectionRow.swift in Sources */,
6FAE562426C19D6F00EBFCD6 /* UICollectionView+Index.swift in Sources */,
@@ -10416,12 +10458,13 @@
buildActionMask = 2147483647;
files = (
6FE86F752719C4CD0082CAE9 /* ProgramCell.swift in Sources */,
+ 047030F02BBD51340032FA74 /* TopicGradientView.swift in Sources */,
04D174EB29AA8D6D00CF2F09 /* ApplicationSectionInfo.m in Sources */,
086499B624F69FF20027373E /* MediaDetailView.swift in Sources */,
6F79E0AE2541647400A28E79 /* Colors.swift in Sources */,
6FC413F524EEEDCB00FDF806 /* ApplicationConfiguration.m in Sources */,
6FA8E5B5261CB794003FFDCF /* DataViewController.m in Sources */,
- 0458A5242B7AC10A0007BA10 /* PageHeaderView.swift in Sources */,
+ 0458A5242B7AC10A0007BA10 /* TitleHeaderView.swift in Sources */,
6F026EF1250134FF00BAE8D1 /* UIView+PlaySRG.m in Sources */,
6F6C0FC0257AAF270077322C /* NSBundle+PlaySRG.m in Sources */,
042F6F6929E0AE6E003F46AA /* UIImage+PlaySRG.swift in Sources */,
@@ -10500,6 +10543,7 @@
6FB03F6525DECB3A0033132B /* ApplicationSettingsConstants.m in Sources */,
6FD4C2EA268C430600F06F63 /* ShowHeaderViewModel.swift in Sources */,
6F8D544B2639ABFE00EF5FE8 /* FeaturedContent.swift in Sources */,
+ 046845AF2BF56A13003A0073 /* ColorsSettable.swift in Sources */,
6FEF23702732C2420098C639 /* ChannelHeaderView.swift in Sources */,
6FE86F702719C4710082CAE9 /* ProgramGuideDailyViewModel.swift in Sources */,
080981392622195900AA586B /* LiveMediaCell.swift in Sources */,
@@ -10522,6 +10566,7 @@
6F1EE83E268A1B0E004A48CA /* ShowHeaderView.swift in Sources */,
6F091D64270DE4FE00210713 /* Publishers.swift in Sources */,
6FFFB9BB252CA310004E40AE /* MediaDetailViewModel.swift in Sources */,
+ 046845A42BF513E2003A0073 /* ShowVisualView.swift in Sources */,
08E6136A25843C8300C5FE4B /* PlayApplication.m in Sources */,
6F8A54632655100500AE78FD /* SectionViewController.swift in Sources */,
6FC44B4E25DE552500DE6E6F /* Recommendation.m in Sources */,
@@ -10557,7 +10602,6 @@
0407EFEF2A509F10004A0FAB /* Bundble+PlaySRG.swift in Sources */,
6FDF70092682022C0004437E /* ApplicationSettings+Common.m in Sources */,
043ECDC829F2ADC600D2EFC8 /* SRGChannel+PlaySRG.swift in Sources */,
- 6FE14E74263EA9B5004AD913 /* TitleView.swift in Sources */,
0481D5A229F41D5B00D174B3 /* SRGMedia+PlaySRG.swift in Sources */,
084EF78126035BB50058A567 /* PageViewModel.swift in Sources */,
6F9FFB52261662D900CDDC26 /* CollectionRow.swift in Sources */,
@@ -19291,7 +19335,7 @@
repositoryURL = "https://github.com/SRGSSR/srgappearance-apple.git";
requirement = {
kind = upToNextMajorVersion;
- minimumVersion = 5.2.0;
+ minimumVersion = 5.2.2;
};
};
6F3B5638283B903C009A2D7D /* XCRemoteSwiftPackageReference "ShowTime" */ = {
@@ -19331,7 +19375,7 @@
repositoryURL = "https://github.com/SRGSSR/srgdataprovider-apple.git";
requirement = {
kind = upToNextMajorVersion;
- minimumVersion = 19.0.2;
+ minimumVersion = 19.0.3;
};
};
6F7269A72836CFE90072BA0B /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = {
diff --git a/PlaySRG.xcodeproj/xcshareddata/xcschemes/Play RSI TV screenshots.xcscheme b/PlaySRG.xcodeproj/xcshareddata/xcschemes/Play RSI TV screenshots.xcscheme
index 32a772d7d..e39cb176f 100644
--- a/PlaySRG.xcodeproj/xcshareddata/xcschemes/Play RSI TV screenshots.xcscheme
+++ b/PlaySRG.xcodeproj/xcshareddata/xcschemes/Play RSI TV screenshots.xcscheme
@@ -1,6 +1,6 @@
: View {
.layoutPriority(1)
label()
- .opacity(isFocused ? 1 : 0.5)
+ .opacity(isFocused ? 1 : 0.8)
.offset(x: 0, y: isFocused ? 10 : 0)
.scaleEffect(isFocused ? 1.1 : 1, anchor: .top)
.animation(.easeInOut(duration: 0.1))
diff --git a/Translations/Accessibility.strings b/Translations/Accessibility.strings
index 3dd7da2b1..651c3e428 100644
--- a/Translations/Accessibility.strings
+++ b/Translations/Accessibility.strings
@@ -1,4 +1,4 @@
-/* Date at time label to spell a date and time value. */
+/* Date at time label to spell a date and time value. */
"%1$@ at %2$@" = "%1$@ at %2$@";
/* Song description. First placeholder is song title, second is artist name */
diff --git a/Translations/Localizable.strings b/Translations/Localizable.strings
index d5dc22c25..381d0fbc7 100644
--- a/Translations/Localizable.strings
+++ b/Translations/Localizable.strings
@@ -1,4 +1,4 @@
-/* Speed factor with current value if different from desired one */
+/* Speed factor with current value if different from desired one */
"%1$@× (Currently: %2$@×)" = "%1$@× (Currently: %2$@×)";
/* Seek backward shortcut label */
@@ -738,7 +738,8 @@
Error message when a media cannot be opened via Handoff, deep linking or a push notification */
"The media cannot be opened." = "The media cannot be opened.";
-/* Error message when a page cannot be opened via Handoff, deep linking or a push notification
+/* Error message when a page cannot be opened from a page section title
+ Error message when a page cannot be opened via Handoff, deep linking or a push notification
Error message when a topic cannot be opened via Handoff, deep linking or a push notification */
"The page cannot be opened." = "The page cannot be opened.";
diff --git a/WhatsNew-iOS-beta.json b/WhatsNew-iOS-beta.json
index 7c5e6bf95..e196ec0d7 100755
--- a/WhatsNew-iOS-beta.json
+++ b/WhatsNew-iOS-beta.json
@@ -227,5 +227,8 @@
"3.8.4-448": "- Fixed a crash when opening radio channel page or audio show page from media player view.",
"3.8.5-449": "- Maintenance build.\n- Add audio and subtitle selections in email support information. \n- Image service maintnance. [RTS]",
"3.8.5-450": "- Fix song list layout in radio player.\n- Move # section to end of AZ show list (TV and radios).",
- "3.8.5-451": "- AppStore release."
+ "3.8.5-451": "- AppStore release.",
+ "3.8.6-452": "Branch beta\n- Show page header updated.\n- Topic colors added.\n- Some font weights and gray colors updated for better readability.",
+ "3.8.6-453": "- Show page header updated.\n- Topic colors added.\n- Page section headers can open an other content page.\n- Shared URLs for Swiss musical radios updated.",
+ "3.8.6-454": "- AppStore release."
}
\ No newline at end of file
diff --git a/WhatsNew-tvOS-beta.json b/WhatsNew-tvOS-beta.json
index 3b5dadd42..86483b907 100755
--- a/WhatsNew-tvOS-beta.json
+++ b/WhatsNew-tvOS-beta.json
@@ -93,5 +93,8 @@
"1.8.4-448": "- AppStore release.",
"1.8.5-449": "- Maintenance build.\n- Image service maintnance. [RTS]",
"1.8.5-450": "- Move # section to end of AZ show list.",
- "1.8.5-451": "- AppStore release."
+ "1.8.5-451": "- AppStore release.",
+ "1.8.6-452": "Branch beta\n- Show page header updated.\n- Topic colors added.\n- Some font weights and gray colors updated for better readability.",
+ "1.8.6-453": "- Show page header updated.\n- Topic colors added.\n- Page section headers can open an other content page.",
+ "1.8.6-454": "- AppStore release."
}
\ No newline at end of file
diff --git a/Xcode/Shared/Common.xcconfig b/Xcode/Shared/Common.xcconfig
index 30f89a0e0..926e9f538 100755
--- a/Xcode/Shared/Common.xcconfig
+++ b/Xcode/Shared/Common.xcconfig
@@ -2,7 +2,7 @@ PRODUCT_BUNDLE_IDENTIFIER = $(BU__BUNDLE_IDENTIFIER_PREFIX)$(BU__BUNDLE_IDENTIFI
PRODUCT_NAME = $(BU__PRODUCT_NAME)$(TARGET__PRODUCT_NAME_SUFFIX)
// Version information
-CURRENT_PROJECT_VERSION = 451
+CURRENT_PROJECT_VERSION = 454
GCC_PREPROCESSOR_DEFINITIONS[config=Beta] = BETA=1
GCC_PREPROCESSOR_DEFINITIONS[config=Beta_AppCenter] = BETA=1 APPCENTER=1
diff --git a/Xcode/Shared/Targets/iOS/Common.xcconfig b/Xcode/Shared/Targets/iOS/Common.xcconfig
index 9a3835f55..c5f7b49ac 100755
--- a/Xcode/Shared/Targets/iOS/Common.xcconfig
+++ b/Xcode/Shared/Targets/iOS/Common.xcconfig
@@ -1,7 +1,7 @@
#include "Xcode/Shared/Common.xcconfig"
// Version information
-MARKETING_VERSION = 3.8.5
+MARKETING_VERSION = 3.8.6
SDKROOT = iphoneos
TARGETED_DEVICE_FAMILY=1,2
diff --git a/Xcode/Shared/Targets/tvOS/Common.xcconfig b/Xcode/Shared/Targets/tvOS/Common.xcconfig
index 76c3425ac..5dca976af 100755
--- a/Xcode/Shared/Targets/tvOS/Common.xcconfig
+++ b/Xcode/Shared/Targets/tvOS/Common.xcconfig
@@ -1,7 +1,7 @@
#include "Xcode/Shared/Common.xcconfig"
// Version information
-MARKETING_VERSION = 1.8.5
+MARKETING_VERSION = 1.8.6
SDKROOT = appletvos
TARGETED_DEVICE_FAMILY=3
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 1edd4558d..0ef826b25 100755
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -29,8 +29,8 @@ If you are not sure about the likelihood of a change you propose to be accepted,
Templates are available when you want to contribute:
-* [Issues](https://github.com/SRGSSR/playsrg-ios/issues/new): Please follow our issue template. You can omit information which does not make sense but, in general, the more details you can provide, the better. This ensures we can quickly reproduce the problem you are facing, increasing the likelihood we can fix it.
-* [Pull requests](https://github.com/SRGSSR/playsrg-ios/compare): Please follow our code conventions, test your code well, and write unit tests when this makes sense. We will review your work and, if successful, merge it back into the main development branch.
+* [Issues](https://github.com/SRGSSR/playsrg-apple/issues): Please follow our issue template. You can omit information which does not make sense but, in general, the more details you can provide, the better. This ensures we can quickly reproduce the problem you are facing, increasing the likelihood we can fix it.
+* [Pull requests](https://github.com/SRGSSR/playsrg-apple/pulls): Please follow our code conventions, test your code well, and write unit tests when this makes sense. We will review your work and, if successful, merge it back into the main development branch.
## Code conventions
@@ -38,7 +38,7 @@ Templates are available when you want to contribute:
Quality checks can be run using:
-```shell
+```
make check-quality
```
@@ -46,22 +46,6 @@ This ensures that Swift files, and scripts conform to common best practices.
Objective-C files currently have no formal code conventions, but we try to keep our codebase consistent. In general, having a look at the code itself should be enough for you to discover how you should write your changes.
-### Git hooks installation
-
-Project git hooks can be installed to help quality checks by running the following command:
-
-```shell
-make git-hook-install
-```
-
-### Git hooks uninstallation
-
-Project git hooks can be uninstalled by running the following command:
-
-```shell
-make git-hook-uninstall
-```
-
## Code review
Pull requests, once complete, can be submitted for review by our team. Depending on the complexity of the involved changes, a few iterations might be needed. Once a pull request has been approved, it will be squashed and merged back into the development trunk and delivered with the next release.
\ No newline at end of file
diff --git a/docs/CUSTOM_URLS_AND_UNIVERSAL_LINKS.md b/docs/CUSTOM_URLS_AND_UNIVERSAL_LINKS.md
index e12945213..5ef094975 100755
--- a/docs/CUSTOM_URLS_AND_UNIVERSAL_LINKS.md
+++ b/docs/CUSTOM_URLS_AND_UNIVERSAL_LINKS.md
@@ -52,31 +52,46 @@ Refer to the _Testing_ section for more information about how custom URLs can be
## Universal Links
-The Play iOS application supports Apple universal links, provided that the associated business unit website declares a corresponding [association file](https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html). If this is the case you can open most of URLs of a Play business unit portal in the associated Play application.
+#### iOS application
-For test purposes, and since this feature requires support from the portal which is not always available (e.g. for internal builds or business units which have not deployed an association file), there is a way to have universal link URLs for `Debug` configuration builds using the [Play MMF Deeplink](https://play-mmf.herokuapp.com/deeplink/index.html) tool to get an associated `https://play-mmf.herokuapp.com/[BU]/[…]` URL.
+The Play iOS application supports Apple universal links, provided that the associated business unit website declares a corresponding [association file](https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html). If this is the case, you can open most of URLs of a Play business unit portal in the associated Play iOS application.
-For example, if you want to open [https://www.rts.ch/play/tv/emissions?index=l](https://www.rts.ch/play/tv/emissions?index=l) with the Play RTS debug app, simply decode this URL with the [Play MMF Deeplink](https://play-mmf.herokuapp.com/deeplink/index.html) tool and get the Play MMF associated URL: [https://play-mmf.herokuapp.com/rts/play/tv/emissions?index=l](https://play-mmf.herokuapp.com/rts/play/tv/emissions?index=l).
+For test purposes, `Debug`, `Nightly` and `Beta` builds are associated to [play-web-staging web portal](https://play-web-staging.herokuapp.com/srf/play/tv). The first path component is the business unit. The [apple-app-site-association](https://play-web-staging.herokuapp.com/.well-known/apple-app-site-association) sorted arrays determine which build to open if more that one build for a BU are installed.
-The Play tvOS application does not support Apple universal links.
+For example, if you want to open [https://www.rts.ch/play/tv/emissions?index=l](https://www.rts.ch/play/tv/emissions?index=l) with the Play RTS debug app, switch the BU domain to `play-web-staging.herokuapp.com/[BU]`: [https://play-web-staging.herokuapp.com/rts/play/tv/emissions?index=l](https://play-web-staging.herokuapp.com/rts/play/tv/emissions?index=l).
Refer to the _Testing_ section for more information about how universal URLs can be supplied to an application during tests.
+#### tvOS application
+
+The Play tvOS application does not support Apple universal links.
+
## Testing
To test custom or universal links, you can either:
-- Use Safari (mobile or simulator) and copy / paste the URL in the address bar.
-- Start the app in the simulator and send the URL to it from the command line with `xcrun simctl openurl booted `.
+- Use Safari (mobile or simulator):
+ - Copy / paste the `url` in the address bar.
+ - Load the page.
+ - For universal links, scroll to the top and see a banner to open the app.
+- Use a text application, like Notes or Messages:
+ - Copy / paste the `url` in the application.
+ - Tap on the link to open it.
+- Use the Simulator and the command line:
+ - Open a simulator.
+ - Run `xcrun simctl openurl booted `.
+
+The `Debug` builds can be associated to Play MMF portal if needed, by changing `BU__DOMAIN[config=Debug]` configuration value. [See MMF documentation](https://github.com/sRGSSR/playsrg-mmf?tab=readme-ov-file#associated-domains).
-## URL generation
+## Custom URL generation
-The [Play MMF Deeplink](https://play-mmf.herokuapp.com/deeplink/index.html) tool is available for QR code generation of custom URLs with supported custom schemes. It can also generate universal links for the `Debug` configuration builds (associated with `https://play-mmf.herokuapp.com/[BU]/[…]` URLs).
+The [Play MMF Deeplink tool](https://play-mmf.herokuapp.com/deeplink/index.html) is available for QR code generation of custom URLs with application supported custom schemes.
## Changelog
#### iOS application
+- 3.8.4 version: Non-production builds are connected to Play web staging domain.
- 3.8.3 version: New micropage action. Share supported hostnames to the JS script.
- 3.6.8 version: New livestreams page action.
- 3.2.0 version: New section page action and module page action removal (modules not available on the web portal and in applications anymore).
diff --git a/docs/GITHUB_ENVIRONMENTS_AND_DEPLOYMENTS.md b/docs/GITHUB_ENVIRONMENTS_AND_DEPLOYMENTS.md
index 1f0861b6f..618add0be 100644
--- a/docs/GITHUB_ENVIRONMENTS_AND_DEPLOYMENTS.md
+++ b/docs/GITHUB_ENVIRONMENTS_AND_DEPLOYMENTS.md
@@ -85,5 +85,5 @@ If the fastlane execution finished with an error, or killed with an exit signal,
### Inactive state
- [By default](https://docs.github.com/en/rest/deployments/deployments?apiVersion=2022-11-28#inactive-deployments), the non-transient, non-production environment deployments created by fastlane scripts have `auto_inactive` = `true`. So that a new `success` deployment sets all previous `success` deployments to `inactive` state. It's also activated to production environment deployments because the App Store distribution only allows the latest version of the application.
-- When closing a PR, a [Github action](https://github.com/SRGSSR/playsrg-apple/actions) (pr-closure.yml) is updating state to `inactive` to lastest `success` deployment for nighty branch environnements.
+- When closing a PR, a [Github action](https://github.com/SRGSSR/playsrg-apple/actions) (pr-closure.yml) is updating state to `inactive` to lastest `success` deployment for nighty branch environnements and beta branch environnements.
diff --git a/docs/README.md b/docs/README.md
index d914b8ba5..bf21d289b 100755
--- a/docs/README.md
+++ b/docs/README.md
@@ -35,10 +35,6 @@ Depending on the business unit some functionalities might not be available (e.g.
The project runs on iOS 14.1, tvOS 14 and above and must be opened with the latest Xcode version.
-## Contributing
-
-If you want to contribute to the project, have a look at our [contributing guide](CONTRIBUTING.md).
-
## Required tools
- Building the project requires command-line tools for icon generation, easily installed with [Homebrew](https://brew.sh/):
@@ -53,6 +49,12 @@ If you want to contribute to the project, have a look at our [contributing guide
which pod
pod --version
```
+
+ If not, install it:
+
+ ```
+ brew install cocoapods
+ ```
## Building the project
@@ -89,6 +91,52 @@ The project can be built without private settings but some features might not be
Simply open the project with Xcode and wait until all dependencies have been retrieved. Then build and run the project.
+## Contributing
+
+If you want to contribute to the project as an external contributor, have a look at our [contributing guide](CONTRIBUTING.md).
+
+### Quality checks
+
+Checking quality, the project requires command-line tools:
+
+```
+brew install swiftlint shellcheck yamllint
+```
+
+For `rubocop`, be sure that this tool is available on your system, or execute:
+
+```
+bundle install --path vendor/bundle
+```
+
+When all command-line tools are installed, check code quality can be done using:
+
+```
+make check-quality
+```
+
+This ensures that Swift files, and scripts are conform to common best practices.
+
+### Licenses for libraries used in the project
+
+In the iOS application settings, licenses of libraries used in the project can be consulted. To build the list, running an iOS target requires [LicensePlist](https://github.com/mono0926/LicensePlist).
+
+```
+brew install licenseplist
+```
+
+### SRGSSR project
+
+Some links to [internal Jira SRGSSR instance](https://srgssr-ch.atlassian.net) can be found in commit messages, branch names or pull request titles.
+
+### Git hooks installation
+
+Project git hooks can be installed to help quality checks and commit messages for internal SRGSSR project. Install them by running the following command:
+
+```
+make git-hook-install
+```
+
## Translations
Translation tool is [crowdin.com](https://crowdin.com/project/play-srg). The following scripts need [Crowdin CLI](https://crowdin.github.io/crowdin-cli/).
diff --git a/docs/REMOTE_CONFIGURATION.md b/docs/REMOTE_CONFIGURATION.md
index 94c70ab9b..2091cc81e 100755
--- a/docs/REMOTE_CONFIGURATION.md
+++ b/docs/REMOTE_CONFIGURATION.md
@@ -67,11 +67,19 @@ The keys common to both TV and radio channels JSON dictionaries are:
* `songsViewStyle` (optional, string): The songs view style when added to the view. Never displayed if not set. Available values are:
* `collapsed`: Collapsed when added to the view.
* `expanded`: Expanded when added to the view.
+* `shareURL` (optional, string): The URL used to share the channel website.
The radio channel JSON dictionaries have one more key:
* `homepageHidden` (optional, boolean): Set to `true` iff a homepage does not have to be displayed for the radio channel. If omitted, `false`.
+## Topics
+
+* `topicColors` (optional, JSON): A JSON dictionary describing all topic colors. Key (string) is the topic `urn` (the topic unique identifier), value (a JSON dictionary) has two properties:
+ * `firstColor` (mandatory, string): The first topic gradient primary hex color. Used in background in topic and show pages.
+ * `secondColor` (mandatory, string): The second topic gradient primary hex color. Used in background in topic and show pages.
+ * `reduceBrightness` (optional, boolean): Set to `true` if the brightness of the colors should be reduced for contrast purpose. If omitted, `false`.
+
## Shows
* `predefinedShowPagePreferred` (optional, boolean): Set to `true` iff show pages need to be displayed with the predefined layout (ie: only one predefined section with available episodes). If omitted, `false`.
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 02237ae88..c835ffa5d 100755
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -1101,7 +1101,7 @@ platform :ios do
if cleaned_lane_condition(lane)
clean_build_artifacts
ENV.delete('DERIVED_DATA_CLEANED')
- reset_git_repo(skip_clean: true, force: true)
+ reset_git_repo(force: true)
end
end
end
diff --git a/hooks/commit-msg b/hooks/commit-msg
new file mode 100755
index 000000000..9ace145e4
--- /dev/null
+++ b/hooks/commit-msg
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+#================================================================
+# Append Jira issue key into commit message
+#================================================================
+
+# Inspired from: https://community.atlassian.com/t5/Bitbucket-questions/automatically-append-JIRA-issue-ID-into-commit-message/qaq-p/605991
+
+BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
+
+# Search jira issue key from the branch name in a pattern such a "feature/ABC-123-description"
+JIRA_ISSUE_KEY=$(echo "$BRANCH_NAME" | sed -nr 's,[a-z]+/([A-Z0-9]+-[0-9]+)-.+,\1,p')
+
+# A developer may have already prepended the commit message with the branch jira issue key
+JIRA_ISSUE_KEY_IN_COMMIT=$(grep -c "$JIRA_ISSUE_KEY" "$1")
+
+# Only amend commit message if jira issue key was found and not already in commit message
+if [ -n "$JIRA_ISSUE_KEY" ] && ! [ "$JIRA_ISSUE_KEY_IN_COMMIT" -ge 1 ]; then
+ echo "📝 Prepending jira issue $JIRA_ISSUE_KEY to commit message"
+ sed -i.bak -e "1s/^/$JIRA_ISSUE_KEY /" "$1"
+fi
\ No newline at end of file