Files
note/Note/FormatLogViewController.swift
T

256 lines
10 KiB
Swift

//
// FormatLogViewController.swift
// Note
//
// Created by fish on 2026/7/12.
//
import UIKit
class FormatLogViewController: UIViewController {
private var textView: UITextView!
private var formatButton: UIButton!
private var floatingDoneButton: UIButton!
private var floatingDoneButtonBottomConstraint: NSLayoutConstraint!
private var doneButton: UIBarButtonItem!
private var pasteButton: UIBarButtonItem!
private var resetButton: UIBarButtonItem!
private var isFormatted = false
private var rightBarButtonMode: RightBarButtonMode = .paste
var initialText: String?
private enum RightBarButtonMode {
case paste
case reset
}
override func viewDidLoad() {
super.viewDidLoad()
title = "格式化日志"
navigationItem.largeTitleDisplayMode = .never
view.backgroundColor = .systemGroupedBackground
setupUI()
if let initialText = initialText {
textView.text = initialText
isFormatted = true
formatButton.setTitle("保存到 iCloud", for: .normal)
rightBarButtonMode = .reset
updateRightBarButtonItem()
}
setupKeyboardObservers()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
private func setupUI() {
textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.font = UIFont.systemFont(ofSize: 16)
textView.backgroundColor = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark ? .secondarySystemBackground : .white
}
textView.layer.cornerRadius = 12
textView.layer.borderWidth = 0.5
textView.layer.borderColor = UIColor.separator.cgColor
textView.textContainerInset = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
textView.delegate = self
view.addSubview(textView)
doneButton = UIBarButtonItem(title: "完成", style: .prominent, target: self, action: #selector(didTapDoneButton))
pasteButton = UIBarButtonItem(title: "粘贴文案", style: .plain, target: self, action: #selector(didTapPasteButton))
resetButton = UIBarButtonItem(title: "重置", style: .plain, target: self, action: #selector(didTapResetButton))
updateRightBarButtonItem()
formatButton = UIButton(type: .system)
formatButton.translatesAutoresizingMaskIntoConstraints = false
formatButton.setTitle("格式化", for: .normal)
formatButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
formatButton.backgroundColor = .systemBlue
formatButton.setTitleColor(.white, for: .normal)
formatButton.layer.cornerRadius = 12
formatButton.addTarget(self, action: #selector(didTapFormatButton), for: .touchUpInside)
view.addSubview(formatButton)
var doneConfig = UIButton.Configuration.glass()
doneConfig.title = "完成"
doneConfig.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in
var outgoing = incoming
outgoing.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
return outgoing
}
doneConfig.baseForegroundColor = .label
doneConfig.cornerStyle = .capsule
doneConfig.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 22, bottom: 15, trailing: 22)
floatingDoneButton = UIButton(configuration: doneConfig)
floatingDoneButton.translatesAutoresizingMaskIntoConstraints = false
floatingDoneButton.alpha = 0
floatingDoneButton.addTarget(self, action: #selector(didTapFloatingDoneButton), for: .touchUpInside)
view.addSubview(floatingDoneButton)
floatingDoneButtonBottomConstraint = floatingDoneButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -16)
NSLayoutConstraint.activate([
floatingDoneButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
floatingDoneButtonBottomConstraint,
textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
textView.bottomAnchor.constraint(equalTo: formatButton.topAnchor, constant: -16),
formatButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
formatButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
formatButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16),
formatButton.heightAnchor.constraint(equalToConstant: 52)
])
}
@objc private func didTapDoneButton() {
textView.resignFirstResponder()
}
@objc private func didTapFloatingDoneButton() {
textView.resignFirstResponder()
}
private func setupKeyboardObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
@objc private func keyboardWillShow(_ notification: Notification) {
guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
let keyboardFrameInView = view.convert(keyboardFrame, from: nil)
let intersection = keyboardFrameInView.intersection(view.bounds)
let keyboardHeight = intersection.height
let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0.25
UIView.animate(withDuration: duration, delay: 0, options: .beginFromCurrentState) {
self.textView.contentInset.bottom = keyboardHeight
self.textView.verticalScrollIndicatorInsets.bottom = keyboardHeight
self.floatingDoneButtonBottomConstraint.constant = -(keyboardHeight + 8)
self.floatingDoneButton.alpha = 1
self.view.layoutIfNeeded()
}
}
@objc private func keyboardWillHide(_ notification: Notification) {
let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0.25
UIView.animate(withDuration: duration, delay: 0, options: .beginFromCurrentState) {
self.textView.contentInset.bottom = 0
self.textView.verticalScrollIndicatorInsets.bottom = 0
self.floatingDoneButtonBottomConstraint.constant = -16
self.floatingDoneButton.alpha = 0
self.view.layoutIfNeeded()
}
}
@objc private func didTapFormatButton() {
guard !trimmedText().isEmpty else {
showEmptyAlert()
return
}
if isFormatted {
saveToFiles()
} else {
textView.text = TextFormatter.formatLog(textView.text)
isFormatted = true
formatButton.setTitle("保存到 iCloud", for: .normal)
rightBarButtonMode = .reset
updateRightBarButtonItem()
}
}
@objc private func didTapPasteButton() {
if let string = UIPasteboard.general.string {
textView.text = string
}
}
private func trimmedText() -> String {
return textView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
}
private func showEmptyAlert() {
let alert = UIAlertController(title: "提示", message: "请输入内容后再进行格式化", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "确定", style: .default))
present(alert, animated: true)
}
@objc private func didTapResetButton() {
textView.text = ""
isFormatted = false
formatButton.setTitle("格式化", for: .normal)
rightBarButtonMode = .paste
updateRightBarButtonItem()
}
private func updateRightBarButtonItem() {
if textView.isFirstResponder {
navigationItem.rightBarButtonItem = doneButton
} else {
switch rightBarButtonMode {
case .paste:
navigationItem.rightBarButtonItem = pasteButton
case .reset:
navigationItem.rightBarButtonItem = resetButton
}
}
}
private func saveToFiles() {
let fileName = generateFileName()
let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(fileName)
do {
try textView.text.write(to: fileURL, atomically: true, encoding: .utf8)
let documentPicker = UIDocumentPickerViewController(forExporting: [fileURL], asCopy: true)
documentPicker.delegate = self
present(documentPicker, animated: true)
} catch {
print("Failed to write file: \(error)")
}
}
private func generateFileName() -> String {
let text = textView.text ?? ""
let firstLine = text.components(separatedBy: .newlines).first ?? ""
let trimmed = firstLine.trimmingCharacters(in: .whitespaces)
let withoutHash = trimmed.replacingOccurrences(of: "^#\\s*", with: "", options: .regularExpression)
let cleanName = withoutHash.trimmingCharacters(in: .whitespaces)
if cleanName.isEmpty {
return "formatted_log_\(Int(Date().timeIntervalSince1970)).md"
}
return cleanName + ".md"
}
}
extension FormatLogViewController: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
updateRightBarButtonItem()
}
func textViewDidEndEditing(_ textView: UITextView) {
updateRightBarButtonItem()
}
}
extension FormatLogViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
// File saved to selected location
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
// User cancelled
}
}