diff --git a/android_hooks/pre-push b/android_hooks/pre-push
new file mode 100755
index 0000000..cc5cdce
--- /dev/null
+++ b/android_hooks/pre-push
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+echo "Pre push static analysis"
+
+output=$(./gradlew staticAnalysis --daemon 2>&1)
+
+status=$?
+
+if [ "$status" = 0 ] ; then
+ exit 0
+else
+ build_date=$(date +'%Y-%m-%d_%T')
+ file_name="build_$build_date"
+ folder_name="./build_log/"
+ file_path="${folder_name}${file_name}"
+ mkdir -p "$folder_name"
+ echo "$output" >> "$file_path"
+ echo "Pre push static failed. Full log path: ${file_path}"
+ exit 1
+fi
diff --git a/gradle/apiGenerator.gradle b/gradle/apiGenerator.gradle
index c8680e4..e8f08af 100644
--- a/gradle/apiGenerator.gradle
+++ b/gradle/apiGenerator.gradle
@@ -7,7 +7,7 @@ configurations {
}
dependencies {
- apigenerator 'ru.touchin:api-generator:1.2.9'
+ apigenerator 'ru.touchin:api-generator:1.4.0-beta1'
}
android.applicationVariants.all { variant ->
diff --git a/gradle/apiGeneratorKotlinServer.gradle b/gradle/apiGeneratorKotlinServer.gradle
index 2b980aa..2f98ab0 100644
--- a/gradle/apiGeneratorKotlinServer.gradle
+++ b/gradle/apiGeneratorKotlinServer.gradle
@@ -7,7 +7,7 @@ configurations {
}
dependencies {
- apigeneratorKotlinServer 'ru.touchin:api-generator:1.3.2'
+ apigeneratorKotlinServer 'ru.touchin:api-generator:1.4.0-alpha1'
}
task generateApiModelsKotlinServer doLast {
diff --git a/gradle/applicationFileNaming.gradle b/gradle/applicationFileNaming.gradle
new file mode 100644
index 0000000..6c2e7e2
--- /dev/null
+++ b/gradle/applicationFileNaming.gradle
@@ -0,0 +1,12 @@
+android {
+ defaultConfig {
+ setProperty(
+ "archivesBaseName",
+ "${project.android.defaultConfig.applicationId}" +
+ "-" +
+ "${project.android.defaultConfig.versionName}" +
+ "-" +
+ "${project.android.defaultConfig.versionCode}"
+ )
+ }
+}
diff --git a/gradle/installGitHooks.gradle b/gradle/installGitHooks.gradle
new file mode 100644
index 0000000..8f1ebc4
--- /dev/null
+++ b/gradle/installGitHooks.gradle
@@ -0,0 +1,7 @@
+task installGitHooks(type: Copy) {
+ from new File(buildScriptsDir, 'android_hooks')
+ into { new File(rootProject.rootDir, '.git/hooks') }
+ fileMode 0777
+}
+
+tasks.getByPath(':app:preBuild').dependsOn installGitHooks
diff --git a/lint/lint.xml b/lint/lint.xml
index 4e33195..3709269 100644
--- a/lint/lint.xml
+++ b/lint/lint.xml
@@ -267,5 +267,6 @@
+
diff --git a/proguard/common.pro b/proguard/common.pro
index 937bc37..52715c2 100644
--- a/proguard/common.pro
+++ b/proguard/common.pro
@@ -6,3 +6,4 @@
-include rules/crashlytics.pro
-include rules/glide.pro
-include rules/kaspersky.pro
+-include rules/appsflyer.pro
diff --git a/proguard/rules/appsflyer.pro b/proguard/rules/appsflyer.pro
new file mode 100644
index 0000000..6494fc5
--- /dev/null
+++ b/proguard/rules/appsflyer.pro
@@ -0,0 +1,5 @@
+-dontwarn com.google.android.gms.iid.**
+-keep class com.appsflyer.** { *; }
+-dontwarn android.app.job.JobParameters
+-dontwarn com.android.installreferrer.**
+-dontwarn com.appsflyer.**
diff --git a/xcode/.swiftlint.yml b/xcode/.swiftlint.yml
index 3e81483..b731824 100644
--- a/xcode/.swiftlint.yml
+++ b/xcode/.swiftlint.yml
@@ -60,6 +60,7 @@ excluded:
- Carthage
- Pods
- Generated
+ - Localization
line_length: 128
diff --git a/xcode/UnusedConfig.yml b/xcode/UnusedConfig.yml
new file mode 100644
index 0000000..619dd71
--- /dev/null
+++ b/xcode/UnusedConfig.yml
@@ -0,0 +1,6 @@
+excluded-resources:
+ - Generated/
+ - Resources/Localization
+ - Carthage/
+ - Pods/
+ - FormattingService.swift
\ No newline at end of file
diff --git a/xcode/aux_scripts/download_file.sh b/xcode/aux_scripts/download_file.sh
index 9bfcbc7..55d74ba 100755
--- a/xcode/aux_scripts/download_file.sh
+++ b/xcode/aux_scripts/download_file.sh
@@ -1,12 +1,29 @@
file_name=$1
file_link=$2
+folder=$3
+flag_of_delete=$4
-folder="Downloads"
-file_path="./${folder}/${file_name}"
+readonly key_of_delete="--remove-cached"
+readonly default_folder="./Downloads"
+
+if [[ ${folder} = ${key_of_delete} ]]; then
+ folder="${default_folder}"
+ flag_of_delete="${key_of_delete}"
+fi
+
+if ! [ -n "$folder" ]; then
+ folder="${default_folder}"
+fi
+
+file_path="${folder}/${file_name}"
+
+if [[ ${flag_of_delete} = ${key_of_delete} ]]; then
+ rm ${file_path}
+fi
# make folder if not exist
if ! [ -e ${folder} ]; then
- mkdir ${folder}
+ mkdir -p ${folder}
fi
# download file if not downloaded
diff --git a/xcode/aux_scripts/import_strings.php b/xcode/aux_scripts/import_strings.php
index 911e989..52f79d6 100644
--- a/xcode/aux_scripts/import_strings.php
+++ b/xcode/aux_scripts/import_strings.php
@@ -1,5 +1,5 @@
$value) {
$value_without_linefeed = preg_replace("/\r|\n/", " ", $value);
$ios_swift_strings .= "\t/// ".$value_without_linefeed."\n\t".'static let '.preg_replace_callback('/_(.?)/', function ($m) { return strtoupper($m[1]); }, $key).' = NSLocalizedString("'.$key.'", comment: "")'."\n".PHP_EOL;
diff --git a/xcode/build_phases/Unused.rb b/xcode/build_phases/Unused.rb
new file mode 100644
index 0000000..478a8d2
--- /dev/null
+++ b/xcode/build_phases/Unused.rb
@@ -0,0 +1,189 @@
+# source: https://github.com/PaulTaykalo/swift-scripts/blob/master/unused.rb
+#!/usr/bin/ruby
+#encoding: utf-8
+require 'yaml'
+require 'optparse'
+
+Encoding.default_external = Encoding::UTF_8
+Encoding.default_internal = Encoding::UTF_8
+
+class Item
+ def initialize(file, line, at)
+ @file = file
+ @line = line
+ @at = at + 1
+ if match = line.match(/(func|let|var|class|enum|struct|protocol)\s+(\w+)/)
+ @type = match.captures[0]
+ @name = match.captures[1]
+ end
+ end
+
+ def modifiers
+ return @modifiers if @modifiers
+ @modifiers = []
+ if match = @line.match(/(.*?)#{@type}/)
+ @modifiers = match.captures[0].split(" ")
+ end
+ return @modifiers
+ end
+
+ def name
+ @name
+ end
+
+ def file
+ @file
+ end
+
+ def to_s
+ serialize
+ end
+ def to_str
+ serialize
+ end
+
+ def full_file_path
+ Dir.pwd + '/' + @file
+ end
+
+ def serialize
+ "#{@file} has unused \"#{@type.to_s} #{@name.to_s}\""
+ end
+ def to_xcode
+ "#{full_file_path}:#{@at}:0: warning: #{@type.to_s} #{@name.to_s} is unused"
+ end
+
+
+end
+class Unused
+ def find
+ items = []
+ unused_warnings = []
+
+ regexps = parse_arguments
+
+ all_files = Dir.glob("**/*.swift").reject do |path|
+ File.directory?(path)
+ end
+
+ all_files.each { |my_text_file|
+ file_items = grab_items(my_text_file)
+ file_items = filter_items(file_items)
+
+ non_private_items, private_items = file_items.partition { |f| !f.modifiers.include?("private") && !f.modifiers.include?("fileprivate") }
+ items += non_private_items
+
+ # Usage within the file
+ if private_items.length > 0
+ unused_warnings += find_usages_in_files([my_text_file], [], private_items, regexps)
+ end
+
+ }
+
+ xibs = Dir.glob("**/*.xib")
+ storyboards = Dir.glob("**/*.storyboard")
+
+ unused_warnings += find_usages_in_files(all_files, xibs + storyboards, items, regexps)
+
+ if unused_warnings.length > 0
+ # show warning
+ puts "Unused Code Warning!: warning: Total Count #{unused_warnings.length}"
+ puts "#{unused_warnings.map { |e| e.to_xcode}.join("\n")}"
+ # write log
+ File.open("UnusedLog.txt", "w") do |file|
+ file.write("Unused code warnings count: #{unused_warnings.length}\n\n")
+ file.write("#{unused_warnings.map { |e| e.serialize }.join("\n")}")
+ end
+ end
+ end
+
+ def parse_arguments
+ resources = []
+
+ options = {}
+ OptionParser.new do |opts|
+ options[:exclude] = []
+
+ opts.on("-c", "--config=FileName") { |c| options[:config] = c }
+ opts.on("-i", "--exclude [a, b, c]", Array) { |i| options[:exclude] += i if !i.nil? }
+
+ end.parse!
+
+ # find --config file
+ if !options[:config].nil?
+ fileName = options[:config]
+ resources += YAML.load_file(fileName).fetch("excluded-resources")
+ elsif
+ puts "---------\n Warning: Config file is not provided \n---------"
+ end
+
+ # find --exclude files
+ if !options[:exclude].nil?
+ resources += options[:exclude]
+ end
+
+ # create and return Regexp
+ resources.map { |r| Regexp.new(r) }
+ end
+
+ def grab_items(file)
+ lines = File.readlines(file).map {|line| line.gsub(/^\s*\/\/.*/, "") }
+ items = lines.each_with_index.select { |line, i| line[/(func|let|var|class|enum|struct|protocol)\s+\w+/] }.map { |line, i| Item.new(file, line, i)}
+ end
+
+ def filter_items(items)
+ items.select { |f|
+ !f.name.start_with?("test") && !f.modifiers.include?("@IBAction") && !f.modifiers.include?("override") && !f.modifiers.include?("@objc") && !f.modifiers.include?("@IBInspectable")
+ }
+ end
+
+ # remove files, that maches excluded Regexps array
+ def ignore_files_with_regexps(files, regexps)
+ files.select { |f| regexps.all? { |r| r.match(f.file).nil? } }
+ end
+
+ def find_usages_in_files(files, xibs, items_in, regexps)
+ items = items_in
+ usages = items.map { |f| 0 }
+ files.each { |file|
+ lines = File.readlines(file).map {|line| line.gsub(/^[^\/]*\/\/.*/, "") }
+ words = lines.join("\n").split(/\W+/)
+ words_arrray = words.group_by { |w| w }.map { |w, ws| [w, ws.length] }.flatten
+
+ wf = Hash[*words_arrray]
+
+ items.each_with_index { |f, i|
+ usages[i] += (wf[f.name] || 0)
+ }
+ # Remove all items which has usage 2+
+ indexes = usages.each_with_index.select { |u, i| u >= 2 }.map { |f, i| i }
+
+ # reduce usage array if we found some functions already
+ indexes.reverse.each { |i| usages.delete_at(i) && items.delete_at(i) }
+ }
+
+ xibs.each { |xib|
+ lines = File.readlines(xib).map {|line| line.gsub(/^\s*\/\/.*/, "") }
+ full_xml = lines.join(" ")
+ classes = full_xml.scan(/(class|customClass)="([^"]+)"/).map { |cd| cd[1] }
+ classes_array = classes.group_by { |w| w }.map { |w, ws| [w, ws.length] }.flatten
+
+ wf = Hash[*classes_array]
+
+ items.each_with_index { |f, i|
+ usages[i] += (wf[f.name] || 0)
+ }
+ # Remove all items which has usage 2+
+ indexes = usages.each_with_index.select { |u, i| u >= 2 }.map { |f, i| i }
+
+ # reduce usage array if we found some functions already
+ indexes.reverse.each { |i| usages.delete_at(i) && items.delete_at(i) }
+
+ }
+
+ items = ignore_files_with_regexps(items, regexps)
+ end
+end
+
+
+Unused.new.find
diff --git a/xcode/build_phases/api_generator.sh b/xcode/build_phases/api_generator.sh
index 552a538..a16d254 100644
--- a/xcode/build_phases/api_generator.sh
+++ b/xcode/build_phases/api_generator.sh
@@ -6,4 +6,4 @@ link="https://dl.bintray.com/touchin/touchin-tools/ru/touchin/api-generator/${VE
. build-scripts/xcode/aux_scripts/download_file.sh ${FILE_NAME} ${link}
# execute api generator
-java -jar "Downloads/${FILE_NAME}" generate-client-code --output-language SWIFT --specification-path common/api --output-path ${PROJECT_NAME}/Generated --single-file true
+java -jar "Downloads/${FILE_NAME}" generate-client-code --output-language SWIFT --specification-path common/api --output-path ${PRODUCT_NAME}/Generated --single-file true
diff --git a/xcode/build_phases/localization.sh b/xcode/build_phases/localization.sh
index e363844..8c8bf61 100644
--- a/xcode/build_phases/localization.sh
+++ b/xcode/build_phases/localization.sh
@@ -1,4 +1,4 @@
-LOCALIZATION_PATH="${PROJECT_NAME}/Resources/Localization"
+LOCALIZATION_PATH="${PRODUCT_NAME}/Resources/Localization"
#first argument set strings folder path
STRINGS_FOLDER=${1:-"common/strings"}
@@ -13,4 +13,4 @@ if ! [ -e "${PROJECT_DIR}/${STRINGS_FOLDER}" ]; then
fi
#second argument set strings script path
-php ${2:-build-scripts/xcode/aux_scripts/import_strings.php} ${PROJECT_NAME} ${STRINGS_FOLDER}
+php ${2:-build-scripts/xcode/aux_scripts/import_strings.php} ${PRODUCT_NAME} ${STRINGS_FOLDER}
diff --git a/xcode/build_phases/unused.sh b/xcode/build_phases/unused.sh
new file mode 100644
index 0000000..5a0a906
--- /dev/null
+++ b/xcode/build_phases/unused.sh
@@ -0,0 +1,4 @@
+arguments=("$@")
+ignored_files=$(IFS=, ; echo "${arguments[*]}")
+
+ruby ${PROJECT_DIR}/build-scripts/xcode/build_phases/Unused.rb --config ${PROJECT_DIR}/build-scripts/xcode/UnusedConfig.yml --exclude ${ignored_files}
diff --git a/xcode/commonFastfile b/xcode/commonFastfile
index 040c3ab..6e8b230 100644
--- a/xcode/commonFastfile
+++ b/xcode/commonFastfile
@@ -9,6 +9,10 @@ private_lane :installDependencies do |options|
sh("rm -rf #{podsReposPath}")
end
+ if File.exists? "../Gemfile"
+ bundle_install(path: "../.gem")
+ end
+
if File.exists? "../Cartfile"
begin
carthage(command: "bootstrap", platform: "iOS")
@@ -20,7 +24,7 @@ private_lane :installDependencies do |options|
end
cocoapods(
- repo_update: false
+ repo_update: true
)
end
@@ -196,6 +200,76 @@ private_lane :openKeychain do |options|
end
end
+lane :ManuallyUpdateCodeSigning do |options|
+ # based on this article https://medium.com/@jonathancardoso/using-fastlane-match-with-existing-certificates-without-revoking-them-a325be69dac6
+ require 'fastlane_core'
+ require 'match'
+
+ conf = FastlaneCore::Configuration.create(Match::Options.available_options, {})
+ conf.load_configuration_file("Matchfile")
+
+ git_url = conf.config_file_options[:git_url]
+ shallow_clone = false
+ branch = 'fastlane_certificates'
+
+ storage_conf = lambda do
+ new_storage = Match::Storage.for_mode('git', { git_url: git_url, shallow_clone: shallow_clone, git_branch: branch, clone_branch_directly: false})
+ new_storage.download
+ return new_storage
+ end
+
+ encryption_conf_for_storage = lambda do |stor|
+ new_encryption = Match::Encryption.for_storage_mode('git', { git_url: git_url, working_directory: stor.working_directory})
+ new_encryption.decrypt_files
+ return new_encryption
+ end
+
+ get_all_files = lambda do |stor|
+ Dir[File.join(stor.working_directory, "**", "*.{cer,p12,mobileprovision}")]
+ end
+
+ storage = storage_conf.call
+ encryption = encryption_conf_for_storage.call(storage)
+ old_files = get_all_files.call(storage)
+
+ sh("open #{storage.working_directory}")
+
+ # we are not using prompt() since it requires non-empty input which is not a case for Enter (\n)
+ puts "Enter any key when you're done"
+ STDIN.gets
+
+ encryption.encrypt_files
+
+ files_to_commit = get_all_files.call(storage)
+ old_directory = storage.working_directory
+ storage.save_changes!(files_to_commit: files_to_commit)
+
+
+ # need to check, because saving changes with delete is another function (update repo if needed)
+ files_diff = old_files - files_to_commit
+
+ # match can not work with both save/delete functionality `You can't provide both files_to_delete and files_to_commit right now`
+ # to avoid this we use storage twice if needed
+
+ if files_diff.length > 0
+ storage = storage_conf.call
+ encryption = encryption_conf_for_storage.call(storage)
+
+ files_to_delete = files_diff.map do |file|
+ old_file = file
+ old_file.slice! old_directory
+ new_file = File.join(storage.working_directory, old_file)
+ File.delete(new_file) if File.exist?(new_file)
+ file = new_file
+ end
+
+ encryption.encrypt_files
+ storage.save_changes!(files_to_delete: files_to_delete)
+ end
+
+end
+
+
def get_keychain_options(options)
keychain_name = options[:keychain_name]
keychain_password = options[:keychain_password]