diff --git a/app-android/build.gradle.kts b/app-android/build.gradle.kts index 8ab192f01..551c3ad91 100644 --- a/app-android/build.gradle.kts +++ b/app-android/build.gradle.kts @@ -7,6 +7,7 @@ plugins { id("droidkaigi.primitive.android.compose") id("droidkaigi.primitive.android.hilt") id("droidkaigi.primitive.android.firebase") + id("droidkaigi.primitive.android.crashlytics") id("droidkaigi.primitive.detekt") id("droidkaigi.primitive.android.roborazzi") id("droidkaigi.primitive.kover") diff --git a/app-android/src/main/java/io/github/droidkaigi/confsched2023/KaigiApp.kt b/app-android/src/main/java/io/github/droidkaigi/confsched2023/KaigiApp.kt index e096af9d9..99f8535a8 100644 --- a/app-android/src/main/java/io/github/droidkaigi/confsched2023/KaigiApp.kt +++ b/app-android/src/main/java/io/github/droidkaigi/confsched2023/KaigiApp.kt @@ -43,7 +43,6 @@ import io.github.droidkaigi.confsched2023.main.MainNestedGraphStateHolder import io.github.droidkaigi.confsched2023.main.MainScreenTab import io.github.droidkaigi.confsched2023.main.MainScreenTab.About import io.github.droidkaigi.confsched2023.main.MainScreenTab.Badges -import io.github.droidkaigi.confsched2023.main.MainScreenTab.Contributor import io.github.droidkaigi.confsched2023.main.MainScreenTab.FloorMap import io.github.droidkaigi.confsched2023.main.MainScreenTab.Timetable import io.github.droidkaigi.confsched2023.main.mainScreen @@ -150,27 +149,18 @@ private fun NavGraphBuilder.mainScreen( ) nestedAboutScreen( onAboutItemClick = { aboutItem -> + val portalBaseUrl = if (defaultLang() == JAPANESE) { + "https://portal.droidkaigi.jp" + } else { + "https://portal.droidkaigi.jp/en" + } when (aboutItem) { Sponsors -> navController.navigateSponsorsScreen() - CodeOfConduct -> { - val url = if (defaultLang() == JAPANESE) { - "https://portal.droidkaigi.jp/about/code-of-conduct" - } else { - "https://portal.droidkaigi.jp/en/about/code-of-conduct" - } - externalNavController.navigate(url = url) - } + CodeOfConduct -> { externalNavController.navigate(url = "$portalBaseUrl/about/code-of-conduct") } Contributors -> mainNestedNavController.navigate(contributorsScreenRoute) License -> externalNavController.navigateToLicenseScreen() Medium -> externalNavController.navigate(url = "https://medium.com/droidkaigi") - PrivacyPolicy -> { - val url = if (defaultLang() == JAPANESE) { - "https://portal.droidkaigi.jp/about/privacy" - } else { - "https://portal.droidkaigi.jp/en/about/privacy" - } - externalNavController.navigate(url = url) - } + PrivacyPolicy -> { externalNavController.navigate(url = "$portalBaseUrl/about/privacy") } Staff -> navController.navigateStaffScreen() X -> externalNavController.navigate(url = "https://twitter.com/DroidKaigi") YouTube -> externalNavController.navigate(url = "https://www.youtube.com/c/DroidKaigi") @@ -215,7 +205,6 @@ class KaigiAppMainNestedGraphStateHolder : MainNestedGraphStateHolder { override fun routeToTab(route: String): MainScreenTab? { return when (route) { timetableScreenRoute -> Timetable - contributorsScreenRoute -> Contributor aboutScreenRoute -> About floorMapScreenRoute -> FloorMap stampsScreenRoute -> Badges @@ -231,11 +220,6 @@ class KaigiAppMainNestedGraphStateHolder : MainNestedGraphStateHolder { Timetable -> mainNestedNavController.navigateTimetableScreen() About -> mainNestedNavController.navigateAboutScreen() FloorMap -> mainNestedNavController.navigateFloorMapScreen() - Contributor -> mainNestedNavController.navigate(contributorsScreenRoute) { - launchSingleTop = true - restoreState = true - } - Badges -> mainNestedNavController.navigateStampsScreen() } } @@ -304,7 +288,7 @@ private class ExternalNavController( shareNavigator.share( "[${timeTableItem.room.name.currentLangTitle}] ${timeTableItem.startsTimeString} - ${timeTableItem.endsTimeString}\n" + "${timeTableItem.title.currentLangTitle}\n" + - "https://2023.droidkaigi.jp/timetable/${timeTableItem.id.value}", + timeTableItem.url, ) } diff --git a/app-android/src/main/java/io/github/droidkaigi/confsched2023/MainActivity.kt b/app-android/src/main/java/io/github/droidkaigi/confsched2023/MainActivity.kt index ae4382d9a..6432485a2 100644 --- a/app-android/src/main/java/io/github/droidkaigi/confsched2023/MainActivity.kt +++ b/app-android/src/main/java/io/github/droidkaigi/confsched2023/MainActivity.kt @@ -1,7 +1,10 @@ package io.github.droidkaigi.confsched2023 +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES import android.os.Bundle import androidx.activity.ComponentActivity +import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi @@ -10,6 +13,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb import androidx.window.layout.DisplayFeature import androidx.window.layout.WindowInfoTracker import dagger.hilt.android.AndroidEntryPoint @@ -23,7 +28,27 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - enableEdgeToEdge() + // Navigation icon color can be changed since API 26(O) + if (VERSION.SDK_INT < VERSION_CODES.O) { + enableEdgeToEdge() + } else { + enableEdgeToEdge( + statusBarStyle = SystemBarStyle.auto( + lightScrim = Color.Transparent.toArgb(), + darkScrim = Color.Transparent.toArgb(), + ), + navigationBarStyle = SystemBarStyle.auto( + lightScrim = Color.Transparent.toArgb(), + darkScrim = Color.Transparent.toArgb(), + ), + ) + + // For API29(Q) or higher and 3-button navigation, + // the following code must be written to make the navigation color completely transparent. + if (VERSION.SDK_INT >= VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced = false + } + } setContent { val windowSize = calculateWindowSizeClass(this) diff --git a/app-android/src/test/java/io/github/droidkaigi/confsched2023/KaigiAppTest.kt b/app-android/src/test/java/io/github/droidkaigi/confsched2023/KaigiAppTest.kt index e062a42ac..53feaa09a 100644 --- a/app-android/src/test/java/io/github/droidkaigi/confsched2023/KaigiAppTest.kt +++ b/app-android/src/test/java/io/github/droidkaigi/confsched2023/KaigiAppTest.kt @@ -100,14 +100,6 @@ class KaigiAppTest { } } - @Test - fun checkNavigateToContributorShot() { - kaigiAppRobot { - goToContributor() - capture() - } - } - @Test fun checkNavigateToTimetableItemDetailShot() { kaigiAppRobot { diff --git a/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.pbxproj b/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.pbxproj index 3de23b0d9..0ebb7b4eb 100644 --- a/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.pbxproj +++ b/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 9EB9E9E92AA2BF3800A9CBA1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; D77DAF6D29FAA148007195DB /* DroidKaigi2023.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DroidKaigi2023.app; sourceTree = BUILT_PRODUCTS_DIR; }; D77DAF7029FAA148007195DB /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; D77DAF7429FAA149007195DB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -109,6 +110,7 @@ D77DAF6F29FAA148007195DB /* DroidKaigi2023 */ = { isa = PBXGroup; children = ( + 9EB9E9E92AA2BF3800A9CBA1 /* Info.plist */, D77DAF7029FAA148007195DB /* App.swift */, D77DAF7429FAA149007195DB /* Assets.xcassets */, D77DAF7629FAA149007195DB /* DroidKaigi2023.entitlements */, @@ -235,10 +237,11 @@ }; buildConfigurationList = D77DAF6829FAA148007195DB /* Build configuration list for PBXProject "DroidKaigi2023" */; compatibilityVersion = "Xcode 14.0"; - developmentRegion = en; + developmentRegion = ja; hasScannedForEncodings = 0; knownRegions = ( en, + ja, Base, ); mainGroup = D77DAF6429FAA148007195DB; @@ -449,6 +452,7 @@ DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = DroidKaigi2023/Info.plist; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; @@ -489,6 +493,7 @@ DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = DroidKaigi2023/Info.plist; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; diff --git a/app-ios/App/DroidKaigi2023/DroidKaigi2023/ContentView.swift b/app-ios/App/DroidKaigi2023/DroidKaigi2023/ContentView.swift deleted file mode 100644 index 12a408afd..000000000 --- a/app-ios/App/DroidKaigi2023/DroidKaigi2023/ContentView.swift +++ /dev/null @@ -1,32 +0,0 @@ -import shared -import SwiftUI - -struct ComposeView: UIViewControllerRepresentable { - func makeUIViewController(context: Context) -> UIViewController { - let vc = DarwinContributorsKt.viewController() - vc.overrideUserInterfaceStyle = .light - return vc - } - - func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} -} - -struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundColor(.accentColor) - Text("Hello, world!") - // Text(EntryPoint().echo()) - ComposeView() - } - .padding() - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/app-ios/App/DroidKaigi2023/DroidKaigi2023/Info.plist b/app-ios/App/DroidKaigi2023/DroidKaigi2023/Info.plist new file mode 100644 index 000000000..0a2d3213d --- /dev/null +++ b/app-ios/App/DroidKaigi2023/DroidKaigi2023/Info.plist @@ -0,0 +1,8 @@ + + + + + CFBundleAllowMixedLocalizations + + + diff --git a/app-ios/Modules/Package.swift b/app-ios/Modules/Package.swift index 3f16b0b89..cd2e0d186 100644 --- a/app-ios/Modules/Package.swift +++ b/app-ios/Modules/Package.swift @@ -5,6 +5,7 @@ import PackageDescription var package = Package( name: "Modules", + defaultLocalization: "ja", platforms: [ .iOS(.v16), ], @@ -84,6 +85,7 @@ var package = Package( "Assets", "Component", "KMPContainer", + "shared", "Model", .product(name: "Dependencies", package: "swift-dependencies"), ] diff --git a/app-ios/Modules/Sources/About/AboutView.swift b/app-ios/Modules/Sources/About/AboutView.swift index cdff5e052..ffed365b8 100644 --- a/app-ios/Modules/Sources/About/AboutView.swift +++ b/app-ios/Modules/Sources/About/AboutView.swift @@ -128,6 +128,12 @@ public struct AboutView: View where Content: View, PlaceHolder: View { - + private let url: URL? private let scale: CGFloat private let contentImage: (Image) -> Content private let placeholder: () -> PlaceHolder - + public init( url: URL?, scale: CGFloat = 1.0, @@ -18,7 +18,7 @@ public struct CacheAsyncImage: View where Content: View, P self.contentImage = contentImage self.placeholder = placeholder } - + public var body: some View { if let cachedImage = ImageCache[url] { contentImage(cachedImage) @@ -44,7 +44,7 @@ private extension CacheAsyncImage { private class ImageCache { static private var cache: [URL: Image] = [:] - + static subscript(url: URL?) -> Image? { get { guard let url else { return nil } diff --git a/app-ios/Modules/Sources/Contributor/ContributorView.swift b/app-ios/Modules/Sources/Contributor/ContributorView.swift index 06d4beb84..10e7f12eb 100644 --- a/app-ios/Modules/Sources/Contributor/ContributorView.swift +++ b/app-ios/Modules/Sources/Contributor/ContributorView.swift @@ -1,15 +1,44 @@ import Component +import Dependencies import Model +import shared import SwiftUI public struct ContributorView: View { @State var presentingURL: IdentifiableURL? - @ObservedObject var viewModel: ContributorViewModel = .init() public init() {} public var body: some View { Group { +// ContributorSwiftUIView { url in +// presentingURL = .init(string: url) +// } + + ContributorComposeView { url in + presentingURL = .init(string: url) + } + } + .navigationTitle("Contributor") + .sheet(item: $presentingURL) { url in + if let url = url.id { + SafariView(url: url) + .ignoresSafeArea() + } + } + } +} + +#Preview { + ContributorView() +} + +struct ContributorSwiftUIView: View { + @ObservedObject var viewModel: ContributorViewModel = .init() + let onContributorItemClick: (String) -> Void + + var body: some View { + Group { switch viewModel.state.contributors { case .initial, .loading: ProgressView() @@ -24,7 +53,7 @@ public struct ContributorView: View { ForEach(contributors, id: \.id) { contributor in Button { if let profileUrl = contributor.profileUrl { - presentingURL = IdentifiableURL(string: profileUrl) + onContributorItemClick(profileUrl) } } label: { PersonLabel( @@ -38,16 +67,26 @@ public struct ContributorView: View { } } } - .navigationTitle("Contributor") - .sheet(item: $presentingURL) { url in - if let url = url.id { - SafariView(url: url) - .ignoresSafeArea() - } - } } } -#Preview { - ContributorView() +struct ContributorComposeView: UIViewControllerRepresentable { + @Dependency(\.contributorsRepositoryData) var contributorsRepositoryData + @Environment(\.colorScheme) var colorScheme + let onContributorItemClick: (String) -> Void + + init(onContributorItemClick: @escaping (String) -> Void) { + self.onContributorItemClick = onContributorItemClick + } + + func makeUIViewController(context: Context) -> UIViewController { + let vc = DarwinContributorsKt.contributorViewController( + contributorsRepository: contributorsRepositoryData.contributorsRepository, + onContributorItemClick: onContributorItemClick + ) + vc.overrideUserInterfaceStyle = .init(colorScheme) + return vc + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} } diff --git a/app-ios/Modules/Sources/Contributor/ContributorViewModel.swift b/app-ios/Modules/Sources/Contributor/ContributorViewModel.swift index eba7227ab..8c3599b43 100644 --- a/app-ios/Modules/Sources/Contributor/ContributorViewModel.swift +++ b/app-ios/Modules/Sources/Contributor/ContributorViewModel.swift @@ -11,6 +11,7 @@ struct ContributorViewState: ViewModelState { @MainActor final class ContributorViewModel: ObservableObject { @Dependency(\.contributorsData) var contributorsData + @Dependency(\.contributorsRepository) var contributorsRepository @Published var state: ContributorViewState = .init() func load() async { @@ -25,4 +26,8 @@ final class ContributorViewModel: ObservableObject { state.contributors = .failed(error) } } + + func getRepositoryForCompose() -> ContributorsRepository{ + return contributorsRepository + } } diff --git a/app-ios/Modules/Sources/KMPContainer/ContributorsDataProvider.swift b/app-ios/Modules/Sources/KMPContainer/ContributorsDataProvider.swift index 1a4ab2b9b..bc2f696f3 100644 --- a/app-ios/Modules/Sources/KMPContainer/ContributorsDataProvider.swift +++ b/app-ios/Modules/Sources/KMPContainer/ContributorsDataProvider.swift @@ -36,4 +36,7 @@ extension ContributorsDataProvider: DependencyKey { get { self[ContributorsDataProvider.self] } set { self[ContributorsDataProvider.self] = newValue } } + var contributorsRepository: ContributorsRepository { + return Container.shared.get(type: ContributorsRepository.self) + } } diff --git a/app-ios/Modules/Sources/KMPContainer/ContributorsRepositoryProvider.swift b/app-ios/Modules/Sources/KMPContainer/ContributorsRepositoryProvider.swift new file mode 100644 index 000000000..19088a7f8 --- /dev/null +++ b/app-ios/Modules/Sources/KMPContainer/ContributorsRepositoryProvider.swift @@ -0,0 +1,26 @@ +import Dependencies +import shared + +public struct ContributorsRepositoryProvider { + public let contributorsRepository: any ContributorsRepository +} + +extension ContributorsRepositoryProvider: DependencyKey { + @MainActor + public static var liveValue: ContributorsRepositoryProvider = ContributorsRepositoryProvider( + contributorsRepository: Container.shared.get(type: ContributorsRepository.self) as ContributorsRepository + ) + + public static var testValue: ContributorsRepositoryProvider = ContributorsRepositoryProvider( + contributorsRepository: DefaultContributorsRepository( + contributorsApi: FakeContributorsApiClient() + ) + ) +} + + public extension DependencyValues { + var contributorsRepositoryData: ContributorsRepositoryProvider { + get { self[ContributorsRepositoryProvider.self] } + set { self[ContributorsRepositoryProvider.self] = newValue } + } +} diff --git a/app-ios/Modules/Sources/Session/SessionView.swift b/app-ios/Modules/Sources/Session/SessionView.swift index 10e3d4b2f..b5d3d995d 100644 --- a/app-ios/Modules/Sources/Session/SessionView.swift +++ b/app-ios/Modules/Sources/Session/SessionView.swift @@ -15,7 +15,8 @@ private let startDateFormatter: DateFormatter = { public struct SessionView: View { let viewModel: SessionViewModel @State private var isDescriptionExpanded: Bool = false - @State private var canBeExpanded = false + @State private var canBeExpanded: Bool = false + @State private var presentingURL: IdentifiableURL? public init(timetableItem: TimetableItem) { self.viewModel = .init(timetableItem: timetableItem) @@ -66,7 +67,7 @@ public struct SessionView: View { if let session = viewModel.timetableItem as? TimetableItem.Session { VStack(alignment: .leading, spacing: 16) { - Text(session.description_.currentLangTitle) + Text(.init(session.description_.currentLangTitle)) .textSelection(.enabled) .lineLimit(isDescriptionExpanded ? nil : 5) .background { @@ -193,7 +194,16 @@ public struct SessionView: View { } } } - + .sheet(item: $presentingURL) { url in + if let url = url.id { + SafariView(url: url) + .ignoresSafeArea() + } + } + .environment(\.openURL, OpenURLAction { url in + presentingURL = IdentifiableURL(url) + return .handled + }) } } diff --git a/app-ios/Modules/Sources/Theme/Resources/Colors.xcassets/About/AndroidRobotDescription.colorset/Contents.json b/app-ios/Modules/Sources/Theme/Resources/Colors.xcassets/About/AndroidRobotDescription.colorset/Contents.json new file mode 100644 index 000000000..4785b22f5 --- /dev/null +++ b/app-ios/Modules/Sources/Theme/Resources/Colors.xcassets/About/AndroidRobotDescription.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x56", + "green" : "0x72", + "red" : "0x6D" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app-ios/Modules/Sources/Theme/Resources/Colors.xcassets/About/Contents.json b/app-ios/Modules/Sources/Theme/Resources/Colors.xcassets/About/Contents.json new file mode 100644 index 000000000..6e965652d --- /dev/null +++ b/app-ios/Modules/Sources/Theme/Resources/Colors.xcassets/About/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/app-ios/Modules/Sources/Timetable/SessionTag.swift b/app-ios/Modules/Sources/Timetable/SessionTag.swift index a8f312e61..2cf95fea3 100644 --- a/app-ios/Modules/Sources/Timetable/SessionTag.swift +++ b/app-ios/Modules/Sources/Timetable/SessionTag.swift @@ -2,12 +2,12 @@ import SwiftUI import Assets struct SessionTag: View { - + private let label: String private let labelColor: Color private let strokeColor: Color? private let backgroundColor: Color - + public init( _ label: String, labelColor: Color, @@ -19,7 +19,7 @@ struct SessionTag: View { self.strokeColor = strokeColor self.backgroundColor = backgroundColor } - + var body: some View { Text(label) .font(Font.custom(FontAssets.Montserrat.medium, size: 12)) diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 73483245d..a2a694f08 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -50,6 +50,10 @@ gradlePlugin { id = "droidkaigi.primitive.android.hilt" implementationClass = "io.github.droidkaigi.confsched2023.primitive.AndroidHiltPlugin" } + register("androidCrashlytics") { + id = "droidkaigi.primitive.android.crashlytics" + implementationClass = "io.github.droidkaigi.confsched2023.primitive.AndroidCrashlyticsPlugin" + } register("androidFirebase") { id = "droidkaigi.primitive.android.firebase" implementationClass = "io.github.droidkaigi.confsched2023.primitive.AndroidFirebasePlugin" diff --git a/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/AndroidCrashlyticsPlugin.kt b/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/AndroidCrashlyticsPlugin.kt new file mode 100644 index 000000000..86d67b47a --- /dev/null +++ b/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/AndroidCrashlyticsPlugin.kt @@ -0,0 +1,15 @@ +package io.github.droidkaigi.confsched2023.primitive + +import org.gradle.api.Plugin +import org.gradle.api.Project + +@Suppress("unused") +class AndroidCrashlyticsPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.google.firebase.crashlytics") + } + } + } +} diff --git a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/DefaultSessionsApiClient.kt b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/DefaultSessionsApiClient.kt index a458aeb20..5be8b9445 100644 --- a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/DefaultSessionsApiClient.kt +++ b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/DefaultSessionsApiClient.kt @@ -85,6 +85,7 @@ fun SessionsAllResponse.toTimetable(): Timetable { id = room.id, name = room.name.toMultiLangText(), type = room.name.toRoomType(), + sort = room.sort, ) }, ) @@ -148,7 +149,7 @@ fun SessionsAllResponse.toTimetable(): Timetable { } .sortedWith( compareBy { it.startsAt } - .thenBy { it.room.name.currentLangTitle }, + .thenBy { it.room }, ) .toPersistentList(), ), diff --git a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/DefaultSessionsRepository.kt b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/DefaultSessionsRepository.kt index ab00be77d..dcd331b61 100644 --- a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/DefaultSessionsRepository.kt +++ b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/DefaultSessionsRepository.kt @@ -7,7 +7,9 @@ import io.github.droidkaigi.confsched2023.model.Timetable import io.github.droidkaigi.confsched2023.model.TimetableItem import io.github.droidkaigi.confsched2023.model.TimetableItemId import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map @@ -20,21 +22,30 @@ public class DefaultSessionsRepository( override fun getTimetableStream(): Flow = flow { var first = true combine( - sessionCacheDataStore.getTimetableStream(), + sessionCacheDataStore.getTimetableStream() + .catch { e -> + Logger.d( + "DefaultSessionsRepository sessionCacheDataStore.getTimetableStream catch", + e, + ) + sessionCacheDataStore.save(sessionsApi.sessionsAllResponse()) + emitAll(sessionCacheDataStore.getTimetableStream()) + }, userDataStore.getFavoriteSessionStream(), ) { timetable, favorites -> timetable.copy(bookmarks = favorites) - }.collect { - if (!it.isEmpty()) { - emit(it) - } - if (first) { - first = false - Logger.d("DefaultSessionsRepository onStart getTimetableStream()") - sessionCacheDataStore.save(sessionsApi.sessionsAllResponse()) - Logger.d("DefaultSessionsRepository onStart fetched") - } } + .collect { + if (!it.isEmpty()) { + emit(it) + } + if (first) { + first = false + Logger.d("DefaultSessionsRepository onStart getTimetableStream()") + sessionCacheDataStore.save(sessionsApi.sessionsAllResponse()) + Logger.d("DefaultSessionsRepository onStart fetched") + } + } } override fun getTimetableItemWithBookmarkStream(id: TimetableItemId): Flow> { diff --git a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/FakeSessionsApiClient.kt b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/FakeSessionsApiClient.kt index 9d424f259..30b34027e 100644 --- a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/FakeSessionsApiClient.kt +++ b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/FakeSessionsApiClient.kt @@ -49,10 +49,18 @@ fun SessionsAllResponse.Companion.fake(): SessionsAllResponse { SpeakerResponse(fullName = "ry", id = "2", isTopSpeaker = true), ) val rooms = listOf( - RoomResponse(name = LocaledResponse(ja = "Chipmunk ja", en = "Chipmunk"), id = 1), - RoomResponse(name = LocaledResponse(ja = "Arctic Fox ja", en = "Arctic Fox"), id = 2), - RoomResponse(name = LocaledResponse(ja = "Bumblebee ja", en = "Bumblebee"), id = 3), - RoomResponse(name = LocaledResponse(ja = "Dolphin ja", en = "Dolphin"), id = 4), + RoomResponse(name = LocaledResponse(ja = "Chipmunk ja", en = "Chipmunk"), id = 1, sort = 1), + RoomResponse( + name = LocaledResponse(ja = "Arctic Fox ja", en = "Arctic Fox"), + id = 2, + sort = 2, + ), + RoomResponse( + name = LocaledResponse(ja = "Bumblebee ja", en = "Bumblebee"), + id = 3, + sort = 3, + ), + RoomResponse(name = LocaledResponse(ja = "Dolphin ja", en = "Dolphin"), id = 4, sort = 3), ) val categories = listOf( CategoryResponse( diff --git a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/response/RoomResponse.kt b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/response/RoomResponse.kt index 7abd00196..1ff303165 100644 --- a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/response/RoomResponse.kt +++ b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/data/sessions/response/RoomResponse.kt @@ -6,4 +6,5 @@ import kotlinx.serialization.Serializable data class RoomResponse( val name: LocaledResponse, val id: Int, + val sort: Int, ) diff --git a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/SideEvent.kt b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/SideEvent.kt index 0b0ab7860..eaa08be43 100644 --- a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/SideEvent.kt +++ b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/SideEvent.kt @@ -1,13 +1,9 @@ package io.github.droidkaigi.confsched2023.model import io.github.droidkaigi.confsched2023.model.FloorLevel.Basement -import io.github.droidkaigi.confsched2023.model.FloorLevel.Ground -import io.github.droidkaigi.confsched2023.model.SideEvent.MarkColor.Blue -import io.github.droidkaigi.confsched2023.model.SideEvent.MarkColor.Orange import io.github.droidkaigi.confsched2023.model.SideEvent.MarkColor.Pink -import io.github.droidkaigi.confsched2023.model.SideEvent.MarkShape.Circle import io.github.droidkaigi.confsched2023.model.SideEvent.MarkShape.Favorite -import io.github.droidkaigi.confsched2023.model.SideEvent.MarkShape.Square +import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf public data class SideEvent( @@ -33,119 +29,122 @@ public data class SideEvent( public companion object } -val SideEvents = persistentListOf( - SideEvent( - title = MultiLangText( - jaTitle = "アプリFiresideチャット(これは仮で後で消えます)", - enTitle = "App Fireside chat(This is demo event and will be deleted later)", - ), - floorLevel = Basement, - description = MultiLangText( - jaTitle = "地下一階でDroidKaigiアプリの開発について、開発者と一緒に語りましょう!(これは仮で後で消えます)", - enTitle = "(Basement)Let's talk about the development of the DroidKaigi app with the developers!(This is demo event and will be deleted later)", - ), - timeText = MultiLangText( - jaTitle = "DAY1-DAY2 10:00-11:00", - enTitle = "DAY1-DAY2 10:00-11:00", - ), - markColor = Pink, - markShape = Favorite, - link = "https://github.com/DroidKaigi/conference-app-2023", - imageLink = "https://2023.droidkaigi.jp/static/12059b53c8c9813a85c1c44f8692a2c0/img_04.jpg", - ), - SideEvent( - title = MultiLangText( - jaTitle = "アプリFiresideチャット(これは仮で後で消えます)", - enTitle = "App Fireside chat(This is demo event and will be deleted later)", - ), - floorLevel = Basement, - description = MultiLangText( - jaTitle = "地下一階でDroidKaigiアプリの開発について、開発者と一緒に語りましょう!(これは仮で後で消えます)", - enTitle = "(Basement)Let's talk about the development of the DroidKaigi app with the developers!(This is demo event and will be deleted later)", - ), - timeText = MultiLangText( - jaTitle = "DAY1-DAY2 10:00-11:00", - enTitle = "DAY1-DAY2 10:00-11:00", - ), - markColor = Blue, - markShape = Square, - link = null, - imageLink = null, - ), - SideEvent( - title = MultiLangText( - jaTitle = "アプリFiresideチャット(これは仮で後で消えます)", - enTitle = "App Fireside chat(This is demo event and will be deleted later)", - ), - floorLevel = Basement, - description = MultiLangText( - jaTitle = "地下一階でDroidKaigiアプリの開発について、開発者と一緒に語りましょう!(これは仮で後で消えます)", - enTitle = "(Basement)Let's talk about the development of the DroidKaigi app with the developers!(This is demo event and will be deleted later)", - ), - timeText = MultiLangText( - jaTitle = "DAY1-DAY2 10:00-11:00", - enTitle = "DAY1-DAY2 10:00-11:00", - ), - markColor = Orange, - markShape = Circle, - link = "https://github.com/DroidKaigi/conference-app-2023", - imageLink = "https://2023.droidkaigi.jp/static/12059b53c8c9813a85c1c44f8692a2c0/img_04.jpg", - ), - SideEvent( - title = MultiLangText( - jaTitle = "アプリFiresideチャット(これは仮で後で消えます)", - enTitle = "App Fireside chat(This is demo event and will be deleted later)", - ), - floorLevel = Ground, - description = MultiLangText( - jaTitle = "地下一階でDroidKaigiアプリの開発について、開発者と一緒に語りましょう!(これは仮で後で消えます)", - enTitle = "(Basement)Let's talk about the development of the DroidKaigi app with the developers!(This is demo event and will be deleted later)", - ), - timeText = MultiLangText( - jaTitle = "DAY1-DAY2 10:00-11:00", - enTitle = "DAY1-DAY2 10:00-11:00", - ), - markColor = Pink, - markShape = Favorite, - link = "https://github.com/DroidKaigi/conference-app-2023", - imageLink = "https://2023.droidkaigi.jp/static/12059b53c8c9813a85c1c44f8692a2c0/img_04.jpg", - ), - SideEvent( - title = MultiLangText( - jaTitle = "アプリFiresideチャット(これは仮で後で消えます)", - enTitle = "App Fireside chat(This is demo event and will be deleted later)", - ), - floorLevel = Ground, - description = MultiLangText( - jaTitle = "地下一階でDroidKaigiアプリの開発について、開発者と一緒に語りましょう!(これは仮で後で消えます)", - enTitle = "(Basement)Let's talk about the development of the DroidKaigi app with the developers!(This is demo event and will be deleted later)", - ), - timeText = MultiLangText( - jaTitle = "DAY1-DAY2 10:00-11:00", - enTitle = "DAY1-DAY2 10:00-11:00", - ), - markColor = Pink, - markShape = Favorite, - link = null, - imageLink = null, - ), - SideEvent( - title = MultiLangText( - jaTitle = "アプリFiresideチャット(これは仮で後で消えます)", - enTitle = "App Fireside chat(This is demo event and will be deleted later)", - ), - floorLevel = Ground, - description = MultiLangText( - jaTitle = "地下一階でDroidKaigiアプリの開発について、開発者と一緒に語りましょう!(これは仮で後で消えます)", - enTitle = "(Basement)Let's talk about the development of the DroidKaigi app with the developers!(This is demo event and will be deleted later)", - ), - timeText = MultiLangText( - jaTitle = "DAY1-DAY2 10:00-11:00", - enTitle = "DAY1-DAY2 10:00-11:00", - ), - markColor = Pink, - markShape = Favorite, - link = "https://github.com/DroidKaigi/conference-app-2023", - imageLink = "https://2023.droidkaigi.jp/static/12059b53c8c9813a85c1c44f8692a2c0/img_04.jpg", - ), -) +val SideEvents: PersistentList = persistentListOf() +val FakeSideEvents + get() = persistentListOf( + SideEvent( + title = MultiLangText( + jaTitle = "アプリFiresideチャット(これは仮で後で消えます)", + enTitle = "App Fireside chat(This is demo event and will be deleted later)", + ), + floorLevel = Basement, + description = MultiLangText( + jaTitle = "地下一階でDroidKaigiアプリの開発について、開発者と一緒に語りましょう!(これは仮で後で消えます)", + enTitle = "(Basement)Let's talk about the development of the DroidKaigi app with the developers!(This is demo event and will be deleted later)", + ), + timeText = MultiLangText( + jaTitle = "DAY1-DAY2 10:00-11:00", + enTitle = "DAY1-DAY2 10:00-11:00", + ), + markColor = Pink, + markShape = Favorite, + link = "https://github.com/DroidKaigi/conference-app-2023", + imageLink = "https://2023.droidkaigi.jp/static/12059b53c8c9813a85c1c44f8692a2c0/img_04.jpg", + ), + ) +// SideEvent( +// title = MultiLangText( +// jaTitle = "アプリFiresideチャット(これは仮で後で消えます)", +// enTitle = "App Fireside chat(This is demo event and will be deleted later)", +// ), +// floorLevel = Basement, +// description = MultiLangText( +// jaTitle = "地下一階でDroidKaigiアプリの開発について、開発者と一緒に語りましょう!(これは仮で後で消えます)", +// enTitle = "(Basement)Let's talk about the development of the DroidKaigi app with the developers!(This is demo event and will be deleted later)", +// ), +// timeText = MultiLangText( +// jaTitle = "DAY1-DAY2 10:00-11:00", +// enTitle = "DAY1-DAY2 10:00-11:00", +// ), +// markColor = Blue, +// markShape = Square, +// link = null, +// imageLink = null, +// ), +// SideEvent( +// title = MultiLangText( +// jaTitle = "アプリFiresideチャット(これは仮で後で消えます)", +// enTitle = "App Fireside chat(This is demo event and will be deleted later)", +// ), +// floorLevel = Basement, +// description = MultiLangText( +// jaTitle = "地下一階でDroidKaigiアプリの開発について、開発者と一緒に語りましょう!(これは仮で後で消えます)", +// enTitle = "(Basement)Let's talk about the development of the DroidKaigi app with the developers!(This is demo event and will be deleted later)", +// ), +// timeText = MultiLangText( +// jaTitle = "DAY1-DAY2 10:00-11:00", +// enTitle = "DAY1-DAY2 10:00-11:00", +// ), +// markColor = Orange, +// markShape = Circle, +// link = "https://github.com/DroidKaigi/conference-app-2023", +// imageLink = "https://2023.droidkaigi.jp/static/12059b53c8c9813a85c1c44f8692a2c0/img_04.jpg", +// ), +// SideEvent( +// title = MultiLangText( +// jaTitle = "アプリFiresideチャット(これは仮で後で消えます)", +// enTitle = "App Fireside chat(This is demo event and will be deleted later)", +// ), +// floorLevel = Ground, +// description = MultiLangText( +// jaTitle = "地下一階でDroidKaigiアプリの開発について、開発者と一緒に語りましょう!(これは仮で後で消えます)", +// enTitle = "(Basement)Let's talk about the development of the DroidKaigi app with the developers!(This is demo event and will be deleted later)", +// ), +// timeText = MultiLangText( +// jaTitle = "DAY1-DAY2 10:00-11:00", +// enTitle = "DAY1-DAY2 10:00-11:00", +// ), +// markColor = Pink, +// markShape = Favorite, +// link = "https://github.com/DroidKaigi/conference-app-2023", +// imageLink = "https://2023.droidkaigi.jp/static/12059b53c8c9813a85c1c44f8692a2c0/img_04.jpg", +// ), +// SideEvent( +// title = MultiLangText( +// jaTitle = "アプリFiresideチャット(これは仮で後で消えます)", +// enTitle = "App Fireside chat(This is demo event and will be deleted later)", +// ), +// floorLevel = Ground, +// description = MultiLangText( +// jaTitle = "地下一階でDroidKaigiアプリの開発について、開発者と一緒に語りましょう!(これは仮で後で消えます)", +// enTitle = "(Basement)Let's talk about the development of the DroidKaigi app with the developers!(This is demo event and will be deleted later)", +// ), +// timeText = MultiLangText( +// jaTitle = "DAY1-DAY2 10:00-11:00", +// enTitle = "DAY1-DAY2 10:00-11:00", +// ), +// markColor = Pink, +// markShape = Favorite, +// link = null, +// imageLink = null, +// ), +// SideEvent( +// title = MultiLangText( +// jaTitle = "アプリFiresideチャット(これは仮で後で消えます)", +// enTitle = "App Fireside chat(This is demo event and will be deleted later)", +// ), +// floorLevel = Ground, +// description = MultiLangText( +// jaTitle = "地下一階でDroidKaigiアプリの開発について、開発者と一緒に語りましょう!(これは仮で後で消えます)", +// enTitle = "(Basement)Let's talk about the development of the DroidKaigi app with the developers!(This is demo event and will be deleted later)", +// ), +// timeText = MultiLangText( +// jaTitle = "DAY1-DAY2 10:00-11:00", +// enTitle = "DAY1-DAY2 10:00-11:00", +// ), +// markColor = Pink, +// markShape = Favorite, +// link = "https://github.com/DroidKaigi/conference-app-2023", +// imageLink = "https://2023.droidkaigi.jp/static/12059b53c8c9813a85c1c44f8692a2c0/img_04.jpg", +// ), +// ) diff --git a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/Timetable.kt b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/Timetable.kt index ebde5b4b3..e2f701df0 100644 --- a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/Timetable.kt +++ b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/Timetable.kt @@ -28,7 +28,7 @@ public data class Timetable( } val rooms: List by lazy { - timetableItems.map { it.room }.toSet().sortedBy { it.name.currentLangTitle } + timetableItems.map { it.room }.toSet().sorted() } val categories: List by lazy { @@ -103,11 +103,11 @@ public fun Timetable?.orEmptyContents(): Timetable = this ?: Timetable() public fun Timetable.Companion.fake(): Timetable { val rooms = mutableListOf( - TimetableRoom(1, MultiLangText("Arctic Fox", "Arctic Fox"), RoomA), - TimetableRoom(2, MultiLangText("Bumblebee", "Bumblebee"), RoomB), - TimetableRoom(3, MultiLangText("Chipmunk", "Chipmunk"), RoomC), - TimetableRoom(4, MultiLangText("Dolphin", "Dolphin"), RoomD), - TimetableRoom(5, MultiLangText("Electric Eel", "Electric Eel"), RoomE), + TimetableRoom(1, MultiLangText("Arctic Fox", "Arctic Fox"), RoomA, 4), + TimetableRoom(2, MultiLangText("Bumblebee", "Bumblebee"), RoomB, 5), + TimetableRoom(3, MultiLangText("Chipmunk", "Chipmunk"), RoomC, 1), + TimetableRoom(4, MultiLangText("Dolphin", "Dolphin"), RoomD, 2), + TimetableRoom(5, MultiLangText("Electric Eel", "Electric Eel"), RoomE, 3), ) repeat(10) { rooms += rooms diff --git a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/TimetableItem.kt b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/TimetableItem.kt index 6616fdec3..447dc5b93 100644 --- a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/TimetableItem.kt +++ b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/TimetableItem.kt @@ -84,11 +84,19 @@ public sealed class TimetableItem { "$minutes" + MultiLangText(jaTitle = "分", enTitle = "min").currentLangTitle } + public val formattedTimeString: String by lazy { + "$startsTimeString ~ $endsTimeString" + } + public val formattedDateTimeString: String by lazy { - "$startsDateString / $startsTimeString ~ $endsTimeString ($minutesString)" + "$startsDateString / $formattedTimeString ($minutesString)" } - public val url: String get() = "https://2023.droidkaigi.jp/timetable/${id.value}" + public val url: String get() = if (defaultLang() == Lang.JAPANESE) { + "https://2023.droidkaigi.jp/timetable/${id.value}" + } else { + "https://2023.droidkaigi.jp/en/timetable/${id.value}" + } fun getSupportedLangString(isJapaneseLocale: Boolean): String { val japanese = if (isJapaneseLocale) "日本語" else "Japanese" @@ -126,6 +134,7 @@ public fun Session.Companion.fake(): Session { id = 1, name = MultiLangText("Room1", "Room2"), type = RoomA, + sort = 1, ), targetAudience = "For App developer アプリ開発者向け", language = TimetableLanguage( diff --git a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/TimetableRoom.kt b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/TimetableRoom.kt index 66844b30e..c496dabeb 100644 --- a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/TimetableRoom.kt +++ b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/TimetableRoom.kt @@ -7,7 +7,15 @@ data class TimetableRoom( val id: Int, val name: MultiLangText, val type: RoomType, -) + val sort: Int, +) : Comparable { + override fun compareTo(other: TimetableRoom): Int { + if (sort < 900 && other.sort < 900) { + return name.currentLangTitle.compareTo(other.name.currentLangTitle) + } + return sort.compareTo(other.sort) + } +} val TimetableRoom.nameAndFloor: String get() { diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/robot/KaigiAppRobot.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/robot/KaigiAppRobot.kt index 69e0d758b..fda3c9a4c 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/robot/KaigiAppRobot.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/robot/KaigiAppRobot.kt @@ -59,13 +59,6 @@ class KaigiAppRobot @Inject constructor( waitUntilIdle() } - fun goToContributor() { - composeTestRule - .onNode(hasTestTag(MainScreenTab.Contributor.testTag)) - .performClick() - waitUntilIdle() - } - fun waitUntilIdle() { composeTestRule.waitForIdle() testDispatcher.scheduler.advanceUntilIdle() diff --git a/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/contributors/ContributorsScreen.kt b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/contributors/ContributorsScreen.kt index 1b16aa946..afa24223f 100644 --- a/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/contributors/ContributorsScreen.kt +++ b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/contributors/ContributorsScreen.kt @@ -39,6 +39,7 @@ data class ContributorsUiState(val contributors: PersistentList) @Composable fun ContributorsScreen( viewModel: ContributorsViewModel, + isTopAppBarHidden: Boolean = false, onNavigationIconClick: () -> Unit, onContributorItemClick: (url: String) -> Unit, contentPadding: PaddingValues = PaddingValues(), @@ -52,6 +53,7 @@ fun ContributorsScreen( ) ContributorsScreen( uiState = uiState, + isTopAppBarHidden = isTopAppBarHidden, snackbarHostState = snackbarHostState, onBackClick = onNavigationIconClick, onContributorItemClick = onContributorItemClick, @@ -67,31 +69,39 @@ private fun ContributorsScreen( onBackClick: () -> Unit, onContributorItemClick: (url: String) -> Unit, contentPadding: PaddingValues, + isTopAppBarHidden: Boolean, ) { - val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + val scrollBehavior = + if (!isTopAppBarHidden) { + TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + } else { + null + } val localLayoutDirection = LocalLayoutDirection.current Scaffold( modifier = Modifier.testTag(ContributorsScreenTestTag), snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, topBar = { - LargeTopAppBar( - title = { - Text(text = "Contributor") - }, - navigationIcon = { - IconButton( - modifier = Modifier.alpha(1f - scrollBehavior.state.collapsedFraction), - onClick = onBackClick, - enabled = scrollBehavior.state.collapsedFraction < 1f, - ) { - Icon( - imageVector = Icons.Default.ArrowBack, - contentDescription = "Back", - ) - } - }, - scrollBehavior = scrollBehavior - ) + if (scrollBehavior != null) { + LargeTopAppBar( + title = { + Text(text = "Contributor") + }, + navigationIcon = { + IconButton( + modifier = Modifier.alpha(1f - scrollBehavior.state.collapsedFraction), + onClick = onBackClick, + enabled = scrollBehavior.state.collapsedFraction < 1f, + ) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = "Back", + ) + } + }, + scrollBehavior = scrollBehavior + ) + } }, contentWindowInsets = WindowInsets( left = contentPadding.calculateLeftPadding(localLayoutDirection), @@ -105,7 +115,13 @@ private fun ContributorsScreen( onContributorItemClick = onContributorItemClick, modifier = Modifier .fillMaxSize() - .nestedScroll(scrollBehavior.nestedScrollConnection), + .let { + if (scrollBehavior != null) { + it.nestedScroll(scrollBehavior.nestedScrollConnection) + } else { + it + } + }, contentPadding = innerContentPadding, ) } diff --git a/feature/contributors/src/iosMain/kotlin/io/github/droidkaigi/confsched2023/contributors/DarwinContributors.kt b/feature/contributors/src/iosMain/kotlin/io/github/droidkaigi/confsched2023/contributors/DarwinContributors.kt index 8bcc7a1a0..0e877c25f 100644 --- a/feature/contributors/src/iosMain/kotlin/io/github/droidkaigi/confsched2023/contributors/DarwinContributors.kt +++ b/feature/contributors/src/iosMain/kotlin/io/github/droidkaigi/confsched2023/contributors/DarwinContributors.kt @@ -3,20 +3,18 @@ package io.github.droidkaigi.confsched2023.contributors import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.interop.LocalUIViewController import androidx.compose.ui.window.ComposeUIViewController -import io.github.droidkaigi.confsched2023.data.contributors.DefaultContributorsRepository -import io.github.droidkaigi.confsched2023.data.contributors.FakeContributorsApiClient +import io.github.droidkaigi.confsched2023.model.ContributorsRepository import io.github.droidkaigi.confsched2023.ui.UserMessageStateHolderImpl import platform.UIKit.UIViewController @Suppress("UNUSED") -// TODO: Pass DefaultContributorRepository from iOS -fun viewController(): UIViewController = ComposeUIViewController { +fun contributorViewController( + contributorsRepository: ContributorsRepository, + onContributorItemClick: (url: String) -> Unit, +): UIViewController = ComposeUIViewController { val viewModel = ContributorsViewModel( - // FIXME: Tentatively passing FakeRepository - DefaultContributorsRepository( - FakeContributorsApiClient() - ), - UserMessageStateHolderImpl() + contributorsRepository = contributorsRepository, + userMessageStateHolder = UserMessageStateHolderImpl() ) val uiViewController = LocalUIViewController.current LaunchedEffect(uiViewController) { @@ -25,5 +23,10 @@ fun viewController(): UIViewController = ComposeUIViewController { // viewModel.viewModelScope.cancel() } - ContributorsScreen(viewModel, onNavigationIconClick = {}, onContributorItemClick = {}) + ContributorsScreen( + viewModel = viewModel, + isTopAppBarHidden = true, + onNavigationIconClick = { /** no action for iOS side **/ }, + onContributorItemClick = onContributorItemClick, + ) } diff --git a/feature/floor-map/src/main/java/io/github/droidkaigi/confsched2023/floormap/component/FloorMapSideEventItem.kt b/feature/floor-map/src/main/java/io/github/droidkaigi/confsched2023/floormap/component/FloorMapSideEventItem.kt index f219727c0..29c08527b 100644 --- a/feature/floor-map/src/main/java/io/github/droidkaigi/confsched2023/floormap/component/FloorMapSideEventItem.kt +++ b/feature/floor-map/src/main/java/io/github/droidkaigi/confsched2023/floormap/component/FloorMapSideEventItem.kt @@ -40,6 +40,7 @@ import io.github.droidkaigi.confsched2023.designsystem.preview.MultiLanguagePrev import io.github.droidkaigi.confsched2023.designsystem.preview.MultiThemePreviews import io.github.droidkaigi.confsched2023.designsystem.theme.KaigiTheme import io.github.droidkaigi.confsched2023.floormap.FloorMapStrings +import io.github.droidkaigi.confsched2023.model.FakeSideEvents import io.github.droidkaigi.confsched2023.model.SideEvent import io.github.droidkaigi.confsched2023.model.SideEvent.MarkColor.Blue import io.github.droidkaigi.confsched2023.model.SideEvent.MarkColor.Orange @@ -49,7 +50,6 @@ import io.github.droidkaigi.confsched2023.model.SideEvent.MarkColor.Red import io.github.droidkaigi.confsched2023.model.SideEvent.MarkShape.Circle import io.github.droidkaigi.confsched2023.model.SideEvent.MarkShape.Favorite import io.github.droidkaigi.confsched2023.model.SideEvent.MarkShape.Square -import io.github.droidkaigi.confsched2023.model.SideEvents import io.github.droidkaigi.confsched2023.ui.previewOverride import io.github.droidkaigi.confsched2023.ui.rememberAsyncImagePainter @@ -173,7 +173,7 @@ fun PreviewFloorMapSideEventItem() { KaigiTheme { Surface { FloorMapSideEventItem( - sideEvent = SideEvents.first(), + sideEvent = FakeSideEvents.first(), onSideEventClick = {}, ) } diff --git a/feature/main/src/main/java/io/github/droidkaigi/confsched2023/main/MainScreen.kt b/feature/main/src/main/java/io/github/droidkaigi/confsched2023/main/MainScreen.kt index 025a25077..32ed25250 100644 --- a/feature/main/src/main/java/io/github/droidkaigi/confsched2023/main/MainScreen.kt +++ b/feature/main/src/main/java/io/github/droidkaigi/confsched2023/main/MainScreen.kt @@ -18,10 +18,8 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CalendarMonth -import androidx.compose.material.icons.filled.Group import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.outlined.CalendarMonth -import androidx.compose.material.icons.outlined.Group import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Map import androidx.compose.material3.Scaffold @@ -150,12 +148,6 @@ enum class MainScreenTab( label = MainStrings.About.asString(), contentDescription = MainStrings.About.asString(), ), - Contributor( - icon = IconRepresentation.Vector(Icons.Outlined.Group), - selectedIcon = IconRepresentation.Vector(Icons.Filled.Group), - label = MainStrings.Contributors.asString(), - contentDescription = MainStrings.Contributors.asString(), - ), } data class MainScreenUiState( diff --git a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableList.kt b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableList.kt index 84ed3d608..5a1d65d13 100644 --- a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableList.kt +++ b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableList.kt @@ -29,6 +29,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp @@ -99,10 +102,14 @@ fun TimetableList( space = 4.dp, alignment = Alignment.CenterVertically, ), + modifier = Modifier.semantics(mergeDescendants = true) { + contentDescription = timetableItem.formattedTimeString + }, ) { Text( text = timetableItem.startsTimeString, fontWeight = FontWeight.Medium, + modifier = Modifier.clearAndSetSemantics {}, ) Box( modifier = Modifier @@ -114,6 +121,7 @@ fun TimetableList( text = timetableItem.endsTimeString, color = MaterialTheme.colorScheme.secondary, fontWeight = FontWeight.Medium, + modifier = Modifier.clearAndSetSemantics {}, ) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 82c10905d..dace3c9ab 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,8 +8,8 @@ kotlin = "1.9.0" androidxCore = "1.10.1" androidDesugarJdkLibs = "2.0.3" compose = "1.5.0" -compose-jb = "1.4.3" -composeCompiler = "1.5.1" +compose-jb = "1.5.0" +composeCompiler = "1.5.2" composeHiltNavigatiaon = "1.0.0" androidxLifecycle = "2.6.1" androidxActivity = "1.8.0-alpha07" @@ -122,6 +122,7 @@ firebaseCommon = { module = "com.google.firebase:firebase-common" } firebaseAuth = { module = "com.google.firebase:firebase-auth" } multiplatformFirebaseAuth = { module = "dev.gitlive:firebase-auth", version.ref = "multiplatformFirebase" } firebaseRemoteConfig = { module = "dev.gitlive:firebase-config", version.ref = "multiplatformFirebase" } +firebaseCrashlytics = { module = "com.google.firebase:firebase-crashlytics-gradle", version = "2.9.9" } ktorfitLib = { module = "de.jensklingenberg.ktorfit:ktorfit-lib", version.ref = "ktorfit" } ktorfitKsp = { module = "de.jensklingenberg.ktorfit:ktorfit-ksp", version.ref = "ktorfit" } @@ -167,5 +168,6 @@ plugins = [ "completeKotlinPlugin", "detektGradlePlugin", "ossLicensesPlugin", - "koverPlugin" + "koverPlugin", + "firebaseCrashlytics" ]