Commit 889b5118 authored by yqz's avatar yqz

引导

parent 8a0939ba
...@@ -218,13 +218,14 @@ ...@@ -218,13 +218,14 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = SpeakEasyLearnEnglish/SpeakEasyLearnEnglish.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 6K23946NQ5; DEVELOPMENT_TEAM = 6K23946NQ5;
ENABLE_USER_SCRIPT_SANDBOXING = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SpeakEasyLearnEnglish/Info.plist; INFOPLIST_FILE = SpeakEasyLearnEnglish/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "SpeakEasy: Learn English"; INFOPLIST_KEY_CFBundleDisplayName = "SpeakEasy AI";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
...@@ -235,7 +236,7 @@ ...@@ -235,7 +236,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.Widget.SpeakEasyLearnEnglish; PRODUCT_BUNDLE_IDENTIFIER = com.speakeasy.ai.ap;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
...@@ -252,13 +253,14 @@ ...@@ -252,13 +253,14 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = SpeakEasyLearnEnglish/SpeakEasyLearnEnglish.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 6K23946NQ5; DEVELOPMENT_TEAM = 6K23946NQ5;
ENABLE_USER_SCRIPT_SANDBOXING = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SpeakEasyLearnEnglish/Info.plist; INFOPLIST_FILE = SpeakEasyLearnEnglish/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "SpeakEasy: Learn English"; INFOPLIST_KEY_CFBundleDisplayName = "SpeakEasy AI";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
...@@ -269,7 +271,7 @@ ...@@ -269,7 +271,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.Widget.SpeakEasyLearnEnglish; PRODUCT_BUNDLE_IDENTIFIER = com.speakeasy.ai.ap;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
......
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon_translate_on@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "icon_translate_on@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon_vioce_off@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "icon_vioce_off@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Search@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Search@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Xmark@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Xmark@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "juxing3@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "juxing3@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "juxing2@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "juxing2@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "juxing2@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "juxing2@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Apple@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Apple@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon_mail_w@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "icon_mail_w@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon_mail_b@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "icon_mail_b@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Group 75@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Group 75@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon_Loading@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "icon_Loading@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "button@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "button@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "button_click@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "button_click@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
...@@ -17,7 +17,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ...@@ -17,7 +17,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
initData() initData()
// SpeakElePublicManager.share.PublicData.state = .home
#if DEBUG #if DEBUG
// // 获取所有可用字体家族名称 // // 获取所有可用字体家族名称
// let fontFamilyNames = UIFont.familyNames // let fontFamilyNames = UIFont.familyNames
......
...@@ -16,6 +16,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { ...@@ -16,6 +16,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
guard let windowScene = (scene as? UIWindowScene) else { return } guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene) window = UIWindow(windowScene: windowScene)
window?.rootViewController = SpeakEleLaunchViewCtr() window?.rootViewController = SpeakEleLaunchViewCtr()
window?.overrideUserInterfaceStyle = .light
window?.makeKeyAndVisible() window?.makeKeyAndVisible()
UIScrollView.appearance().contentInsetAdjustmentBehavior = .never UIScrollView.appearance().contentInsetAdjustmentBehavior = .never
UITableView.appearance().contentInsetAdjustmentBehavior = .never UITableView.appearance().contentInsetAdjustmentBehavior = .never
......
...@@ -33,6 +33,7 @@ class SpeakEleBaseViewCtr: UIViewController { ...@@ -33,6 +33,7 @@ class SpeakEleBaseViewCtr: UIViewController {
} }
deinit { deinit {
display.invalidate()
Print("\(type(of: self)) deinit") Print("\(type(of: self)) deinit")
} }
...@@ -48,6 +49,18 @@ class SpeakEleBaseViewCtr: UIViewController { ...@@ -48,6 +49,18 @@ class SpeakEleBaseViewCtr: UIViewController {
titleV.backBtn.addTarget(self, action: #selector(PopViewCtr), for: .touchUpInside) titleV.backBtn.addTarget(self, action: #selector(PopViewCtr), for: .touchUpInside)
return titleV return titleV
}() }()
lazy var display: CADisplayLink = {
let display = CADisplayLink(target: self, selector: #selector(SpeakUpdate))
display.add(to: RunLoop.main, forMode: .common)
display.preferredFramesPerSecond = 1
display.isPaused = true
return display
}()
@objc public func SpeakUpdate() -> Void {
}
} }
class SpeakTitleView: UIView { class SpeakTitleView: UIView {
......
...@@ -12,10 +12,10 @@ struct Dev { ...@@ -12,10 +12,10 @@ struct Dev {
static let screenH = UIScreen.main.bounds.size.height static let screenH = UIScreen.main.bounds.size.height
static let navHeight:CGFloat = 44 static let navHeight:CGFloat = 44
static let tabHeight = 49 static let tabHeight = 49
static let safeAreaInsets:UIEdgeInsets = window.safeAreaInsets static let safeAreaInsets:UIEdgeInsets = SpWindow.safeAreaInsets
} }
let window:UIWindow = { let SpWindow:UIWindow = {
if #available(iOS 13, *) { if #available(iOS 13, *) {
return UIApplication.shared.connectedScenes return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene } .compactMap { $0 as? UIWindowScene }
......
...@@ -14,7 +14,12 @@ let isDebug = false ...@@ -14,7 +14,12 @@ let isDebug = false
#endif #endif
let marginLR = 24 let marginLR:Double = 24
let SpeakIAPWeekIdentifier = "com.speakeasy.ai.ap.week"
let SpeakIAPYearIdentifier = "com.speakeasy.ai.ap.year"
// MARK: - 所有分类属性 Key // MARK: - 所有分类属性 Key
enum SpeakEasyAssociatedKeys : UInt32 { enum SpeakEasyAssociatedKeys : UInt32 {
...@@ -38,4 +43,12 @@ protocol CutomFontProtocol : NSObjectProtocol { ...@@ -38,4 +43,12 @@ protocol CutomFontProtocol : NSObjectProtocol {
enum UnsafeRawUserDefaultsKey:String { enum UnsafeRawUserDefaultsKey:String {
case UnsafeThreeDayGuide = "UnsafeThreeDayGuide" case UnsafeThreeDayGuide = "UnsafeThreeDayGuide"
case UnsafePublicDataKey = "UnsafePublicDataKey" case UnsafePublicDataKey = "UnsafePublicDataKey"
case UnsafeGuideQAKey = "UnsafeGuideQAKey"
}
// MARK: - Notification Names
extension Notification.Name {
static let subscriptionStatusChanged = Notification.Name("IAPSubscriptionStatusChanged")
static let subscriptionVerify = Notification.Name("IAPSubscriptionStatusVerify")
} }
//
// Date+Extension.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/17.
//
import UIKit
extension Date {
public func toStr(_ formatter:String = "yyyyMMdd") -> String? {
let format = DateFormatter()
format.dateFormat = formatter
return format.string(from: self)
}
}
...@@ -10,6 +10,46 @@ import UIKit ...@@ -10,6 +10,46 @@ import UIKit
extension String { extension String {
func loadData() -> Data? {
let path = Bundle.main.bundleURL.appendingPathComponent("\(self).json")
do{
return try Data(contentsOf: path)
}catch{
return Data()
}
}
func size(withFont font: UIFont, constrainedToWidth width: CGFloat = .greatestFiniteMagnitude) -> CGSize {
let attributes = [NSAttributedString.Key.font: font]
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(
with: constraintRect,
options: [.usesLineFragmentOrigin, .usesFontLeading],
attributes: attributes,
context: nil
)
return CGSize(width: ceil(boundingBox.width), height: ceil(boundingBox.height))
}
/// 去掉首尾的特殊符号
func whitespacesAndNew() -> String {
return self.trimmingCharacters(in: .whitespacesAndNewlines)
}
/// 计算字符串在指定字体和约束高度下的显示尺寸
func size(withFont font: UIFont, constrainedToHeight height: CGFloat) -> CGSize {
let attributes = [NSAttributedString.Key.font: font]
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(
with: constraintRect,
options: [.usesLineFragmentOrigin, .usesFontLeading],
attributes: attributes,
context: nil
)
return CGSize(width: ceil(boundingBox.width), height: ceil(boundingBox.height))
}
/// 判断是否是邮箱
func isValidEmail() -> Bool { func isValidEmail() -> Bool {
let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}" let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex) let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
...@@ -50,7 +90,6 @@ extension NSAttributedString { ...@@ -50,7 +90,6 @@ extension NSAttributedString {
result.append(v) result.append(v)
} }
} }
return result return result
} }
} }
......
...@@ -35,4 +35,20 @@ extension UIColor { ...@@ -35,4 +35,20 @@ extension UIColor {
} }
} }
public func generate(_ size:CGSize = CGSize(width: 1, height: 1),
_ cornerRadius:CGFloat = 0) -> UIImage? {
let rect = CGRect(origin: .zero, size: size)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
if cornerRadius > 0 {
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius/2.0)
path.addClip()
context.addPath(path.cgPath)
}
context.setFillColor(self.cgColor)
context.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
} }
...@@ -11,10 +11,9 @@ import CoreText ...@@ -11,10 +11,9 @@ import CoreText
fileprivate var textTapState = SpeakEasyAssociatedKeys.labelTextTap.rawValue fileprivate var textTapState = SpeakEasyAssociatedKeys.labelTextTap.rawValue
@IBDesignable @IBDesignable
extension UILabel : CutomFontProtocol { extension UILabel {
/// 设置文字间距 /// 设置文字间距
func lineSpacing(_ lineSpacing:CGFloat = 5) { func lineSpacing(_ lineSpacing:CGFloat = 5) {
let style = NSMutableParagraphStyle() let style = NSMutableParagraphStyle()
...@@ -62,50 +61,50 @@ extension UILabel : CutomFontProtocol { ...@@ -62,50 +61,50 @@ extension UILabel : CutomFontProtocol {
} }
@objc private func handleTap(_ gesture: UITapGestureRecognizer) { @objc private func handleTap(_ gesture: UITapGestureRecognizer) {
guard let attributedText = attributedText else { return } // 确保有文本和布局
let framesetter = CTFramesetterCreateWithAttributedString(attributedText as CFAttributedString) let attributedText = self.attributedText ?? NSAttributedString(string: text ?? "")
let path = CGMutablePath() if attributedText.string.count <= 0 {
path.addRect(bounds) return
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil) }
let location = gesture.location(in: self)
let lines = CTFrameGetLines(frame) as! [CTLine] let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: bounds.size)
let textStorage = NSTextStorage(attributedString: attributedText)
var lineOrigins = [CGPoint](repeating: .zero, count: lines.count) textContainer.lineFragmentPadding = 0
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), &lineOrigins) textContainer.maximumNumberOfLines = numberOfLines
for (index, line) in lines.enumerated() { textContainer.lineBreakMode = lineBreakMode
let lineOrigin = lineOrigins[index]
var ascent: CGFloat = 0 layoutManager.addTextContainer(textContainer)
var descent: CGFloat = 0 textStorage.addLayoutManager(layoutManager)
var leading: CGFloat = 0
let lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &leading) let textRect = self.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
let lineHeight = ascent + descent + leading
let lineTop = lineOrigin.y var adjustedPoint = gesture.location(in: self)
let lineBottom = lineOrigin.y + lineHeight switch textAlignment {
if location.y >= lineTop && location.y <= lineBottom { case .center:
let xOffset: CGFloat adjustedPoint.x -= (bounds.width - textRect.width) / 2
switch textAlignment { case .right, .justified, .natural:
case .left, .natural, .justified: adjustedPoint.x -= (bounds.width - textRect.width)
xOffset = lineOrigin.x default:
case .center: break
xOffset = lineOrigin.x + (bounds.width - lineWidth) / 2 }
case .right: let characterIndex = layoutManager.characterIndex(for: adjustedPoint,
xOffset = lineOrigin.x + bounds.width - lineWidth in: textContainer,
@unknown default: fractionOfDistanceBetweenInsertionPoints: nil)
xOffset = lineOrigin.x for (_,v) in textTaps.enumerated() {
} if let range = attributedText.string.range(of: v) {
let offsetInLine = location.x - xOffset let rangeN = NSRange(range, in: attributedText.string)
let indexOfCharacter = CTLineGetStringIndexForPosition(line, CGPoint(x: offsetInLine, y: location.y))
for targetText in textTaps { if characterIndex < (rangeN.location + rangeN.length) && characterIndex >= rangeN.location {
if let range = attributedText.string.range(of: targetText) { guard let callblack = callblack else { return }
let nsRange = NSRange(range, in: attributedText.string) Print(characterIndex,attributedText.string.count)
if indexOfCharacter >= nsRange.location && indexOfCharacter < nsRange.location + nsRange.length { callblack(v)
return return
}
}
} }
break
} }
} }
} }
} }
...@@ -159,13 +159,24 @@ extension UIView : CutomFontProtocol { ...@@ -159,13 +159,24 @@ extension UIView : CutomFontProtocol {
} }
/** 设置阴影 */ /** 设置阴影 */
public func shadow(cornerRadius:CGFloat = 4, shadowColor:UIColor = .clear, offset:CGSize = CGSize(width: 0, height: 0), opacity:Float = 1, radius:CGFloat = 0) { public func shadow(cornerRadius:CGFloat = 4, shadowColor:UIColor = .clear, offset:CGSize = CGSize(width: 0, height: 0), opacity:Float = 1, radius:CGFloat = 0 ,_ path:CGPath? = nil) {
self.layer.masksToBounds = false self.layer.masksToBounds = false
self.layer.shadowColor = shadowColor.cgColor self.layer.shadowColor = shadowColor.cgColor
self.layer.shadowOpacity = opacity self.layer.shadowOpacity = opacity
self.layer.shadowRadius = radius
self.layer.shadowOffset = offset self.layer.shadowOffset = offset
self.layer.cornerRadius = cornerRadius if path == nil {
self.layer.shadowRadius = radius
self.layer.cornerRadius = cornerRadius
}else{
self.layer.shadowPath = path
}
}
public func shadowPath(radius:CGFloat = 0 , shadowColor:UIColor = .clear,offset:CGSize = CGSize() ,opacity:Float = 1,path:CGPath) -> Void {
layer.shadowColor = shadowColor.cgColor
layer.shadowOffset = offset
layer.shadowOpacity = opacity
layer.shadowPath = path
} }
/// 添加渐变 /// 添加渐变
......
...@@ -55,6 +55,25 @@ struct PublicModel : Codable { ...@@ -55,6 +55,25 @@ struct PublicModel : Codable {
var state:guideState = .start var state:guideState = .start
var token:String = ""
var userName:String = ""
/// 母语
var MLanguage:String = ""
/// 其它语言
var LikeLanguage:String = ""
/// 爱好
var MHobby:String = ""
/// 改善
var MImproving:String = ""
/// 水平
var MProficiency:String = ""
/// 目标时间
var MGoalDays:String = ""
/// 学习时间
var MStudytime:String = ""
/// 练习时间
var MPractice:String = ""
} }
......
...@@ -10,9 +10,32 @@ import UIKit ...@@ -10,9 +10,32 @@ import UIKit
class SpeakEleHomeViewCtr: SpeakEleBaseViewCtr { class SpeakEleHomeViewCtr: SpeakEleBaseViewCtr {
@IBOutlet weak var PlayerView: UIView!
@IBOutlet weak var inputText: UITextField!
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
} }
@IBAction func voice(_ sender: Any) {
self.view.layoutIfNeeded()
let sss = ["Practice Sanfu in summer and Sanjiu in winter, but on hot and humid Sanfu days, a slight movement can cause sweating and discomfort to the body.So what are the recommendations for exercising during the dog days when you want to maintain your health but don't want to sweat too much?","Hello, let's start learning the first lesson"]
let i = 0
let text = sss[i]//inputText.text ?? ""
let playerLayer = SpeakVideoPlayer.share.playerLayers
playerLayer?.frame = PlayerView.bounds
PlayerView.layer.addSublayer(playerLayer ?? CALayer())
let sb = [15.07,2.65]
let secend = sb[i]
let num = ceil(secend / 4.3)
let rate = (num * 4.3) / secend
SpeakVideoPlayer.share.videoPlayer(rate,playCount: Int(num))
SpeakSpeechSynthesizer.share.speakText(text)
}
} }
...@@ -5,11 +5,14 @@ ...@@ -5,11 +5,14 @@
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SpeakEleHomeViewCtr" customModule="SpeakEasyLearnEnglish" customModuleProvider="target"> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SpeakEleHomeViewCtr" customModule="SpeakEasyLearnEnglish" customModuleProvider="target">
<connections> <connections>
<outlet property="PlayerView" destination="jZl-V1-ZlM" id="NAM-XZ-wEr"/>
<outlet property="inputText" destination="W3R-0H-fep" id="HP3-d9-aiS"/>
<outlet property="view" destination="Ia2-3z-Nib" id="Fy8-sN-P1j"/> <outlet property="view" destination="Ia2-3z-Nib" id="Fy8-sN-P1j"/>
</connections> </connections>
</placeholder> </placeholder>
...@@ -17,9 +20,50 @@ ...@@ -17,9 +20,50 @@
<view contentMode="scaleToFill" id="Ia2-3z-Nib"> <view contentMode="scaleToFill" id="Ia2-3z-Nib">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/> <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="248" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="bezel" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="W3R-0H-fep">
<rect key="frame" x="23" y="521" width="347" height="55"/>
<constraints>
<constraint firstAttribute="height" constant="55" id="S4g-gl-gR4"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jZl-V1-ZlM">
<rect key="frame" x="0.0" y="118" width="393" height="393"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="width" secondItem="jZl-V1-ZlM" secondAttribute="height" id="fYx-ZS-O6I"/>
</constraints>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="y0g-g8-kpr">
<rect key="frame" x="23" y="599" width="347" height="35"/>
<state key="normal" title="Button"/>
<buttonConfiguration key="configuration" style="plain" title="Button"/>
<connections>
<action selector="voice:" destination="-1" eventType="touchUpInside" id="9ht-YX-XRD"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="rqH-oW-agY"/> <viewLayoutGuide key="safeArea" id="rqH-oW-agY"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="rqH-oW-agY" firstAttribute="trailing" secondItem="jZl-V1-ZlM" secondAttribute="trailing" id="0C1-Qf-cu5"/>
<constraint firstItem="y0g-g8-kpr" firstAttribute="top" secondItem="W3R-0H-fep" secondAttribute="bottom" constant="23" id="AbR-Sa-G7K"/>
<constraint firstItem="jZl-V1-ZlM" firstAttribute="leading" secondItem="rqH-oW-agY" secondAttribute="leading" id="IDT-H0-Hha"/>
<constraint firstItem="rqH-oW-agY" firstAttribute="trailing" secondItem="W3R-0H-fep" secondAttribute="trailing" constant="23" id="OSJ-Zq-Ndc"/>
<constraint firstItem="jZl-V1-ZlM" firstAttribute="top" secondItem="rqH-oW-agY" secondAttribute="top" id="U55-vL-hVd"/>
<constraint firstItem="W3R-0H-fep" firstAttribute="leading" secondItem="rqH-oW-agY" secondAttribute="leading" constant="23" id="Wcy-5C-4HJ"/>
<constraint firstItem="y0g-g8-kpr" firstAttribute="leading" secondItem="rqH-oW-agY" secondAttribute="leading" constant="23" id="Y5w-Ks-aOc"/>
<constraint firstItem="W3R-0H-fep" firstAttribute="top" secondItem="jZl-V1-ZlM" secondAttribute="bottom" constant="10" id="dff-yJ-XwP"/>
<constraint firstItem="rqH-oW-agY" firstAttribute="trailing" secondItem="y0g-g8-kpr" secondAttribute="trailing" constant="23" id="jzE-Ji-lv1"/>
</constraints>
<point key="canvasLocation" x="519.84732824427476" y="-37.323943661971832"/> <point key="canvasLocation" x="519.84732824427476" y="-37.323943661971832"/>
</view> </view>
</objects> </objects>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document> </document>
//
// SpeakEleGeneratePlanViewCtr.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/17.
//
import UIKit
class SpeakEleGeneratePlanViewCtr: SpeakEleBaseViewCtr {
// MARK: - ib
@IBOutlet weak var SpeakStackView: UIStackView!
@IBOutlet weak var SpeakContinueBtn: UIButton!
private var offset:Int = 0
private var SpeakPlanChilds:[SpeakEleGeneratePlanView] = [
SpeakEleGeneratePlanView(string: "Creating diverse topics"),
SpeakEleGeneratePlanView(string: "Preparing interactive dialogues"),
SpeakEleGeneratePlanView(string: "Optimizing your learning path"),
SpeakEleGeneratePlanView(string: "Finalizing your plan")
]
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
SpeakPlanChilds.forEach { plan in
SpeakStackView.addArrangedSubview(plan)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
titleView.isHidden = true
start()
}
@IBAction func SpeakCountiueTaps(_ sender: Any) {
//FIXME: 判断是否订阅和登录
if true {
let iap = SpeakEleIAPViewCtr()
iap.state = .plan
self.navigationController?.AnimationState = .present
self.navigationController?.pushViewController(iap, animated: true)
}else if SpeakElePublicManager.share.PublicData.token.count <= 0 {
let login = SpeakEleFLoginViewCtr()
self.navigationController?.pushViewController(login, animated: true)
}else{
let nav = SpeakEleBaseNavigationCtr(rootViewController: SpeakEleTabbarViewCtr())
SpWindow.switchToController(nav)
}
}
private func start() -> Void {
offset = 0
display.preferredFramesPerSecond = 10
display.isPaused = false
}
override func SpeakUpdate() {
offset += 3//step
switch offset {
case 0..<100:
let child = SpeakPlanChilds.first
child?.state = .start
child?.startRotation()
child?.offset = offset
break
case 100..<200:
let child = SpeakPlanChilds[0]
child.stop()
child.offset = 100
child.state = .finish
let child1 = SpeakPlanChilds[1]
child1.state = .start
child1.startRotation()
child1.offset = offset-100
break
case 200..<300:
let child = SpeakPlanChilds[1]
child.stop()
child.offset = 100
child.state = .finish
let child2 = SpeakPlanChilds[2]
child2.state = .start
child2.startRotation()
child2.offset = offset-200
break
default:
let child = SpeakPlanChilds[2]
child.stop()
child.offset = 100
child.state = .finish
let child1 = SpeakPlanChilds[3]
child1.state = .start
child1.startRotation()
child1.offset = offset-300
if offset - 300 > 100 {
child1.offset = 100
child1.state = .finish
UIView.animate(withDuration: 0.1, delay: 0.5) {
self.SpeakContinueBtn.alpha = 1
}
display.isPaused = true
}
break
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SpeakEleGeneratePlanViewCtr" customModule="SpeakEasyLearnEnglish" customModuleProvider="target">
<connections>
<outlet property="SpeakContinueBtn" destination="8IG-gE-laz" id="Hoj-OU-rje"/>
<outlet property="SpeakStackView" destination="Hk4-d1-82s" id="dkq-q1-vsU"/>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="personalizing your learning plan···" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sU1-Vw-IvM">
<rect key="frame" x="24" y="148" width="345" height="20.333333333333343"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="cutomFont">
<integer key="value" value="2"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="fontSize">
<real key="value" value="24"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</label>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XWC-Q3-vW4">
<rect key="frame" x="24" y="221" width="345" height="240"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" preservesSuperviewLayoutMargins="YES" axis="vertical" distribution="equalSpacing" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="Hk4-d1-82s">
<rect key="frame" x="0.0" y="20" width="345" height="200"/>
</stackView>
</subviews>
<constraints>
<constraint firstItem="Hk4-d1-82s" firstAttribute="leading" secondItem="XWC-Q3-vW4" secondAttribute="leading" id="1KL-DI-u4C"/>
<constraint firstAttribute="bottom" secondItem="Hk4-d1-82s" secondAttribute="bottom" id="J9w-f2-Mq6"/>
<constraint firstAttribute="trailing" secondItem="Hk4-d1-82s" secondAttribute="trailing" id="Zo2-pm-Kp3"/>
<constraint firstItem="Hk4-d1-82s" firstAttribute="centerX" secondItem="XWC-Q3-vW4" secondAttribute="centerX" id="dMj-km-r8Z"/>
<constraint firstItem="Hk4-d1-82s" firstAttribute="top" secondItem="XWC-Q3-vW4" secondAttribute="top" id="oS6-iT-oFY"/>
</constraints>
</scrollView>
<button opaque="NO" alpha="0.0" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8IG-gE-laz">
<rect key="frame" x="24" y="673.66666666666663" width="345" height="62.333333333333371"/>
<constraints>
<constraint firstAttribute="width" secondItem="8IG-gE-laz" secondAttribute="height" multiplier="354:64" id="c2m-Ie-oFu"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="Continue" backgroundImage="icon_Plan_NorButton"/>
<state key="highlighted" backgroundImage="icon_Plan_button_click"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="cutomFont">
<integer key="value" value="1"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="fontSize">
<real key="value" value="16"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
<connections>
<action selector="SpeakCountiueTaps:" destination="-1" eventType="touchUpInside" id="xA0-5T-bS6"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="8IG-gE-laz" firstAttribute="bottom" secondItem="fnl-2z-Ty3" secondAttribute="bottom" constant="-48" id="O1g-Dm-HQ8"/>
<constraint firstItem="XWC-Q3-vW4" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="24" id="X8c-Uf-cEo"/>
<constraint firstItem="sU1-Vw-IvM" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" constant="30" id="XZQ-VA-vXg"/>
<constraint firstItem="8IG-gE-laz" firstAttribute="top" secondItem="XWC-Q3-vW4" secondAttribute="bottom" constant="20" id="io0-0R-c9G"/>
<constraint firstItem="sU1-Vw-IvM" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="24" id="kB6-Tm-cGx"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="XWC-Q3-vW4" secondAttribute="trailing" constant="24" id="nCw-rb-IKC"/>
<constraint firstItem="8IG-gE-laz" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="24" id="njY-IA-7La"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="sU1-Vw-IvM" secondAttribute="trailing" constant="24" id="tFi-0S-sxu"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="8IG-gE-laz" secondAttribute="trailing" constant="24" id="xjF-kR-WkW"/>
<constraint firstItem="XWC-Q3-vW4" firstAttribute="top" secondItem="sU1-Vw-IvM" secondAttribute="bottom" constant="40" id="yLx-Vc-uxo"/>
</constraints>
<point key="canvasLocation" x="132" y="-11"/>
</view>
</objects>
<resources>
<image name="icon_Plan_NorButton" width="354" height="64"/>
<image name="icon_Plan_button_click" width="354" height="60"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
//
// SpeakEleGeneratePlanView.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/17.
//
import UIKit
class SpeakEleGeneratePlanView: UIView {
enum generatePlanState {
case none
case start
case finish
}
private var Plan:String = ""
var state:generatePlanState = .none
var offset:Int = 0 {
didSet{
DispatchQueue.main.async {
if self.state == .none {
}else if self.state == .start {
self.info.isHidden = false
self.SpeakOffsetL.text = "\(self.offset)%"
self.ProgressV.snp.remakeConstraints({ make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(self.SpeakL.snp.bottom).offset(10)
make.height.equalTo(self.ProgressV.snp.width).multipliedBy(60.0/338.0)
})
UIView.animate(withDuration: 0.01) {
self.animationLayer.frame = CGRectMake(0, 0, self.ProgressV.width * (CGFloat(self.offset) / 100.0), self.ProgressV.height)
}
}else{
self.SpeakOffsetL.isHidden = true
self.ProgressV.snp.remakeConstraints({ make in
make.height.equalTo(12)
make.left.right.bottom.equalToSuperview()
make.top.equalTo(self.SpeakL.snp.bottom).offset(10)
})
self.animationLayer.frame = CGRectMake(0, 0, self.ProgressV.width, self.ProgressV.height)
self.stop()
}
}
}
}
init(string:String) {
self.Plan = string
super.init(frame: CGRect())
self.setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() -> Void {
SpeakL.snp.makeConstraints { make in
make.left.top.equalToSuperview()
make.height.greaterThanOrEqualTo(20)
make.right.equalTo(info.snp.left).inset(10)
}
info.snp.makeConstraints { make in
make.right.equalToSuperview()
make.size.equalTo(CGSizeMake(20, 20))
}
ProgressV.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(SpeakL.snp.bottom).offset(10)
make.height.equalTo(12)
}
SpeakOffsetL.snp.makeConstraints { make in
make.left.equalTo(ProgressV.snp.left).offset(20)
make.centerY.equalTo(ProgressV.snp.centerY)
}
}
private lazy var SpeakL: UILabel = {
let l = UILabel()
l.font = UIFont.montserrat(.semiBold,size: 18)
l.textColor = .init(hex: 0x000000 ,alpha: 0.9)
l.text = Plan
l.numberOfLines = 0
addSubview(l)
return l
}()
private lazy var info: UIImageView = {
let info = UIImageView(image: UIImage(named: "icon_Plan_Loading"))
info.contentMode = .scaleAspectFill
info.isHidden = true
addSubview(info)
return info
}()
private lazy var ProgressV: UIView = {
let v = UIView()
v.corners = 6
v.backgroundColor = .init(hex: 0x6B99E3,alpha: 0.1)
addSubview(v)
v.clipsToBounds = true
return v
}()
private lazy var SpeakOffsetL: UILabel = {
let l = UILabel()
l.font = UIFont.montserrat(.semiBold,size: 28)
l.textColor = .white
l.numberOfLines = 0
addSubview(l)
return l
}()
func startRotation() -> Void {
if info.layer.animationKeys()?.count ?? 0 <= 0{
let rotation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.toValue = NSNumber(value: .pi * 2.0) // 旋转 360 度
rotation.duration = 1
rotation.repeatCount = .infinity // 无限旋转
rotation.isRemovedOnCompletion = false
rotation.fillMode = .forwards
info.layer.add(rotation, forKey: "rotationAnimation")
}
}
func stop() -> Void {
info.layer.removeAllAnimations()
info.image = UIImage(named: "icon_Plan_Finish")
}
private lazy var animationLayer: CALayer = {
let l = CALayer()
l.backgroundColor = UIColor.init(hex: 0x3980F6).cgColor
ProgressV.layer.insertSublayer(l, at: 1)
return l
}()
override func layoutSubviews() {
super.layoutSubviews()
self.layoutIfNeeded()
animationLayer.frame = CGRectMake(0, 0, ProgressV.width * (CGFloat(offset) / 100.0), ProgressV.height)
}
}
...@@ -38,6 +38,7 @@ class SpeakEleGuideViewCtr: SpeakEleBaseViewCtr ,UIScrollViewDelegate { ...@@ -38,6 +38,7 @@ class SpeakEleGuideViewCtr: SpeakEleBaseViewCtr ,UIScrollViewDelegate {
if isThree { if isThree {
SpeakElePublicManager.share.PublicData.state = .subscribe SpeakElePublicManager.share.PublicData.state = .subscribe
let iap = SpeakEleIAPViewCtr() let iap = SpeakEleIAPViewCtr()
iap.state = .guide
self.navigationController?.AnimationState = .present self.navigationController?.AnimationState = .present
self.navigationController?.pushViewController(iap, animated: true) self.navigationController?.pushViewController(iap, animated: true)
} }
......
//
// SpeakEleQAMoreViewCtr.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/16.
//
import UIKit
class SpeakEleQAMoreViewCtr: SpeakEleBaseViewCtr {
private var items:[String] = []
private var callblock:((String)->Void)?
// MARK: - ib
@IBOutlet weak var SpeakTitleL: UILabel!
@IBOutlet weak var ContentV: UIView!
@IBOutlet weak var SpeakInputBox: SpeakResizableTextView!
@IBOutlet weak var SpeakMoreTableView: UITableView!
private let viewModel = SpeakEleQAViewModel()
var state:SpeakMessageType = .language
init(items: [String] ,state:SpeakMessageType) {
self.items = items
self.state = state
super.init(nibName: "SpeakEleQAMoreViewCtr", bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .init(hex: 0x000000,alpha: 0.5)
viewModel.fetchData()
titleView.isHidden = true
}
override func setup() -> Void {
SpeakMoreTableView.delegate = self
SpeakMoreTableView.dataSource = self
SpeakMoreTableView.sectionHeaderTopPadding = 0
SpeakMoreTableView.rowHeight = 50
SpeakMoreTableView.register(SpeakMoreTableViewCell.self, forCellReuseIdentifier: SpeakMoreTableViewCell.id)
SpeakInputBox.onTextChanged = {[weak self] string in
self?.filter()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if state == .language {
SpeakInputBox.placeholder = "Enter the language"
SpeakTitleL.text = "More Language"
}
if state == .language {
items = viewModel.SpeakLanguages
}else if state == .hobby{
items = viewModel.SpeakHoby
}
filter()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
ContentV.cornerRect(radius: 16, [.topLeft,.topRight])
}
func show(_ complate:@escaping(String)->Void) -> Void {
self.callblock = complate
self.modalPresentationStyle = .overFullScreen
SpWindow.rootViewController?.present(self, animated: true)
}
@IBAction func closeTaps(_ sender: Any) {
self.dismiss(animated: true)
}
@IBAction func SpeakSearch(_ sender: Any) {
}
private func filter() -> Void {
let search = SpeakInputBox.text ?? ""
if search.count > 0 {
filterItem = items.filter({$0.contains(search)})
}else{
filterItem = items
}
}
var filterItem:[String] = [] {
didSet{
self.SpeakMoreTableView.reloadData()
}
}
}
extension SpeakEleQAMoreViewCtr : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filterItem.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: SpeakMoreTableViewCell.id, for: indexPath) as! SpeakMoreTableViewCell
cell.titleL.text = filterItem[safe: indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let call = callblock else { return }
let sts = filterItem[indexPath.row]
self.dismiss(animated: true) {
call(sts)
}
}
}
class SpeakMoreTableViewCell: UITableViewCell {
static let id = "SpeakMoreTableViewCell"
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() -> Void {
titleL.snp.makeConstraints { make in
make.left.right.bottom.top.equalToSuperview()
}
}
lazy var titleL: UILabel = {
let t = UILabel()
t.textColor = .black
t.font = UIFont.montserrat(.medium,size: 16)
t.textAlignment = .center
contentView.addSubview(t)
return t
}()
}
...@@ -11,11 +11,18 @@ class SpeakEleThrDaysFreeViewCtr: SpeakEleBaseViewCtr { ...@@ -11,11 +11,18 @@ class SpeakEleThrDaysFreeViewCtr: SpeakEleBaseViewCtr {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.toIAP() self.toIAP()
} }
} }
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
titleView.isHidden = true
}
private func toIAP() -> Void { private func toIAP() -> Void {
SpeakElePublicManager.share.PublicData.state = .subscribe SpeakElePublicManager.share.PublicData.state = .subscribe
let iap = SpeakEleIAPViewCtr() let iap = SpeakEleIAPViewCtr()
......
...@@ -10,6 +10,7 @@ import UIKit ...@@ -10,6 +10,7 @@ import UIKit
enum SpeakRoleState : Int,Codable{ enum SpeakRoleState : Int,Codable{
case speakAI = 0 case speakAI = 0
case speakUser = 1 case speakUser = 1
case speakNone = 2
} }
enum SpeakMessageType:Int,Codable { enum SpeakMessageType:Int,Codable {
...@@ -30,6 +31,7 @@ enum SpeakMessageType:Int,Codable { ...@@ -30,6 +31,7 @@ enum SpeakMessageType:Int,Codable {
struct SpeakEleQAModel : Codable { struct SpeakEleQAModel : Codable {
var speakMsg:String = "" var speakMsg:String = ""
var transtr:String = ""
var role:SpeakRoleState = .speakAI var role:SpeakRoleState = .speakAI
var messageType:SpeakMessageType = .none var messageType:SpeakMessageType = .none
......
//
// SpeakEleSelectBoxViewCell.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/15.
//
import UIKit
class SpeakEleMutabSelectBoxViewCell: SpeakEleQABaseViewCell {
static let id = "SpeakEleMutabSelectBoxViewCell"
var type:SpeakMessageType = .language
private var items:[UIButton] = []
var selectSets = NSMutableSet()
var change = false
var max = 7
var isMore = true
var datastr:[String] = [] {
didSet {
bgView.type = .speakNone
let len = min(datastr.count, max)
var btns:[String] = datastr[0..<len].map({String($0)})
if !isMore {
btns = datastr
}
self.layoutIfNeeded()
if items.count != btns.count || change {
change = false
contentV.subviews.forEach { view in
if view is SpeakEleImageFill {
}else{
view.removeFromSuperview()
}
}
items.removeAll()
let height:CGFloat = 30
let content = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
var offsetX:CGFloat = content.left ,offsetY:CGFloat = content.top
let spaing:CGFloat = 10;
for (i,str) in btns.enumerated() {
let btn = UIButton(type: .custom)
btn.setTitle(str, for: .normal)
btn.titleLabel?.font = UIFont.montserrat(.regular,size: 16)
btn.setTitleColor(.black, for: .normal)
btn.corners = 8
btn.borderWidth = 1
btn.borderColor = .init(hex: 0x6B71E3)
btn.addTarget(self, action: #selector(btnTaps(_:)), for: .touchUpInside)
contentV.addSubview(btn)
items.append(btn)
btn.tag = i
let size = str.size(withFont: UIFont.montserrat(.regular,size: 16))
let width = size.width + 10.0
if ((offsetX + width + content.right) > contentV.width) {
offsetX = content.left
offsetY += (height+spaing)
}
btn.snp.makeConstraints { make in
make.left.equalToSuperview().inset(offsetX)
make.top.equalToSuperview().inset(offsetY)
make.size.equalTo(CGSize(width: width, height: height))
if !isMore && i == (btns.count-1) {
make.bottom.equalToSuperview().inset(16)
}
}
offsetX += (width + spaing)
}
if isMore {
let more = UIButton(type: .custom)
more.setTitle("More", for: .normal)
more.titleLabel?.font = UIFont.montserrat(.regular,size: 16)
more.setTitleColor(.init(hex: 0x6B71E3), for: .normal)
more.backgroundColor = .init(hex: 0x6B71E3,alpha: 0.2)
more.corners = 8
more.addTarget(self, action: #selector(moreTaps), for: .touchUpInside)
contentV.addSubview(more)
let size = "More".size(withFont: UIFont.montserrat(.regular,size: 16))
let width = size.width + 10.0
if ((offsetX + width + content.right) > contentV.width) {
offsetX = content.left
offsetY += (height+spaing)
}
more.snp.makeConstraints { make in
make.left.equalToSuperview().inset(offsetX)
make.top.equalToSuperview().inset(offsetY)
make.size.equalTo(CGSize(width: width, height: height))
make.bottom.equalToSuperview().inset(16)
}
}
}
}
}
override func setup() -> Void {
bgView.snp.makeConstraints { make in
make.left.right.bottom.top.equalToSuperview().inset(UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10))
}
contentV.snp.makeConstraints { make in
make.right.top.bottom.equalToSuperview().inset(UIEdgeInsets(top: 2, left: 0, bottom: 16, right: (marginLR)))
make.width.equalTo(Dev.screenW * 0.6 )
}
}
override func reload() {
self.setNeedsLayout()
self.layoutIfNeeded()
items.forEach { btn in
if self.selectSets.contains(btn.tag) {
btn.backgroundColor = .init(hex: 0x6B71E3)
btn.setTitleColor(.white, for: .normal)
btn.borderColor = .clear
}else{
btn.backgroundColor = .white
btn.setTitleColor(.black, for: .normal)
btn.borderColor = .init(hex: 0x6B71E3)
}
}
}
@objc private func moreTaps(){
let moreView = SpeakEleQAMoreViewCtr(items: datastr, state: self.type)
moreView.show { [weak self] ts in
let it = self?.items.map({$0.titleLabel?.text ?? ""}) ?? []
if it.contains(ts) {
for (i,v) in it.enumerated() {
if v == ts {
guard let call = self?.callblack else { return }
call(i)
break
}
}
}else{
self?.change = true
guard let call = self?.callblack else { return }
call(ts)
}
}
}
@objc private func btnTaps(_ sender:UIButton) -> Void {
guard let call = callblack else { return }
call(sender.tag)
}
}
//
// SpeakEleQABaseViewCell.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/15.
//
import UIKit
class SpeakEleQABaseViewCell: UITableViewCell {
var data:SpeakEleQAModel?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func awakeFromNib() {
super.awakeFromNib()
setup()
}
func reload() -> Void {
}
func setup() -> Void {
bgView.snp.makeConstraints { make in
make.left.right.bottom.top.equalToSuperview().inset(UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10))
}
self.titleL.snp.makeConstraints { make in
make.left.top.bottom.right.equalToSuperview().inset(16)
make.width.lessThanOrEqualTo(Dev.screenW * 0.7)
make.width.greaterThanOrEqualTo(20)
make.height.greaterThanOrEqualTo(20)
}
}
lazy var bgView: SpeakEleImageFill = {
let bg = SpeakEleImageFill()
contentV.addSubview(bg)
return bg
}()
lazy var contentV: UIView = {
let cv = UIView()
contentView.addSubview(cv)
cv.backgroundColor = .white
return cv
}()
lazy var titleL: UILabel = {
let title = UILabel()
title.font = UIFont.montserrat(.regular,size: 16)
title.textColor = .init(hex:0x000000 ,alpha: 0.9 )
title.numberOfLines = 0
contentV.addSubview(title)
return title
}()
}
//
// SpeakEleQATextCell.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/15.
//
import UIKit
class SpeakEleQATextCell: SpeakEleQABaseViewCell {
static let id = "SpeakEleQATextCell"
override var data:SpeakEleQAModel? {
didSet {
bgView.type = data?.role ?? .speakAI
var title = data?.speakMsg.replacingOccurrences(of: "$$$N", with: SpeakElePublicManager.share.PublicData.userName)
title = title?.replacingOccurrences(of: "$$$L", with: SpeakElePublicManager.share.PublicData.MLanguage)
title = title?.whitespacesAndNew()
titleL.text = title
titleL.textAlignment = (data?.role == .speakAI ? .left : .center)
titleL.sizeToFit()
let role = (data?.role == .speakAI ? true : false)
contentV.snp.remakeConstraints { make in
if role {
make.left.equalToSuperview().inset(marginLR)
make.top.equalToSuperview().inset(5)
}else{
make.right.equalToSuperview().inset(marginLR)
make.top.equalToSuperview().inset(5)
}
make.width.equalTo(titleL.snp.width).offset(32)
make.bottom.equalToSuperview().inset(16)
}
}
}
override func reload() {
setNeedsLayout()
layoutIfNeeded()
}
override func layoutSubviews() {
super.layoutSubviews()
}
}
//
// SpeakEleSelectBoxViewCell.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/15.
//
import UIKit
class SpeakEleSelectBoxViewCell: SpeakEleQABaseViewCell {
static let id = "SpeakEleSelectBoxViewCell"
var type:SpeakMessageType = .language
private var items:[UIButton] = []
var selectIdx = -1
var change = false
var max = 7
var isMore = true
var datastr:[String] = [] {
didSet {
bgView.type = .speakNone
let len = min(datastr.count, max)
var btns:[String] = datastr[0..<len].map({String($0)})
if !isMore {
btns = datastr
}
self.layoutIfNeeded()
if items.count != btns.count || change {
change = false
contentV.subviews.forEach { view in
if view is SpeakEleImageFill {
}else{
view.removeFromSuperview()
}
}
items.removeAll()
let height:CGFloat = 30
let content = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
var offsetX:CGFloat = content.left ,offsetY:CGFloat = content.top
let spaing:CGFloat = 10;
for (i,str) in btns.enumerated() {
let btn = UIButton(type: .custom)
btn.setTitle(str, for: .normal)
btn.titleLabel?.font = UIFont.montserrat(.regular,size: 16)
btn.setTitleColor(.black, for: .normal)
btn.corners = 8
btn.borderWidth = 1
btn.borderColor = .init(hex: 0x6B71E3)
btn.addTarget(self, action: #selector(btnTaps(_:)), for: .touchUpInside)
contentV.addSubview(btn)
items.append(btn)
btn.tag = i
let size = str.size(withFont: UIFont.montserrat(.regular,size: 16))
let width = size.width + 10.0
if ((offsetX + width + content.right) > contentV.width) {
offsetX = content.left
offsetY += (height+spaing)
}
btn.snp.makeConstraints { make in
make.left.equalToSuperview().inset(offsetX)
make.top.equalToSuperview().inset(offsetY)
make.size.equalTo(CGSize(width: width, height: height))
if !isMore && i == (btns.count-1) {
make.bottom.equalToSuperview().inset(16)
}
}
offsetX += (width + spaing)
}
if isMore {
let more = UIButton(type: .custom)
more.setTitle("More", for: .normal)
more.titleLabel?.font = UIFont.montserrat(.regular,size: 16)
more.setTitleColor(.init(hex: 0x6B71E3), for: .normal)
more.backgroundColor = .init(hex: 0x6B71E3,alpha: 0.2)
more.corners = 8
more.addTarget(self, action: #selector(moreTaps), for: .touchUpInside)
contentV.addSubview(more)
let size = "More".size(withFont: UIFont.montserrat(.regular,size: 16))
let width = size.width + 10.0
if ((offsetX + width + content.right) > contentV.width) {
offsetX = content.left
offsetY += (height+spaing)
}
more.snp.makeConstraints { make in
make.left.equalToSuperview().inset(offsetX)
make.top.equalToSuperview().inset(offsetY)
make.size.equalTo(CGSize(width: width, height: height))
make.bottom.equalToSuperview().inset(16)
}
}
}
}
}
override func setup() -> Void {
bgView.snp.makeConstraints { make in
make.left.right.bottom.top.equalToSuperview().inset(UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10))
}
contentV.snp.makeConstraints { make in
make.right.top.bottom.equalToSuperview().inset(UIEdgeInsets(top: 2, left: 0, bottom: 16, right: (marginLR)))
make.width.equalTo(Dev.screenW * 0.6 )
}
}
override func reload() {
self.setNeedsLayout()
self.layoutIfNeeded()
items.forEach { btn in
if btn.tag == selectIdx {
btn.backgroundColor = .init(hex: 0x6B71E3)
btn.setTitleColor(.white, for: .normal)
btn.borderColor = .clear
}else{
btn.backgroundColor = .white
btn.setTitleColor(.black, for: .normal)
btn.borderColor = .init(hex: 0x6B71E3)
}
}
}
@objc private func moreTaps(){
let moreView = SpeakEleQAMoreViewCtr(items: datastr, state: self.type)
moreView.show { [weak self] ts in
let it = self?.items.map({$0.titleLabel?.text ?? ""}) ?? []
if it.contains(ts) {
for (i,v) in it.enumerated() {
if v == ts {
guard let call = self?.callblack else { return }
call(i)
break
}
}
}else{
self?.change = true
guard let call = self?.callblack else { return }
call(ts)
}
}
}
@objc private func btnTaps(_ sender:UIButton) -> Void {
guard let call = callblack else { return }
call(sender.tag)
}
}
//
// SpeakEleSelectTimeViewCell.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/17.
//
import UIKit
class SpeakEleSelectTimeViewCell: SpeakEleQABaseViewCell {
static let id = "SpeakEleQABaseViewCell"
override func setup() {
bgView.type = .speakNone
bgView.snp.makeConstraints { make in
make.left.right.bottom.top.equalToSuperview().inset(UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10))
}
contentV.snp.makeConstraints { make in
make.right.top.bottom.equalToSuperview().inset(UIEdgeInsets(top: 2, left: 0, bottom: 16, right: (marginLR)))
make.width.equalTo(Dev.screenW * 0.6 )
}
datePicker.snp.makeConstraints { make in
make.left.right.top.bottom.equalToSuperview()
make.height.equalTo(166)
}
}
private lazy var datePicker: UIDatePicker = {
let date = UIDatePicker()
date.preferredDatePickerStyle = .wheels
date.datePickerMode = .time
date.addTarget(self, action: #selector(valueChange(_:)), for: .valueChanged)
contentV.addSubview(date)
return date
}()
@objc func valueChange(_ sender:UIDatePicker) -> Void {
let tm = sender.date.toStr("hh:mm:aa")
guard let call = callblack else { return }
call(tm)
}
}
//
// SpeakEleTranSCell.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/17.
//
import UIKit
class SpeakEleTranSCell: SpeakEleQABaseViewCell {
static let id = "SpeakEleTranSCell"
override func setup() {
bgView.snp.makeConstraints { make in
make.left.right.bottom.top.equalToSuperview().inset(UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10))
}
self.titleL.snp.makeConstraints { make in
make.left.top.right.equalToSuperview().inset(16)
make.width.lessThanOrEqualTo(Dev.screenW * 0.7)
make.width.greaterThanOrEqualTo(20)
make.height.greaterThanOrEqualTo(20)
}
self.lineV.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(16)
make.top.equalTo(self.titleL.snp.bottom).offset(16)
make.height.equalTo(0.6)
}
self.stack.snp.makeConstraints { make in
make.left.equalToSuperview().inset(16)
make.top.equalTo(self.lineV.snp.bottom).offset(16)
}
self.transLabel.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(16)
make.top.equalTo(self.stack.snp.bottom).offset(10)
make.bottom.equalToSuperview().inset(16)
make.height.greaterThanOrEqualTo(0)
}
contentV.snp.makeConstraints { make in
make.right.top.bottom.equalToSuperview().inset(UIEdgeInsets(top: 2, left: 0, bottom: 16, right: (marginLR)))
make.width.equalTo(Dev.screenW * 0.6 )
}
}
override var data:SpeakEleQAModel? {
didSet {
bgView.type = data?.role ?? .speakAI
var title = data?.speakMsg.replacingOccurrences(of: "$$$N", with: SpeakElePublicManager.share.PublicData.userName)
title = title?.replacingOccurrences(of: "$$$L", with: SpeakElePublicManager.share.PublicData.MLanguage)
title = title?.whitespacesAndNew()
titleL.text = title
titleL.textAlignment = (data?.role == .speakAI ? .left : .center)
titleL.sizeToFit()
transLabel.text = data?.transtr
let role = (data?.role == .speakAI ? true : false)
contentV.snp.remakeConstraints { make in
if role {
make.left.equalToSuperview().inset(marginLR)
make.top.equalToSuperview().inset(5)
}else{
make.right.equalToSuperview().inset(marginLR)
make.top.equalToSuperview().inset(5)
}
make.width.equalTo(titleL.snp.width).offset(32)
make.bottom.equalToSuperview().inset(16)
}
}
}
private func stackItem() -> [UIButton] {
let icon = ["icon_vioce_off","icon_translate_on"]
var i = 0
let btns = icon.map { text in
let btn = UIButton(type: .custom)
btn.setBackgroundImage(UIImage(named: text), for: .normal)
btn.tag = i
i += 1
btn.addTarget(self, action: #selector(transTaps(_:)), for: .touchUpInside)
return btn
}
return btns
}
override func reload() {
setNeedsLayout()
layoutIfNeeded()
}
override func layoutSubviews() {
super.layoutSubviews()
}
private lazy var lineV: UIView = {
let l = UIView()
l.backgroundColor = .init(hex: 0x000000,alpha: 0.12)
contentV.addSubview(l)
return l
}()
private lazy var stack: UIStackView = {
let st = UIStackView()
contentV.addSubview(st)
st.spacing = 16
st.distribution = .fill
st.alignment = .fill
let b = stackItem()
b.forEach { stackBtn in
st.addArrangedSubview(stackBtn)
stackBtn.snp.makeConstraints { make in
make.width.equalTo(30.adapterW())
make.height.equalTo(stackBtn.snp.width)
}
}
return st
}()
private lazy var transLabel: UILabel = {
let l = UILabel()
l.font = UIFont.montserrat(.regular,size: 16)
l.textColor = .init(hex:0x000000 ,alpha: 0.9 )
l.numberOfLines = 0
contentV.addSubview(l)
return l
}()
@objc private func transTaps(_ sender:UIButton) -> Void {
if sender.tag == 0 {
SpeakSpeechSynthesizer.share.speakText(titleL.text ?? "")
}else{
guard let call = callblack else { return }
call("")
}
}
}
...@@ -9,26 +9,118 @@ import UIKit ...@@ -9,26 +9,118 @@ import UIKit
class SpeakEleQAViewModel: NSObject { class SpeakEleQAViewModel: NSObject {
var anwerUpdate:(()->Void)?
var SpeakLanguages:[String] = []
var SpeakHoby:[String] = []
var anwer:[SpeakEleQAModel] = [] {
didSet {
guard let anwerUpdate = anwerUpdate else { return }
anwerUpdate()
SaveData(anwer)
}
}
func fetchData() -> Void {
let data = UserDefaults.standard.object(forKey: UnsafeRawUserDefaultsKey.UnsafeGuideQAKey.rawValue) as? Data ?? Data()
do {
anwer = try JSONDecoder().decode([SpeakEleQAModel].self, from: data)
}catch {
anwer = []
}
do {
let data = "SpeakGuideQandALanguage".loadData() ?? Data()
let ds = try JSONDecoder().decode([String].self, from: data)
SpeakLanguages = ds
}catch{}
do {
let data = "SpeakGuideQandAHoby".loadData() ?? Data()
let hoby = try JSONDecoder().decode([String].self, from: data)
SpeakHoby = hoby
}catch{}
}
func SaveData(_ data:[SpeakEleQAModel]) -> Void {
do {
let data = try JSONEncoder().encode(data)
UserDefaults.standard.setValue(data, forKey: UnsafeRawUserDefaultsKey.UnsafeGuideQAKey.rawValue)
UserDefaults.standard.synchronize()
}catch{ }
}
var questionArrys:[SpeakEleQAModel] { var questionArrys:[SpeakEleQAModel] {
get { get {
return [ return [
SpeakEleQAModel(speakMsg: "Hello! I’m Speakly, an AI tutor designed just for you. I’m here to assist with boosting your English skills." ), SpeakEleQAModel(speakMsg: "Hello! I’m Speakly, an AI tutor designed just for you. I’m here to assist with boosting your English skills." ),
SpeakEleQAModel(speakMsg: "May I know your name?" ,messageType: .name), SpeakEleQAModel(speakMsg: "May I know your name?" ,messageType: .name),
SpeakEleQAModel(speakMsg: "Nice to make your acquaintance, $$$ !"), SpeakEleQAModel(speakMsg: "" ,role: .speakUser ,messageType: .name),
SpeakEleQAModel(speakMsg: "Nice to make your acquaintance, $$$N !"),
SpeakEleQAModel(speakMsg: "I have a few questions to help me get a better sense of you."), SpeakEleQAModel(speakMsg: "I have a few questions to help me get a better sense of you."),
SpeakEleQAModel(speakMsg: "What language do you speak natively?",messageType: .language), SpeakEleQAModel(speakMsg: "What language do you speak natively?",messageType: .language),
SpeakEleQAModel(speakMsg: "Would you prefer English lessons with explanations in $$$ or in English itself?",messageType: .like), SpeakEleQAModel(speakMsg: "",role: .speakUser,messageType: .language),
SpeakEleQAModel(speakMsg: "Would you prefer English lessons with explanations in $$$L or in English itself?",messageType: .like),
SpeakEleQAModel(speakMsg: "",role: .speakUser,messageType: .like),
SpeakEleQAModel(speakMsg: "Go ahead and pick your interests. You can select a maximum of 4 choices." , messageType: .hobby), SpeakEleQAModel(speakMsg: "Go ahead and pick your interests. You can select a maximum of 4 choices." , messageType: .hobby),
SpeakEleQAModel(speakMsg: "" ,role: .speakUser , messageType: .hobby),
SpeakEleQAModel(speakMsg: "Which aspects of English do you want to work on improving?",messageType: .improving), SpeakEleQAModel(speakMsg: "Which aspects of English do you want to work on improving?",messageType: .improving),
SpeakEleQAModel(speakMsg: "" ,role: .speakUser ,messageType: .improving),
SpeakEleQAModel(speakMsg: "How would you describe your current English proficiency?",messageType: .proficiency), SpeakEleQAModel(speakMsg: "How would you describe your current English proficiency?",messageType: .proficiency),
SpeakEleQAModel(speakMsg: "" ,role: .speakUser ,messageType: .proficiency),
SpeakEleQAModel(speakMsg: "How soon do you hope to achieve your English learning goals?",messageType: .goalDays), SpeakEleQAModel(speakMsg: "How soon do you hope to achieve your English learning goals?",messageType: .goalDays),
SpeakEleQAModel(speakMsg: "" ,role: .speakUser ,messageType: .goalDays),
SpeakEleQAModel(speakMsg: "What’s your daily target for practice?",messageType: .studytime), SpeakEleQAModel(speakMsg: "What’s your daily target for practice?",messageType: .studytime),
SpeakEleQAModel(speakMsg: "" ,role: .speakUser ,messageType: .studytime),
SpeakEleQAModel(speakMsg: "Choose a specific time each day when you’d like to start your practice sessions.",messageType: .practice), SpeakEleQAModel(speakMsg: "Choose a specific time each day when you’d like to start your practice sessions.",messageType: .practice),
SpeakEleQAModel(speakMsg: "" ,role: .speakUser ,messageType: .practice),
SpeakEleQAModel(speakMsg: "I’ll send you a reminder for your practice time every day. To ensure you don’t skip a session, please enable notifications!", messageType: .finish) SpeakEleQAModel(speakMsg: "I’ll send you a reminder for your practice time every day. To ensure you don’t skip a session, please enable notifications!", messageType: .finish)
] ]
} }
} }
var improving:[String] {
set {}
get{
return ["Vocabulary",
"Grammar",
"Pronunciation",
"Listening",
"Speaking"]
}
}
var proficiency:[String] {
set {}
get {
return ["Entry-level",
"Lower-intermediate",
"Mid-level",
"Upper-mid",
"High-advanced",
"Expert-level"]
}
}
var goalDays:[String] {
set {}
get {
return ["15days","30days","45days","60days"]
}
}
var studyTime:[String] {
set{}
get {
return ["10min/day" ,"15min/day" ,"30min/day" ,"45min/day","60min/day"]
}
}
} }
//
// SpeakEleIAPViewCtr.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/11.
//
import UIKit
class SpeakEleIAPViewCtr: SpeakEleBaseViewCtr {
enum iapstate {
case guide
case plan
case other
}
// MARK: - ib
@IBOutlet weak var SpeakEasyV: UIView!
@IBOutlet weak var SpeakPayDescritionL: UILabel!
@IBOutlet weak var SpeakPayContentV: UIView!
@IBOutlet weak var SpeakPayTitleL: UILabel!
@IBOutlet weak var SpeakSwitch: UISwitch!
@IBOutlet weak var SpeakTL1: UILabel!
@IBOutlet weak var SpeakFreeTL1: UILabel!
@IBOutlet weak var SpeakRTL1: UILabel!
@IBOutlet weak var SpeakTL2: UILabel!
@IBOutlet weak var SpeakRTL2: UILabel!
@IBOutlet weak var SpeakPayBut: SpeakEleButton!
@IBOutlet weak var SpeakBotDescription: UILabel!
var state:iapstate = .other
var isFree:Bool = true {
didSet {
SpeakSwitch.isOn = isFree
}
}
override func viewDidLoad() {
super.viewDidLoad()
SpeakSwitch.addTarget(self, action: #selector(valueChange(_:)), for: .touchUpInside)
isFree = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.AnimationState = .none
titleView.isHidden = true
}
override func setup() {
let privacy = "Privacy".attributed().underline(.single).paragraphStyle(.center,lineSpacing: 4).build()
let terms = "Terms".attributed().underline(.single).paragraphStyle(.center,lineSpacing: 4).build()
let att = "Cancel Anytime & ".combined(with: [privacy,"|",terms])
self.SpeakBotDescription.attributedText = att
self.SpeakBotDescription.AddTextTap(["Privacy","Terms"])
self.SpeakBotDescription.callblack = { [weak self] text in
guard let text:String = text as? String else { return }
if text == privacy.string {
} else {
}
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
SpeakEasyV.shadow(cornerRadius: 16,shadowColor: .init(hex: 0x000000,alpha: 1),opacity: 0.06,radius: 16)
SpeakPayContentV.shadow(cornerRadius: 8,shadowColor: .init(hex: 0x000000,alpha: 1),opacity: 0.06,radius: 8)
}
@IBAction func SpeakCloseTaps(_ sender: Any) {
if state == .guide {
let QAndA = SpeakEleQAViewCtr()
self.navigationController?.pushViewController(QAndA, animated: true)
}else if state == .plan {
let login = SpeakEleFLoginViewCtr()
self.navigationController?.pushViewController(login, animated: true)
}else{
self.navigationController?.popViewController(animated: true)
}
}
@objc private func valueChange(_ switch:UISwitch) -> Void {
isFree = SpeakSwitch.isOn
}
@IBAction func SpeakPayTaps(_ sender: Any) {
}
@IBAction func SpeakRestoreTaps(_ sender: Any) {
}
}
//
// SpeakEleIAPViewCtr.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/11.
//
import UIKit
class SpeakEleIAPViewCtr: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.AnimationState = .none
}
@IBAction func SpeakCloseTaps(_ sender: Any) {
let QAndA = SpeakEleQAViewCtr()
self.navigationController?.pushViewController(QAndA, animated: true)
}
}
...@@ -21,19 +21,21 @@ class SpeakEleLaunchViewCtr: SpeakEleBaseViewCtr { ...@@ -21,19 +21,21 @@ class SpeakEleLaunchViewCtr: SpeakEleBaseViewCtr {
switch SpeakElePublicManager.share.PublicData.state { switch SpeakElePublicManager.share.PublicData.state {
case .start: case .start:
let nav = SpeakEleBaseNavigationCtr(rootViewController: SpeakEleLoginGuideCtr()) let nav = SpeakEleBaseNavigationCtr(rootViewController: SpeakEleLoginGuideCtr())
window.switchToController(nav) SpWindow.switchToController(nav)
break break
case .subscribe: case .subscribe:
let nav = SpeakEleBaseNavigationCtr(rootViewController: SpeakEleIAPViewCtr()) let nav = SpeakEleBaseNavigationCtr(rootViewController: SpeakEleIAPViewCtr())
window.switchToController(nav) SpWindow.switchToController(nav)
case .qanda: case .qanda:
let nav = SpeakEleBaseNavigationCtr(rootViewController: SpeakEleQAViewCtr()) let nav = SpeakEleBaseNavigationCtr(rootViewController: SpeakEleQAViewCtr())
window.switchToController(nav) SpWindow.switchToController(nav)
case .login: case .login:
let nav = SpeakEleBaseNavigationCtr(rootViewController: SpeakEleFLoginViewCtr())
SpWindow.switchToController(nav)
break break
default: default:
let nav = SpeakEleBaseNavigationCtr(rootViewController: SpeakEleTabbarViewCtr()) let nav = SpeakEleBaseNavigationCtr(rootViewController: SpeakEleTabbarViewCtr())
window.switchToController(nav) SpWindow.switchToController(nav)
break break
} }
......
//
// SpeakEleFLoginViewCtr.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/17.
//
import UIKit
class SpeakEleFLoginViewCtr: SpeakEleBaseViewCtr {
// MARK: - ib
@IBOutlet var LoginBtns: [UIButton]!
private let viewModel = SpeakEleLoginViewModel()
override func viewDidLoad() {
super.viewDidLoad()
SpeakElePublicManager.share.PublicData.state = .login
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
titleView.isHidden = true
self.view.layoutIfNeeded()
LoginBtns.forEach { btn in
btn.setBackgroundImage(UIColor.white.generate(btn.size,20), for: .normal)
btn.setBackgroundImage(UIColor.init(hex: 0x191919).generate(btn.size,20), for: .highlighted)
btn.clipsToBounds = false
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.view.layoutIfNeeded()
LoginBtns.forEach { btn in
btn.shadow(cornerRadius: 20,shadowColor: .init(hex: 0x000000),offset: CGSize(width: 0, height: 2) ,opacity: 0.06,radius: 20)
}
}
@IBAction func LoginBtnTaps(_ sender: UIButton) {
if sender == LoginBtns[0] {
viewModel.AppleLogin { response in
switch response {
case .success(let token):
break
case .failure(let error):
break
}
}
}else if sender == LoginBtns[1] {
}else{
let vc = SpeakEleLoginViewCtr()
vc.state = .register
self.navigationController?.pushViewController(vc, animated: true)
}
}
@IBAction func EmailLoginTaps(_ sender: UIButton) {
let vc = SpeakEleLoginViewCtr()
self.navigationController?.pushViewController(vc, animated: true)
}
}
//
// SpeakEleForgetViewCtr.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/14.
//
import UIKit
class SpeakEleForgetViewCtr: SpeakEleBaseViewCtr {
// MARK: - ib
@IBOutlet weak var SpeakEmailTextField: UITextField!
@IBOutlet weak var SpeakSendCodeBtn: SpeakEleButton!
override func viewDidLoad() {
super.viewDidLoad()
SpeakEmailTextField.delegate = self
}
@IBAction func SpeakSendCode(_ sender: Any) {
if (SpeakEmailTextField.text ?? "").isValidEmail() {
let alert = SpeakEleSendCodeAlert.xib()
alert.show()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
alert.hide()
}
}
}
}
extension SpeakEleForgetViewCtr : UITextFieldDelegate {
func textFieldDidChangeSelection(_ textField: UITextField) {
let text = textField.text ?? ""
SpeakSendCodeBtn.isEnabled = !text.isEmpty
}
}
...@@ -46,32 +46,18 @@ class SpeakEleLoginGuideCtr: SpeakEleBaseViewCtr { ...@@ -46,32 +46,18 @@ class SpeakEleLoginGuideCtr: SpeakEleBaseViewCtr {
private func setDescription() -> Void { private func setDescription() -> Void {
SpeakDescLabel.AddTextTap(["Privacy Policy","Terms of Service."]) SpeakDescLabel.AddTextTap(["Privacy Policy","Terms of Service."])
let privacy = "Privacy Policy".attributed().font(UIFont.montserrat(.medium,size: 10)).underline(.single).build() let privacy = "Privacy Policy".attributed().font(UIFont.montserrat(.medium,size: 10)).underline(.single).paragraphStyle(.center,lineSpacing: 4).build()
let terms = "Terms of Service.".attributed().font(UIFont.montserrat(.medium,size: 10)).underline(.single).build() let terms = "Terms of Service.".attributed().font(UIFont.montserrat(.medium,size: 10)).underline(.single).paragraphStyle(.center,lineSpacing: 4).build()
let att = "Proceeding further indicates your agreement to our ".combined(with: [privacy," and ", terms]) let att = "Proceeding further indicates your agreement to our ".attributed().paragraphStyle(.center,lineSpacing: 4).build().combined(with: [privacy," and ", terms])
SpeakDescLabel.attributedText = att SpeakDescLabel.attributedText = att
SpeakDescLabel.callblack = { [weak self] text in
// let range1 = (att.string as NSString).range(of: "Privacy Policy") guard let v:String = text as? String else { return }
// let highlight1 = YYTextHighlight() if v == "Privacy Policy" {
// self?.PrivacyPolicy()
// highlight1.tapAction = { containerView, text, range, rect in }else{
// print("用户点击了'点击这里'") self?.TermsOfService()
// // 处理点击事件 }
// } }
//
// // 应用高亮到文本
// SpeakDescLabel.yy_setTextHighlight(highlight1, range: range1)
//
// // 同样设置"官网"为可点击文本
// let range2 = (text as NSString).range(of: "官网")
// let highlight2 = YYTextHighlight()
// highlight2.setColor(UIColor.blue)
// highlight2.setBackgroundColor(UIColor.lightGray.withAlphaComponent(0.3))
//
// highlight2.tapAction = { containerView, text, range, rect in
// print("用户点击了'官网'")
// // 处理点击事件
// }
// attributedText.yy_setTextHighlight(highlight2, range: range2)
} }
} }
...@@ -12,19 +12,41 @@ import AuthenticationServices ...@@ -12,19 +12,41 @@ import AuthenticationServices
class SpeakEleLoginViewCtr: SpeakEleBaseViewCtr { class SpeakEleLoginViewCtr: SpeakEleBaseViewCtr {
enum loginState {
case login
case register
}
private let viewModel = SpeakEleLoginViewModel()
// MARK: - ib // MARK: - ib
@IBOutlet weak var SpeakLgTitleL: UILabel!
@IBOutlet weak var SpeakLgSubTitleL: UILabel!
@IBOutlet weak var SpeakEmailContentV: UIView! @IBOutlet weak var SpeakEmailContentV: UIView!
@IBOutlet weak var SpeakEamil: UITextField! @IBOutlet weak var SpeakEamil: UITextField!
@IBOutlet weak var SpeakPsContentV: UIView! @IBOutlet weak var SpeakPsContentV: UIView!
@IBOutlet weak var SpeakPs: UITextField! @IBOutlet weak var SpeakPs: UITextField!
@IBOutlet weak var SpeakSecure: UIButton! @IBOutlet weak var SpeakSecure: UIButton!
@IBOutlet weak var SpeakSetPsContentV: UIView!
@IBOutlet weak var SpeakSetPs: UITextField!
@IBOutlet weak var SpeakSetSecure: UIButton!
@IBOutlet weak var SpeakDesc: UILabel! @IBOutlet weak var SpeakDesc: UILabel!
@IBOutlet var SpeakOtherLogin: [UIButton]! @IBOutlet var SpeakOtherLogin: [UIButton]!
@IBOutlet weak var SpeakForgotPs: UIButton!
@IBOutlet weak var SpeakEmailIncorrect: UIView! @IBOutlet weak var SpeakEmailIncorrect: UIView!
@IBOutlet weak var SpeakPsIncorrect: UIView! @IBOutlet weak var SpeakPsIncorrect: UIView!
@IBOutlet weak var SpeakSetPsIncorrect: UIView!
@IBOutlet weak var SpeakLoginBtn: SpeakEleButton!
var state:loginState = .login
private var secureTextEntry:Bool = true { private var secureTextEntry:Bool = true {
didSet{ didSet{
...@@ -33,15 +55,54 @@ class SpeakEleLoginViewCtr: SpeakEleBaseViewCtr { ...@@ -33,15 +55,54 @@ class SpeakEleLoginViewCtr: SpeakEleBaseViewCtr {
} }
} }
private var secureSetTextEntry:Bool = true {
didSet{
SpeakSetPs.isSecureTextEntry = secureSetTextEntry
SpeakSetSecure.isSelected = !secureSetTextEntry
}
}
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
SpeakEmailIncorrect.isHidden = true SpeakEmailIncorrect.isHidden = true
SpeakPsIncorrect.isHidden = true SpeakPsIncorrect.isHidden = true
SpeakSetPsIncorrect.isHidden = true
secureTextEntry = true secureTextEntry = true
secureSetTextEntry = true
} }
override func setup() { override func setup() {
SpeakForgotPs.isHidden = !(state == .login)
SpeakSetPsContentV.isHidden = state == .login
SpeakSetPs.isHidden = state == .login
SpeakSetSecure.isHidden = state == .login
if state == .register {
SpeakLgTitleL.text = "Sign up with E-mail"
SpeakLgSubTitleL.text = "Please fill in your email address and password"
SpeakEamil.placeholder = "E-mail address"
SpeakPs.placeholder = "Password"
SpeakSetPs.placeholder = "Confirm Password"
}else{
SpeakLgTitleL.text = "Welcome back"
SpeakLgSubTitleL.text = "Log in to continue learning with your private AI Tutor"
SpeakEamil.placeholder = "E-mail address"
SpeakPs.placeholder = "Password"
}
SpeakDesc.AddTextTap(["Privacy Policy","Terms of Service."])
let privacy = "Privacy Policy".attributed().font(UIFont.montserrat(.medium,size: 10)).underline(.single).paragraphStyle(.center,lineSpacing: 4).build()
let terms = "Terms of Service.".attributed().font(UIFont.montserrat(.medium,size: 10)).underline(.single).paragraphStyle(.center,lineSpacing: 4).build()
let att = "Proceeding further indicates your agreement to our ".attributed().paragraphStyle(.center,lineSpacing: 4).build().combined(with: [privacy," and ", terms])
SpeakDesc.attributedText = att
SpeakDesc.callblack = { [weak self] text in
guard let v:String = text as? String else { return }
if v == "Privacy Policy" {
self?.PrivacyPolicy()
}else{
self?.TermsOfService()
}
}
} }
override func viewDidLayoutSubviews() { override func viewDidLayoutSubviews() {
...@@ -50,9 +111,18 @@ class SpeakEleLoginViewCtr: SpeakEleBaseViewCtr { ...@@ -50,9 +111,18 @@ class SpeakEleLoginViewCtr: SpeakEleBaseViewCtr {
button.shadow(cornerRadius: 20, shadowColor: .init(hex: 0x000000,alpha: 0.06), offset: CGSizeMake(0, 4), opacity: 1, radius: 20) button.shadow(cornerRadius: 20, shadowColor: .init(hex: 0x000000,alpha: 0.06), offset: CGSizeMake(0, 4), opacity: 1, radius: 20)
} }
} }
private func PrivacyPolicy() -> Void {
}
private func TermsOfService() -> Void {
}
@IBAction func SpeakForgot(_ sender: Any) { @IBAction func SpeakForgot(_ sender: Any) {
let forget = SpeakEleForgetViewCtr()
self.navigationController?.pushViewController(forget, animated: true)
} }
@IBAction func SpeakCreate(_ sender: Any) { @IBAction func SpeakCreate(_ sender: Any) {
...@@ -71,57 +141,54 @@ class SpeakEleLoginViewCtr: SpeakEleBaseViewCtr { ...@@ -71,57 +141,54 @@ class SpeakEleLoginViewCtr: SpeakEleBaseViewCtr {
SpeakPsContentV.borderColor = .init(hex: 0xEB3B2F) SpeakPsContentV.borderColor = .init(hex: 0xEB3B2F)
return return
} }
if state == .register {
let setps = SpeakSetPs.text ?? ""
if setps.count <= 0 {
SpeakSetPsIncorrect.isHidden = false
SpeakSetPsContentV.borderColor = .init(hex: 0xEB3B2F)
}else{
SpeakSetPsIncorrect.isHidden = true
SpeakSetPsContentV.borderColor = .clear
}
}
SpeakPsIncorrect.isHidden = true SpeakPsIncorrect.isHidden = true
SpeakPsContentV.borderColor = .clear SpeakPsContentV.borderColor = .clear
if state == .register { //FIXME: 登录注册流程
}else{
}
} }
@IBAction func SpeakSecureButton(_ sender: UIButton) { @IBAction func SpeakSecureButton(_ sender: UIButton) {
sender.isSelected = !sender.isSelected sender.isSelected = !sender.isSelected
secureTextEntry = !sender.isSelected if sender == SpeakSecure{
secureTextEntry = !sender.isSelected
}else{
secureSetTextEntry = !sender.isSelected
}
} }
@IBAction func SpeakThreeLoginTaps(_ sender: UIButton) { @IBAction func SpeakThreeLoginTaps(_ sender: UIButton) {
if sender == SpeakOtherLogin.last { if sender == SpeakOtherLogin.last {
let provider = ASAuthorizationAppleIDProvider() viewModel.AppleLogin { response in
let request = provider.createRequest() switch response {
request.requestedScopes = [.fullName, .email] case .success(let token):
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self break
controller.presentationContextProvider = self case .failure(let error):
controller.performRequests()
break
}
}
}else{ }else{
} }
} }
private func toGuide() -> Void {
func toGuide() -> Void {
let guide = SpeakEleGuideViewCtr() let guide = SpeakEleGuideViewCtr()
self.navigationController?.pushViewController(guide, animated: true) self.navigationController?.pushViewController(guide, animated: true)
} }
} }
extension SpeakEleLoginViewCtr : ASAuthorizationControllerDelegate , ASAuthorizationControllerPresentationContextProviding {
func authorizationController(controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
let userIdentifier = appleIDCredential.user
_ = appleIDCredential.fullName
_ = appleIDCredential.email
}
}
func authorizationController(controller: ASAuthorizationController,
didCompleteWithError error: Error) {
print("Apple Login Error: \(error.localizedDescription)")
}
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return self.view.window!
}
}
//
// SpeakEleCreatAViewCtr.swift
// SpeakEasyLearnEnglish
//
// Created by edy on 2025/7/11.
//
import UIKit
class SpeakEleCreatAViewCtr: SpeakEleBaseViewCtr {
override func viewDidLoad() {
super.viewDidLoad()
}
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SpeakEleCreatAViewCtr" customModuleProvider="target">
<connections>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
</view>
</objects>
</document>
...@@ -17,7 +17,11 @@ class SpeakEleButton: UIButton { ...@@ -17,7 +17,11 @@ class SpeakEleButton: UIButton {
super.layoutSubviews() super.layoutSubviews()
self.layoutIfNeeded() self.layoutIfNeeded()
self.corners = 20 self.corners = 20
self.gradient(colors: [.init(hex: 0x6B71E3) ,.init(hex: 0x8D6AD3)]) if state == .disabled {
self.backgroundColor = .init(hex: 0x3980F6,alpha: 0.5)
}else{
self.backgroundColor = .init(hex: 0x3980F6)
}
} }
} }
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment