Skip to content

Commit

Permalink
Merge pull request #115 from DroidKaigi/feature/toast_ios
Browse files Browse the repository at this point in the history
  • Loading branch information
shin-usu authored Jul 11, 2024
2 parents cebcae5 + 62fe3e6 commit 072c0c5
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 56 deletions.
8 changes: 5 additions & 3 deletions app-ios/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ let package = Package(
.timetableFeature,
.timetableDetailFeature,
.tca,
"KMPClient",
.product(name: "LicenseList", package: "LicenseList"),
.kmpClient,
.licenseList,
]
),
.testTarget(
Expand Down Expand Up @@ -114,6 +114,7 @@ let package = Package(
dependencies: [
.tca,
.theme,
.commonComponents
]
),
.testTarget(
Expand Down Expand Up @@ -193,7 +194,7 @@ let package = Package(
.tca
]
),
.target(name: "CommonComponents"),
.target(name: "CommonComponents", dependencies: [.theme]),
// Please run ./gradlew app-ios-shared:assembleSharedXCFramework first
.binaryTarget(name: "KmpModule", path: "../app-ios-shared/build/XCFrameworks/debug/shared.xcframework"),
]
Expand Down Expand Up @@ -228,6 +229,7 @@ extension Target.Dependency {
static let firebaseAuth: Target.Dependency = .product(name: "FirebaseAuth", package: "firebase-ios-sdk")
static let firebaseRemoteConfig: Target.Dependency = .product(name: "FirebaseRemoteConfig", package: "firebase-ios-sdk")
static let tca: Target.Dependency = .product(name: "ComposableArchitecture", package: "swift-composable-architecture")
static let licenseList: Target.Dependency = .product(name: "LicenseList", package: "LicenseList")
}

/// ref: https://github.com/treastrain/swift-upcomingfeatureflags-cheatsheet?tab=readme-ov-file#short
Expand Down
68 changes: 68 additions & 0 deletions app-ios/Sources/CommonComponents/Modifier/ToastModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import SwiftUI
import Theme

public struct ToastState: Equatable {
public let text: String

public init(text: String) {
self.text = text
}
}

struct ToastModifier: ViewModifier {
@Binding var item: ToastState?
@State private var task: Task<Void, Never>? = nil
private let animationDelay = 1.0

func body(content: Content) -> some View {
ZStack {
content

if item != nil {
ToastView(item: $item)
.zIndex(1)
.transition(.move(edge: .bottom).combined(with: .opacity))
.onTapGesture {
item = nil
}
}
}
.onChange(of: item) {
task?.cancel()
if item != nil {
task = Task {
try? await Task.sleep(for: .seconds(4))
withAnimation(.easeInOut(duration: animationDelay)) {
item = nil
}
}
}
}
.animation(.easeInOut(duration: animationDelay), value: item)
}
}

struct ToastView: View {
@Binding var item: ToastState?
var body: some View {
ZStack {
Text(item?.text ?? "")
.textStyle(.bodyMedium)
.foregroundColor(AssetColors.Inverse.inverseOnSurface.swiftUIColor)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 16)
.padding(.vertical, 14)
.background(AssetColors.Inverse.inverseSurface.swiftUIColor)
.padding(.horizontal, 12)
.clipShape(RoundedRectangle(cornerRadius: 4))
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
.padding(.vertical, 16)
}
}

extension View {
public func toast(_ item: Binding<ToastState?>) -> some View {
modifier(ToastModifier(item: item))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@
},
"name" : {

},
"TimetableDetailAddBookmark" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Added to bookmarks"
}
},
"ja" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "ブックマークに追加されました"
}
}
}
},
"TimeTableDetailApplicants" : {
"localizations" : {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import ComposableArchitecture
import CommonComponents

@Reducer
public struct TimetableDetailReducer {
public init() {}

@ObservableState
public struct State: Equatable {
var title: String
var toast: ToastState?
}

public enum Action {
case onAppear
public enum Action: BindableAction {
case binding(BindingAction<State>)
case view(View)

public enum View {
case favoriteButtonTapped
}
}

public var body: some ReducerOf<Self> {
BindingReducer()
Reduce { state, action in
switch action {
case .onAppear:
state.title = "Timetable Detail"
case .view(.favoriteButtonTapped):
state.toast = .init(text: String(localized: "TimetableDetailAddBookmark", bundle: .module))
return .none
case .binding:
return .none
}
}
Expand Down
10 changes: 6 additions & 4 deletions app-ios/Sources/TimetableDetailFeature/TimetableDetailView.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import SwiftUI
import ComposableArchitecture
import Theme
import CommonComponents

public struct TimetableDetailView: View {
private let store: StoreOf<TimetableDetailReducer>

@Bindable private var store: StoreOf<TimetableDetailReducer>
public var body: some View {
GeometryReader { proxy in
VStack(spacing: 0) {
Expand All @@ -22,6 +23,7 @@ public struct TimetableDetailView: View {
archive
.padding(16)
}
.toast($store.toast)

footer
}
Expand Down Expand Up @@ -51,7 +53,7 @@ public struct TimetableDetailView: View {
}
Spacer()
Button {
// do something
store.send(.view(.favoriteButtonTapped))
} label: {
Group {
Image(.icFavorite)
Expand Down Expand Up @@ -207,7 +209,7 @@ public struct TimetableDetailView: View {

#Preview {
TimetableDetailView(
store: .init(initialState: .init(title: "")) {
store: .init(initialState: .init(toast: nil)) {
TimetableDetailReducer()
}
)
Expand Down
41 changes: 0 additions & 41 deletions app-ios/Tests/AboutFeatureTests/AboutFeatureTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,6 @@ import ComposableArchitecture

final class AboutFeatureTests: XCTestCase {

@MainActor
func testTappedStaffs() async {
let store = TestStore(initialState: AboutReducer.State()) {
AboutReducer()
}

await store.send(\.view.staffsTapped) {
$0.path[id: 0] = .staffs
}
}

@MainActor
func testTappedControbuters() async {
let store = TestStore(initialState: AboutReducer.State()) {
AboutReducer()
}
await store.send(\.view.contributersTapped) {
$0.path[id: 0] = .contributers
}
}

@MainActor
func testTappedSponsors() async {
let store = TestStore(initialState: AboutReducer.State()) {
AboutReducer()
}
await store.send(\.view.sponsorsTapped) {
$0.path[id: 0] = .sponsors
}
}

@MainActor
func testTappedCodeOfConduct() async {
let store = TestStore(initialState: AboutReducer.State()) {
Expand All @@ -45,16 +14,6 @@ final class AboutFeatureTests: XCTestCase {
}
}

@MainActor
func testTappedAcknowledgements() async {
let store = TestStore(initialState: AboutReducer.State()) {
AboutReducer()
}
await store.send(\.view.acknowledgementsTapped) {
$0.path[id: 0] = .acknowledgements
}
}

@MainActor
func testTappedPrivacyPolicy() async {
let store = TestStore(initialState: AboutReducer.State()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import ComposableArchitecture

final class TimetableDetail_iosTests: XCTestCase {
@MainActor func testExample() async throws {
let store = TestStore(initialState: TimetableDetailReducer.State(title: "Test")) {
let store = TestStore(initialState: TimetableDetailReducer.State()) {
TimetableDetailReducer()
}
await store.send(.onAppear) {
$0.title = "Timetable Detail"

await store.send(.favoriteButtonTapped) {
$0.toast = .init(text: String(localized: "TimetableDetailAddBookmark", bundle: .module))
}

}
}

0 comments on commit 072c0c5

Please sign in to comment.