diff --git a/.rubocop.yml b/.rubocop.yml index a7006ba3eb9..9b86e9ad67d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,5 @@ +inherit_from: .rubocop_todo.yml + AllCops: Exclude: - vendor/**/* @@ -7,15 +9,24 @@ Layout/LineLength: Max: 180 Exclude: - fastlane/Fastfile + - scripts/themes/download_themes.rb - scripts/themes/generate_themes.rb + - scripts/themes/generate_kotlin.rb Metrics/BlockLength: Exclude: - fastlane/Fastfile + - scripts/themes/download_themes.rb - scripts/themes/generate_themes.rb + - scripts/themes/generate_kotlin.rb Metrics/MethodLength: Max: 30 Exclude: - fastlane/Fastfile + - scripts/themes/download_themes.rb - scripts/themes/generate_themes.rb + - scripts/themes/generate_kotlin.rb + +Style/HashSyntax: + EnforcedShorthandSyntax: never diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 00000000000..ea8266bef1f --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,37 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2023-09-20 10:40:52 UTC using RuboCop version 1.56.3. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 3 +# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. +Metrics/AbcSize: + Max: 75 + +# Offense count: 1 +# Configuration parameters: CountBlocks. +Metrics/BlockNesting: + Max: 4 + +# Offense count: 2 +# Configuration parameters: AllowedMethods, AllowedPatterns. +Metrics/CyclomaticComplexity: + Max: 26 + +# Offense count: 1 +# Configuration parameters: AllowedMethods, AllowedPatterns. +Metrics/PerceivedComplexity: + Max: 33 + +# Offense count: 3 +# Configuration parameters: AllowedConstants. +Style/Documentation: + Exclude: + - 'spec/**/*' + - 'test/**/*' + - 'scripts/themes/download_themes.rb' + - 'scripts/themes/generate_kotlin.rb' + - 'scripts/themes/generate_kotlin_compose.rb' diff --git a/Gemfile.lock b/Gemfile.lock index 9b1e257fadd..29bd600d248 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -33,7 +33,7 @@ GEM atomos (0.1.3) aws-eventstream (1.2.0) aws-partitions (1.843.0) - aws-sdk-core (3.185.1) + aws-sdk-core (3.185.2) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 460c1463bcf..c1dc0d3589f 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,9 +1,9 @@ +# frozen_string_literal: true + default_platform(:android) fastlane_require 'dotenv' -unless FastlaneCore::Helper.bundler? - UI.user_error!('Please run fastlane via `bundle exec`') -end +UI.user_error!('Please run fastlane via `bundle exec`') unless FastlaneCore::Helper.bundler? ######################################################################## # Constants @@ -16,12 +16,12 @@ GOOGLE_FIREBASE_SECRETS_PATH = File.join(PROJECT_ROOT_FOLDER, '.configure-files' ORIGINALS_METADATA_DIR_PATH = File.join(PROJECT_ROOT_FOLDER, 'metadata') RELEASE_NOTES_SOURCE_PATH = File.join(PROJECT_ROOT_FOLDER, 'CHANGELOG.md') EXTRACTED_RELEASE_NOTES_PATH = File.join(ORIGINALS_METADATA_DIR_PATH, 'release_notes.txt') -PLAY_STORE_TRACK_AUTOMOTIVE_BETA = "automotive:beta" -PLAY_STORE_TRACK_AUTOMOTIVE_PRODUCTION = "automotive:production" -PLAY_STORE_TRACK_WEAR_BETA = "wear:beta" -PLAY_STORE_TRACK_WEAR_PRODUCTION = "wear:production" -PLAY_STORE_TRACK_BETA = "beta" -PLAY_STORE_TRACK_PRODUCTION = "production" +PLAY_STORE_TRACK_AUTOMOTIVE_BETA = 'automotive:beta' +PLAY_STORE_TRACK_AUTOMOTIVE_PRODUCTION = 'automotive:production' +PLAY_STORE_TRACK_WEAR_BETA = 'wear:beta' +PLAY_STORE_TRACK_WEAR_PRODUCTION = 'wear:production' +PLAY_STORE_TRACK_BETA = 'beta' +PLAY_STORE_TRACK_PRODUCTION = 'production' GLOTPRESS_APP_STRINGS_PROJECT_URL = 'https://translate.wordpress.com/projects/pocket-casts/android/' VERSION_PROPERTIES_PATH = File.join(PROJECT_ROOT_FOLDER, 'version.properties') @@ -33,23 +33,23 @@ BUILD_CODE_FORMATTER = Fastlane::Wpmreleasetoolkit::Versioning::SimpleBuildCodeF VERSION_FILE = Fastlane::Wpmreleasetoolkit::Versioning::AndroidVersionFile.new(version_properties_path: VERSION_PROPERTIES_PATH) SUPPORTED_LOCALES = [ - { glotpress: "ar", android: "ar", google_play: "ar", promo_config: {}}, - { glotpress: "de", android: "de", google_play: "de-DE", promo_config: {} }, - { glotpress: "es", android: "es", google_play: "es-ES", promo_config: {} }, - { glotpress: "es", android: "es-rMX", google_play: "es-MX", promo_config: {} }, - { glotpress: "en-gb", android: "en-rGB", google_play: "en-GB", promo_config: {} }, - { glotpress: "fr", android: "fr", google_play: "fr-FR", promo_config: {} }, - { glotpress: "fr", android: "fr-rCA", google_play: "fr-CA", promo_config: {} }, - { glotpress: "it", android: "it", google_play: "it-IT", promo_config: {} }, - { glotpress: "ja", android: "ja", google_play: "ja-JP", promo_config: {} }, - { glotpress: "ko", android: "ko", google_play: "ko-KR", promo_config: {} }, - { glotpress: "nl", android: "nl", google_play: "nl-NL", promo_config: {} }, - { glotpress: "nb", android: "nb", google_play: "nb-NB", promo_config: {} }, - { glotpress: "pt-br", android: "pt-rBR", google_play: "pt-BR", promo_config: {} }, - { glotpress: "ru", android: "ru", google_play: "ru-RU", promo_config: {} }, - { glotpress: "sv", android: "sv", google_play: "sv-SE", promo_config: {} }, - { glotpress: "zh-cn", android: "zh", google_play: "zh-CN", promo_config: {} }, - { glotpress: "zh-tw", android: "zh-rTW", google_play: "zh-TW", promo_config: {} }, + { glotpress: 'ar', android: 'ar', google_play: 'ar', promo_config: {} }, + { glotpress: 'de', android: 'de', google_play: 'de-DE', promo_config: {} }, + { glotpress: 'es', android: 'es', google_play: 'es-ES', promo_config: {} }, + { glotpress: 'es', android: 'es-rMX', google_play: 'es-MX', promo_config: {} }, + { glotpress: 'en-gb', android: 'en-rGB', google_play: 'en-GB', promo_config: {} }, + { glotpress: 'fr', android: 'fr', google_play: 'fr-FR', promo_config: {} }, + { glotpress: 'fr', android: 'fr-rCA', google_play: 'fr-CA', promo_config: {} }, + { glotpress: 'it', android: 'it', google_play: 'it-IT', promo_config: {} }, + { glotpress: 'ja', android: 'ja', google_play: 'ja-JP', promo_config: {} }, + { glotpress: 'ko', android: 'ko', google_play: 'ko-KR', promo_config: {} }, + { glotpress: 'nl', android: 'nl', google_play: 'nl-NL', promo_config: {} }, + { glotpress: 'nb', android: 'nb', google_play: 'nb-NB', promo_config: {} }, + { glotpress: 'pt-br', android: 'pt-rBR', google_play: 'pt-BR', promo_config: {} }, + { glotpress: 'ru', android: 'ru', google_play: 'ru-RU', promo_config: {} }, + { glotpress: 'sv', android: 'sv', google_play: 'sv-SE', promo_config: {} }, + { glotpress: 'zh-cn', android: 'zh', google_play: 'zh-CN', promo_config: {} }, + { glotpress: 'zh-tw', android: 'zh-rTW', google_play: 'zh-TW', promo_config: {} } ].freeze ######################################################################## @@ -57,21 +57,20 @@ SUPPORTED_LOCALES = [ ######################################################################## Dotenv.load(USER_ENV_FILE_PATH) DEFAULT_BRANCH = 'main' -ENV['SUPPLY_UPLOAD_MAX_RETRIES']='5' -GH_REPOSITORY = "automattic/pocket-casts-android" -APPS_APP = "app" -APPS_AUTOMOTIVE = "automotive" -APPS_WEAR = "wear" -APPS = [APPS_APP, APPS_AUTOMOTIVE, APPS_WEAR] +ENV['SUPPLY_UPLOAD_MAX_RETRIES'] = '5' +GH_REPOSITORY = 'automattic/pocket-casts-android' +APPS_APP = 'app' +APPS_AUTOMOTIVE = 'automotive' +APPS_WEAR = 'wear' +APPS = [APPS_APP, APPS_AUTOMOTIVE, APPS_WEAR].freeze UPLOAD_TO_PLAY_STORE_JSON_KEY = File.join(PROJECT_ROOT_FOLDER, 'google-upload-credentials.json') -before_all do |lane| +before_all do |_lane| # Ensure we use the latest version of the toolkit check_for_toolkit_updates unless is_ci || ENV['FASTLANE_SKIP_TOOLKIT_UPDATE_CHECK'] # Check that the env files exist - # rubocop:disable Style/IfUnlessModifier unless is_ci || File.file?(USER_ENV_FILE_PATH) example_path = File.join(PROJECT_ROOT_FOLDER, 'fastlane/env/user.env-example') UI.user_error! "#{ENV_FILE_NAME} not found: Please copy '#{example_path}' to '#{USER_ENV_FILE_PATH}' and fill in the values." @@ -79,7 +78,7 @@ before_all do |lane| end platform :android do - lane :code_freeze do | options | + lane :code_freeze do |options| ensure_git_status_clean Fastlane::Helper::GitHelper.checkout_and_pull(DEFAULT_BRANCH) ensure_git_branch(branch: DEFAULT_BRANCH) @@ -127,11 +126,11 @@ platform :android do release_notes_file_path: RELEASE_NOTES_SOURCE_PATH ) push_to_git_remote(tags: false) - setbranchprotection(repository:GH_REPOSITORY, branch: "release/#{new_version}") - setfrozentag(repository:GH_REPOSITORY, milestone: new_version) + setbranchprotection(repository: GH_REPOSITORY, branch: "release/#{new_version}") + setfrozentag(repository: GH_REPOSITORY, milestone: new_version) end - lane :complete_code_freeze do | options | + lane :complete_code_freeze do |options| ensure_git_branch(branch: '^release/') # Match branch names that begin with `release/` ensure_git_status_clean @@ -139,14 +138,12 @@ platform :android do UI.important("Completing code freeze for: #{new_version}") - unless options[:skip_confirm] - UI.user_error!('Aborted by user request') unless UI.confirm('Do you want to continue?') - end + UI.user_error!('Aborted by user request') if !options[:skip_confirm] && !UI.confirm('Do you want to continue?') trigger_release_build(branch_to_build: "release/#{new_version}") end - lane :new_beta_release do | options | + lane :new_beta_release do |options| ensure_git_status_clean Fastlane::Helper::GitHelper.checkout_and_pull(DEFAULT_BRANCH) @@ -171,9 +168,7 @@ platform :android do UI.important(message) - unless options[:skip_confirm] - UI.user_error!('Aborted by user request') unless UI.confirm('Do you want to continue?') - end + UI.user_error!('Aborted by user request') if !options[:skip_confirm] && !UI.confirm('Do you want to continue?') # Bump the release version and build code UI.message 'Bumping beta version and build code...' @@ -225,9 +220,7 @@ platform :android do UI.important(message) - unless options[:skip_confirm] - UI.user_error!('Aborted by user request') unless UI.confirm('Do you want to continue?') - end + UI.user_error!('Aborted by user request') if !options[:skip_confirm] && !UI.confirm('Do you want to continue?') # Check tags UI.user_error!("The version `#{new_version}` tag already exists!") if git_tag_exists(tag: new_version) @@ -255,15 +248,13 @@ platform :android do UI.important("Triggering hotfix build for version: #{current_release_version}") - unless options[:skip_confirm] - UI.user_error!('Aborted by user request') unless UI.confirm('Do you want to continue?') - end + UI.user_error!('Aborted by user request') if !options[:skip_confirm] && !UI.confirm('Do you want to continue?') trigger_release_build(branch_to_build: "release/#{current_release_version}") end # @param [String] branch_to_build (default: current git branch) The branch to build - lane :trigger_release_build do | options | + lane :trigger_release_build do |options| buildkite_trigger_build( buildkite_organization: 'automattic', buildkite_pipeline: 'pocket-casts-android', @@ -281,20 +272,18 @@ platform :android do # @option [Boolean] skip_prechecks (default: false) If true, skips android_build_preflight # @option [Boolean] create_gh_release (default: false) If true, creates a draft GitHub release # - desc "Builds and uploads a new build to Google Play (without releasing it)" - lane :build_and_upload_to_play_store do | options | + desc 'Builds and uploads a new build to Google Play (without releasing it)' + lane :build_and_upload_to_play_store do |options| version = current_version_name build_code = current_build_code - is_beta = is_beta_version(version) - unless (options[:skip_prechecks]) + is_beta = beta_version?(version) + unless options[:skip_prechecks] # Match branch names that begin with `release/` ensure_git_branch(branch: '^release/') unless is_ci UI.important("Building version #{current_version_name} (#{current_build_code}) for upload to Google Play Console") - unless options[:skip_confirm] - UI.user_error!('Aborted by user request') unless UI.confirm('Do you want to continue?') - end + UI.user_error!('Aborted by user request') if !options[:skip_confirm] && !UI.confirm('Do you want to continue?') # Check local repo status ensure_git_status_clean unless is_ci @@ -305,50 +294,46 @@ platform :android do release_assets = [] APPS.each do |app| - build_bundle(app: app, version: version, build_code: build_code) - - aab_artifact_path = aab_artifact_path(app, version) - UI.error("Unable to find a build artifact at #{aab_artifact_path}") unless File.exist? aab_artifact_path - - track = if (app == APPS_AUTOMOTIVE) - is_beta ? PLAY_STORE_TRACK_AUTOMOTIVE_BETA : PLAY_STORE_TRACK_AUTOMOTIVE_PRODUCTION - elsif (app == APPS_WEAR) - is_beta ? PLAY_STORE_TRACK_WEAR_BETA : PLAY_STORE_TRACK_WEAR_PRODUCTION - else - is_beta ? PLAY_STORE_TRACK_BETA : PLAY_STORE_TRACK_PRODUCTION - end - - upload_to_play_store( - package_name: APP_PACKAGE_NAME, - aab: aab_artifact_path, - track: track, - release_status: 'draft', - skip_upload_apk: true, - skip_upload_metadata: true, - skip_upload_changelogs: true, - skip_upload_images: true, - skip_upload_screenshots: true, - json_key: UPLOAD_TO_PLAY_STORE_JSON_KEY - ) - - release_assets << aab_artifact_path + build_bundle(app: app, version: version, build_code: build_code) + + aab_artifact_path = aab_artifact_path(app, version) + UI.error("Unable to find a build artifact at #{aab_artifact_path}") unless File.exist? aab_artifact_path + + track = if app == APPS_AUTOMOTIVE + is_beta ? PLAY_STORE_TRACK_AUTOMOTIVE_BETA : PLAY_STORE_TRACK_AUTOMOTIVE_PRODUCTION + elsif app == APPS_WEAR + is_beta ? PLAY_STORE_TRACK_WEAR_BETA : PLAY_STORE_TRACK_WEAR_PRODUCTION + else + is_beta ? PLAY_STORE_TRACK_BETA : PLAY_STORE_TRACK_PRODUCTION + end + + upload_to_play_store( + package_name: APP_PACKAGE_NAME, + aab: aab_artifact_path, + track: track, + release_status: 'draft', + skip_upload_apk: true, + skip_upload_metadata: true, + skip_upload_changelogs: true, + skip_upload_images: true, + skip_upload_screenshots: true, + json_key: UPLOAD_TO_PLAY_STORE_JSON_KEY + ) + + release_assets << aab_artifact_path end create_gh_release(version: version, prerelease: is_beta, release_assets: release_assets.compact) if options[:create_gh_release] end - lane :finalize_release do | options | - if android_current_branch_is_hotfix(version_properties_path: VERSION_PROPERTIES_PATH) - UI.user_error!('Please use `finalize_hotfix_release` lane for hotfixes') - end + lane :finalize_release do |options| + UI.user_error!('Please use `finalize_hotfix_release` lane for hotfixes') if android_current_branch_is_hotfix(version_properties_path: VERSION_PROPERTIES_PATH) ensure_git_branch(branch: '^release/') # Match branch names that begin with `release/` ensure_git_status_clean UI.important("Finalizing release: #{current_release_version}") - unless options[:skip_confirm] - UI.user_error!('Aborted by user request') unless UI.confirm('Do you want to continue?') - end + UI.user_error!('Aborted by user request') if !options[:skip_confirm] && !UI.confirm('Do you want to continue?') configure_apply(force: is_ci) @@ -372,10 +357,10 @@ platform :android do version = current_release_version # Wrap up - removebranchprotection(repository:GH_REPOSITORY, branch: "release/#{version}") - setfrozentag(repository:GH_REPOSITORY, milestone: version, freeze: false) - create_new_milestone(repository:GH_REPOSITORY, need_appstore_submission: true) - close_milestone(repository:GH_REPOSITORY, milestone: version) + removebranchprotection(repository: GH_REPOSITORY, branch: "release/#{version}") + setfrozentag(repository: GH_REPOSITORY, milestone: version, freeze: false) + create_new_milestone(repository: GH_REPOSITORY, need_appstore_submission: true) + close_milestone(repository: GH_REPOSITORY, milestone: version) push_to_git_remote(tags: false) trigger_release_build(branch_to_build: "release/#{version}") @@ -384,37 +369,37 @@ platform :android do # @param [String] version The version to create # @param [String] build_code The build code to create # @param [String] app The Android app to build (i.e 'app', 'automotive', or 'wear') - desc "Builds and bundles the given app" - lane :build_bundle do | options | + desc 'Builds and bundles the given app' + lane :build_bundle do |options| version = options[:version] build_code = options[:build_code] app = options[:app] aab_artifact_path = aab_artifact_path(app, version) - build_dir = "artifacts/" + build_dir = 'artifacts/' - gradle(task: "clean") - UI.message("Running lint...") + gradle(task: 'clean') + UI.message('Running lint...') gradle(task: ":#{app}:lintRelease") UI.message("Building #{version} / #{build_code} - #{aab_artifact_path}...") gradle( - task: ":#{app}:bundle", - build_type: "Release", - properties: { - "IS_AUTOMOTIVE_BUILD" => app == APPS_AUTOMOTIVE, - "IS_WEAR_BUILD" => app == APPS_WEAR - } + task: ":#{app}:bundle", + build_type: 'Release', + properties: { + 'IS_AUTOMOTIVE_BUILD' => app == APPS_AUTOMOTIVE, + 'IS_WEAR_BUILD' => app == APPS_WEAR + } ) - Dir.chdir("..") do + Dir.chdir('..') do sh("mkdir -p #{build_dir} && cp -v #{bundle_output_path(app)} #{aab_artifact_path}") UI.message("Bundle ready: #{aab_artifact_path}") end end # Run instrumented tests in Google Firebase Test Lab - desc "Build the application and instrumented tests, then run the tests in Firebase Test Lab" - lane :build_and_instrumented_test do | options | - gradle(tasks: ['assembleDebug', 'assembleDebugAndroidTest']) + desc 'Build the application and instrumented tests, then run the tests in Firebase Test Lab' + lane :build_and_instrumented_test do |_options| + gradle(tasks: %w[assembleDebug assembleDebugAndroidTest]) # Run the instrumented tests in Firebase Test Lab firebase_login( @@ -431,7 +416,7 @@ platform :android do test_apk_path: File.join(apk_dir, 'androidTest', 'debug', 'app-debug-androidTest.apk'), apk_path: File.join(apk_dir, 'debug', 'app-debug.apk'), results_output_dir: File.join(PROJECT_ROOT_FOLDER, 'build', 'instrumented-tests') - ) + ) end ##################################################################################### @@ -443,7 +428,7 @@ platform :android do # @param [Hash] version The version to create. Expects keys "name" and "code" # @param [Bool] prerelease If true, the GitHub Release will have the prerelease flag # - private_lane :create_gh_release do | options | + private_lane :create_gh_release do |options| version = options[:version] prerelease = options[:prerelease] || false release_assets = options[:release_assets] @@ -475,8 +460,8 @@ platform :android do key_file_secrets[name] end - def is_beta_version(version) - version.include? "-rc-" + def beta_version?(version) + version.include? '-rc-' end def override_default_release_branch(version) diff --git a/scripts/generate_themes_image.rb b/scripts/generate_themes_image.rb old mode 100644 new mode 100755 index fc5bebf3c61..8b2f852c017 --- a/scripts/generate_themes_image.rb +++ b/scripts/generate_themes_image.rb @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + require 'rubygems' require 'fileutils' @@ -6,31 +8,31 @@ screens_count = 17 def run(command) - puts command - `#{command}` + puts command + `#{command}` end -run("mkdir -p ~/Downloads/android_themes/") -run("rm -f ~/Downloads/android_themes/*.jpg") -run("rm -f ~/Downloads/android_themes/*.png") +run('mkdir -p ~/Downloads/android_themes/') +run('rm -f ~/Downloads/android_themes/*.jpg') +run('rm -f ~/Downloads/android_themes/*.png') # You may need to run 'adb root' to get access to these screenshots. -run("adb pull /data/user/0/au.com.shiftyjelly.pocketcasts.debug/cache/app_screenshots ~/Downloads/android_themes") -run("mv ~/Downloads/android_themes/app_screenshots/* ~/Downloads/android_themes/") +run('adb pull /data/user/0/au.com.shiftyjelly.pocketcasts.debug/cache/app_screenshots ~/Downloads/android_themes') +run('mv ~/Downloads/android_themes/app_screenshots/* ~/Downloads/android_themes/') image_count = 0 -Dir.chdir(File.expand_path("~/Downloads/android_themes/")) do - themes_count.times do |theme_index| - image_names = "" - screens_count.times do |screen_index| - image_count += 1 - image_names += "#{image_count.to_s.rjust(2, '0')}.jpg " - end - run("convert #{image_names}-background black -splice 10x10+0+0 -append -chop 10x10+0+0 out_#{theme_index+1}.png") +Dir.chdir(File.expand_path('~/Downloads/android_themes/')) do + themes_count.times do |theme_index| + image_names = '' + screens_count.times do |_screen_index| + image_count += 1 + image_names += "#{image_count.to_s.rjust(2, '0')}.jpg " end - run("convert #{Array.new(themes_count) { |e| "out_#{e+1}.png" }.join(" ")} -background white -splice 10x0+0+0 +append -chop 10x0+0+0 out.png") - run("magick convert out.png -resize 50% themes.png") + run("convert #{image_names}-background black -splice 10x10+0+0 -append -chop 10x10+0+0 out_#{theme_index + 1}.png") + end + run("convert #{Array.new(themes_count) { |e| "out_#{e + 1}.png" }.join(' ')} -background white -splice 10x0+0+0 +append -chop 10x0+0+0 out.png") + run('magick convert out.png -resize 50% themes.png') end puts 'open ~/Downloads/android_themes/themes.png' diff --git a/scripts/themes/Gemfile b/scripts/themes/Gemfile index 45761d58f8d..11f74164669 100644 --- a/scripts/themes/Gemfile +++ b/scripts/themes/Gemfile @@ -1,4 +1,5 @@ +# frozen_string_literal: true + source 'http://rubygems.org' gem 'google-api-client' - diff --git a/scripts/themes/Gemfile.lock b/scripts/themes/Gemfile.lock index 6063e7a1103..bec93ab6d9f 100644 --- a/scripts/themes/Gemfile.lock +++ b/scripts/themes/Gemfile.lock @@ -1,46 +1,85 @@ GEM remote: http://rubygems.org/ specs: - addressable (2.7.0) - public_suffix (>= 2.0.2, < 5.0) - declarative (0.0.10) - declarative-option (0.1.0) - faraday (0.15.4) - multipart-post (>= 1.2, < 3) - google-api-client (0.30.8) + activesupport (7.1.1) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + addressable (2.8.5) + public_suffix (>= 2.0.2, < 6.0) + base64 (0.2.0) + bigdecimal (3.1.4) + concurrent-ruby (1.2.2) + connection_pool (2.4.1) + declarative (0.0.20) + drb (2.2.0) + ruby2_keywords + faraday (2.7.11) + base64 + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.2) + gems (1.2.0) + google-api-client (0.53.0) + google-apis-core (~> 0.1) + google-apis-generator (~> 0.1) + google-apis-core (0.11.2) addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.5, < 0.10.0) - httpclient (>= 2.8.1, < 3.0) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) mini_mime (~> 1.0) representable (~> 3.0) - retriable (>= 2.0, < 4.0) - signet (~> 0.10) - googleauth (0.9.0) - faraday (~> 0.12) + retriable (>= 2.0, < 4.a) + rexml + webrick + google-apis-discovery_v1 (0.14.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-generator (0.12.0) + activesupport (>= 5.0) + gems (~> 1.2) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-discovery_v1 (~> 0.5) + thor (>= 0.20, < 2.a) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.7) + signet (>= 0.16, < 2.a) httpclient (2.8.3) - jwt (2.2.1) - memoist (0.16.0) - mini_mime (1.0.2) - multi_json (1.13.1) - multipart-post (2.1.1) - os (1.0.1) - public_suffix (4.0.1) - representable (3.0.4) + i18n (1.14.1) + concurrent-ruby (~> 1.0) + jwt (2.7.1) + mini_mime (1.1.5) + minitest (5.20.0) + multi_json (1.15.0) + mutex_m (0.2.0) + os (1.1.4) + public_suffix (5.0.3) + representable (3.2.0) declarative (< 0.1.0) - declarative-option (< 0.2.0) + trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - signet (0.11.0) - addressable (~> 2.3) - faraday (~> 0.9) + rexml (3.2.6) + ruby2_keywords (0.0.5) + signet (0.18.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) + thor (1.3.0) + trailblazer-option (0.1.2) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) uber (0.1.0) + webrick (1.8.1) PLATFORMS ruby @@ -49,4 +88,4 @@ DEPENDENCIES google-api-client BUNDLED WITH - 2.0.2 + 2.4.21 diff --git a/scripts/themes/download_themes.rb b/scripts/themes/download_themes.rb index b400380b0c8..c3b7f3de140 100644 --- a/scripts/themes/download_themes.rb +++ b/scripts/themes/download_themes.rb @@ -1,26 +1,29 @@ -require "google/apis/sheets_v4" -require "googleauth" -require "fileutils" +# frozen_string_literal: true -APPLICATION_NAME = "Google Sheets API Pocket Casts Themes".freeze -CREDENTIALS_PATH = "google_credentials.json".freeze +require 'google/apis/sheets_v4' +require 'googleauth' +require 'fileutils' + +APPLICATION_NAME = 'Google Sheets API Pocket Casts Themes' +CREDENTIALS_PATH = 'google_credentials.json' SCOPE = Google::Apis::SheetsV4::AUTH_SPREADSHEETS_READONLY class String def uncapitalize - self[0, 1].downcase + self[1..-1] + self[0, 1].downcase + self[1..] end end def response_to_tokens_map(response) tokens = [] - response.values.each do |row| + response.each_value do |row| key = row[0] - next if key.nil? || key.length == 0 + next if key.nil? || key.empty? + tokens << { - key: key.gsub("$", "").gsub("-", "_"), + key: key.gsub('$', '').gsub('-', '_'), token_name: key, - kotlin_name: key.gsub("$", "").split('-').collect(&:capitalize).join.uncapitalize, + kotlin_name: key.gsub('$', '').split('-').collect(&:capitalize).join.uncapitalize, themes: { light: { hex: row[2], @@ -70,13 +73,13 @@ def response_to_tokens_map(response) } end tokens.each do |token_attrs| - token_attrs[:user_input] = !token_attrs[:themes].select { |key,value| value[:hex] == "$podcast" || value[:hex] == "$filter" }.empty? + token_attrs[:user_input] = token_attrs[:themes].any? { |_key, value| value[:hex] == '$podcast' || value[:hex] == '$filter' } end - return tokens + tokens end -def download_themes() - if !File.exists?(CREDENTIALS_PATH) +def download_themes + unless File.exist?(CREDENTIALS_PATH) puts "Download Google Sheet credentials 'google_credentials.json' from: https://developers.google.com/sheets/api/quickstart/ruby" return nil end @@ -90,10 +93,10 @@ def download_themes() ) # https://docs.google.com/spreadsheets/d/1BZWwQo8ZhTt9jRz5eX6iJqt4r9o6ekWeYi_t8AlNDCM/edit - spreadsheet_id = "1BZWwQo8ZhTt9jRz5eX6iJqt4r9o6ekWeYi_t8AlNDCM" - range = "A3:AZ200" + spreadsheet_id = '1BZWwQo8ZhTt9jRz5eX6iJqt4r9o6ekWeYi_t8AlNDCM' + range = 'A3:AZ200' response = service.get_spreadsheet_values spreadsheet_id, range - puts "No data found." if response.values.empty? + puts 'No data found.' if response.values.empty? - return response_to_tokens_map(response) -end \ No newline at end of file + response_to_tokens_map(response) +end diff --git a/scripts/themes/generate_kotlin.rb b/scripts/themes/generate_kotlin.rb index c578c1efce2..a579bc6af4c 100644 --- a/scripts/themes/generate_kotlin.rb +++ b/scripts/themes/generate_kotlin.rb @@ -1,118 +1,118 @@ +# frozen_string_literal: true + # Generate android theme colours from exported CSV from Google Sheet # To use: ruby generate_themes2.rb themes.csv require 'csv' -require './download_themes.rb' +require './download_themes' -filePathColors = '../../modules/services/ui/src/main/java/au/com/shiftyjelly/pocketcasts/ui/theme/ThemeColor.kt' -filePathStyles = '../../modules/services/ui/src/main/java/au/com/shiftyjelly/pocketcasts/ui/theme/ThemeStyle.kt' +FILE_PATH_COLORS = '../../modules/services/ui/src/main/java/au/com/shiftyjelly/pocketcasts/ui/theme/ThemeColor.kt' +FILE_PATH_STYLES = '../../modules/services/ui/src/main/java/au/com/shiftyjelly/pocketcasts/ui/theme/ThemeStyle.kt' class String - def uncapitalize - self[0, 1].downcase + self[1..-1] + def uncapitalize + self[0, 1].downcase + self[1..] end end -def writeThemeValue(hex_val, opacity, tokenName, filePath, themeName) - if tokenName.start_with?("filterU") || tokenName.start_with?("filterI") || tokenName.start_with?("filterT") - str = "" - # deal with special filter overlay colours - if hex_val == "filter" || hex_val == "$filter" || hex_val == "#filter" - # the ones without any custom opacity are easy - if opacity == "100%" || opacity.nil? || opacity.empty? - str = " @ColorInt fun #{tokenName}#{themeName}(@ColorInt filterColor: Int): Int { +def write_theme_value(hex_val, opacity, token_name, file_path, theme_name) + if token_name.start_with?('filterU') || token_name.start_with?('filterI') || token_name.start_with?('filterT') + str = '' + # deal with special filter overlay colours + if ['filter', '$filter', '#filter'].include?(hex_val) + # the ones without any custom opacity are easy + if opacity == '100%' || opacity.nil? || opacity.empty? + str = " @ColorInt fun #{token_name}#{theme_name}(@ColorInt filterColor: Int): Int { return filterColor }\n\n" - else - # tokenize the filter colour to figure out what it should be - # example string: filter 15% on white - words = opacity.split - - actual_opacity = words[1].gsub("%", "") - if words[3] == "white" - originalColor = "Color.WHITE" - elsif words[3].start_with?("#") - originalColor = "Color.parseColor(\"#{words[3]}\")" - else - originalColor = "Color.BLACK" - end - - overlayColor = "ColorUtils.colorWithAlpha(filterColor, #{(actual_opacity.to_f / 100.0 * 255.0).round})" - - str = " @ColorInt fun #{tokenName}#{themeName}(@ColorInt filterColor: Int): Int { - return ColorUtils.calculateCombinedColor(#{originalColor}, #{overlayColor}) + else + # tokenize the filter colour to figure out what it should be + # example string: filter 15% on white + words = opacity.split + + actual_opacity = words[1].gsub('%', '') + original_color = if words[3] == 'white' + 'Color.WHITE' + elsif words[3].start_with?('#') + "Color.parseColor(\"#{words[3]}\")" + else + 'Color.BLACK' + end + + overlay_color = "ColorUtils.colorWithAlpha(filterColor, #{(actual_opacity.to_f / 100.0 * 255.0).round})" + + str = " @ColorInt fun #{token_name}#{theme_name}(@ColorInt filterColor: Int): Int { + return ColorUtils.calculateCombinedColor(#{original_color}, #{overlay_color}) }\n\n" - end - - else - str = "@ColorInt fun #{tokenName}#{themeName}(@ColorInt filterColor: Int): Int { return Color.parseColor(\"#{hex_val}\") }\n\n" - end - - File.write(filePath, str, mode: 'a') - return - elsif tokenName.start_with?("podcast") || tokenName.start_with?("playerBackground") || tokenName.start_with?("playerHighlight") - str = "" - # deal with special podcast overlay colours - if hex_val == "podcast" || hex_val == "$podcast" || hex_val == "#podcast" - # the ones without any custom opacity are easy - if opacity == "100%" || opacity.nil? || opacity.empty? - str = " @ColorInt fun #{tokenName}#{themeName}(@ColorInt podcastColor: Int): Int { + end + + else + str = "@ColorInt fun #{token_name}#{theme_name}(@ColorInt filterColor: Int): Int { return Color.parseColor(\"#{hex_val}\") }\n\n" + end + + File.write(file_path, str, mode: 'a') + return + elsif token_name.start_with?('podcast') || token_name.start_with?('playerBackground') || token_name.start_with?('playerHighlight') + str = '' + # deal with special podcast overlay colours + if ['podcast', '$podcast', '#podcast'].include?(hex_val) + # the ones without any custom opacity are easy + if opacity == '100%' || opacity.nil? || opacity.empty? + str = " @ColorInt fun #{token_name}#{theme_name}(@ColorInt podcastColor: Int): Int { return podcastColor }\n\n" - elsif opacity.split.size == 1 - opacity = opacity.gsub("%", "") - str = " @ColorInt fun #{tokenName}#{themeName}(@ColorInt podcastColor: Int): Int { + elsif opacity.split.size == 1 + opacity = opacity.gsub('%', '') + str = " @ColorInt fun #{token_name}#{theme_name}(@ColorInt podcastColor: Int): Int { return ColorUtils.colorWithAlpha(podcastColor, #{(opacity.to_f / 100.0 * 255.0).round}) }\n\n" - else - # tokenize the podcast colour to figure out what it should be - # example string: podcast 15% on #ffffff - words = opacity.split + else + # tokenize the podcast colour to figure out what it should be + # example string: podcast 15% on #ffffff + words = opacity.split - actual_opacity = words[1].gsub("%", "") - originalColor = "Color.parseColor(\"#{words[3]}\")" - overlayColor = "ColorUtils.colorWithAlpha(podcastColor, #{(actual_opacity.to_f / 100.0 * 255.0).round})" + actual_opacity = words[1].gsub('%', '') + original_color = "Color.parseColor(\"#{words[3]}\")" + overlay_color = "ColorUtils.colorWithAlpha(podcastColor, #{(actual_opacity.to_f / 100.0 * 255.0).round})" - str = " @ColorInt fun #{tokenName}#{themeName}(@ColorInt podcastColor: Int): Int { - return ColorUtils.calculateCombinedColor(#{originalColor}, #{overlayColor}) + str = " @ColorInt fun #{token_name}#{theme_name}(@ColorInt podcastColor: Int): Int { + return ColorUtils.calculateCombinedColor(#{original_color}, #{overlay_color}) }\n\n" - end - else - if opacity == "100%" || opacity.nil? || opacity.empty? - str = " @ColorInt fun #{tokenName}#{themeName}(@ColorInt podcastColor: Int): Int { return Color.parseColor(\"#{hex_val}\") }\n\n" - elsif opacity.split.size == 1 - opacity = opacity.gsub("%", "") - originalColor = "Color.parseColor(\"#{hex_val}\")" - str = " @ColorInt fun #{tokenName}#{themeName}(@ColorInt podcastColor: Int): Int { - return ColorUtils.colorWithAlpha(#{originalColor}, #{(opacity.to_f / 100.0 * 255.0).round}) + end + elsif opacity == '100%' || opacity.nil? || opacity.empty? + str = " @ColorInt fun #{token_name}#{theme_name}(@ColorInt podcastColor: Int): Int { return Color.parseColor(\"#{hex_val}\") }\n\n" + elsif opacity.split.size == 1 + opacity = opacity.gsub('%', '') + original_color = "Color.parseColor(\"#{hex_val}\")" + str = " @ColorInt fun #{token_name}#{theme_name}(@ColorInt podcastColor: Int): Int { + return ColorUtils.colorWithAlpha(#{original_color}, #{(opacity.to_f / 100.0 * 255.0).round}) }\n\n" - end - end - - File.write(filePath, str, mode: 'a') - return end - if !hex_val.start_with?("#") - puts "Invalid hex value found #{hex_val}, found in #{tokenName} ignoring" - return - end + File.write(file_path, str, mode: 'a') + return + end - variable_str = " val #{tokenName}#{themeName} = Color.parseColor(\"#{hex_val}\")" - if opacity == "100%" || opacity.nil? || opacity.empty? - File.write(filePath, "#{variable_str}\n", mode: 'a') - else - opacity = opacity.gsub("%", "") - File.write(filePath, "#{variable_str}.colorIntWithAlpha(#{(opacity.to_f / 100.0 * 255.0).round})\n", mode: 'a') - end + unless hex_val.start_with?('#') + puts "Invalid hex value found #{hex_val}, found in #{token_name} ignoring" + return + end + + variable_str = " val #{token_name}#{theme_name} = Color.parseColor(\"#{hex_val}\")" + if opacity == '100%' || opacity.nil? || opacity.empty? + File.write(file_path, "#{variable_str}\n", mode: 'a') + else + opacity = opacity.gsub('%', '') + File.write(file_path, "#{variable_str}.colorIntWithAlpha(#{(opacity.to_f / 100.0 * 255.0).round})\n", mode: 'a') + end end -tokens = download_themes() +tokens = download_themes exit if tokens.nil? -File.truncate(filePathColors, 0) if File.exist?(filePathColors) -File.truncate(filePathStyles, 0) if File.exist?(filePathStyles) +File.truncate(FILE_PATH_COLORS, 0) if File.exist?(FILE_PATH_COLORS) +File.truncate(FILE_PATH_STYLES, 0) if File.exist?(FILE_PATH_STYLES) -File.write(filePathColors, "// ************ WARNING AUTO GENERATED, DO NOT EDIT ************ +File.write(FILE_PATH_COLORS, "// ************ WARNING AUTO GENERATED, DO NOT EDIT ************ @file:Suppress(\"unused\", \"MemberVisibilityCanBePrivate\", \"UNUSED_PARAMETER\") package au.com.shiftyjelly.pocketcasts.ui.theme @@ -123,76 +123,74 @@ def writeThemeValue(hex_val, opacity, tokenName, filePath, themeName) import au.com.shiftyjelly.pocketcasts.ui.helper.colorIntWithAlpha object ThemeColor {\n", mode: 'a') -File.write(filePathStyles, "package au.com.shiftyjelly.pocketcasts.ui.theme +File.write(FILE_PATH_STYLES, "package au.com.shiftyjelly.pocketcasts.ui.theme // ************ WARNING AUTO GENERATED, DO NOT EDIT ************\nenum class ThemeStyle {\n", mode: 'a') -index = 0 all_token_names = [] tokens.each do |token_attrs| - token_name = token_attrs[:token_name] - - themes = token_attrs[:themes] + token_name = token_attrs[:token_name] - light_hex_value = themes[:light][:hex] - light_opacity = themes[:light][:opacity] + themes = token_attrs[:themes] - dark_hex_value = themes[:dark][:hex] - dark_opacity = themes[:dark][:opacity] + light_hex_value = themes[:light][:hex] + light_opacity = themes[:light][:opacity] - extra_dark_hex_value = themes[:extra_dark][:hex] - extra_dark_opacity = themes[:extra_dark][:opacity] + dark_hex_value = themes[:dark][:hex] + dark_opacity = themes[:dark][:opacity] - classic_light_hex_value = themes[:classic_light][:hex] - classic_light_opacity = themes[:classic_light][:opacity] + extra_dark_hex_value = themes[:extra_dark][:hex] + extra_dark_opacity = themes[:extra_dark][:opacity] - classic_dark_hex_value = themes[:classic_dark][:hex] - classic_dark_opacity = themes[:classic_dark][:opacity] + classic_light_hex_value = themes[:classic_light][:hex] + classic_light_opacity = themes[:classic_light][:opacity] - electric_hex_value = themes[:electricity][:hex] - electric_opacity = themes[:electricity][:opacity] + # unused + # classic_dark_hex_value = themes[:classic_dark][:hex] + # classic_dark_opacity = themes[:classic_dark][:opacity] - indigo_hex_value = themes[:indigo][:hex] - indigo_opacity = themes[:indigo][:opacity] + electric_hex_value = themes[:electricity][:hex] + electric_opacity = themes[:electricity][:opacity] - radioactive_hex_value = themes[:radioactive][:hex] - radioactive_opacity = themes[:radioactive][:opacity] + indigo_hex_value = themes[:indigo][:hex] + indigo_opacity = themes[:indigo][:opacity] - rose_hex_value = themes[:rose][:hex] - rose_opacity = themes[:rose][:opacity] + radioactive_hex_value = themes[:radioactive][:hex] + radioactive_opacity = themes[:radioactive][:opacity] - light_contrast_hex_value = themes[:light_contrast][:hex] - light_contrast_opacity = themes[:light_contrast][:opacity] + rose_hex_value = themes[:rose][:hex] + rose_opacity = themes[:rose][:opacity] - dark_contrast_hex_value = themes[:dark_contrast][:hex] - dark_contrast_opacity = themes[:dark_contrast][:opacity] + light_contrast_hex_value = themes[:light_contrast][:hex] + light_contrast_opacity = themes[:light_contrast][:opacity] - unless token_name == nil || token_name == "Token" || light_hex_value == nil || dark_hex_value == nil - token_name = token_name.gsub("$", "").split('-').collect(&:capitalize).join.uncapitalize - all_token_names << token_name + dark_contrast_hex_value = themes[:dark_contrast][:hex] + dark_contrast_opacity = themes[:dark_contrast][:opacity] - File.write(filePathStyles, " #{token_name},\n", mode: 'a') + next if token_name.nil? || token_name == 'Token' || light_hex_value.nil? || dark_hex_value.nil? - writeThemeValue(light_hex_value, light_opacity, token_name, filePathColors, "Light") - writeThemeValue(dark_hex_value, dark_opacity, token_name, filePathColors, "Dark") - writeThemeValue(extra_dark_hex_value, extra_dark_opacity, token_name, filePathColors, "ExtraDark") - writeThemeValue(classic_light_hex_value, classic_light_opacity, token_name, filePathColors, "ClassicLight") - writeThemeValue(electric_hex_value, electric_opacity, token_name, filePathColors, "Electric") - writeThemeValue(indigo_hex_value, indigo_opacity, token_name, filePathColors, "Indigo") - writeThemeValue(radioactive_hex_value, radioactive_opacity, token_name, filePathColors, "Radioactive") - writeThemeValue(rose_hex_value, rose_opacity, token_name, filePathColors, "Rose") - writeThemeValue(light_contrast_hex_value, light_contrast_opacity, token_name, filePathColors, "LightContrast") - writeThemeValue(dark_contrast_hex_value, dark_contrast_opacity, token_name, filePathColors, "DarkContrast") + token_name = token_name.gsub('$', '').split('-').collect(&:capitalize).join.uncapitalize + all_token_names << token_name - index += 1 - end + File.write(FILE_PATH_STYLES, " #{token_name},\n", mode: 'a') + + write_theme_value(light_hex_value, light_opacity, token_name, FILE_PATH_COLORS, 'Light') + write_theme_value(dark_hex_value, dark_opacity, token_name, FILE_PATH_COLORS, 'Dark') + write_theme_value(extra_dark_hex_value, extra_dark_opacity, token_name, FILE_PATH_COLORS, 'ExtraDark') + write_theme_value(classic_light_hex_value, classic_light_opacity, token_name, FILE_PATH_COLORS, 'ClassicLight') + write_theme_value(electric_hex_value, electric_opacity, token_name, FILE_PATH_COLORS, 'Electric') + write_theme_value(indigo_hex_value, indigo_opacity, token_name, FILE_PATH_COLORS, 'Indigo') + write_theme_value(radioactive_hex_value, radioactive_opacity, token_name, FILE_PATH_COLORS, 'Radioactive') + write_theme_value(rose_hex_value, rose_opacity, token_name, FILE_PATH_COLORS, 'Rose') + write_theme_value(light_contrast_hex_value, light_contrast_opacity, token_name, FILE_PATH_COLORS, 'LightContrast') + write_theme_value(dark_contrast_hex_value, dark_contrast_opacity, token_name, FILE_PATH_COLORS, 'DarkContrast') end -File.write(filePathColors, "\n", mode: 'a') +File.write(FILE_PATH_COLORS, "\n", mode: 'a') all_token_names.each_with_index do |token, index| - if token.start_with?("podcast") || token.start_with?("playerBackground") || token.start_with?("playerHighlight") - token_str = " @ColorInt fun #{token}(activeTheme: Theme.ThemeType, @ColorInt podcastColor: Int): Int { + token_str = if token.start_with?('podcast') || token.start_with?('playerBackground') || token.start_with?('playerHighlight') + " @ColorInt fun #{token}(activeTheme: Theme.ThemeType, @ColorInt podcastColor: Int): Int { return when (activeTheme) { Theme.ThemeType.LIGHT -> #{token}Light(podcastColor) @@ -216,9 +214,8 @@ def writeThemeValue(hex_val, opacity, tokenName, filePath, themeName) #{token}DarkContrast(podcastColor) } }\n" - File.write(filePathColors, token_str, mode: 'a') - elsif token.start_with?("filterU") || token.start_with?("filterI") || token.start_with?("filterT") - token_str = " @ColorInt fun #{token}(activeTheme: Theme.ThemeType, @ColorInt filterColor: Int): Int { + elsif token.start_with?('filterU') || token.start_with?('filterI') || token.start_with?('filterT') + " @ColorInt fun #{token}(activeTheme: Theme.ThemeType, @ColorInt filterColor: Int): Int { return when (activeTheme) { Theme.ThemeType.LIGHT -> #{token}Light(filterColor) @@ -242,9 +239,8 @@ def writeThemeValue(hex_val, opacity, tokenName, filePath, themeName) #{token}DarkContrast(filterColor) } }\n" - File.write(filePathColors, token_str, mode: 'a') - else - token_str = " @ColorInt fun #{token}(theme: Theme.ThemeType): Int { + else + " @ColorInt fun #{token}(theme: Theme.ThemeType): Int { return when (theme) { Theme.ThemeType.LIGHT -> #{token}Light @@ -268,15 +264,13 @@ def writeThemeValue(hex_val, opacity, tokenName, filePath, themeName) #{token}DarkContrast } }\n" - File.write(filePathColors, token_str, mode: 'a') - end + end + File.write(FILE_PATH_COLORS, token_str, mode: 'a') - if index != all_token_names.length - 1 - File.write(filePathColors, "\n", mode: 'a') - end + File.write(FILE_PATH_COLORS, "\n", mode: 'a') if index != all_token_names.length - 1 end -File.write(filePathColors, "}\n", mode: 'a') +File.write(FILE_PATH_COLORS, "}\n", mode: 'a') -File.truncate(filePathStyles, File.size(filePathStyles) - 2) #remove the trailing comma -File.write(filePathStyles, "\n}\n", mode: 'a') \ No newline at end of file +File.truncate(FILE_PATH_STYLES, File.size(FILE_PATH_STYLES) - 2) # remove the trailing comma +File.write(FILE_PATH_STYLES, "\n}\n", mode: 'a') diff --git a/scripts/themes/generate_kotlin_compose.rb b/scripts/themes/generate_kotlin_compose.rb index d2daa5d5c1c..1a652960dbb 100644 --- a/scripts/themes/generate_kotlin_compose.rb +++ b/scripts/themes/generate_kotlin_compose.rb @@ -1,66 +1,70 @@ +# frozen_string_literal: true + # Generate android theme colours from exported CSV from Google Sheet # To use: ruby generate_themes2.rb themes.csv require 'csv' -require './download_themes.rb' +require './download_themes' -filePath = '../../modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/Colors.kt' +COLORS_FILE_PATH = '../../modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/Colors.kt' class String - def uncapitalize - self[0, 1].downcase + self[1..-1] + def uncapitalize + self[0, 1].downcase + self[1..] end end def int_to_hex(value) - (2.55 * value.to_f()).round.to_s(16).upcase.rjust(2, '0') + (2.55 * value.to_f).round.to_s(16).upcase.rjust(2, '0') end -tokens = download_themes() +tokens = download_themes exit if tokens.nil? -themeToCodeLines = {} +theme_to_code_lines = {} tokens.each do |token_attrs| - key = token_attrs[:key] - next if key.start_with?("filter_ui_") || - key.start_with?("filter_interactive_") || - key.start_with?("filter_icon_") || - key.start_with?("filter_text_") || - key.start_with?("image_") || - key.start_with?("podcast_icon_") || - key.start_with?("podcast_ui_") || - key.start_with?("podcast_text_") || - key.start_with?("podcast_interactive_") || - key.start_with?("player_background_") || - key.start_with?("player_highlight_") || - key.start_with?("podcast_on") - - - kotlin_name = token_attrs[:kotlin_name] - - token_attrs[:themes].each do |name, attrs| - next if name.to_s == "classic_dark" - cleanThemeName = name.to_s.split('_').collect(&:capitalize).join - lines = themeToCodeLines[cleanThemeName] || [] - hex = attrs[:hex].gsub("#","") - next if hex.include?("$") - opacity = attrs[:opacity] - next if opacity.to_i == 0 - lines << "#{kotlin_name} = Color(0x#{int_to_hex(opacity)}#{hex})" - themeToCodeLines[cleanThemeName] = lines - end + key = token_attrs[:key] + next if key.start_with?('filter_ui_') || + key.start_with?('filter_interactive_') || + key.start_with?('filter_icon_') || + key.start_with?('filter_text_') || + key.start_with?('image_') || + key.start_with?('podcast_icon_') || + key.start_with?('podcast_ui_') || + key.start_with?('podcast_text_') || + key.start_with?('podcast_interactive_') || + key.start_with?('player_background_') || + key.start_with?('player_highlight_') || + key.start_with?('podcast_on') + + kotlin_name = token_attrs[:kotlin_name] + + token_attrs[:themes].each do |name, attrs| + next if name.to_s == 'classic_dark' + + clean_theme_name = name.to_s.split('_').collect(&:capitalize).join + lines = theme_to_code_lines[clean_theme_name] || [] + hex = attrs[:hex].gsub('#', '') + next if hex.include?('$') + + opacity = attrs[:opacity] + next if opacity.to_i.zero? + + lines << "#{kotlin_name} = Color(0x#{int_to_hex(opacity)}#{hex})" + theme_to_code_lines[clean_theme_name] = lines + end end -File.truncate(filePath, 0) if File.exist?(filePath) +File.truncate(COLORS_FILE_PATH, 0) if File.exist?(COLORS_FILE_PATH) -File.write(filePath, "// ************ WARNING AUTO GENERATED, DO NOT EDIT ************ +File.write(COLORS_FILE_PATH, "// ************ WARNING AUTO GENERATED, DO NOT EDIT ************ package au.com.shiftyjelly.pocketcasts.compose import androidx.compose.ui.graphics.Color ", mode: 'a') -themeToCodeLines.each do |theme, lines| - File.write(filePath, " +theme_to_code_lines.each do |theme, lines| + File.write(COLORS_FILE_PATH, " val Theme#{theme}Colors = ThemeColors( #{lines.join(",\n ")} ) diff --git a/scripts/themes/generate_xml.rb b/scripts/themes/generate_xml.rb index 33a5b468649..4584d7cfb56 100644 --- a/scripts/themes/generate_xml.rb +++ b/scripts/themes/generate_xml.rb @@ -1,63 +1,64 @@ -require "fileutils" -require './download_themes.rb' +# frozen_string_literal: true -THEME_FILE = "../../modules/services/ui/src/main/res/values/themes.xml" +require 'fileutils' +require './download_themes' + +THEME_FILE = '../../modules/services/ui/src/main/res/values/themes.xml' def write_to_theme_file(output, file_marker) - contents = IO.read(THEME_FILE) - new_contents = "" + contents = File.read(THEME_FILE) + new_contents = '' reading = true contents.lines.each do |line| reading = true if line.include?("#{file_marker} - WARNING AUTO GENERATED, DO NOT EDIT - end") new_contents << line if reading if line.include?("#{file_marker} - WARNING AUTO GENERATED, DO NOT EDIT - begin") - reading = false + reading = false new_contents << output end end - File.open(THEME_FILE, "w") do |file| - file.write(new_contents) - end + File.write(THEME_FILE, new_contents) end def write_theme_attrs(tokens) - output = "" + output = '' tokens.each do |token_attrs| next if token_attrs[:user_input] + key = token_attrs[:key] - output += %Q[ \n] + output += %( \n) end - write_to_theme_file(output, "Theme tokens") + write_to_theme_file(output, 'Theme tokens') end def write_theme_colors(tokens, theme_name, file_marker) - output = "" + output = '' tokens.each do |token_attrs| key = token_attrs[:key] next if token_attrs[:user_input] + theme = token_attrs[:themes][theme_name.to_sym] color = theme[:hex] opacity = theme[:opacity] - alpha = (opacity[0...-1].to_f() / 100.0 * 255.0).to_i().to_s(16) - alpha = alpha.size == 1 ? "0" + alpha : alpha - colorWithAlpha = opacity == "100%" ? color : "##{alpha}#{color[1..-1]}" - output += %Q[ #{colorWithAlpha}\n] + alpha = (opacity[0...-1].to_f / 100.0 * 255.0).to_i.to_s(16) + alpha = "0#{alpha}" if alpha.size == 1 + color_with_alpha = opacity == '100%' ? color : "##{alpha}#{color[1..]}" + output += %( #{color_with_alpha}\n) end write_to_theme_file(output, file_marker) end -tokens = download_themes() +tokens = download_themes exit if tokens.nil? write_theme_attrs(tokens) -write_theme_colors(tokens, "light", "Light theme tokens") -write_theme_colors(tokens, "dark", "Dark theme tokens") -write_theme_colors(tokens, "extra_dark", "Extra dark theme tokens") -write_theme_colors(tokens, "classic_light", "Classic light theme tokens") -write_theme_colors(tokens, "electricity", "Electricity theme tokens") -write_theme_colors(tokens, "indigo", "Indigo theme tokens") -write_theme_colors(tokens, "radioactive", "Radioactive theme tokens") -write_theme_colors(tokens, "rose", "Rose theme tokens") -write_theme_colors(tokens, "light_contrast", "Light contrast theme tokens") -write_theme_colors(tokens, "dark_contrast", "Dark contrast theme tokens") - +write_theme_colors(tokens, 'light', 'Light theme tokens') +write_theme_colors(tokens, 'dark', 'Dark theme tokens') +write_theme_colors(tokens, 'extra_dark', 'Extra dark theme tokens') +write_theme_colors(tokens, 'classic_light', 'Classic light theme tokens') +write_theme_colors(tokens, 'electricity', 'Electricity theme tokens') +write_theme_colors(tokens, 'indigo', 'Indigo theme tokens') +write_theme_colors(tokens, 'radioactive', 'Radioactive theme tokens') +write_theme_colors(tokens, 'rose', 'Rose theme tokens') +write_theme_colors(tokens, 'light_contrast', 'Light contrast theme tokens') +write_theme_colors(tokens, 'dark_contrast', 'Dark contrast theme tokens')