Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
332dca6
Start work on homemade Twitter API client
daneden Jan 10, 2022
4fea287
Chipping away
daneden Jan 10, 2022
e0070d2
Get API client basics working
daneden Jan 10, 2022
66aa424
Begin generalising twitter lib code
daneden Jan 11, 2022
de21ea0
Remove .xcuserstate
daneden Jan 11, 2022
ed74532
Add Twift as package dependency
daneden Jan 11, 2022
00f7334
Get initial Twift integration working
daneden Jan 27, 2022
2f7c7e7
Fix sign out view
daneden Jan 27, 2022
d5ba7ea
Update version number
daneden Jan 27, 2022
5d4243a
Fix initialiser
daneden Jan 27, 2022
e8ab363
Fix replies view (I think)
daneden Jan 27, 2022
816783f
Add alt text support and multiple media
daneden Jan 28, 2022
968ae02
Fix client initialisation lol
daneden Jan 28, 2022
a907524
Fix media uploads
daneden Jan 28, 2022
c372ca5
Disable media buttons when the app is busy
daneden Jan 28, 2022
b12dbb5
Fix typeahead search
daneden Jan 28, 2022
ba45637
Add tentative attributed string extension
daneden Jan 28, 2022
d896dff
Use better media loading strategy and get ready for multiple account …
daneden Jan 29, 2022
e481c0b
Ok so that didn't do anything
daneden Jan 29, 2022
ddba6f3
Fixes
daneden Jan 29, 2022
c5837a7
Housekeeping
daneden Jan 29, 2022
3f8489c
Housekeeping
daneden Jan 29, 2022
8d378ed
Housekeeping
daneden Jan 29, 2022
93ef086
Update allowed alt text to include png
daneden Jan 29, 2022
414926b
Housekeeping
daneden Jan 29, 2022
8ee816d
Design fixes
daneden Jan 30, 2022
308333c
Fix tests
daneden Jan 30, 2022
e9d0cbe
Try to generate thumbnails for videos
daneden Jan 30, 2022
264e73c
Try to improve UTType casting and add some debugging code
daneden Feb 1, 2022
cdebe45
Fixes and improvements
daneden Feb 5, 2022
fed4523
Fix media tweeting
daneden Feb 7, 2022
ad3cb2b
try to fix video tweeting
daneden Feb 7, 2022
94c1b00
Oh my god video finally works
daneden Feb 8, 2022
da06639
Start working on drag-and-drop
daneden Feb 8, 2022
3e9f1bb
UX improvements for drag-and-dropping media
daneden Feb 9, 2022
c736d6e
Fix video uploads and compression
daneden Feb 13, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
TwitterAPI-Info.plist
**/*.xcuserstate
108 changes: 56 additions & 52 deletions Broadcast.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 2 additions & 6 deletions Broadcast/BroadcastApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SwiftUI
struct BroadcastApp: App {
@Environment(\.scenePhase) var scenePhase
@StateObject var themeHelper = ThemeHelper.shared
@StateObject var twitterClient = TwitterClient()
@StateObject var twitterClient = TwitterClientManager()
let persistenceController = PersistanceController.shared

var body: some Scene {
Expand All @@ -24,16 +24,12 @@ struct BroadcastApp: App {
.environment(\.managedObjectContext, persistenceController.container.viewContext)
.accentColor(themeHelper.color)
.onChange(of: scenePhase) { newPhase in
if newPhase == .active {
twitterClient.revalidateAccount()
}

persistenceController.save()
}

VisualEffectView(effect: UIBlurEffect(style: .regular))
.frame(height: geom.safeAreaInsets.top)
.ignoresSafeArea(.all, edges: .top)
.ignoresSafeArea(.container, edges: .top)
}
}
}
Expand Down
76 changes: 39 additions & 37 deletions Broadcast/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,33 @@
import SwiftUI
import Introspect
import TwitterText
import Twift

struct ContentView: View {
@ScaledMetric private var captionSize: CGFloat = 14
@Environment(\.cornerRadius) var cornerRadius: Double
@ScaledMetric private var captionSize: CGFloat = 20
@ScaledMetric private var bottomPadding: CGFloat = 80
@ScaledMetric private var replyBoxLimit: CGFloat = 96

@EnvironmentObject var twitterClient: TwitterClient
@EnvironmentObject var twitterClient: TwitterClientManager

@State private var photoPickerIsPresented = false
@State private var signOutScreenIsPresented = false
@State private var repliesSheetIsPresented = false

@State private var sendingTweet = false
@State private var replying = false
@State private var replyBoxHeight: CGFloat = 0

private var imageHeightCompensation: CGFloat {
(twitterClient.draft.media == nil ? 0 : bottomPadding) +
(twitterClient.selectedMedia.isEmpty ? 0 : bottomPadding) +
(replying ? min(replyBoxHeight, replyBoxLimit) : 0)
}

var body: some View {
GeometryReader { geom in
ZStack(alignment: .bottom) {
ScrollView {
VStack {
VStack(spacing: captionSize / 2) {
if replying, let lastTweet = twitterClient.lastTweet {
LastTweetReplyView(lastTweet: lastTweet)
.background(GeometryReader { geometry in
Expand All @@ -54,68 +55,69 @@ struct ContentView: View {
.padding()
.frame(maxWidth: .infinity)
.background(Color(.systemRed).opacity(0.2))
.cornerRadius(captionSize)
.cornerRadius(cornerRadius)
.onTapGesture {
withAnimation {
twitterClient.state = .idle
}
}
}

if $twitterClient.user.wrappedValue != nil {
ComposerView(signOutScreenIsPresented: $signOutScreenIsPresented)
if twitterClient.user != nil {
ComposerView()
.frame(
height: geom.size.height - (bottomPadding + (captionSize * 2)) - imageHeightCompensation,
alignment: .topLeading
)
.animation(.springAnimation, value: imageHeightCompensation)

AttachmentThumbnail(image: $twitterClient.draft.media)
AttachmentThumbnail(media: $twitterClient.selectedMedia)
.disabled(twitterClient.state.isBusy)
} else {
WelcomeView()
}
}
.padding()
.padding(.bottom, bottomPadding)
.padding(.top, captionSize)
.padding(.horizontal)
.frame(maxWidth: geom.size.width)
}

VStack {
if twitterClient.user != nil {
ActionBarView(replying: $replying)
} else {
Button(action: { twitterClient.signIn() }) {
Label("Sign In With Twitter", image: "twitter.fill")
.font(.broadcastHeadline)
.safeAreaInset(edge: .bottom, content: {
Group {
if twitterClient.user != nil {
ActionBarView(replying: $replying)
} else {
Button(action: { Task { await twitterClient.signIn() } }) {
Label("Sign In With Twitter", image: "twitter.fill")
.font(.broadcastHeadline)
}
.accessibilityIdentifier("loginButton")
}
.buttonStyle(BroadcastButtonStyle())
.accessibilityIdentifier("loginButton")
}
}
.padding()
.animation(.springAnimation)
.background(
VisualEffectView(effect: UIBlurEffect(style: .regular))
.ignoresSafeArea()
.opacity(twitterClient.user == nil ? 0 : 1)
)
.gesture(DragGesture().onEnded({ _ in UIApplication.shared.endEditing() }))
}
.sheet(isPresented: $signOutScreenIsPresented) {
SignOutView()
.buttonStyle(BroadcastButtonStyle(isLoading: twitterClient.state != .idle))
.padding()
.background(VisualEffectView(effect: UIBlurEffect(style: .regular)).ignoresSafeArea())
.gesture(DragGesture().onEnded({ _ in UIApplication.shared.endEditing() }))
})
}
.sheet(isPresented: $repliesSheetIsPresented) {
RepliesListView(tweet: twitterClient.lastTweet)
.accentColor(ThemeHelper.shared.color)
.font(.broadcastBody)
.environmentObject(twitterClient)
}
.onAppear {
UITextView.appearance().backgroundColor = .clear
}
.onChange(of: replying) { _ in
twitterClient.revalidateAccount()
}
.onPreferenceChange(ReplyBoxSizePreferenceKey.self) { newValue in
withAnimation(.easeInOut(duration: 0.1)) { replyBoxHeight = newValue }
withAnimation(.springAnimation) { replyBoxHeight = newValue + (captionSize / 2) }
}
.overlay {
if twitterClient.state == .initializing {
ZStack {
ProgressView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}.background(.background)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="18154" systemVersion="20F71" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21C52" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Draft" representedClassName="Draft" syncable="YES" codeGenerationType="class">
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="id" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="media" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES"/>
<attribute name="mediaId" optional="YES" attributeType="String"/>
<attribute name="text" optional="YES" attributeType="String"/>
</entity>
<elements>
<element name="Draft" positionX="-63" positionY="-18" width="128" height="103"/>
<element name="Draft" positionX="-63" positionY="-18" width="128" height="89"/>
</elements>
</model>
20 changes: 20 additions & 0 deletions Broadcast/Extensions/EnvironmentKeys+CornerRadius.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// EnvironmentKeys+CornerRadius.swift
// Broadcast
//
// Created by Daniel Eden on 13/02/2022.
//

import Foundation
import SwiftUI

struct CornerRadiusKey: EnvironmentKey {
static let defaultValue: Double = 12
}

extension EnvironmentValues {
var cornerRadius: Double {
get { self[CornerRadiusKey.self] }
set { self[CornerRadiusKey.self] = newValue }
}
}
12 changes: 0 additions & 12 deletions Broadcast/Extensions/Notification.extension.swift

This file was deleted.

41 changes: 23 additions & 18 deletions Broadcast/Extensions/TwitterClient+MockTweet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,32 @@
//

import Foundation
import Twift

extension TwitterClient.Tweet {
static var mockTweet: TwitterClient.Tweet {
TwitterClient.Tweet(
numericId: 0,
id: "0",
text: "just setting up my twttr",
likes: 420,
retweets: 69,
date: Date(),
author: .mockUser
)
extension Tweet {
static var mockTweet: Tweet {
let jsonString = """
{
"id": "0",
"text": "just setting up my twttr",
"createdAt": \(Date().timeIntervalSince1970),
"authorId": "0"
}
"""
return try! JSONDecoder().decode(Tweet.self, from: jsonString.data(using: .utf8)!)
}
}

extension TwitterClient.User {
static var mockUser: TwitterClient.User {
TwitterClient.User(
id: "0",
screenName: "_dte",
originalProfileImageURL: URL(string: "https://pbs.twimg.com/profile_images/1337359860409790469/javRMXyG_x96.jpg")!
)
extension User {
static var mockUser: User {
let jsonString = """
{
"id": "0",
"name": "Daniel Eden",
"username": "_dte",
"profileImageUrl": "https://pbs.twimg.com/profile_images/1337359860409790469/javRMXyG_x96.jpg"
}
"""
return try! JSONDecoder().decode(User.self, from: jsonString.data(using: .utf8)!)
}
}
31 changes: 31 additions & 0 deletions Broadcast/Extensions/TwitterClientManager+CompressMedia.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// TwitterClientManager+CompressMedia.swift
// Broadcast
//
// Created by Daniel Eden on 11/02/2022.
//

import Foundation
import AVFoundation

extension TwitterClientManager {
func compressVideo(
inputURL: URL,
outputURL: URL
) async -> AVAssetExportSession? {
let urlAsset = AVURLAsset(url: inputURL, options: nil)

guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetHighestQuality) else {
return nil
}

exportSession.fileLengthLimit = 14 * 2^20 // 15mb limit
exportSession.timeRange = CMTimeRange(start: CMTime.zero, duration: urlAsset.duration)
exportSession.outputURL = outputURL
exportSession.outputFileType = AVFileType.mp4
exportSession.shouldOptimizeForNetworkUse = true

await exportSession.export()
return exportSession
}
}
30 changes: 0 additions & 30 deletions Broadcast/Extensions/UIImage.extension.swift

This file was deleted.

Loading