diff --git a/README.md b/README.md
index 30d330d3d..6e2475afa 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ In addition to the standard features of a conference app, the DroidKaigi 2024 of
- **Contributors**: Discover the contributors behind the app.
...and more!
-![image](https://github.com/user-attachments/assets/ffed2cb2-455b-4de8-a9d2-be9ca0842b99)
+![image](https://github.com/user-attachments/assets/d1aeccc1-1e8e-475f-9c51-72fb595c6563)
## Try the app
diff --git a/app-android/src/main/AndroidManifest.xml b/app-android/src/main/AndroidManifest.xml
index a3e472e3e..27dd16aa5 100644
--- a/app-android/src/main/AndroidManifest.xml
+++ b/app-android/src/main/AndroidManifest.xml
@@ -58,6 +58,16 @@
android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar" />
+
+
+
+
diff --git a/app-android/src/main/java/io/github/droidkaigi/confsched/KaigiApp.kt b/app-android/src/main/java/io/github/droidkaigi/confsched/KaigiApp.kt
index 36dfed7ef..c9cb21b00 100644
--- a/app-android/src/main/java/io/github/droidkaigi/confsched/KaigiApp.kt
+++ b/app-android/src/main/java/io/github/droidkaigi/confsched/KaigiApp.kt
@@ -28,6 +28,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontFamily
@@ -80,6 +81,7 @@ import io.github.droidkaigi.confsched.sessions.timetableScreenRoute
import io.github.droidkaigi.confsched.settings.settingsScreenRoute
import io.github.droidkaigi.confsched.settings.settingsScreens
import io.github.droidkaigi.confsched.share.ShareNavigator
+import io.github.droidkaigi.confsched.share.saveToDisk
import io.github.droidkaigi.confsched.sponsors.sponsorsScreenRoute
import io.github.droidkaigi.confsched.sponsors.sponsorsScreens
import io.github.droidkaigi.confsched.staff.staffScreenRoute
@@ -134,11 +136,20 @@ private fun KaigiNavHost(
CompositionLocalProvider(
LocalSharedTransitionScope provides this,
) {
+ val context = LocalContext.current
NavHostWithSharedAxisX(
navController = navController,
startDestination = mainScreenRoute,
) {
- mainScreen(windowSize, navController, externalNavController)
+ mainScreen(
+ windowSize,
+ navController,
+ externalNavController,
+ onClickShareProfileCard = { shareText, imageBitmap ->
+ val imageAbsolutePath = imageBitmap.saveToDisk(context)
+ externalNavController.onShareProfileCardClick(shareText, imageAbsolutePath)
+ },
+ )
sessionScreens(
onNavigationIconClick = navController::popBackStack,
onLinkClick = externalNavController::navigate,
@@ -185,6 +196,7 @@ private fun NavGraphBuilder.mainScreen(
navController: NavHostController,
@Suppress("UnusedParameter")
externalNavController: ExternalNavController,
+ onClickShareProfileCard: (String, ImageBitmap) -> Unit,
) {
mainScreen(
windowSize = windowSize,
@@ -197,6 +209,7 @@ private fun NavGraphBuilder.mainScreen(
contentPadding = contentPadding,
)
eventMapScreens(
+ contentPadding = contentPadding,
onEventMapItemClick = externalNavController::navigate,
)
favoritesScreens(
@@ -248,7 +261,10 @@ private fun NavGraphBuilder.mainScreen(
}
},
)
- profileCardScreen(contentPadding)
+ profileCardScreen(
+ contentPadding = contentPadding,
+ onClickShareProfileCard = onClickShareProfileCard,
+ )
},
)
}
@@ -346,6 +362,16 @@ private class ExternalNavController(
)
}
+ fun onShareProfileCardClick(
+ text: String,
+ filePath: String,
+ ) {
+ shareNavigator.shareTextWithImage(
+ text = text,
+ filePath = filePath,
+ )
+ }
+
@Suppress("SwallowedException")
@RequiresApi(Build.VERSION_CODES.R)
private fun navigateToNativeAppApi30(
diff --git a/app-android/src/main/java/io/github/droidkaigi/confsched/share/SaveToDisk.kt b/app-android/src/main/java/io/github/droidkaigi/confsched/share/SaveToDisk.kt
new file mode 100644
index 000000000..f7a366965
--- /dev/null
+++ b/app-android/src/main/java/io/github/droidkaigi/confsched/share/SaveToDisk.kt
@@ -0,0 +1,31 @@
+package io.github.droidkaigi.confsched.share
+
+import android.content.Context
+import android.graphics.Bitmap.CompressFormat.PNG
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.asAndroidBitmap
+import kotlinx.datetime.Clock.System
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.toLocalDateTime
+import java.io.File
+import java.io.FileOutputStream
+
+fun ImageBitmap.saveToDisk(context: Context): String {
+ val timestamp = System.now()
+ .toLocalDateTime(TimeZone.UTC)
+ .toString()
+ .replace(":", "")
+ .replace(".", "")
+ val fileName = "shared_image_$timestamp.png"
+
+ val cachePath = File(context.cacheDir, "images")
+ cachePath.mkdirs()
+ val file = File(cachePath, fileName)
+ val outputStream = FileOutputStream(file)
+
+ this.asAndroidBitmap().compress(PNG, 100, outputStream)
+ outputStream.flush()
+ outputStream.close()
+
+ return file.absolutePath
+}
diff --git a/app-android/src/main/java/io/github/droidkaigi/confsched/share/ShareNavigator.kt b/app-android/src/main/java/io/github/droidkaigi/confsched/share/ShareNavigator.kt
index 107a13598..82872c6ee 100644
--- a/app-android/src/main/java/io/github/droidkaigi/confsched/share/ShareNavigator.kt
+++ b/app-android/src/main/java/io/github/droidkaigi/confsched/share/ShareNavigator.kt
@@ -3,7 +3,9 @@ package io.github.droidkaigi.confsched.share
import android.content.ActivityNotFoundException
import android.content.Context
import androidx.core.app.ShareCompat
+import androidx.core.content.FileProvider
import co.touchlab.kermit.Logger
+import java.io.File
class ShareNavigator(private val context: Context) {
fun share(text: String) {
@@ -16,4 +18,19 @@ class ShareNavigator(private val context: Context) {
Logger.e("ActivityNotFoundException Fail startActivity: $e")
}
}
+
+ fun shareTextWithImage(text: String, filePath: String) {
+ try {
+ val file = File(filePath)
+ val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
+
+ ShareCompat.IntentBuilder(context)
+ .setStream(uri)
+ .setText(text)
+ .setType("image/png")
+ .startChooser()
+ } catch (e: ActivityNotFoundException) {
+ Logger.e("ActivityNotFoundException Fail startActivity: $e")
+ }
+ }
}
diff --git a/app-android/src/main/res/xml/file_paths.xml b/app-android/src/main/res/xml/file_paths.xml
new file mode 100644
index 000000000..0ba6f2ad9
--- /dev/null
+++ b/app-android/src/main/res/xml/file_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/app-ios/App/App.xcodeproj/project.pbxproj b/app-ios/App/App.xcodeproj/project.pbxproj
index 75c9839e4..0e6e439e4 100644
--- a/app-ios/App/App.xcodeproj/project.pbxproj
+++ b/app-ios/App/App.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 830BFA7C2C74EBF40017A600 /* compose-resources in Resources */ = {isa = PBXBuildFile; fileRef = 830BFA7B2C74EBF40017A600 /* compose-resources */; };
8C31F46B2BF6909A003F1BBA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 8C31F46A2BF6909A003F1BBA /* GoogleService-Info.plist */; };
8C7DACB72BCBCCB0002C298A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8C7DACB02BCBCCB0002C298A /* Preview Assets.xcassets */; };
8C7DACB82BCBCCB0002C298A /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7DACB22BCBCCB0002C298A /* App.swift */; };
@@ -15,6 +16,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ 830BFA7B2C74EBF40017A600 /* compose-resources */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "compose-resources"; path = "../../app-ios-shared/build/kotlin-multiplatform-resources/aggregated-resources/iosSimulatorArm64/compose-resources"; sourceTree = ""; };
8C31F46A2BF6909A003F1BBA /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; };
8C772B152BCBCBCA00F2BADC /* DroidKaigi2024App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DroidKaigi2024App.app; sourceTree = BUILT_PRODUCTS_DIR; };
8C7DACB02BCBCCB0002C298A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
@@ -42,6 +44,7 @@
8C772B0C2BCBCBCA00F2BADC = {
isa = PBXGroup;
children = (
+ 830BFA7B2C74EBF40017A600 /* compose-resources */,
C412816C2C149FB500B458D1 /* DroidKaigi2024App-Info.plist */,
8C7DACC22BCBD111002C298A /* App */,
8C7DACC12BCBD0F1002C298A /* app-ios */,
@@ -152,6 +155,7 @@
8C7DACB92BCBCCB0002C298A /* Assets.xcassets in Resources */,
8C7DACB72BCBCCB0002C298A /* Preview Assets.xcassets in Resources */,
8C31F46B2BF6909A003F1BBA /* GoogleService-Info.plist in Resources */,
+ 830BFA7C2C74EBF40017A600 /* compose-resources in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -173,7 +177,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/zsh;
- shellScript = "cd ${SRCROOT}/../..\n./gradlew assembleSharedXCFramework --no-configuration-cache\n";
+ shellScript = "cd ${SRCROOT}/../..\n./gradlew assembleSharedXCFramework --no-configuration-cache\n./gradlew iosSimulatorArm64AggregateResources --no-configuration-cache\n\nRESOURCE_DIR=\"./app-ios-shared/build/kotlin-multiplatform-resources/aggregated-resources/iosSimulatorArm64\"\n\nmkdir ${RESOURCE_DIR}/compose-resources\ncp -R ${RESOURCE_DIR}/composeResources ${RESOURCE_DIR}/compose-resources\n";
};
/* End PBXShellScriptBuildPhase section */
diff --git a/app-ios/Sources/CommonComponents/Timetable/CircularUserIcon.swift b/app-ios/Sources/CommonComponents/Timetable/CircularUserIcon.swift
new file mode 100644
index 000000000..3101378f3
--- /dev/null
+++ b/app-ios/Sources/CommonComponents/Timetable/CircularUserIcon.swift
@@ -0,0 +1,59 @@
+import Foundation
+import SwiftUI
+
+private actor CircularUserIconInMemoryCache {
+ static let shared = CircularUserIconInMemoryCache()
+ private init() {}
+
+ private var cache: [String: Data] = [:]
+
+ func data(urlString: String) -> Data? {
+ return cache[urlString]
+ }
+
+ func set(data: Data, urlString: String) {
+ cache[urlString] = data
+ }
+}
+
+public struct CircularUserIcon: View {
+ let urlString: String
+ @State private var iconData: Data?
+
+ public init(urlString: String) {
+ self.urlString = urlString
+ }
+
+ public var body: some View {
+ Group {
+ if let data = iconData,
+ let uiImage = UIImage(data: data) {
+ Image(uiImage: uiImage)
+ .resizable()
+ } else {
+ Circle().stroke(Color.gray)
+ }
+ }
+ .clipShape(Circle())
+ .task {
+ if let data = await CircularUserIconInMemoryCache.shared.data(urlString: urlString) {
+ iconData = data
+ return
+ }
+
+ guard let url = URL(string: urlString) else {
+ return
+ }
+ let urlRequest = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy)
+ if let (data, _) = try? await URLSession.shared.data(for: urlRequest) {
+ iconData = data
+ await CircularUserIconInMemoryCache.shared.set(data: data, urlString: urlString)
+ }
+ }
+ }
+}
+
+#Preview {
+ CircularUserIcon(urlString: "https://avatars.githubusercontent.com/u/10727543?s=96&v=4")
+ .frame(width: 32, height: 32)
+}
diff --git a/app-ios/Sources/CommonComponents/Timetable/RoomTag.swift b/app-ios/Sources/CommonComponents/Timetable/RoomTag.swift
index d21ef50f4..7a3aeb31d 100644
--- a/app-ios/Sources/CommonComponents/Timetable/RoomTag.swift
+++ b/app-ios/Sources/CommonComponents/Timetable/RoomTag.swift
@@ -1,74 +1,37 @@
import SwiftUI
import Theme
-import shared
import Model
public struct RoomTag: View {
let roomName: MultiLangText
+ let roomType: RoomType
public init(_ roomName: MultiLangText) {
self.roomName = roomName
+ self.roomType = .init(enTitle: roomName.enTitle)
}
public var body: some View {
HStack(spacing: 4) {
- roomName.roomType.shape
+ RoomTypeShape(roomType: roomType)
Text(roomName.currentLangTitle)
.textStyle(.labelMedium)
}
- .foregroundStyle(roomName.roomType.theme.primaryColor)
+ .foregroundStyle(roomType.theme.primaryColor)
.padding(.horizontal, 6)
.overlay(
RoundedRectangle(cornerRadius: 2)
- .stroke(roomName.roomType.theme.primaryColor, lineWidth: 1)
+ .stroke(roomType.theme.primaryColor, lineWidth: 1)
)
}
}
-enum ThemeKey {
- static let iguana = "iguana"
- static let hedgehog = "hedgehog"
- static let giraffe = "giraffe"
- static let flamingo = "flamingo"
- static let jellyfish = "jellyfish"
-}
-
-
-extension MultiLangText {
- var roomType: RoomType {
- switch enTitle.lowercased() {
- case ThemeKey.flamingo: .roomF
- case ThemeKey.giraffe: .roomG
- case ThemeKey.hedgehog: .roomH
- case ThemeKey.iguana: .roomI
- case ThemeKey.jellyfish: .roomJ
- default: .roomIj
- }
- }
-}
-
-extension RoomType {
- public var shape: some View {
- Group {
- switch self {
- case .roomG: Image(.icCircleFill).renderingMode(.template)
- case .roomH: Image(.icDiamondFill).renderingMode(.template)
- case .roomF: Image(.icSharpDiamondFill).renderingMode(.template)
- case .roomI: Image(.icSquareFill).renderingMode(.template)
- case .roomJ: Image(.icTriangleFill).renderingMode(.template)
- case .roomIj: Image(.icSquareFill).renderingMode(.template)
- }
- }
- .foregroundStyle(theme.primaryColor)
- .frame(width: 12, height: 12)
- }
-}
-
#Preview {
RoomTag(
MultiLangText(
- jaTitle: "Iguana",
- enTitle: "Iguana"
+ currentLangTitle: "Iguana",
+ enTitle: "Iguana",
+ jaTitle: "Iguana"
)
)
}
diff --git a/app-ios/Sources/CommonComponents/Timetable/RoomTypeShape.swift b/app-ios/Sources/CommonComponents/Timetable/RoomTypeShape.swift
new file mode 100644
index 000000000..3692cdc0c
--- /dev/null
+++ b/app-ios/Sources/CommonComponents/Timetable/RoomTypeShape.swift
@@ -0,0 +1,36 @@
+import SwiftUI
+import Model
+
+public struct RoomTypeShape: View {
+ let roomType: RoomType
+
+ public init(roomType: RoomType) {
+ self.roomType = roomType
+ }
+
+ public var body: some View {
+ Group {
+ switch roomType {
+ case .roomG: Image(.icCircleFill).renderingMode(.template)
+ case .roomH: Image(.icDiamondFill).renderingMode(.template)
+ case .roomF: Image(.icSharpDiamondFill).renderingMode(.template)
+ case .roomI: Image(.icSquareFill).renderingMode(.template)
+ case .roomJ: Image(.icTriangleFill).renderingMode(.template)
+ case .roomIj: Image(.icSquareFill).renderingMode(.template)
+ }
+ }
+ .foregroundStyle(roomType.theme.primaryColor)
+ .frame(width: 12, height: 12)
+ }
+}
+
+#Preview {
+ Group {
+ RoomTypeShape(roomType: .roomF)
+ RoomTypeShape(roomType: .roomG)
+ RoomTypeShape(roomType: .roomH)
+ RoomTypeShape(roomType: .roomI)
+ RoomTypeShape(roomType: .roomJ)
+ RoomTypeShape(roomType: .roomIj)
+ }
+}
diff --git a/app-ios/Sources/CommonComponents/Timetable/TimetableCard.swift b/app-ios/Sources/CommonComponents/Timetable/TimetableCard.swift
index 3afe811a2..a3d26d265 100644
--- a/app-ios/Sources/CommonComponents/Timetable/TimetableCard.swift
+++ b/app-ios/Sources/CommonComponents/Timetable/TimetableCard.swift
@@ -26,7 +26,11 @@ public struct TimetableCard: View {
} label: {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 4) {
- RoomTag(timetableItem.room.name)
+ RoomTag(.init(
+ currentLangTitle: timetableItem.room.name.currentLangTitle,
+ enTitle: timetableItem.room.name.enTitle,
+ jaTitle: timetableItem.room.name.jaTitle
+ ))
ForEach(timetableItem.language.labels, id: \.self) { label in
LanguageTag(label)
}
@@ -54,18 +58,8 @@ public struct TimetableCard: View {
ForEach(timetableItem.speakers, id: \.id) { speaker in
HStack(spacing: 8) {
- Group {
- if let url = URL(string: speaker.iconUrl) {
- AsyncImage(url: url) {
- $0.image?.resizable()
- }
- } else {
- Circle().stroke(Color.gray)
- }
- }
- .frame(width: 32, height: 32)
- .clipShape(Circle())
-
+ CircularUserIcon(urlString: speaker.iconUrl)
+ .frame(width: 32, height: 32)
Text(speaker.name)
.textStyle(.titleSmall)
.foregroundStyle(AssetColors.Surface.onSurfaceVariant.swiftUIColor)
diff --git a/app-ios/Sources/ContributorFeature/ContributorListItemView.swift b/app-ios/Sources/ContributorFeature/ContributorListItemView.swift
index 3adc985ef..9c77c31bc 100644
--- a/app-ios/Sources/ContributorFeature/ContributorListItemView.swift
+++ b/app-ios/Sources/ContributorFeature/ContributorListItemView.swift
@@ -1,6 +1,7 @@
import SwiftUI
import Theme
import Model
+import CommonComponents
struct ContributorListItemView: View {
let contributor: Contributor
@@ -13,11 +14,8 @@ struct ContributorListItemView: View {
}
} label: {
HStack(alignment: .center, spacing: 12) {
- AsyncImage(url: contributor.iconUrl) {
- $0.image?.resizable()
- }
- .frame(width: 52, height: 52)
- .clipShape(Circle())
+ CircularUserIcon(urlString: contributor.iconUrl.absoluteString)
+ .frame(width: 52, height: 52)
Text(contributor.userName)
.textStyle(.bodyLarge)
diff --git a/app-ios/Sources/ContributorFeature/ContributorView.swift b/app-ios/Sources/ContributorFeature/ContributorView.swift
index 97544a93e..2188d28f5 100644
--- a/app-ios/Sources/ContributorFeature/ContributorView.swift
+++ b/app-ios/Sources/ContributorFeature/ContributorView.swift
@@ -20,13 +20,13 @@ public struct ContributorView: View {
"KMP Presenter"
case .fullKmp:
- "KMP Compose view"
+ "KMP Compose View"
}
}
}
- @State private var viewType: ViewType = .swift
-
+ @State private var selectedTab: ViewType = .swift
+ @Namespace var namespace
@Bindable var store: StoreOf
public init(store: StoreOf) {
@@ -35,23 +35,24 @@ public struct ContributorView: View {
public var body: some View {
VStack(spacing: 0) {
- Picker("", selection: $viewType) {
- ForEach(ViewType.allCases, id: \.self) { segment in
- Text(segment.title)
- }
- }
- .pickerStyle(.segmented)
- .padding(16)
+ tabBar
- switch viewType {
+ switch selectedTab {
case .swift:
SwiftUIContributorView(store: store)
case .kmpPresenter:
- KmpPresenterContributorView()
-
+ KmpPresenterContributorView {
+ store.send(.view(.contributorButtonTapped($0)))
+ }
+ .tag(ViewType.kmpPresenter)
case .fullKmp:
- KmpContributorComposeViewControllerWrapper()
+ KmpContributorComposeViewControllerWrapper { urlString in
+ guard let url = URL(string: urlString) else {
+ return
+ }
+ store.send(.view(.contributorButtonTapped(url)))
+ }
}
}
.background(AssetColors.Surface.surface.swiftUIColor)
@@ -62,6 +63,41 @@ public struct ContributorView: View {
.ignoresSafeArea()
})
}
+
+ @MainActor
+ private var tabBar: some View {
+ HStack {
+ ForEach(ViewType.allCases, id: \.self) { tab in
+ Button {
+ selectedTab = tab
+ } label: {
+ ZStack {
+ Text(tab.title)
+ .textStyle(.titleMedium)
+ .foregroundStyle(
+ selectedTab == tab ? AssetColors.Primary.primaryFixed.swiftUIColor : AssetColors.Surface.onSurface.swiftUIColor
+ )
+ VStack {
+ Spacer()
+ Group {
+ if selectedTab == tab {
+ AssetColors.Primary.primaryFixed.swiftUIColor
+ .matchedGeometryEffect(id: "underline", in: namespace, properties: .frame)
+ } else {
+ Color.clear
+ }
+ }
+ .frame(height: 3)
+ }
+ }
+ .frame(height: 52, alignment: .center)
+ .frame(maxWidth: .infinity)
+ .animation(.spring(), value: selectedTab)
+ }
+ .frame(maxWidth: .infinity)
+ }
+ }
+ }
}
#Preview {
diff --git a/app-ios/Sources/ContributorFeature/KmpPresenterContributorView.swift b/app-ios/Sources/ContributorFeature/KmpPresenterContributorView.swift
index de91f8455..b7f9187d3 100644
--- a/app-ios/Sources/ContributorFeature/KmpPresenterContributorView.swift
+++ b/app-ios/Sources/ContributorFeature/KmpPresenterContributorView.swift
@@ -8,14 +8,15 @@ import Theme
struct KmpPresenterContributorView: View {
private let repositories: any Repositories
private let events: SkieSwiftMutableSharedFlow
+ private let onContributorButtonTapped: (URL) -> Void
@State private var currentState: ContributorsUiState? = nil
- @State private var showingUrl: IdentifiableURL?
- init() {
+ init(onContributorButtonTapped: @escaping (URL) -> Void) {
self.repositories = Container.shared.get(type: (any Repositories).self)
self.events = SkieKotlinSharedFlowFactory()
.createSkieKotlinSharedFlow(replay: 0, extraBufferCapacity: 0)
+ self.onContributorButtonTapped = onContributorButtonTapped
}
var body: some View {
@@ -30,9 +31,10 @@ struct KmpPresenterContributorView: View {
profileUrl: value.profileUrl.map { URL(string: $0)! } ,
iconUrl: URL(string: value.iconUrl)!
)
- ContributorListItemView(contributor: contributor) { url in
- showingUrl = IdentifiableURL(url)
- }
+ ContributorListItemView(
+ contributor: contributor,
+ onContributorButtonTapped: onContributorButtonTapped
+ )
}
}
}
@@ -45,10 +47,6 @@ struct KmpPresenterContributorView: View {
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(AssetColors.Surface.surface.swiftUIColor)
- .sheet(item: $showingUrl, content: { url in
- SafariView(url: url.id)
- .ignoresSafeArea()
- })
}
@MainActor
diff --git a/app-ios/Sources/ContributorFeature/Resources/Localizable.xcstrings b/app-ios/Sources/ContributorFeature/Resources/Localizable.xcstrings
index d13424538..b2ad6f9c7 100644
--- a/app-ios/Sources/ContributorFeature/Resources/Localizable.xcstrings
+++ b/app-ios/Sources/ContributorFeature/Resources/Localizable.xcstrings
@@ -1,9 +1,6 @@
{
"sourceLanguage" : "en",
"strings" : {
- "" : {
-
- },
"Contributor" : {
"localizations" : {
"en" : {
diff --git a/app-ios/Sources/EventMapFeature/EventItem.swift b/app-ios/Sources/EventMapFeature/EventItem.swift
index 2fd6d19ef..5a3cf7e23 100644
--- a/app-ios/Sources/EventMapFeature/EventItem.swift
+++ b/app-ios/Sources/EventMapFeature/EventItem.swift
@@ -11,7 +11,12 @@ struct EventItem: View {
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 12) {
- RoomTag(event.roomName)
+ RoomTag(.init(
+ currentLangTitle: event.roomName.currentLangTitle,
+ enTitle: event.roomName.enTitle,
+ jaTitle: event.roomName.jaTitle
+ ))
+
Text(event.name.currentLangTitle)
.foregroundStyle(AssetColors.Primary.primaryFixed.swiftUIColor)
.textStyle(.titleMedium)
diff --git a/app-ios/Sources/KMPClient/Views/KmpContributorComposeViewControllerWrapper.swift b/app-ios/Sources/KMPClient/Views/KmpContributorComposeViewControllerWrapper.swift
index 92e05dca8..871d23637 100644
--- a/app-ios/Sources/KMPClient/Views/KmpContributorComposeViewControllerWrapper.swift
+++ b/app-ios/Sources/KMPClient/Views/KmpContributorComposeViewControllerWrapper.swift
@@ -1,17 +1,21 @@
import SwiftUI
-@preconcurrency import shared
+import shared
public struct KmpContributorComposeViewControllerWrapper: UIViewControllerRepresentable {
+ public typealias URLString = String
+
public let repositories: any Repositories
+ private let onContributorsItemClick: (URLString) -> Void
- public init() {
+ public init(onContributorsItemClick: @escaping (URLString) -> Void) {
self.repositories = Container.shared.get(type: (any Repositories).self)
+ self.onContributorsItemClick = onContributorsItemClick
}
public func makeUIViewController(context: Context) -> UIViewController {
return contributorsViewController(
repositories: repositories,
- onContributorsItemClick: {_ in}
+ onContributorsItemClick: onContributorsItemClick
)
}
diff --git a/app-ios/Sources/Model/Entity/MultiLangText.swift b/app-ios/Sources/Model/Entity/MultiLangText.swift
new file mode 100644
index 000000000..e2a5c708b
--- /dev/null
+++ b/app-ios/Sources/Model/Entity/MultiLangText.swift
@@ -0,0 +1,13 @@
+import Foundation
+
+public struct MultiLangText {
+ public let currentLangTitle: String
+ public let enTitle: String
+ public let jaTitle: String
+
+ public init(currentLangTitle: String, enTitle: String, jaTitle: String) {
+ self.currentLangTitle = currentLangTitle
+ self.enTitle = enTitle
+ self.jaTitle = jaTitle
+ }
+}
diff --git a/app-ios/Sources/Model/Entity/RoomTheme.swift b/app-ios/Sources/Model/Entity/RoomTheme.swift
new file mode 100644
index 000000000..5923eb88a
--- /dev/null
+++ b/app-ios/Sources/Model/Entity/RoomTheme.swift
@@ -0,0 +1,7 @@
+import SwiftUI
+
+public struct RoomTheme {
+ public let primaryColor: Color
+ public let containerColor: Color
+ public let dimColor: Color
+}
diff --git a/app-ios/Sources/Model/Entity/RoomType.swift b/app-ios/Sources/Model/Entity/RoomType.swift
new file mode 100644
index 000000000..aa493db36
--- /dev/null
+++ b/app-ios/Sources/Model/Entity/RoomType.swift
@@ -0,0 +1,29 @@
+import Foundation
+
+public enum RoomType {
+ case roomF
+ case roomG
+ case roomH
+ case roomI
+ case roomJ
+ case roomIj
+
+ enum ThemeKey: String {
+ case iguana
+ case hedgehog
+ case giraffe
+ case flamingo
+ case jellyfish
+ }
+
+ public init(enTitle: String) {
+ self = switch enTitle.lowercased() {
+ case ThemeKey.flamingo.rawValue: Self.roomF
+ case ThemeKey.giraffe.rawValue: Self.roomG
+ case ThemeKey.hedgehog.rawValue: Self.roomH
+ case ThemeKey.iguana.rawValue: Self.roomI
+ case ThemeKey.jellyfish.rawValue: Self.roomJ
+ default: Self.roomIj
+ }
+ }
+}
diff --git a/app-ios/Sources/Model/Extension/RoomType+Extension.swift b/app-ios/Sources/Model/Extension/RoomType+Extension.swift
index 38b3b69ac..dbe32f3c7 100644
--- a/app-ios/Sources/Model/Extension/RoomType+Extension.swift
+++ b/app-ios/Sources/Model/Extension/RoomType+Extension.swift
@@ -1,6 +1,5 @@
import Foundation
import Theme
-import shared
extension RoomType {
public var theme: RoomTheme {
diff --git a/app-ios/Sources/Model/Extension/TimetableRoom+Extension.swift b/app-ios/Sources/Model/Extension/TimetableRoom+Extension.swift
index d2b32717f..3eb0c705d 100644
--- a/app-ios/Sources/Model/Extension/TimetableRoom+Extension.swift
+++ b/app-ios/Sources/Model/Extension/TimetableRoom+Extension.swift
@@ -1,13 +1,16 @@
import shared
-import SwiftUI
import Theme
-public struct RoomTheme {
- public let primaryColor: Color
- public let containerColor: Color
- public let dimColor: Color
-}
-
extension TimetableRoom {
- public var roomTheme: RoomTheme { type.theme }
+ public var roomTheme: RoomTheme {
+ let roomType: Model.RoomType = switch type {
+ case .roomF: .roomF
+ case .roomG: .roomG
+ case .roomH: .roomH
+ case .roomI: .roomI
+ case .roomJ: .roomJ
+ case .roomIj: .roomIj
+ }
+ return roomType.theme
+ }
}
diff --git a/app-ios/Sources/SearchFeature/Resources/Localizable.xcstrings b/app-ios/Sources/SearchFeature/Resources/Localizable.xcstrings
index e0856dca9..67b82baf0 100644
--- a/app-ios/Sources/SearchFeature/Resources/Localizable.xcstrings
+++ b/app-ios/Sources/SearchFeature/Resources/Localizable.xcstrings
@@ -2,7 +2,20 @@
"sourceLanguage" : "en",
"strings" : {
"「%@」と一致する検索結果がありません" : {
-
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nothing matched your search criteria \"%@\""
+ }
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "「%@」と一致する検索結果がありません"
+ }
+ }
+ }
},
"9/11" : {
"localizations" : {
@@ -11,6 +24,12 @@
"state" : "translated",
"value" : "9/11"
}
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "9/11"
+ }
}
}
},
@@ -21,6 +40,12 @@
"state" : "translated",
"value" : "9/12"
}
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "9/12"
+ }
}
}
},
@@ -31,6 +56,12 @@
"state" : "translated",
"value" : "9/13"
}
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "9/13"
+ }
}
}
},
@@ -41,6 +72,12 @@
"state" : "translated",
"value" : "Category"
}
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "カテゴリ"
+ }
}
}
},
@@ -51,6 +88,12 @@
"state" : "translated",
"value" : "Session type"
}
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "セッション種別"
+ }
}
}
},
@@ -61,6 +104,12 @@
"state" : "translated",
"value" : "Supported languages"
}
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "対応言語"
+ }
}
}
},
@@ -71,6 +120,12 @@
"state" : "translated",
"value" : "Day"
}
+ },
+ "ja" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "開催日"
+ }
}
}
}
diff --git a/app-ios/Sources/SearchFeature/SearchView.swift b/app-ios/Sources/SearchFeature/SearchView.swift
index e0c09d253..e3a24b34c 100644
--- a/app-ios/Sources/SearchFeature/SearchView.swift
+++ b/app-ios/Sources/SearchFeature/SearchView.swift
@@ -91,7 +91,8 @@ public struct SearchView: View {
store.send(.view(.selectedDayChanged($0)))
}
)
- searchFilterChip(
+ searchCategoryFilterChip(
+ allCategories: store.timetable?.categories ?? [],
selection: store.selectedCategory,
defaultTitle: String(localized: "カテゴリ", bundle: .module),
onSelect: {
@@ -119,6 +120,37 @@ public struct SearchView: View {
}
}
+ // MEMO: All Category can get from timetable TimetableCategories get timetable model.
+ // (TimetableCategory don't have to conform to Selectable protocol.)
+ private func searchCategoryFilterChip(
+ allCategories: [TimetableCategory],
+ selection: TimetableCategory?,
+ defaultTitle: String,
+ onSelect: @escaping (TimetableCategory) -> Void
+ ) -> some View {
+ Menu {
+ ForEach(allCategories, id: \.id) { category in
+ Button {
+ onSelect(category)
+ } label: {
+ HStack {
+ if category == selection {
+ Image(.icCheck)
+ }
+ Text(category.title.currentLangTitle)
+ }
+ }
+ }
+
+ } label: {
+ SelectionChip(
+ title: selection?.title.currentLangTitle ?? defaultTitle,
+ isMultiSelect: true,
+ isSelected: selection != nil
+ ) {}
+ }
+ }
+
private func searchFilterChip(
selection: T?,
defaultTitle: String,
@@ -177,23 +209,6 @@ extension DroidKaigi2024Day {
}
}
-#if hasFeature(RetroactiveAttribute)
-extension TimetableCategory: @retroactive Selectable {}
-#else
-extension TimetableCategory: Selectable {}
-#endif
-
-extension TimetableCategory {
- public var caseTitle: String {
- title.currentLangTitle
- }
-
- static public var allCases: [TimetableCategory] {
- // TODO: use correct
- []
- }
-}
-
#if hasFeature(RetroactiveAttribute)
extension TimetableSessionType: @retroactive Selectable {}
#else
diff --git a/app-ios/Sources/StaffFeature/StaffLabel.swift b/app-ios/Sources/StaffFeature/StaffLabel.swift
index 88d9a0814..564bd7f14 100644
--- a/app-ios/Sources/StaffFeature/StaffLabel.swift
+++ b/app-ios/Sources/StaffFeature/StaffLabel.swift
@@ -1,5 +1,6 @@
import SwiftUI
import Theme
+import CommonComponents
struct StaffLabel: View {
let name: String
@@ -7,15 +8,12 @@ struct StaffLabel: View {
var body: some View {
HStack(alignment: .center, spacing: 12) {
- AsyncImage(url: icon) {
- $0.image?.resizable()
- }
- .frame(width: 52, height: 52)
- .clipShape(Circle())
- .overlay(
- Circle()
- .stroke(AssetColors.Outline.outline.swiftUIColor, lineWidth: 1)
- )
+ CircularUserIcon(urlString: icon.absoluteString)
+ .frame(width: 52, height: 52)
+ .overlay(
+ Circle()
+ .stroke(AssetColors.Outline.outline.swiftUIColor, lineWidth: 1)
+ )
Text(name)
.textStyle(.bodyLarge)
@@ -27,5 +25,5 @@ struct StaffLabel: View {
}
#Preview {
- StaffLabel(name: "hoge", icon: .init(string: "")!)
+ StaffLabel(name: "hoge", icon: .init(string: "https://avatars.githubusercontent.com/u/10727543?s=156&v=4")!)
}
diff --git a/app-ios/Sources/TimetableDetailFeature/TimetableDetailView.swift b/app-ios/Sources/TimetableDetailFeature/TimetableDetailView.swift
index c3704045b..3007d20df 100644
--- a/app-ios/Sources/TimetableDetailFeature/TimetableDetailView.swift
+++ b/app-ios/Sources/TimetableDetailFeature/TimetableDetailView.swift
@@ -94,7 +94,12 @@ public struct TimetableDetailView: View {
@MainActor var headLine: some View {
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 4) {
- RoomTag(store.timetableItem.room.name)
+ RoomTag(.init(
+ currentLangTitle: store.timetableItem.room.name.currentLangTitle,
+ enTitle: store.timetableItem.room.name.enTitle,
+ jaTitle: store.timetableItem.room.name.jaTitle
+ ))
+
ForEach(store.timetableItem.language.labels, id: \.self) { label in
LanguageTag(label)
}
@@ -109,13 +114,8 @@ public struct TimetableDetailView: View {
ForEach(store.timetableItem.speakers, id: \.id) { speaker in
HStack(spacing: 12) {
- if let url = URL(string: speaker.iconUrl) {
- AsyncImage(url: url) {
- $0.image?.resizable()
- }
+ CircularUserIcon(urlString: speaker.iconUrl)
.frame(width: 52, height: 52)
- .clipShape(Circle())
- }
VStack(alignment: .leading, spacing: 8) {
Text(speaker.name)
diff --git a/app-ios/Sources/TimetableFeature/Resource/Localizable.xcstrings b/app-ios/Sources/TimetableFeature/Resource/Localizable.xcstrings
index 43fd5afb2..8af4e207e 100644
--- a/app-ios/Sources/TimetableFeature/Resource/Localizable.xcstrings
+++ b/app-ios/Sources/TimetableFeature/Resource/Localizable.xcstrings
@@ -13,16 +13,6 @@
},
"|" : {
- },
- "AddFavorite" : {
- "localizations" : {
- "ja" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "ブックマークに追加されました"
- }
- }
- }
},
"Timetable" : {
"localizations" : {
diff --git a/app-ios/Sources/TimetableFeature/TimetableGridCard.swift b/app-ios/Sources/TimetableFeature/TimetableGridCard.swift
index 41b10afff..61eff283a 100644
--- a/app-ios/Sources/TimetableFeature/TimetableGridCard.swift
+++ b/app-ios/Sources/TimetableFeature/TimetableGridCard.swift
@@ -2,6 +2,7 @@ import Foundation
import SwiftUI
import Theme
import class shared.TimetableItem
+import CommonComponents
public struct TimetableGridCard: View {
let timetableItem: TimetableItem
@@ -21,7 +22,7 @@ public struct TimetableGridCard: View {
} label: {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 4) {
- timetableItem.room.type.shape
+ RoomTypeShape(roomType: .init(enTitle: timetableItem.room.name.enTitle))
.foregroundStyle(timetableItem.room.roomTheme.primaryColor)
Text("\(timetableItem.startsTimeString) - \(timetableItem.endsTimeString)")
.textStyle(.labelMedium)
diff --git a/app-ios/Sources/TimetableFeature/TimetableListView.swift b/app-ios/Sources/TimetableFeature/TimetableListView.swift
index 926e4346e..a479bd387 100644
--- a/app-ios/Sources/TimetableFeature/TimetableListView.swift
+++ b/app-ios/Sources/TimetableFeature/TimetableListView.swift
@@ -40,7 +40,6 @@ public struct TimetableView: View {
}
Spacer()
}
- .toast($store.toast)
.background(AssetColors.Surface.surface.swiftUIColor)
.frame(maxWidth: .infinity)
.toolbar{
diff --git a/app-ios/Sources/TimetableFeature/TimetableReducer.swift b/app-ios/Sources/TimetableFeature/TimetableReducer.swift
index fbc4a0c14..37dcf5aae 100644
--- a/app-ios/Sources/TimetableFeature/TimetableReducer.swift
+++ b/app-ios/Sources/TimetableFeature/TimetableReducer.swift
@@ -14,7 +14,6 @@ public struct TimetableReducer : Sendable{
@ObservableState
public struct State: Equatable {
var timetableItems: [TimetableTimeGroupItems] = [] //Should be simple objects
- var toast: ToastState?
public init(timetableItems: [TimetableTimeGroupItems] = []) {
self.timetableItems = timetableItems
@@ -26,7 +25,7 @@ public struct TimetableReducer : Sendable{
case view(View)
case requestDay(DayTab)
case response(Result<[TimetableItemWithFavorite], any Error>)
- case favoriteResponse(Result)
+ case favoriteResponse(Result)
public enum View : Sendable {
case onAppear
@@ -101,13 +100,9 @@ public struct TimetableReducer : Sendable{
return .run { send in
await send(.favoriteResponse(Result {
try await timetableClient.toggleBookmark(id: item.timetableItem.id)
- return item.isFavorited
}))
}
- case let .favoriteResponse(.success(isFavorited)):
- if !isFavorited {
- state.toast = .init(text: String(localized: "AddFavorite", bundle: .module))
- }
+ case .favoriteResponse(.success):
return .none
case let .favoriteResponse(.failure(error)):
print(error.localizedDescription)
diff --git a/core/data/src/iosMain/kotlin/io/github/droidkaigi/confsched/data/DataModule.kt b/core/data/src/iosMain/kotlin/io/github/droidkaigi/confsched/data/DataModule.kt
index 40ecafd09..10a666474 100644
--- a/core/data/src/iosMain/kotlin/io/github/droidkaigi/confsched/data/DataModule.kt
+++ b/core/data/src/iosMain/kotlin/io/github/droidkaigi/confsched/data/DataModule.kt
@@ -8,9 +8,9 @@ import io.github.droidkaigi.confsched.data.contributors.DefaultContributorsApiCl
import io.github.droidkaigi.confsched.data.contributors.DefaultContributorsRepository
import io.github.droidkaigi.confsched.data.core.defaultJson
import io.github.droidkaigi.confsched.data.core.defaultKtorConfig
+import io.github.droidkaigi.confsched.data.eventmap.DefaultEventMapApiClient
import io.github.droidkaigi.confsched.data.eventmap.DefaultEventMapRepository
import io.github.droidkaigi.confsched.data.eventmap.EventMapApiClient
-import io.github.droidkaigi.confsched.data.eventmap.FakeEventMapApiClient
import io.github.droidkaigi.confsched.data.sessions.DefaultSessionsApiClient
import io.github.droidkaigi.confsched.data.sessions.DefaultSessionsRepository
import io.github.droidkaigi.confsched.data.sessions.SessionCacheDataStore
@@ -121,7 +121,7 @@ public val dataModule: Module = module {
singleOf(::DefaultContributorsApiClient) bind ContributorsApiClient::class
singleOf(::DefaultSponsorsApiClient) bind SponsorsApiClient::class
singleOf(::DefaultStaffApiClient) bind StaffApiClient::class
- singleOf(::FakeEventMapApiClient) bind EventMapApiClient::class
+ singleOf(::DefaultEventMapApiClient) bind EventMapApiClient::class
singleOf(::NetworkService)
singleOf(::DefaultSessionsRepository) bind SessionsRepository::class
diff --git a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/EventMapRepository.kt b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/EventMapRepository.kt
index a62b06b22..5ca1b38b9 100644
--- a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/EventMapRepository.kt
+++ b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/EventMapRepository.kt
@@ -4,9 +4,11 @@ import androidx.compose.runtime.Composable
import io.github.droidkaigi.confsched.model.compositionlocal.LocalRepositories
import kotlinx.collections.immutable.PersistentList
import kotlinx.coroutines.flow.Flow
+import kotlin.coroutines.cancellation.CancellationException
interface EventMapRepository {
+ @Throws(CancellationException::class)
suspend fun refresh()
@Composable
diff --git a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/Filters.kt b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/Filters.kt
index 31bc030f2..a0179606f 100644
--- a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/Filters.kt
+++ b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/Filters.kt
@@ -7,4 +7,19 @@ public data class Filters(
val languages: List = emptyList(),
val filterFavorite: Boolean = false,
val searchWord: String = "",
-)
+) {
+
+ /**
+ * Checks if all filtering criteria are empty.
+ *
+ * @return True if all criteria are empty; false otherwise.
+ */
+ fun isEmpty() = days.isEmpty() &&
+ categories.isEmpty() &&
+ sessionTypes.isEmpty() &&
+ languages.isEmpty() &&
+ filterFavorite.not() &&
+ searchWord.isEmpty()
+
+ fun isNotEmpty() = isEmpty().not()
+}
diff --git a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/TimeLine.kt b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/TimeLine.kt
new file mode 100644
index 000000000..620621c81
--- /dev/null
+++ b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/TimeLine.kt
@@ -0,0 +1,34 @@
+package io.github.droidkaigi.confsched.model
+
+import kotlinx.datetime.Clock
+import kotlinx.datetime.Instant
+import kotlinx.datetime.LocalTime
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.toLocalDateTime
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.minutes
+
+data class TimeLine(
+ private val currentTime: Instant,
+ private val currentDay: DroidKaigi2024Day,
+) {
+ fun durationFromScheduleStart(targetDay: DroidKaigi2024Day): Duration? {
+ if (currentDay != targetDay) return null
+ val currentTimeSecondOfDay = currentTime.toLocalDateTime(TimeZone.currentSystemDefault()).time.toSecondOfDay()
+ val scheduleStartTimeSecondOfDay = LocalTime(hour = 10, minute = 0).toSecondOfDay()
+ return ((currentTimeSecondOfDay - scheduleStartTimeSecondOfDay) / 60).minutes
+ }
+
+ companion object {
+ fun now(clock: Clock): TimeLine? {
+ val currentTime = clock.now()
+ val currentDay = DroidKaigi2024Day.ofOrNull(currentTime)
+ return currentDay?.let {
+ TimeLine(
+ currentTime = currentTime,
+ currentDay = currentDay,
+ )
+ }
+ }
+ }
+}
diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/MiniRobots.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/MiniRobots.kt
index 806ddaa7a..28b538fa0 100644
--- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/MiniRobots.kt
+++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/MiniRobots.kt
@@ -10,18 +10,25 @@ import io.github.droidkaigi.confsched.data.eventmap.FakeEventMapApiClient
import io.github.droidkaigi.confsched.data.profilecard.ProfileCardDataStore
import io.github.droidkaigi.confsched.data.sessions.FakeSessionsApiClient
import io.github.droidkaigi.confsched.data.sessions.SessionsApiClient
+import io.github.droidkaigi.confsched.data.settings.SettingsDataStore
import io.github.droidkaigi.confsched.data.sponsors.FakeSponsorsApiClient
import io.github.droidkaigi.confsched.data.sponsors.SponsorsApiClient
import io.github.droidkaigi.confsched.data.staff.FakeStaffApiClient
import io.github.droidkaigi.confsched.data.staff.StaffApiClient
+import io.github.droidkaigi.confsched.model.FontFamily
import io.github.droidkaigi.confsched.model.ProfileCard
+import io.github.droidkaigi.confsched.model.Settings
import io.github.droidkaigi.confsched.model.fake
import io.github.droidkaigi.confsched.testing.coroutines.runTestWithLogging
import io.github.droidkaigi.confsched.testing.robot.ProfileCardDataStoreRobot.ProfileCardInputStatus
import io.github.droidkaigi.confsched.testing.robot.ProfileCardDataStoreRobot.ProfileCardInputStatus.AllNotEntered
import io.github.droidkaigi.confsched.testing.robot.ProfileCardDataStoreRobot.ProfileCardInputStatus.NoInputOtherThanImage
+import io.github.droidkaigi.confsched.testing.robot.SettingsDataStoreRobot.SettingsStatus
+import io.github.droidkaigi.confsched.testing.robot.SettingsDataStoreRobot.SettingsStatus.UseDotGothic16FontFamily
+import io.github.droidkaigi.confsched.testing.robot.SettingsDataStoreRobot.SettingsStatus.UseSystemDefaultFont
import io.github.droidkaigi.confsched.testing.robot.SponsorsServerRobot.ServerStatus
import io.github.droidkaigi.confsched.testing.rules.RobotTestRule
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.test.TestDispatcher
import org.robolectric.RuntimeEnvironment
import org.robolectric.shadows.ShadowLooper
@@ -287,3 +294,38 @@ class DefaultProfileCardDataStoreRobot @Inject constructor(
}
}
}
+
+interface SettingsDataStoreRobot {
+ enum class SettingsStatus {
+ UseDotGothic16FontFamily,
+ UseSystemDefaultFont,
+ }
+
+ suspend fun setupSettings(settingsStatus: SettingsStatus)
+ fun get(): Flow
+}
+
+class DefaultSettingsDataStoreRobot @Inject constructor(
+ private val settingsDataStore: SettingsDataStore,
+) : SettingsDataStoreRobot {
+ override suspend fun setupSettings(settingsStatus: SettingsStatus) {
+ when (settingsStatus) {
+ UseDotGothic16FontFamily -> {
+ settingsDataStore.save(
+ Settings.Exists(
+ useFontFamily = FontFamily.DotGothic16Regular,
+ ),
+ )
+ }
+ UseSystemDefaultFont -> {
+ settingsDataStore.save(
+ Settings.Exists(
+ useFontFamily = FontFamily.SystemDefault,
+ ),
+ )
+ }
+ }
+ }
+
+ override fun get(): Flow = settingsDataStore.get()
+}
diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ProfileCardScreenRobot.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ProfileCardScreenRobot.kt
index c30c08031..1d2918a78 100644
--- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ProfileCardScreenRobot.kt
+++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/ProfileCardScreenRobot.kt
@@ -30,7 +30,9 @@ class ProfileCardScreenRobot @Inject constructor(
fun setupScreenContent() {
robotTestRule.setContent {
KaigiTheme {
- ProfileCardScreen()
+ ProfileCardScreen(
+ onClickShareProfileCard = { _, _ -> },
+ )
}
}
waitUntilIdle()
diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/SearchScreenRobot.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/SearchScreenRobot.kt
index 28454877b..ed2b2aa18 100644
--- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/SearchScreenRobot.kt
+++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/SearchScreenRobot.kt
@@ -2,6 +2,7 @@ package io.github.droidkaigi.confsched.testing.robot
import androidx.compose.ui.test.assertAll
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertTextContains
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.filter
@@ -267,6 +268,12 @@ class SearchScreenRobot @Inject constructor(
.assertAll(hasText(text = searchWord, ignoreCase = true))
}
+ fun checkTimetableListExists() {
+ composeTestRule
+ .onNode(hasTestTag(TimetableListTestTag))
+ .assertExists()
+ }
+
fun checkTimetableListDisplayed() {
composeTestRule
.onNode(hasTestTag(TimetableListTestTag))
@@ -279,4 +286,11 @@ class SearchScreenRobot @Inject constructor(
.onFirst()
.assertIsDisplayed()
}
+
+ fun checkTimetableListItemsNotDisplayed() {
+ composeTestRule
+ .onAllNodesWithTag(TimetableItemCardTestTag)
+ .onFirst()
+ .assertIsNotDisplayed()
+ }
}
diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/SettingsScreenRobot.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/SettingsScreenRobot.kt
index 184ad4b12..ab5e7b3fb 100644
--- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/SettingsScreenRobot.kt
+++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/SettingsScreenRobot.kt
@@ -1,15 +1,54 @@
package io.github.droidkaigi.confsched.testing.robot
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.hasTestTag
+import androidx.compose.ui.test.performClick
+import io.github.droidkaigi.confsched.compose.safeCollectAsRetainedState
import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme
+import io.github.droidkaigi.confsched.designsystem.theme.dotGothic16FontFamily
+import io.github.droidkaigi.confsched.model.FontFamily.DotGothic16Regular
+import io.github.droidkaigi.confsched.model.FontFamily.SystemDefault
+import io.github.droidkaigi.confsched.model.Settings.DoesNotExists
+import io.github.droidkaigi.confsched.model.Settings.Exists
+import io.github.droidkaigi.confsched.model.Settings.Loading
import io.github.droidkaigi.confsched.settings.SettingsScreen
+import io.github.droidkaigi.confsched.settings.component.SettingsItemRowCurrentValueTextTestTag
+import io.github.droidkaigi.confsched.settings.section.SettingsAccessibilityUseFontFamilyTestTag
+import io.github.droidkaigi.confsched.settings.section.SettingsAccessibilityUseFontFamilyTestTagPrefix
import javax.inject.Inject
class SettingsScreenRobot @Inject constructor(
screenRobot: DefaultScreenRobot,
-) : ScreenRobot by screenRobot {
+ settingsDataStoreRobot: DefaultSettingsDataStoreRobot,
+) : ScreenRobot by screenRobot,
+ SettingsDataStoreRobot by settingsDataStoreRobot {
+ private enum class FontFamily(
+ val displayName: String,
+ ) {
+ DotGothic16Regular("DotGothic"),
+ SystemDefault("System Default"),
+ }
+
fun setupScreenContent() {
robotTestRule.setContent {
- KaigiTheme {
+ val settings by remember { get() }.safeCollectAsRetainedState(Loading)
+
+ val fontFamily = when (settings) {
+ DoesNotExists, Loading -> dotGothic16FontFamily()
+ is Exists -> {
+ when ((settings as Exists).useFontFamily) {
+ DotGothic16Regular -> dotGothic16FontFamily()
+ SystemDefault -> null
+ }
+ }
+ }
+
+ KaigiTheme(
+ fontFamily = fontFamily,
+ ) {
SettingsScreen(
onNavigationIconClick = {},
)
@@ -17,4 +56,53 @@ class SettingsScreenRobot @Inject constructor(
}
waitUntilIdle()
}
+
+ fun clickUseFontItem() {
+ composeTestRule
+ .onNode(hasTestTag(SettingsAccessibilityUseFontFamilyTestTag))
+ .performClick()
+ waitUntilIdle()
+ }
+
+ fun clickSystemDefaultFontItem() {
+ composeTestRule
+ .onNode(hasTestTag(SettingsAccessibilityUseFontFamilyTestTagPrefix.plus(FontFamily.SystemDefault.displayName)))
+ .performClick()
+ waitUntilIdle()
+ }
+
+ fun clickDotGothicFontItem() {
+ composeTestRule
+ .onNode(hasTestTag(SettingsAccessibilityUseFontFamilyTestTagPrefix.plus(FontFamily.DotGothic16Regular.displayName)))
+ .performClick()
+ waitUntilIdle()
+ }
+
+ fun checkAllDisplayedAvailableFont() {
+ FontFamily.entries.forEach { fontFamily ->
+ composeTestRule
+ .onNode(hasTestTag(SettingsAccessibilityUseFontFamilyTestTagPrefix.plus(fontFamily.displayName)))
+ .assertIsDisplayed()
+ .assertExists()
+ .assertTextEquals(fontFamily.displayName)
+ }
+ }
+
+ fun checkEnsureThatSystemDefaultFontIsUsed() {
+ composeTestRule
+ .onNode(
+ matcher = hasTestTag(SettingsItemRowCurrentValueTextTestTag),
+ useUnmergedTree = true,
+ )
+ .assertTextEquals(FontFamily.SystemDefault.displayName)
+ }
+
+ fun checkEnsureThatDotGothicFontIsUsed() {
+ composeTestRule
+ .onNode(
+ matcher = hasTestTag(SettingsItemRowCurrentValueTextTestTag),
+ useUnmergedTree = true,
+ )
+ .assertTextEquals(FontFamily.DotGothic16Regular.displayName)
+ }
}
diff --git a/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched/ui/CanShowLargeVector.android.kt b/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched/ui/CanShowLargeVector.android.kt
new file mode 100644
index 000000000..555488b3b
--- /dev/null
+++ b/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched/ui/CanShowLargeVector.android.kt
@@ -0,0 +1,10 @@
+package io.github.droidkaigi.confsched.ui
+
+import android.os.Build
+import android.os.Build.VERSION_CODES
+import androidx.annotation.ChecksSdkIntAtLeast
+
+@ChecksSdkIntAtLeast(api = VERSION_CODES.O_MR1)
+actual fun canShowLargeVector(): Boolean {
+ return Build.VERSION.SDK_INT > 26
+}
diff --git a/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched/ui/ProvideAboutHeaderPainter.kt b/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched/ui/ProvideAboutHeaderPainter.kt
index fdbf5fab2..1912eb057 100644
--- a/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched/ui/ProvideAboutHeaderPainter.kt
+++ b/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched/ui/ProvideAboutHeaderPainter.kt
@@ -11,21 +11,27 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.painter.Painter
+import conference_app_2024.core.ui.generated.resources.about_header_title
import io.github.droidkaigi.confsched.core.ui.R
+import org.jetbrains.compose.resources.painterResource
@OptIn(ExperimentalAnimationGraphicsApi::class)
@Composable
-actual fun provideAboutHeaderTitlePainter(): Painter {
- var animationPlayed by remember { mutableStateOf(false) }
+actual fun provideAboutHeaderTitlePainter(enableAnimation: Boolean): Painter {
+ return if (enableAnimation) {
+ var animationPlayed by remember { mutableStateOf(false) }
- val painter = rememberAnimatedVectorPainter(
- animatedImageVector = AnimatedImageVector.animatedVectorResource(id = R.drawable.anim_header_title),
- atEnd = animationPlayed,
- )
+ val painter = rememberAnimatedVectorPainter(
+ animatedImageVector = AnimatedImageVector.animatedVectorResource(id = R.drawable.anim_header_title),
+ atEnd = animationPlayed,
+ )
- LaunchedEffect(Unit) {
- animationPlayed = true
- }
+ LaunchedEffect(Unit) {
+ animationPlayed = true
+ }
- return painter
+ painter
+ } else {
+ painterResource(UiRes.drawable.about_header_title)
+ }
}
diff --git a/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/CanShowLargeVector.kt b/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/CanShowLargeVector.kt
new file mode 100644
index 000000000..aad62f486
--- /dev/null
+++ b/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/CanShowLargeVector.kt
@@ -0,0 +1,3 @@
+package io.github.droidkaigi.confsched.ui
+
+expect fun canShowLargeVector(): Boolean
diff --git a/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/PresenterDefaultsProvider.kt b/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/PresenterDefaultsProvider.kt
index c302b5b5f..c15463f2c 100644
--- a/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/PresenterDefaultsProvider.kt
+++ b/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/PresenterDefaultsProvider.kt
@@ -1,7 +1,10 @@
package io.github.droidkaigi.confsched.ui
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
import io.github.droidkaigi.confsched.compose.ComposeEffectErrorHandler
import io.github.droidkaigi.confsched.compose.LocalComposeEffectErrorHandler
import io.github.droidkaigi.confsched.compose.compositionLocalProviderWithReturnValue
@@ -11,7 +14,11 @@ fun providePresenterDefaults(
userMessageStateHolder: UserMessageStateHolder = rememberUserMessageStateHolder(),
block: @Composable (UserMessageStateHolder) -> T,
): T {
- val composeResourceErrorMessages = composeResourceErrorMessages()
+ var composeResourceErrorMessages: List = listOf()
+ // For iOS
+ CompositionLocalProvider(LocalDensity provides Density(1F)) {
+ composeResourceErrorMessages = composeResourceErrorMessages()
+ }
val handler = remember(userMessageStateHolder) {
object : ComposeEffectErrorHandler {
override suspend fun emit(throwable: Throwable) {
diff --git a/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/ProvideAboutHeaderPainter.kt b/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/ProvideAboutHeaderPainter.kt
index 915da1d83..de22c54e3 100644
--- a/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/ProvideAboutHeaderPainter.kt
+++ b/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/ProvideAboutHeaderPainter.kt
@@ -4,4 +4,4 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.painter.Painter
@Composable
-expect fun provideAboutHeaderTitlePainter(): Painter
+expect fun provideAboutHeaderTitlePainter(enableAnimation: Boolean = true): Painter
diff --git a/core/ui/src/iosMain/kotlin/io/github/droidkaigi/confsched/ui/CanShowLargeVector.ios.kt b/core/ui/src/iosMain/kotlin/io/github/droidkaigi/confsched/ui/CanShowLargeVector.ios.kt
new file mode 100644
index 000000000..e5494cff9
--- /dev/null
+++ b/core/ui/src/iosMain/kotlin/io/github/droidkaigi/confsched/ui/CanShowLargeVector.ios.kt
@@ -0,0 +1,6 @@
+package io.github.droidkaigi.confsched.ui
+
+actual fun canShowLargeVector(): Boolean {
+ // The process is only relevant to Android, so it always returns true.
+ return true
+}
diff --git a/core/ui/src/iosMain/kotlin/io/github/droidkaigi/confsched/ui/ProvideAboutHeaderPainter.kt b/core/ui/src/iosMain/kotlin/io/github/droidkaigi/confsched/ui/ProvideAboutHeaderPainter.kt
index 5ce3ee2dc..1c8439bf1 100644
--- a/core/ui/src/iosMain/kotlin/io/github/droidkaigi/confsched/ui/ProvideAboutHeaderPainter.kt
+++ b/core/ui/src/iosMain/kotlin/io/github/droidkaigi/confsched/ui/ProvideAboutHeaderPainter.kt
@@ -6,6 +6,6 @@ import conference_app_2024.core.ui.generated.resources.about_header_title
import org.jetbrains.compose.resources.painterResource
@Composable
-actual fun provideAboutHeaderTitlePainter(): Painter {
+actual fun provideAboutHeaderTitlePainter(enableAnimation: Boolean): Painter {
return painterResource(UiRes.drawable.about_header_title)
}
diff --git a/feature/about/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/about/AboutScreenTest.kt b/feature/about/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/about/AboutScreenTest.kt
index 0884adcd0..132621976 100644
--- a/feature/about/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/about/AboutScreenTest.kt
+++ b/feature/about/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/about/AboutScreenTest.kt
@@ -40,7 +40,7 @@ class AboutScreenTest(
fun behaviors(): List> {
return describeBehaviors("AboutScreen") {
describe("when launch") {
- run {
+ doIt {
setupScreenContent()
}
itShould("show detail section") {
diff --git a/feature/about/src/commonMain/kotlin/io/github/droidkaigi/confsched/about/section/AboutDroidKaigiDetail.kt b/feature/about/src/commonMain/kotlin/io/github/droidkaigi/confsched/about/section/AboutDroidKaigiDetail.kt
index e4922eb29..c68d5813e 100644
--- a/feature/about/src/commonMain/kotlin/io/github/droidkaigi/confsched/about/section/AboutDroidKaigiDetail.kt
+++ b/feature/about/src/commonMain/kotlin/io/github/droidkaigi/confsched/about/section/AboutDroidKaigiDetail.kt
@@ -15,6 +15,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.testTag
@@ -26,6 +27,7 @@ import io.github.droidkaigi.confsched.about.AboutRes
import io.github.droidkaigi.confsched.about.component.AboutDroidKaigiDetailSummaryCard
import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme
import io.github.droidkaigi.confsched.ui.UiRes
+import io.github.droidkaigi.confsched.ui.canShowLargeVector
import io.github.droidkaigi.confsched.ui.provideAboutHeaderTitlePainter
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
@@ -58,22 +60,30 @@ fun AboutDroidKaigiDetail(
Column(
modifier = modifier.testTag(AboutDetailTestTag),
) {
- Box {
+ Box(
+ modifier = Modifier.fillMaxWidth(),
+ contentAlignment = Alignment.Center,
+ ) {
+ val imageModifier = if (canShowLargeVector()) {
+ Modifier
+ .fillMaxWidth()
+ .offset(y = aboutHeaderOffset.dp)
+ } else {
+ // Some API Levels are not optimized to handle VectorDrawable, so OOM occurs when large VectorDrawable is displayed.
+ // Therefore, depending on the API Level, whether or not to display an Image in its full width should be separated.
+ Modifier
+ }
Image(
painter = painterResource(UiRes.drawable.about_header_year),
contentDescription = null,
contentScale = ContentScale.FillWidth,
- modifier = Modifier
- .fillMaxWidth()
- .offset(y = aboutHeaderOffset.dp),
+ modifier = imageModifier,
)
Image(
- painter = provideAboutHeaderTitlePainter(),
+ painter = provideAboutHeaderTitlePainter(canShowLargeVector()),
contentDescription = null,
contentScale = ContentScale.FillWidth,
- modifier = Modifier
- .fillMaxWidth()
- .offset(y = aboutHeaderOffset.dp),
+ modifier = imageModifier,
)
}
Text(
diff --git a/feature/contributors/src/commonMain/composeResources/values-ja/strings.xml b/feature/contributors/src/commonMain/composeResources/values-ja/strings.xml
index 8f3723939..dd7db785c 100644
--- a/feature/contributors/src/commonMain/composeResources/values-ja/strings.xml
+++ b/feature/contributors/src/commonMain/composeResources/values-ja/strings.xml
@@ -1,4 +1,4 @@
- !!Please remove this resource when you add string resource!!
+ コントリビューター
diff --git a/feature/contributors/src/commonMain/composeResources/values/strings.xml b/feature/contributors/src/commonMain/composeResources/values/strings.xml
index 8f3723939..5ac45c4ff 100644
--- a/feature/contributors/src/commonMain/composeResources/values/strings.xml
+++ b/feature/contributors/src/commonMain/composeResources/values/strings.xml
@@ -1,4 +1,4 @@
- !!Please remove this resource when you add string resource!!
+ Contributor
diff --git a/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreen.kt b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreen.kt
index 28a0a13af..a6b583ca2 100644
--- a/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreen.kt
+++ b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreen.kt
@@ -19,6 +19,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.testTag
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
+import conference_app_2024.feature.contributors.generated.resources.contributor_title
import io.github.droidkaigi.confsched.compose.rememberEventEmitter
import io.github.droidkaigi.confsched.contributors.component.ContributorsItem
import io.github.droidkaigi.confsched.model.Contributor
@@ -27,6 +28,7 @@ import io.github.droidkaigi.confsched.ui.UserMessageStateHolder
import io.github.droidkaigi.confsched.ui.component.AnimatedLargeTopAppBar
import io.github.droidkaigi.confsched.ui.handleOnClickIfNotNavigating
import kotlinx.collections.immutable.PersistentList
+import org.jetbrains.compose.resources.stringResource
const val contributorsScreenRoute = "contributors"
const val ContributorsScreenTestTag = "ContributorsScreenTestTag"
@@ -106,7 +108,7 @@ fun ContributorsScreen(
topBar = {
if (!isTopAppBarHidden) {
AnimatedLargeTopAppBar(
- title = "Contributor",
+ title = stringResource(ContributorsRes.string.contributor_title),
onBackClick = onBackClick,
scrollBehavior = scrollBehavior,
navIconContentDescription = "Back",
diff --git a/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/EventMapScreen.kt b/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/EventMapScreen.kt
index 3531ee6a8..02c96f324 100644
--- a/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/EventMapScreen.kt
+++ b/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/EventMapScreen.kt
@@ -2,6 +2,7 @@ package io.github.droidkaigi.confsched.eventmap
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -19,6 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
@@ -47,10 +49,12 @@ const val EventMapLazyColumnTestTag = "EventMapLazyColumnTestTag"
const val EventMapItemTestTag = "EventMapItemTestTag:"
fun NavGraphBuilder.eventMapScreens(
+ contentPadding: PaddingValues,
onEventMapItemClick: (url: String) -> Unit,
) {
composable(eventMapScreenRoute) {
EventMapScreen(
+ contentPadding = contentPadding,
onEventMapItemClick = onEventMapItemClick,
)
}
@@ -75,6 +79,7 @@ data class EventMapUiState(
fun EventMapScreen(
onEventMapItemClick: (url: String) -> Unit,
modifier: Modifier = Modifier,
+ contentPadding: PaddingValues = PaddingValues(),
) {
val eventEmitter = rememberEventEmitter()
val uiState = eventMapScreenPresenter(
@@ -88,6 +93,7 @@ fun EventMapScreen(
userMessageStateHolder = uiState.userMessageStateHolder,
)
EventMapScreen(
+ contentPadding = contentPadding,
uiState = uiState,
snackbarHostState = snackbarHostState,
onEventMapItemClick = onEventMapItemClick,
@@ -102,9 +108,11 @@ fun EventMapScreen(
snackbarHostState: SnackbarHostState,
onEventMapItemClick: (url: String) -> Unit,
modifier: Modifier = Modifier,
+ contentPadding: PaddingValues = PaddingValues(),
) {
Logger.d { "EventMapScreen: $uiState" }
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
+ val layoutDirection = LocalLayoutDirection.current
Scaffold(
modifier = modifier.testTag(EventMapScreenTestTag),
@@ -115,6 +123,12 @@ fun EventMapScreen(
scrollBehavior = scrollBehavior,
)
},
+ contentWindowInsets = WindowInsets(
+ left = contentPadding.calculateLeftPadding(layoutDirection),
+ top = contentPadding.calculateTopPadding(),
+ right = contentPadding.calculateRightPadding(layoutDirection),
+ bottom = contentPadding.calculateBottomPadding(),
+ ),
) { padding ->
EventMap(
eventMapEvents = uiState.eventMap,
diff --git a/feature/favorites/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/favorites/FavoritesScreenTest.kt b/feature/favorites/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/favorites/FavoritesScreenTest.kt
index 007d841dd..e7cd1f6a0 100644
--- a/feature/favorites/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/favorites/FavoritesScreenTest.kt
+++ b/feature/favorites/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/favorites/FavoritesScreenTest.kt
@@ -39,7 +39,7 @@ class FavoritesScreenTest(
fun behaviors(): List> {
return describeBehaviors(name = "FavoritesScreen") {
describe("when server is operational") {
- run {
+ doIt {
setupTimetableServer(ServerStatus.Operational)
setupFavoriteSession()
setupFavoritesScreenContent()
@@ -50,7 +50,7 @@ class FavoritesScreenTest(
)
}
describe("click first session bookmark") {
- run {
+ doIt {
clickFirstSessionBookmark()
}
itShould("display empty view") {
@@ -62,11 +62,11 @@ class FavoritesScreenTest(
}
describe("when server is down") {
- run {
+ doIt {
setupTimetableServer(ServerStatus.Error)
}
describe("when launch") {
- run {
+ doIt {
setupFavoritesScreenContent()
}
itShould("show error message") {
diff --git a/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/FavoritesScreen.kt b/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/FavoritesScreen.kt
index ae498406d..17b433a0e 100644
--- a/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/FavoritesScreen.kt
+++ b/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/FavoritesScreen.kt
@@ -1,16 +1,23 @@
package io.github.droidkaigi.confsched.favorites
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.FastOutLinearInEasing
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.testTag
import androidx.navigation.NavController
@@ -115,6 +122,17 @@ fun FavoritesScreen(
) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
+ val fraction = if (scrollBehavior.state.overlappedFraction > 0.01f) 1f else 0f
+
+ val filterBackgroundColor by animateColorAsState(
+ targetValue = lerp(
+ MaterialTheme.colorScheme.surface,
+ MaterialTheme.colorScheme.surfaceContainer,
+ FastOutLinearInEasing.transform(fraction),
+ ),
+ animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
+ )
+
Scaffold(
modifier = modifier
.testTag(FavoritesScreenTestTag),
@@ -123,11 +141,15 @@ fun FavoritesScreen(
AnimatedTextTopAppBar(
title = stringResource(FavoritesRes.string.favorite),
scrollBehavior = scrollBehavior,
+ colors = TopAppBarDefaults.topAppBarColors().copy(
+ scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
+ ),
)
},
) { padding ->
FavoriteSheet(
uiState = uiState.favoritesSheetUiState,
+ filterBackgroundColor = filterBackgroundColor,
onTimetableItemClick = onTimetableItemClick,
onAllFilterChipClick = onAllFilterChipClick,
onDay1FilterChipClick = onDay1FilterChipClick,
diff --git a/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/FavoritesScreenPresenter.kt b/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/FavoritesScreenPresenter.kt
index b1e024a84..f0fe0afab 100644
--- a/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/FavoritesScreenPresenter.kt
+++ b/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/FavoritesScreenPresenter.kt
@@ -99,7 +99,12 @@ private fun favoritesSheet(
): FavoritesSheetUiState {
val filteredSessions by rememberUpdatedState(
favoriteSessions
- .filtered(Filters(days = selectedDayFilters.toList())),
+ .filtered(
+ Filters(
+ filterFavorite = true,
+ days = selectedDayFilters.toList(),
+ ),
+ ),
)
return if (filteredSessions.isEmpty()) {
diff --git a/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/component/FavoriteFilters.kt b/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/component/FavoriteFilters.kt
index d71e30318..8fa051c94 100644
--- a/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/component/FavoriteFilters.kt
+++ b/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/component/FavoriteFilters.kt
@@ -1,16 +1,20 @@
package io.github.droidkaigi.confsched.favorites.component
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import conference_app_2024.feature.favorites.generated.resources.filter_all
import conference_app_2024.feature.favorites.generated.resources.filter_day1
@@ -25,13 +29,17 @@ fun FavoriteFilters(
allFilterSelected: Boolean,
day1FilterSelected: Boolean,
day2FilterSelected: Boolean,
+ backgroundColor: Color,
onAllFilterChipClick: () -> Unit,
onDay1FilterChipClick: () -> Unit,
onDay2FilterChipClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
- modifier = modifier.padding(start = 16.dp),
+ modifier = modifier
+ .fillMaxWidth()
+ .background(backgroundColor)
+ .padding(start = 16.dp),
horizontalArrangement = Arrangement.spacedBy(6.dp),
) {
FavoriteFilterChip(
@@ -82,6 +90,7 @@ fun FavoriteFiltersPreview() {
allFilterSelected = false,
day1FilterSelected = true,
day2FilterSelected = true,
+ backgroundColor = MaterialTheme.colorScheme.surface,
onAllFilterChipClick = {},
onDay1FilterChipClick = {},
onDay2FilterChipClick = {},
diff --git a/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/section/FavoriteSheet.kt b/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/section/FavoriteSheet.kt
index 3fabc9006..087485c0e 100644
--- a/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/section/FavoriteSheet.kt
+++ b/feature/favorites/src/commonMain/kotlin/io/github/droidkaigi/confsched/favorites/section/FavoriteSheet.kt
@@ -66,6 +66,7 @@ sealed interface FavoritesSheetUiState {
@Composable
fun FavoriteSheet(
uiState: FavoritesSheetUiState,
+ filterBackgroundColor: Color,
onTimetableItemClick: (TimetableItem) -> Unit,
onAllFilterChipClick: () -> Unit,
onDay1FilterChipClick: () -> Unit,
@@ -79,6 +80,7 @@ fun FavoriteSheet(
allFilterSelected = uiState.isAllFilterSelected,
day1FilterSelected = uiState.isDay1FilterSelected,
day2FilterSelected = uiState.isDay2FilterSelected,
+ backgroundColor = filterBackgroundColor,
onAllFilterChipClick = onAllFilterChipClick,
onDay1FilterChipClick = onDay1FilterChipClick,
onDay2FilterChipClick = onDay2FilterChipClick,
@@ -152,6 +154,7 @@ fun FavoriteSheetPreview() {
currentDayFilter = persistentListOf(ConferenceDay1, ConferenceDay2),
timeTable = Timetable.fake(),
),
+ filterBackgroundColor = MaterialTheme.colorScheme.surface,
onAllFilterChipClick = {},
onDay1FilterChipClick = {},
onDay2FilterChipClick = {},
@@ -172,6 +175,7 @@ fun FavoriteSheetNoFavoritesPreview() {
allFilterSelected = false,
currentDayFilter = persistentListOf(ConferenceDay1, ConferenceDay2),
),
+ filterBackgroundColor = MaterialTheme.colorScheme.surface,
onAllFilterChipClick = {},
onDay1FilterChipClick = {},
onDay2FilterChipClick = {},
diff --git a/feature/profilecard/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreenTest.kt b/feature/profilecard/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreenTest.kt
index 96f07b509..bfbebf0f8 100644
--- a/feature/profilecard/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreenTest.kt
+++ b/feature/profilecard/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreenTest.kt
@@ -40,7 +40,7 @@ class ProfileCardScreenTest(
fun behaviors(): List> {
return describeBehaviors("ProfileCardScreen") {
describe("when profile card is does not exists") {
- run {
+ doIt {
setupSavedProfileCard(ProfileCardInputStatus.AllNotEntered)
setupScreenContent()
}
@@ -53,7 +53,7 @@ class ProfileCardScreenTest(
// FIXME Currently, the test code does not allow the user to select and input an image from the Add Image button.
}
describe("when profile card is exists") {
- run {
+ doIt {
setupSavedProfileCard(ProfileCardInputStatus.NoInputOtherThanImage)
setupScreenContent()
}
@@ -64,7 +64,7 @@ class ProfileCardScreenTest(
}
}
describe("flip prifle card") {
- run {
+ doIt {
flipProfileCard()
}
itShould("back side of the profile card is displayed") {
@@ -74,7 +74,7 @@ class ProfileCardScreenTest(
}
}
describe("when click edit button") {
- run {
+ doIt {
clickEditButton()
}
itShould("show edit screen") {
@@ -83,7 +83,7 @@ class ProfileCardScreenTest(
}
}
describe("when if a required field has not been filled in") {
- run {
+ doIt {
scrollToTestTag(ProfileCardCreateButtonTestTag)
}
itShould("make sure the Create button is deactivated") {
@@ -96,7 +96,7 @@ class ProfileCardScreenTest(
val nickname = "test"
val occupation = "test"
val link = "test"
- run {
+ doIt {
inputNickName(nickname)
inputOccupation(occupation)
inputLink(link)
diff --git a/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreen.kt b/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreen.kt
index fa024a83b..4d9097940 100644
--- a/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreen.kt
+++ b/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreen.kt
@@ -47,6 +47,7 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -62,6 +63,8 @@ import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.layer.drawLayer
+import androidx.compose.ui.graphics.rememberGraphicsLayer
import androidx.compose.ui.graphics.vector.VectorPainter
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.input.nestedscroll.nestedScroll
@@ -97,6 +100,8 @@ import io.github.droidkaigi.confsched.profilecard.component.PhotoPickerButton
import io.github.droidkaigi.confsched.ui.SnackbarMessageEffect
import io.github.droidkaigi.confsched.ui.UserMessageStateHolder
import io.github.droidkaigi.confsched.ui.component.AnimatedTextTopAppBar
+import io.ktor.util.decodeBase64Bytes
+import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import kotlin.io.encoding.Base64
@@ -113,9 +118,15 @@ const val ProfileCardCreateButtonTestTag = "ProfileCardCreateButtonTestTag"
const val ProfileCardCardScreenTestTag = "ProfileCardCardScreenTestTag"
const val ProfileCardEditButtonTestTag = "ProfileCardEditButtonTestTag"
-fun NavGraphBuilder.profileCardScreen(contentPadding: PaddingValues) {
+fun NavGraphBuilder.profileCardScreen(
+ contentPadding: PaddingValues,
+ onClickShareProfileCard: (String, ImageBitmap) -> Unit,
+) {
composable(profileCardScreenRoute) {
- ProfileCardScreen(contentPadding)
+ ProfileCardScreen(
+ contentPadding = contentPadding,
+ onClickShareProfileCard = onClickShareProfileCard,
+ )
}
}
@@ -171,11 +182,13 @@ internal data class ProfileCardScreenState(
@Composable
fun ProfileCardScreen(
+ onClickShareProfileCard: (String, ImageBitmap) -> Unit,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(),
) {
ProfileCardScreen(
contentPadding = contentPadding,
+ onClickShareProfileCard = onClickShareProfileCard,
modifier = modifier,
rememberEventEmitter(),
)
@@ -185,6 +198,7 @@ fun ProfileCardScreen(
@Composable
internal fun ProfileCardScreen(
contentPadding: PaddingValues,
+ onClickShareProfileCard: (String, ImageBitmap) -> Unit,
modifier: Modifier = Modifier,
eventEmitter: EventEmitter = rememberEventEmitter(),
uiState: ProfileCardScreenState = profileCardScreenPresenter(eventEmitter),
@@ -276,8 +290,10 @@ internal fun ProfileCardScreen(
onClickEdit = {
eventEmitter.tryEmit(CardScreenEvent.Edit)
},
- onClickShareProfileCard = {
- eventEmitter.tryEmit(CardScreenEvent.Share)
+ onClickShareProfileCard = { imageBitmap ->
+ // TODO Make it better written.
+ val shareText = "${uiState.cardUiState.nickname}'s profile card"
+ onClickShareProfileCard(shareText, imageBitmap)
},
contentPadding = padding,
isCreated = true,
@@ -643,11 +659,14 @@ fun Modifier.selectedBorder(
internal fun CardScreen(
uiState: ProfileCardUiState.Card,
onClickEdit: () -> Unit,
- onClickShareProfileCard: () -> Unit,
+ onClickShareProfileCard: (ImageBitmap) -> Unit,
modifier: Modifier = Modifier,
isCreated: Boolean = false,
contentPadding: PaddingValues = PaddingValues(16.dp),
) {
+ val coroutineScope = rememberCoroutineScope()
+ val graphicsLayer = rememberGraphicsLayer()
+
ProvideProfileCardTheme(uiState.cardType.toString()) {
Column(
modifier = modifier
@@ -662,12 +681,23 @@ internal fun CardScreen(
verticalArrangement = Arrangement.Center,
) {
FlipCard(
+ modifier = Modifier
+ .drawWithContent {
+ graphicsLayer.record {
+ this@drawWithContent.drawContent()
+ }
+ drawLayer(graphicsLayer)
+ },
uiState = uiState,
isCreated = isCreated,
)
Spacer(Modifier.height(32.dp))
Button(
- onClick = { onClickShareProfileCard() },
+ onClick = {
+ coroutineScope.launch {
+ onClickShareProfileCard(graphicsLayer.toImageBitmap())
+ }
+ },
colors = ButtonDefaults.buttonColors(containerColor = Color.White),
border = if (uiState.cardType == ProfileCardType.None) BorderStroke(0.5.dp, Color.Black) else null,
modifier = Modifier
diff --git a/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreenPresenter.kt b/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreenPresenter.kt
index 59d0b3777..1b6d9ba33 100644
--- a/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreenPresenter.kt
+++ b/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreenPresenter.kt
@@ -19,6 +19,7 @@ import io.github.droidkaigi.confsched.model.localProfileCardRepository
import io.github.droidkaigi.confsched.profilecard.ProfileCardUiType.Card
import io.github.droidkaigi.confsched.ui.providePresenterDefaults
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource
internal sealed interface ProfileCardScreenEvent
@@ -45,7 +46,6 @@ internal sealed interface EditScreenEvent : ProfileCardScreenEvent {
}
internal sealed interface CardScreenEvent : ProfileCardScreenEvent {
- data object Share : CardScreenEvent
data object Edit : CardScreenEvent
}
@@ -120,20 +120,18 @@ internal fun profileCardScreenPresenter(
when (event) {
is CardScreenEvent.Edit -> {
isLoading = true
- userMessageStateHolder.showMessage("Edit")
+ launch {
+ userMessageStateHolder.showMessage("Edit")
+ }
uiType = ProfileCardUiType.Edit
isLoading = false
}
- is CardScreenEvent.Share -> {
- isLoading = true
- userMessageStateHolder.showMessage("Share Profile Card")
- isLoading = false
- }
-
is EditScreenEvent.Create -> {
isLoading = true
- userMessageStateHolder.showMessage("Create Profile Card")
+ launch {
+ userMessageStateHolder.showMessage("Create Profile Card")
+ }
repository.save(event.profileCard)
uiType = Card
isLoading = false
@@ -141,7 +139,9 @@ internal fun profileCardScreenPresenter(
is EditScreenEvent.SelectImage -> {
isLoading = true
- userMessageStateHolder.showMessage("Select Image")
+ launch {
+ userMessageStateHolder.showMessage("Select Image")
+ }
isLoading = false
}
diff --git a/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/SearchScreenTest.kt b/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/SearchScreenTest.kt
index da3400177..e971c2658 100644
--- a/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/SearchScreenTest.kt
+++ b/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/SearchScreenTest.kt
@@ -46,10 +46,10 @@ class SearchScreenTest(
setupTimetableServer(ServerStatus.Operational)
setupSearchScreenContent()
}
- itShould("show non-filtered timetable items") {
+ itShould("no timetable items are displayed") {
captureScreenWithChecks {
- checkTimetableListDisplayed()
- checkTimetableListItemsDisplayed()
+ checkTimetableListExists()
+ checkTimetableListItemsNotDisplayed()
}
}
describe("input search word to TextField") {
@@ -60,6 +60,8 @@ class SearchScreenTest(
captureScreenWithChecks {
checkDemoSearchWordDisplayed()
checkTimetableListItemsHasDemoText()
+ checkTimetableListDisplayed()
+ checkTimetableListItemsDisplayed()
}
}
}
@@ -84,6 +86,8 @@ class SearchScreenTest(
checkTimetableListItemByConferenceDay(
checkDay = conference,
)
+ checkTimetableListDisplayed()
+ checkTimetableListItemsDisplayed()
}
}
}
@@ -108,6 +112,8 @@ class SearchScreenTest(
itShould("selected category ${category.categoryName}") {
captureScreenWithChecks {
checkTimetableListItemByCategory(category)
+ checkTimetableListDisplayed()
+ checkTimetableListItemsDisplayed()
}
}
}
@@ -133,6 +139,8 @@ class SearchScreenTest(
itShould("selected language ${language.name}") {
captureScreenWithChecks {
checkTimetableListItemByLanguage(language)
+ checkTimetableListDisplayed()
+ checkTimetableListItemsDisplayed()
}
}
}
diff --git a/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreenTest.kt b/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreenTest.kt
index 108e28434..24ed5c31c 100644
--- a/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreenTest.kt
+++ b/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreenTest.kt
@@ -47,11 +47,11 @@ class TimetableItemDetailScreenTest(private val testCase: DescribedBehavior