fastlane-plugin-polidea 0.6.0.pre.1 → 2.2.0.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +3 -5
  3. data/lib/fastlane/plugin/polidea.rb +7 -0
  4. data/lib/fastlane/plugin/polidea/actions/add_prefix_schema.rb +4 -1
  5. data/lib/fastlane/plugin/polidea/actions/extract_app_icon.rb +4 -0
  6. data/lib/fastlane/plugin/polidea/actions/extract_app_info.rb +12 -9
  7. data/lib/fastlane/plugin/polidea/actions/extract_app_name.rb +4 -1
  8. data/lib/fastlane/plugin/polidea/actions/extract_version.rb +4 -0
  9. data/lib/fastlane/plugin/polidea/actions/{mailgun.rb → fota_mail.rb} +25 -21
  10. data/lib/fastlane/plugin/polidea/actions/{s3.rb → fota_s3.rb} +72 -51
  11. data/lib/fastlane/plugin/polidea/actions/get_binary_size.rb +4 -0
  12. data/lib/fastlane/plugin/polidea/actions/import_provisioning.rb +5 -0
  13. data/lib/fastlane/plugin/polidea/actions/release_notes.rb +5 -0
  14. data/lib/fastlane/plugin/polidea/actions/release_notes_from_commits.rb +54 -0
  15. data/lib/fastlane/plugin/polidea/actions/shuttle.rb +6 -38
  16. data/lib/fastlane/plugin/polidea/helper/analytics.rb +43 -0
  17. data/lib/fastlane/plugin/polidea/helper/mime.rb +22 -0
  18. data/lib/fastlane/plugin/polidea/helper/page_generator.rb +41 -3
  19. data/lib/fastlane/plugin/polidea/helper/qr_generator.rb +7 -7
  20. data/lib/fastlane/plugin/polidea/helper/shuttle.rb +39 -0
  21. data/lib/fastlane/plugin/polidea/templates/download.erb +481 -0
  22. data/lib/fastlane/plugin/polidea/templates/images/cover-shuttle.jpg +0 -0
  23. data/lib/fastlane/plugin/polidea/templates/images/icon-placeholder.png +0 -0
  24. data/lib/fastlane/plugin/polidea/templates/images/polidea-logo.png +0 -0
  25. data/lib/fastlane/plugin/polidea/templates/images/social-behance.png +0 -0
  26. data/lib/fastlane/plugin/polidea/templates/images/social-dribbble.png +0 -0
  27. data/lib/fastlane/plugin/polidea/templates/images/social-facebook.png +0 -0
  28. data/lib/fastlane/plugin/polidea/templates/images/social-github.png +0 -0
  29. data/lib/fastlane/plugin/polidea/templates/images/social-instagram.png +0 -0
  30. data/lib/fastlane/plugin/polidea/templates/images/social-linkedin.png +0 -0
  31. data/lib/fastlane/plugin/polidea/templates/images/social-twitter.png +0 -0
  32. data/lib/fastlane/plugin/polidea/templates/installation-page/css/installation-page.css +2835 -1082
  33. data/lib/fastlane/plugin/polidea/templates/installation_template.erb +3 -7
  34. data/lib/fastlane/plugin/polidea/templates/mailgun_template.erb +56 -24
  35. data/lib/fastlane/plugin/polidea/templates/secure_installation_template.erb +6 -10
  36. data/lib/fastlane/plugin/polidea/version.rb +1 -1
  37. metadata +100 -23
  38. data/lib/fastlane/plugin/polidea/actions/polidea_store.rb +0 -251
  39. data/lib/fastlane/plugin/polidea/templates/images/logo.png +0 -0
  40. data/lib/fastlane/plugin/polidea/templates/images/polidea-facebook-icon.png +0 -0
  41. data/lib/fastlane/plugin/polidea/templates/images/polidea-github-icon.png +0 -0
  42. data/lib/fastlane/plugin/polidea/templates/images/polidea-twitter-icon.png +0 -0
@@ -6,6 +6,8 @@ module Fastlane
6
6
 
7
7
  class GetBinarySizeAction < Action
8
8
  def self.run(config)
9
+ Fastlane::Polidea.session.action_launched("get_binary_size", config)
10
+
9
11
  binary_file = config[:binary]
10
12
 
11
13
  UI.user_error!("No IPA or APK file path given, pass using `binary: 'ipa or apk path'`") unless binary_file.to_s.length > 0
@@ -18,6 +20,8 @@ module Fastlane
18
20
 
19
21
  UI.success("Size of #{binary_file} is #{binary_size_in_mb} MB (#{binary_size} bytes)")
20
22
 
23
+ Fastlane::Polidea.session.action_completed("get_binary_size")
24
+
21
25
  binary_size
22
26
  end
23
27
 
@@ -2,6 +2,8 @@ module Fastlane
2
2
  module Actions
3
3
  class ImportProvisioningAction < Action
4
4
  def self.run(config)
5
+ Fastlane::Polidea.session.action_launched("import_provisioning", config)
6
+
5
7
  path = config[:path]
6
8
  profiles = Dir.glob File.join(path, "*.mobileprovision")
7
9
 
@@ -10,6 +12,9 @@ module Fastlane
10
12
  FastlaneCore::ProvisioningProfile.install(profile)
11
13
  end
12
14
  UI.success "Successfully imported:\n#{profiles.join("\n")}"
15
+
16
+ Fastlane::Polidea.session.action_completed("import_provisioning")
17
+
13
18
  true
14
19
  end
15
20
 
@@ -6,12 +6,17 @@ module Fastlane
6
6
 
7
7
  class ReleaseNotesAction < Action
8
8
  def self.run(config)
9
+ Fastlane::Polidea.session.action_launched("release_notes", config)
10
+
9
11
  current_tag = sh "git describe --exact-match --tags HEAD"
10
12
  tag_info = sh "git cat-file -p #{current_tag}"
11
13
  release_notes = tag_info.split("\n").drop(5).join("\n")
12
14
  UI.success "Extracted release notes:\n#{release_notes}"
13
15
  Actions.lane_context[SharedValues::RELEASE_NOTES] = release_notes
14
16
  ENV[SharedValues::RELEASE_NOTES.to_s] = release_notes
17
+
18
+ Fastlane::Polidea.session.action_completed("release_notes")
19
+
15
20
  release_notes
16
21
  end
17
22
 
@@ -0,0 +1,54 @@
1
+ module Fastlane
2
+ module Actions
3
+ module SharedValues
4
+ RELEASE_NOTES = :RELEASE_NOTES
5
+ end
6
+
7
+ class ReleaseNotesFromCommitsAction < Action
8
+ def self.run(config)
9
+ Fastlane::Polidea.session.action_launched("release_notes_from_commits", config)
10
+
11
+ current_version = config[:current_version]
12
+ previous_version = sh("git describe --abbrev=0 --tags #{current_version}^").strip
13
+ messages = sh("git log --oneline --pretty=format:'%s' #{previous_version}..#{current_version} | grep -Ev '^Merge'").strip
14
+ release_notes = messages.split("\n").map { |note| "- #{note}" }.join("\n")
15
+
16
+ UI.success "Extracted release notes:\n#{release_notes}"
17
+ Actions.lane_context[SharedValues::RELEASE_NOTES] = release_notes
18
+ ENV[SharedValues::RELEASE_NOTES.to_s] = release_notes
19
+
20
+ Fastlane::Polidea.session.action_completed("release_notes_from_commits")
21
+
22
+ release_notes
23
+ end
24
+
25
+ def self.description
26
+ "Extracts release notes from git commit messages since last tag"
27
+ end
28
+
29
+ def self.available_options
30
+ [
31
+ FastlaneCore::ConfigItem.new(key: :current_version,
32
+ env_name: "VERSION_NAME",
33
+ description: "Git ref of current version",
34
+ optional: true,
35
+ default_value: 'HEAD')
36
+ ]
37
+ end
38
+
39
+ def self.output
40
+ [
41
+ ['RELEASE_NOTES', 'Release notes extracted from git commits']
42
+ ]
43
+ end
44
+
45
+ def self.author
46
+ "Piotrek Dubiel"
47
+ end
48
+
49
+ def self.is_supported?(platform)
50
+ true
51
+ end
52
+ end
53
+ end
54
+ end
@@ -6,6 +6,7 @@ module Fastlane
6
6
  module Actions
7
7
  class ShuttleAction < Action
8
8
  def self.run(params)
9
+ Fastlane::Polidea.session.action_launched("shuttle", params)
9
10
  platform = Actions.lane_context[Actions::SharedValues::PLATFORM_NAME].to_sym
10
11
  options = {
11
12
  api_token: params[:api_token],
@@ -25,6 +26,8 @@ module Fastlane
25
26
  notify(platform, config)
26
27
 
27
28
  UI.success("Successfully uploaded to #{params[:app_identifier]} #{params[:app_version]}.#{params[:build_number]} to Shuttle")
29
+
30
+ Fastlane::Polidea.session.action_completed("shuttle")
28
31
  end
29
32
 
30
33
  #####################################################
@@ -154,8 +157,6 @@ module Fastlane
154
157
  end
155
158
 
156
159
  def self.notify(platform, params)
157
- uri = URI.parse(url(params[:environment], platform, params[:app_identifier]))
158
-
159
160
  build = {
160
161
  href: params[:href],
161
162
  version: params[:app_version],
@@ -167,17 +168,11 @@ module Fastlane
167
168
  build[:prefixSchema] = params[:prefix_schema] if platform == :ios
168
169
  build[:versionCode] = params[:build_number] if platform == :android
169
170
 
170
- UI.verbose("POST #{uri}\n#{JSON.pretty_generate({ build: build })}")
171
- make_request(uri, create_request(uri, params[:api_token], {
172
- build: build
173
- }))
174
- end
175
- private_class_method :notify
171
+ client = Shuttle::Client.new(base_url(params[:environment]), params[:api_token])
176
172
 
177
- def self.url(environment, platform, app_identifier)
178
- "#{base_url(environment)}/cd/apps/#{platform}/#{app_identifier}/builds"
173
+ client.create_build(platform, params[:app_identifier], build)
179
174
  end
180
- private_class_method :url
175
+ private_class_method :notify
181
176
 
182
177
  def self.base_url(environment)
183
178
  case environment
@@ -189,14 +184,6 @@ module Fastlane
189
184
  end
190
185
  private_class_method :base_url
191
186
 
192
- def self.create_request(uri, access_token, params)
193
- req = Net::HTTP::Post.new(uri.request_uri)
194
- req['Content-Type'] = 'application/json'
195
- req['Access-Token'] = access_token
196
- req.body = JSON.generate(params)
197
- req
198
- end
199
-
200
187
  def self.itms_href(plist_url)
201
188
  "itms-services://?action=download-manifest&url=#{URI.encode_www_form_component(plist_url)}"
202
189
  end
@@ -206,25 +193,6 @@ module Fastlane
206
193
  sh("git --no-pager show -s --format='%ae'", print_command: false, print_command_output: false).strip
207
194
  end
208
195
  private_class_method :commit_author
209
-
210
- def self.make_request(uri, request, limit = 10)
211
- raise ArgumentError, 'HTTP redirect too deep' if limit.zero?
212
-
213
- http = Net::HTTP.new(uri.host, uri.port)
214
-
215
- if uri.instance_of?(URI::HTTPS)
216
- http.use_ssl = true
217
- end
218
-
219
- response = http.request(request)
220
- UI.verbose(response.body)
221
- case response
222
- when Net::HTTPSuccess then response
223
- when Net::HTTPRedirection then make_request(URI.parse(response['location']), request, limit - 1)
224
- else
225
- UI.user_error! JSON.parse(response.body)['message']
226
- end
227
- end
228
196
  end
229
197
  end
230
198
  end
@@ -0,0 +1,43 @@
1
+ module Fastlane
2
+ module Polidea
3
+ class Analytics
4
+ GA_TRACKING = "UA-165836496-1"
5
+ private_constant :GA_TRACKING
6
+
7
+ attr_accessor :session_id
8
+ attr_accessor :client
9
+
10
+ def initialize(session_id)
11
+ @session_id = session_id
12
+ @client = FastlaneCore::AnalyticsIngesterClient.new(GA_TRACKING)
13
+ @client_id = "fastlane-plugin-polidea@#{Fastlane::Polidea::VERSION}"
14
+ end
15
+
16
+ def action_launched(action_name, config)
17
+ client.post_request({
18
+ client_id: @session_id,
19
+ category: @client_id,
20
+ action: action_name,
21
+ label: "launched"
22
+ })
23
+ config.values.filter { |k, v| !v.nil? }.keys.each do |param|
24
+ client.post_request({
25
+ client_id: @session_id,
26
+ category: @client_id,
27
+ action: "#{action_name}::#{param}",
28
+ label: "param"
29
+ })
30
+ end
31
+ end
32
+
33
+ def action_completed(action_name)
34
+ client.post_request({
35
+ client_id: @session_id,
36
+ category: @client_id,
37
+ action: action_name,
38
+ label: "completed"
39
+ })
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ module Fastlane
2
+ module Mime
3
+ def self.content_type_for_file(file)
4
+ file_extension = File.extname(file)
5
+ extensions_to_type = {
6
+ ".html" => "text/html",
7
+ ".png" => "image/png",
8
+ ".jpg" => "image/jpeg",
9
+ ".gif" => "image/gif",
10
+ ".svg" => "image/svg+xml",
11
+ ".log" => "text/plain",
12
+ ".css" => "text/css",
13
+ ".js" => "application/javascript"
14
+ }
15
+ if extensions_to_type[file_extension].nil?
16
+ "application/octet-stream"
17
+ else
18
+ extensions_to_type[file_extension]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -4,7 +4,28 @@ require 'json'
4
4
 
5
5
  module Fastlane
6
6
  module PageGenerator
7
- def self.generate(config)
7
+ def self.mail(config)
8
+ UI.message("Generating e-mail...")
9
+ eth = Fastlane::ErbTemplateHelper
10
+ html_template = eth.load_from_path("#{__dir__}/../templates/download.erb")
11
+ eth.render(html_template, {
12
+ author: config[:author],
13
+ last_commit: config[:last_commit],
14
+ is_android: config[:is_android],
15
+ app_icon: config[:app_icon] ? "cid:#{config[:app_icon]}" : "https://s3.eu-central-1.amazonaws.com/fota.polidea.com/mails/images/icon-placeholder.png",
16
+ app_name: config[:app_name],
17
+ app_version: config[:app_version],
18
+ build_number: config[:build_number],
19
+ installation_link: config[:installation_link],
20
+ release_notes: parse_release_notes(config[:release_notes]),
21
+ platform: config[:platform],
22
+ release_date: config[:release_date],
23
+ binary_size: config[:binary_size],
24
+ qr_code: "cid:#{config[:qr_code]}"
25
+ })
26
+ end
27
+
28
+ def self.installation_page(config)
8
29
  if config[:installation_password]
9
30
  generate_private(config)
10
31
  else
@@ -25,7 +46,8 @@ module Fastlane
25
46
  build_number: config[:build_number],
26
47
  app_name: config[:app_name],
27
48
  app_icon: config[:app_icon],
28
- platform: config[:platform]
49
+ platform: config[:platform],
50
+ release_notes: parse_release_notes(config[:release_notes])
29
51
  })
30
52
  end
31
53
  private_class_method :generate_private
@@ -40,7 +62,8 @@ module Fastlane
40
62
  build_number: config[:build_number],
41
63
  app_name: config[:app_name],
42
64
  app_icon: config[:app_icon],
43
- platform: config[:platform]
65
+ platform: config[:platform],
66
+ release_notes: parse_release_notes(config[:release_notes])
44
67
  })
45
68
  end
46
69
  private_class_method :generate_public
@@ -65,5 +88,20 @@ module Fastlane
65
88
  return installation_link, salt, iv
66
89
  end
67
90
  private_class_method :encrypt
91
+
92
+ def self.parse_release_notes(release_notes)
93
+ renderer = Redcarpet::Render::HTML.new({
94
+ filter_html: true,
95
+ no_styles: true
96
+ })
97
+ markdown = Redcarpet::Markdown.new(
98
+ renderer,
99
+ fenced_code_blocks: true,
100
+ autolink: false,
101
+ tables: false
102
+ )
103
+ markdown.render(release_notes || "No release notes.")
104
+ end
105
+ private_class_method :parse_release_notes
68
106
  end
69
107
  end
@@ -8,10 +8,10 @@ module Fastlane
8
8
  @dark_color = dark_color
9
9
 
10
10
  @margin = 20
11
- @image = ::ChunkyPNG::Image.new(2 * @margin + @qr_code.module_count * 5, 2 * @margin + @qr_code.module_count * 5, ::ChunkyPNG::Color::WHITE)
11
+ @image = ::ChunkyPNG::Image.new(2 * @margin + @qr_code.qrcode.module_count * 5, 2 * @margin + @qr_code.qrcode.module_count * 5, ::ChunkyPNG::Color::WHITE)
12
12
 
13
- @logo = ::ChunkyPNG::Image.from_file("#{path_to_resources}/logo.png")
14
- @logo = @logo.resize(2 * @margin + @qr_code.module_count * 5, 2 * @margin + @qr_code.module_count * 5)
13
+ @logo = ::ChunkyPNG::Image.from_file("#{path_to_resources}/polidea-logo.png")
14
+ @logo = @logo.resize(2 * @margin + @qr_code.qrcode.module_count * 5, 2 * @margin + @qr_code.qrcode.module_count * 5)
15
15
  end
16
16
 
17
17
  def generate(path)
@@ -25,11 +25,11 @@ module Fastlane
25
25
  end
26
26
 
27
27
  @qr_code.modules.each_index do |row|
28
- @qr_code.modules.each_index do |column|
29
- if @qr_code.is_dark(row, column)
30
- print_symbol(dark_color, column, row, (column < 8 && row < 8) || (column < 8 && row >= @qr_code.module_count - 8) || (column >= @qr_code.module_count - 8 && row < 8))
28
+ @qr_code.modules[row].each_index do |column|
29
+ if @qr_code.modules[row][column]
30
+ print_symbol(dark_color, column, row, (column < 8 && row < 8) || (column < 8 && row >= @qr_code.qrcode.module_count - 8) || (column >= @qr_code.qrcode.module_count - 8 && row < 8))
31
31
  else
32
- print_symbol(white, column, row, (column < 8 && row < 8) || (column < 8 && row >= @qr_code.module_count - 8) || (column >= @qr_code.module_count - 8 && row < 8))
32
+ print_symbol(white, column, row, (column < 8 && row < 8) || (column < 8 && row >= @qr_code.qrcode.module_count - 8) || (column >= @qr_code.qrcode.module_count - 8 && row < 8))
33
33
  end
34
34
  end
35
35
  end
@@ -0,0 +1,39 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module Fastlane
5
+ module Shuttle
6
+ class Client
7
+ def initialize(base_url, access_token)
8
+ retry_options = {
9
+ max: 2,
10
+ interval: 0.05,
11
+ interval_randomness: 0.5,
12
+ backoff_factor: 2
13
+ }
14
+
15
+ @conn = Faraday.new(
16
+ url: base_url,
17
+ headers: {
18
+ 'Content-Type' => 'application/json',
19
+ 'Access-Token' => access_token
20
+ }
21
+ ) do |conn|
22
+ conn.request :retry, retry_options
23
+ conn.request :multipart
24
+
25
+ conn.response :raise_error
26
+
27
+ conn.adapter :net_http
28
+ end
29
+ end
30
+
31
+ def create_build(platform, app_identifier, build)
32
+ @conn.post(
33
+ "cd/apps/#{platform}/#{app_identifier}/builds",
34
+ { build: build }.to_json
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,481 @@
1
+ <!doctype html>
2
+ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
3
+
4
+ <head>
5
+ <title> <%= app_name %> <%= app_version %> (<%= build_number %>) for <%= platform %> is ready to install </title>
6
+ <!--[if !mso]><!-- -->
7
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
8
+ <!--<![endif]-->
9
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
10
+ <meta name="viewport" content="width=device-width, initial-scale=1">
11
+ <style type="text/css">
12
+ #outlook a {
13
+ padding: 0;
14
+ }
15
+
16
+ body {
17
+ margin: 0;
18
+ padding: 0;
19
+ -webkit-text-size-adjust: 100%;
20
+ -ms-text-size-adjust: 100%;
21
+ }
22
+
23
+ table,
24
+ td {
25
+ border-collapse: collapse;
26
+ mso-table-lspace: 0pt;
27
+ mso-table-rspace: 0pt;
28
+ }
29
+
30
+ img {
31
+ border: 0;
32
+ height: auto;
33
+ line-height: 100%;
34
+ outline: none;
35
+ text-decoration: none;
36
+ -ms-interpolation-mode: bicubic;
37
+ }
38
+
39
+ p {
40
+ display: block;
41
+ margin: 13px 0;
42
+ }
43
+ </style>
44
+ <!--[if mso]>
45
+ <xml>
46
+ <o:OfficeDocumentSettings>
47
+ <o:AllowPNG/>
48
+ <o:PixelsPerInch>96</o:PixelsPerInch>
49
+ </o:OfficeDocumentSettings>
50
+ </xml>
51
+ <![endif]-->
52
+ <!--[if lte mso 11]>
53
+ <style type="text/css">
54
+ .mj-outlook-group-fix { width:100% !important; }
55
+ </style>
56
+ <![endif]-->
57
+ <!--[if !mso]><!-->
58
+ <link href="https://fonts.googleapis.com/css?family=Overpass+Mono:700" rel="stylesheet" type="text/css">
59
+ <style type="text/css">
60
+ @import url(https://fonts.googleapis.com/css?family=Overpass+Mono:700);
61
+ </style>
62
+ <!--<![endif]-->
63
+ <style type="text/css">
64
+ @media only screen and (min-width:480px) {
65
+ .mj-column-per-100 {
66
+ width: 100% !important;
67
+ max-width: 100%;
68
+ }
69
+
70
+ .mj-column-px-480 {
71
+ width: 480px !important;
72
+ max-width: 480px;
73
+ }
74
+ }
75
+ </style>
76
+ <style type="text/css">
77
+ @media only screen and (max-width:480px) {
78
+ table.mj-full-width-mobile {
79
+ width: 100% !important;
80
+ }
81
+
82
+ td.mj-full-width-mobile {
83
+ width: auto !important;
84
+ }
85
+ }
86
+ </style>
87
+ </head>
88
+
89
+ <body style="background-color:#FFFFFF;">
90
+ <div style="background-color:#FFFFFF;">
91
+ <!-- Company Header -->
92
+ <!--[if mso | IE]>
93
+ <table
94
+ align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
95
+ >
96
+ <tr>
97
+ <td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
98
+ <![endif]-->
99
+ <div style="background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:600px;">
100
+ <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;">
101
+ <tbody>
102
+ <tr>
103
+ <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
104
+ <!--[if mso | IE]>
105
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0">
106
+
107
+ <tr>
108
+
109
+ <td
110
+ class="" style="vertical-align:top;width:600px;"
111
+ >
112
+ <![endif]-->
113
+ <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
114
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
115
+ <tbody>
116
+ <tr>
117
+ <td style="vertical-align:top;padding:20px 32px;">
118
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation" style width="100%">
119
+ <tr>
120
+ <td align="left" style="font-size:0px;padding:0px;word-break:break-word;">
121
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
122
+ <tbody>
123
+ <tr>
124
+ <td style="width:156px;">
125
+ <a href="https://www.polidea.com/" target="_blank">
126
+ <img height="auto" src="https://s3.eu-central-1.amazonaws.com/fota.polidea.com/mails/images/polidea-logo.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:14px;" width="156">
127
+ </a>
128
+ </td>
129
+ </tr>
130
+ </tbody>
131
+ </table>
132
+ </td>
133
+ </tr>
134
+ </table>
135
+ </td>
136
+ </tr>
137
+ </tbody>
138
+ </table>
139
+ </div>
140
+ <!--[if mso | IE]>
141
+ </td>
142
+
143
+ </tr>
144
+
145
+ </table>
146
+ <![endif]-->
147
+ </td>
148
+ </tr>
149
+ </tbody>
150
+ </table>
151
+ </div>
152
+ <!--[if mso | IE]>
153
+ </td>
154
+ </tr>
155
+ </table>
156
+ <![endif]-->
157
+ <!-- Image Header -->
158
+ <!--[if mso | IE]>
159
+ <table
160
+ align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
161
+ >
162
+ <tr>
163
+ <td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
164
+ <![endif]-->
165
+ <div style="margin:0px auto;max-width:600px;">
166
+ <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
167
+ <tbody>
168
+ <tr>
169
+ <td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
170
+ <!--[if mso | IE]>
171
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0">
172
+
173
+ <tr>
174
+
175
+ <td
176
+ class="" style="vertical-align:top;width:600px;"
177
+ >
178
+ <![endif]-->
179
+ <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
180
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
181
+ <tbody>
182
+ <tr>
183
+ <td style="vertical-align:top;padding:0;">
184
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation" style width="100%">
185
+ <tr>
186
+ <td align="center" style="font-size:0px;padding:0;word-break:break-word;">
187
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
188
+ <tbody>
189
+ <tr>
190
+ <td style="width:600px;">
191
+ <img height="auto" src="https://s3.eu-central-1.amazonaws.com/fota.polidea.com/mails/images/cover-shuttle.jpg" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:14px;" width="600">
192
+ </td>
193
+ </tr>
194
+ </tbody>
195
+ </table>
196
+ </td>
197
+ </tr>
198
+ </table>
199
+ </td>
200
+ </tr>
201
+ </tbody>
202
+ </table>
203
+ </div>
204
+ <!--[if mso | IE]>
205
+ </td>
206
+
207
+ </tr>
208
+
209
+ </table>
210
+ <![endif]-->
211
+ </td>
212
+ </tr>
213
+ </tbody>
214
+ </table>
215
+ </div>
216
+ <!--[if mso | IE]>
217
+ </td>
218
+ </tr>
219
+ </table>
220
+ <![endif]-->
221
+ <!-- Body -->
222
+ <!--[if mso | IE]>
223
+ <table
224
+ align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
225
+ >
226
+ <tr>
227
+ <td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
228
+ <![endif]-->
229
+ <div style="background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:600px;">
230
+ <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;">
231
+ <tbody>
232
+ <tr>
233
+ <td style="direction:ltr;font-size:0px;padding:0 32px 0;text-align:center;">
234
+ <!--[if mso | IE]>
235
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0">
236
+
237
+ <tr>
238
+
239
+ <td
240
+ class="" style="vertical-align:top;width:480px;"
241
+ >
242
+ <![endif]-->
243
+ <div class="mj-column-px-480 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
244
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
245
+ <tbody>
246
+ <tr>
247
+ <td style="vertical-align:top;padding:32px 0 0;">
248
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation" style width="100%">
249
+ <tr>
250
+ <td align="left" style="font-size:0px;padding:0;padding-bottom:18px;word-break:break-word;">
251
+ <div style="font-family:'Acumin', 'Helvetica', Arial;font-size:24px;font-weight:bold;line-height:32px;text-align:left;color:black;">New version is ready to install!</div>
252
+ </td>
253
+ </tr>
254
+ <table>
255
+ <tr>
256
+ <td width="40" vertical-align="top">
257
+ <img width="64" height="64" src="<%= app_icon %>">
258
+ </td>
259
+ <td width="10">
260
+ </td>
261
+ <td style="line-height: 2; font-family: 'Helvetica', Arial;">
262
+ <div style="color: black; font-size: 13px; line-height: 20px; font-weight: bold;"> <%= app_name %> </div>
263
+ <div style="color: black; font-size: 12px; line-height: 20px"> Version: <%= app_version %> (<%= build_number %>) </div>
264
+ </td>
265
+ <td width="20"></td>
266
+ </tr>
267
+ </table>
268
+ <tr>
269
+ <td align="left" vertical-align="middle" style="font-size:0px;padding:32px 0;word-break:break-word;">
270
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
271
+ <tr>
272
+ <td align="center" bgcolor="white" role="presentation" style="border:2px solid #2924fb;border-radius:30px;cursor:auto;mso-padding-alt:12px 42px;background:white;" valign="middle">
273
+ <a href="<%= installation_link %>" style="display:inline-block;background:white;color:black;font-family:'Overpass Mono', monospace;font-size:14px;font-weight:bold;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:12px 42px;mso-padding-alt:0px;border-radius:30px;" target="_blank">
274
+ <meta itemprop="url" content="<%= installation_link %>">
275
+ <meta itemprop="name" content="Installation page"> Download </a>
276
+ </td>
277
+ </tr>
278
+ </table>
279
+ </td>
280
+ </tr>
281
+ <tr>
282
+ <td align="left" style="font-size:0px;padding:0;padding-bottom:18px;word-break:break-word;">
283
+ <div style="font-family:'Acumin', 'Helvetica', Arial;font-size:18px;font-weight:bold;line-height:24px;text-align:left;color:black;">What's new:</div>
284
+ </td>
285
+ </tr>
286
+ <tr>
287
+ <td align="left" style="font-size:0px;padding:0;word-break:break-word;">
288
+ <table cellpadding="0" cellspacing="0" width="100%" border="0" style="color:#000000;font-family:'Acumin', 'Helvetica', Arial;font-size:14px;line-height:22px;table-layout:auto;width:100%;border:none;">
289
+ <tr>
290
+ <td class="release-notes" style="padding-left: 16px; padding-right: 16px; padding-top: 8px; padding-bottom: 8px; color: black; background-color: #F8F8F8;" bgcolor="#F8F8F8"> <%= release_notes %> </td>
291
+ </tr>
292
+ </table>
293
+ </td>
294
+ </tr>
295
+ <tr>
296
+ <td align="left" style="font-size:0px;padding:16px 0;word-break:break-word;">
297
+ <table cellpadding="0" cellspacing="0" width="37" border="0" style="color:#000000;font-family:'Acumin', 'Helvetica', Arial;font-size:14px;line-height:22px;table-layout:auto;width:37px;border:none;">
298
+ <tr>
299
+ <td width="37" bgcolor="#f96248" style="background-color: #f96248; padding-bottom: 1px">
300
+ </td>
301
+ </tr>
302
+ </table>
303
+ </td>
304
+ </tr>
305
+ <tr>
306
+ <td align="left" style="font-size:0px;padding:0;word-break:break-word;">
307
+ <div style="font-family:'Acumin', 'Helvetica', Arial;font-size:14px;line-height:20px;text-align:left;color:black;">Platform: <%= platform %></div>
308
+ </td>
309
+ </tr>
310
+ <tr>
311
+ <td align="left" style="font-size:0px;padding:0;word-break:break-word;">
312
+ <div style="font-family:'Acumin', 'Helvetica', Arial;font-size:14px;line-height:20px;text-align:left;color:black;">Version: <%= app_version %> (<%= build_number %>)</div>
313
+ </td>
314
+ </tr>
315
+ <tr>
316
+ <td align="left" style="font-size:0px;padding:0;word-break:break-word;">
317
+ <div style="font-family:'Acumin', 'Helvetica', Arial;font-size:14px;line-height:20px;text-align:left;color:black;">Release date: <%= release_date %></div>
318
+ </td>
319
+ </tr>
320
+ <tr>
321
+ <td align="left" style="font-size:0px;padding:0;word-break:break-word;">
322
+ <div style="font-family:'Acumin', 'Helvetica', Arial;font-size:14px;line-height:20px;text-align:left;color:black;">Size: <%= binary_size %> MB</div>
323
+ </td>
324
+ </tr>
325
+ </table>
326
+ </td>
327
+ </tr>
328
+ </tbody>
329
+ </table>
330
+ </div>
331
+ <!--[if mso | IE]>
332
+ </td>
333
+
334
+ </tr>
335
+
336
+ </table>
337
+ <![endif]-->
338
+ </td>
339
+ </tr>
340
+ </tbody>
341
+ </table>
342
+ </div>
343
+ <!--[if mso | IE]>
344
+ </td>
345
+ </tr>
346
+ </table>
347
+ <![endif]-->
348
+ <!-- Projectr -->
349
+ <!--[if mso | IE]>
350
+ <table
351
+ align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
352
+ >
353
+ <tr>
354
+ <td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
355
+ <![endif]-->
356
+ <div style="margin:0px auto;max-width:600px;">
357
+ <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
358
+ <tbody>
359
+ <tr>
360
+ <td style="direction:ltr;font-size:0px;padding:20px 32px 20px;text-align:center;">
361
+ <!--[if mso | IE]>
362
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0">
363
+
364
+ <tr>
365
+
366
+ <td
367
+ class="" style="vertical-align:top;width:480px;"
368
+ >
369
+ <![endif]-->
370
+ <div class="mj-column-px-480 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
371
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
372
+ <table>
373
+ <tr>
374
+ <td>
375
+ <a href="https://github.com/Polidea">
376
+ <img src="https://s3.eu-central-1.amazonaws.com/fota.polidea.com/mails/images/social-github.png" width="29" height="29" alt="Github">
377
+ </a>
378
+ </td>
379
+ <td width="16"></td>
380
+ <td>
381
+ <a href="https://www.facebook.com/Polidea.Software">
382
+ <img src="https://s3.eu-central-1.amazonaws.com/fota.polidea.com/mails/images/social-facebook.png" width="29" height="29" alt="Facebook">
383
+ </a>
384
+ </td>
385
+ <td width="16"></td>
386
+ <td>
387
+ <a href="https://twitter.com/polidea">
388
+ <img src="https://s3.eu-central-1.amazonaws.com/fota.polidea.com/mails/images/social-twitter.png" width="29" height="29" alt="Twitter">
389
+ </a>
390
+ </td>
391
+ <td width="16"></td>
392
+ <td>
393
+ <a href="https://www.linkedin.com/company/polidea">
394
+ <img src="https://s3.eu-central-1.amazonaws.com/fota.polidea.com/mails/images/social-linkedin.png" width="29" height="29" alt="Linkedin">
395
+ </a>
396
+ </td>
397
+ <td width="16"></td>
398
+ <td>
399
+ <a href="https://instagram.com/polidea">
400
+ <img src="https://s3.eu-central-1.amazonaws.com/fota.polidea.com/mails/images/social-instagram.png" width="29" height="29" alt="Instagram">
401
+ </a>
402
+ </td>
403
+ </tr>
404
+ </table>
405
+ </table>
406
+ </div>
407
+ <!--[if mso | IE]>
408
+ </td>
409
+
410
+ </tr>
411
+
412
+ </table>
413
+ <![endif]-->
414
+ </td>
415
+ </tr>
416
+ </tbody>
417
+ </table>
418
+ </div>
419
+ <!--[if mso | IE]>
420
+ </td>
421
+ </tr>
422
+ </table>
423
+
424
+ <table
425
+ align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
426
+ >
427
+ <tr>
428
+ <td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
429
+ <![endif]-->
430
+ <div style="margin:0px auto;max-width:600px;">
431
+ <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
432
+ <tbody>
433
+ <tr>
434
+ <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
435
+ <!--[if mso | IE]>
436
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0">
437
+
438
+ <tr>
439
+
440
+ <td
441
+ class="" style="vertical-align:top;width:600px;"
442
+ >
443
+ <![endif]-->
444
+ <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
445
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
446
+ <tr>
447
+ <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
448
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
449
+ <tbody>
450
+ <tr>
451
+ <td style="width:300px;">
452
+ <img height="auto" src="<%= qr_code %>" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:14px;" width="300">
453
+ </td>
454
+ </tr>
455
+ </tbody>
456
+ </table>
457
+ </td>
458
+ </tr>
459
+ </table>
460
+ </div>
461
+ <!--[if mso | IE]>
462
+ </td>
463
+
464
+ </tr>
465
+
466
+ </table>
467
+ <![endif]-->
468
+ </td>
469
+ </tr>
470
+ </tbody>
471
+ </table>
472
+ </div>
473
+ <!--[if mso | IE]>
474
+ </td>
475
+ </tr>
476
+ </table>
477
+ <![endif]-->
478
+ </div>
479
+ </body>
480
+
481
+ </html>