Setup

Unzip "main_files.zip", Download Xcode from App Store.

Prepare your enviroment by installing Cocoapods Dependency.

Drag and drop your project to terminal and run command:

          pod install
          

Open in Xcode "MailGen.xcworkspace"

Before running the project, first, please configure your project, find AppConfiguration.swift

And change all values to your own, don't use default added values:


struct AppConfiguration {
  // Change to your own app name
  static let appName = "MailGen"
  
  // Change to your own website url
  static let appWebsiteURL = "https://example.com"
  
  // Change to your own price
  static let appPrice = "0.99$"
  
  struct UI {
    static let cornerRadius: CGFloat = 10.0
  }

  struct ChatGPT {
    // Generate your own token here: https://platform.openai.com/account/api-keys
    static let authToken: String = "your token auth here"
    
    // All information can be found here: https://openai.com/api/pricing/
    /*
     Note:
     Prices are per 1,000 tokens. You can think of tokens as pieces of words, where 1,000 tokens is about 750 words.
     This paragraph is 35 tokens.
     */
    // Change to your own value
    static let maxTokens: Int = 100
  }
  
  struct Links {
    // Change to your own
    static let termsAndConditions = "https://example.com/terms-of-use.html"
    
    // Change to your own
    static let privacyPolicy = "https://example.com/privacy-policy.html"
  }
  
  struct Admob {
    // Change to your own admob banner id
    static let banner = "ca-app-pub-6388290447917792/5950492762"
    
    // Change to your own interstitial banner id
    static let interstitial = "ca-app-pub-6388290447917792/9986489438"
    
    // Change to your own rewarded banner id
    static let rewarded = "ca-app-pub-6388290447917792/3530329086"
  }
  
  struct LiteVersionChecks {
    // Maximum number of free emails - set your own value
    static let MaximumNumberOfFreeScans = 1
  }
  
  // In app purchases
  enum IAP: String {
    // Change this value to your own, just create a Non-Consumable App Store subscription.
    case premiumUnlimitedSubscription = "com.example.App.Premium"
  }
}

          

Start Debugger message:


      debugPrint("Thank you for purchasing this item! \nMister Grizzly\nhttps://mistergrizzly.com/")

      

Congratulations, Your project was builded! :)

Architecture

The app uses MVC architecture. More information can be found by folowing this link.


Every module, contains a UIViewController class and Storyboard file (with the same name). More information about how to use storyboards, can be found here.


Do not forget, to put a storyboard identifier, in case you'll create new screens.

In the Storyboard, select the view controller that you want to instantiate in code. Make sure the yellow circle is highlighted, and click on the Identity Inspector. Set the custom class as well as the field called "Storyboard ID". You can use the class name as the Storyboard ID.


More information about how to use storyboards identifiers, can be found here.


To initialize a controller, you'll have to call it like this:



let yourController = instantiateViewController(fromClass: MyNewViewController.self)
// Now, do everything with yourController. Remember, in order to avoid crashes, please set a storyboard identifier.

Splash Screen

"SplashViewController" class, is the main point of the app that presents home screen, which is MailGenViewController.



perform(#selector(navigateToMailGen), with: self, afterDelay: Constants.delay)

From here, all the data can be changed, like delay, or another screen that need to be presented from here.

MailGen Screen

"MailGenViewController" file, is the main controller of the app.

Everyghin regarding "MailGen" module, can be found inside of MailGen folder.

Here also have a "Camera" button, will open a camera to scan a single or multiple documents.

More information about what view models are:


// Preparing view models
func setupViewModels()
To display the tone or length cell, the app uses UICollectionViewDataSource: Learn more about UICollectionViewDataSource:

  private var toneDataSource: MailGenDataSource?
When user clicks on length or tone cell, we handle this using UICollectionViewDelegate: Learn more about UICollectionViewDelegate:

  private var lengthDataSource: MailGenDataSource?
   
The generate button by default is enabled. In this method, can be found some logic about:

func onGenerateTapped(_ sender: ...)
  

Result Screen

"ResultViewController" class, presents the result received on MailGenViewController after the enabled generate button is pressed.


private func onRegenerateTapped(...)

private func onSendTapped(...)

These two methods, are called when the regenerate or send button is tapped.

Settings Screen

"SettingsViewController" class, shows the app settings or other things related to the app. Things like remove ads, terms and conditions, privacy colicy, about.


SettingsViewModel

Check the SettingsViewModel and SettingsDataSource for more information.

To display the tone or length cell, the app uses UITableViewDataSource: Learn more about UITableViewDataSource:

When user clicks on a cell, we handle this using UITableViewDelegate: Learn more about UITableViewDelegate:

Loading Screen

"LoadingViewController" class, shows a spinner and a label. The app presents this screen when a network request happens.


DefaultVisualEffectView

Check the DefaultVisualEffectView to manage or lear more information about the blurred background.

ChatGPT

The whole ChatGPT business logic, is located in Common/ChatGPT folder. ChatGPT.swift is the main file which needs to be used in order to create requests.


MailGenManager.swift, is a wrapper, used around ChatGPT.swift. It has a simple method, that takes some parameters, which are sent via API to ChatGPT

Assign recognized text:

  /// Send a Completion to the OpenAI API
  /// - Parameters:
  ///   - prompt: The Text Prompt
  ///   - model: The AI Model to Use. Set to `OpenAIModelType.gpt3(.davinci)` by default which is the most capable model
  ///   - maxTokens: The limit character for the returned response, defaults to 16 as per the API
  ///   - completionHandler: Returns an OpenAI Data Model
  func generate(with instruction: MailGenInstruction,
                model: ChatGPTModelType = .gpt3(.davinci),
                maxTokens: Int,
                completion: @escaping (String?, Error?) -> Void)
  

ChatGPT vs. Davinci: Which is better?. Read here.

IAP

The app has included In app Purchase manager which uses API.

PurchaseManager contains all workaround IAP needed.

Setup (AppDelegate, didFinishLaunchingWithOptions method):

            PurchaseManager.shared.setupPurchases { success in
     if success {
       PurchaseManager.shared.getProducts()
     }
   }
}

Saving value when user has successfully become a pro:

Persistance (IAPPersistanceManager):

IAPPersistanceManager.shared.isProVersion = true or false

Checking if user becomed a pro:

PurchaseManager:

PurchaseManager.shared.isProVersionActive()

Admob

Admob Mobile Ads SDK (iOS) documentation.

PurchaseManager contains all workaround IAP needed.

Setup (AppDelegate, didFinishLaunchingWithOptions method):

            GoogleMobileAdsManager.default.setup()
          

Showing alert when user has finished his free scans:

Persistance:

  IAPPersistanceManager.shared.isProVersion = true or false
Check maximum number of scans:

class func hasMaximumNumberOfScans() -> Bool
          
Save (increase) rewarded bonus and using(decrease):

class func saveRewardedBonus(bonus: Int) 

class func decreaseNumberOfFreeScans()
UIViewController+Admob:

Replace the GoogleService-Info.plist file with your own. Create a firebase iOS app, register download and replace this file.


// Prepare the admob banner, usually in viewDidload method.
func setupAdmob(bannerView: GADBannerView, admobBannerID: String = AppConfiguration.Admob.banner)

// Register for the IAP notifications, usually in viewDidload method.
func registerIAPNotification(proTitle: String? = nil, liteTitle: String? = nil)

// Remove the IAP notifications, usually in deinit method.
func removeIAPNotification()

// Updates the premium UI with title PRO and Lite. MailGenViewController viewDidload.
func updatePremiumUI(proTitle: String?, liteTitle: String?)

// Shows the purchase alert from the settings.
func showPurchaseProAlert()

// Register for the reward admob, usually in viewDidload method.
func setupRewarded()

// Shows for the reward admob, usually in viewDidload method.
func presentRewarded(with delay: TimeInterval)
Be aware, if the user has already purchased the pro version, the app checks this like:

 guard !PurchaseManager.shared.isProVersionActive() else { return }

This avoid to register/run unuseful code.

Reskin

Assets.xcassets file, contains all the assets used. They also are prepared in UIImage+Extensions.swift.

Colors.xcassets contains all app colors. They also are prepared in UIColor+Extensions.swift.

Be free to modify the Colors.xcassets or Assets.xcassets, but do not change the name of the resources.

Localization is 50% ready (which means, no localization file is added, the default language is english. Almost the strings from the app are called by "some text.localized. And you can use it also:

extension String {
  var localized: String { logic }
}
          

UI Elements (be free to customize them)

To apply other things like cornerRadius, or shadow, check Extensions folder and files related to CALayer or UIView.