diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 15b736b08..834af91d8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,2 @@ * @DroidKaigi/ConferenceApp2023Reviewer +/app-ios/ @DroidKaigi/appios diff --git a/.github/workflows/UnitTest.yml b/.github/workflows/UnitTest.yml index efe0f18a8..68c268e27 100644 --- a/.github/workflows/UnitTest.yml +++ b/.github/workflows/UnitTest.yml @@ -42,7 +42,7 @@ jobs: - run: ./gradlew testDevDebugUnitTest testDebugUnitTest --stacktrace - - run: ./gradlew koverHtmlReportDebug koverHtmlReportDevDebug --stacktrace + - run: ./gradlew koverHtmlReportDevDebug --stacktrace - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 if: ${{ always() }} diff --git a/app-android/build.gradle.kts b/app-android/build.gradle.kts index 690ce4d15..46b004faf 100644 --- a/app-android/build.gradle.kts +++ b/app-android/build.gradle.kts @@ -100,3 +100,25 @@ dependencies { implementation(libs.kermit) testImplementation(projects.core.testing) } + +// Dependency configuration to aggregate Kover coverage reports +// TODO: extract report aggregation to build-logic +dependencies { + kover(projects.appIosShared) + + kover(projects.feature.about) + kover(projects.feature.contributors) + kover(projects.feature.floorMap) + kover(projects.feature.main) + kover(projects.feature.sessions) + kover(projects.feature.sponsors) + kover(projects.feature.staff) + kover(projects.feature.stamps) + + kover(projects.core.common) + kover(projects.core.data) + kover(projects.core.designsystem) + kover(projects.core.model) + kover(projects.core.testing) + kover(projects.core.ui) +} diff --git a/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 705fe5e7e..5fe23d9ce 100644 --- a/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -36,22 +36,13 @@ "version" : "1.7.2" } }, - { - "identity" : "licenselist", - "kind" : "remoteSourceControl", - "location" : "https://github.com/cybozu/LicenseList", - "state" : { - "revision" : "4cbf80b821167042e562639dd875c3b81e8ba1c7", - "version" : "0.2.1" - } - }, { "identity" : "firebase-ios-sdk", "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk", "state" : { - "revision" : "df2171b0c6afb9e9d4f7e07669d558c510b9f6be", - "version" : "10.13.0" + "revision" : "2bfe6abe1014aafe5cf28401708f7d39f9926a76", + "version" : "10.14.0" } }, { @@ -99,6 +90,15 @@ "version" : "3.1.1" } }, + { + "identity" : "interop-ios-for-google-sdks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/interop-ios-for-google-sdks.git", + "state" : { + "revision" : "2d12673670417654f08f5f90fdd62926dc3a2648", + "version" : "100.0.0" + } + }, { "identity" : "leveldb", "kind" : "remoteSourceControl", @@ -108,6 +108,15 @@ "version" : "1.22.2" } }, + { + "identity" : "licenselist", + "kind" : "remoteSourceControl", + "location" : "https://github.com/cybozu/LicenseList", + "state" : { + "revision" : "4cbf80b821167042e562639dd875c3b81e8ba1c7", + "version" : "0.2.1" + } + }, { "identity" : "nanopb", "kind" : "remoteSourceControl", @@ -149,8 +158,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser.git", "state" : { - "revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a", - "version" : "1.2.2" + "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", + "version" : "1.2.3" } }, { diff --git a/app-ios/App/DroidKaigi2023/license-list.plist b/app-ios/App/DroidKaigi2023/license-list.plist index d2525adb9..bb5e4d0b2 100644 --- a/app-ios/App/DroidKaigi2023/license-list.plist +++ b/app-ios/App/DroidKaigi2023/license-list.plist @@ -1790,6 +1790,214 @@ Fuller's blog</a> name GTMSessionFetcher + + licenseBody + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + name + InteropForGoogle + licenseBody Copyright (c) 2011 The LevelDB Authors. All rights reserved. diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/badge.imageset/badge.pdf b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/badge.imageset/badge.pdf deleted file mode 100644 index 6d19d8dbd..000000000 Binary files a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/badge.imageset/badge.pdf and /dev/null differ diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/map.imageset/Contents.json b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/floor_map.imageset/Contents.json similarity index 77% rename from app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/map.imageset/Contents.json rename to app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/floor_map.imageset/Contents.json index 60ad78a00..50a6edc76 100644 --- a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/map.imageset/Contents.json +++ b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/floor_map.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "map.pdf", + "filename" : "floormap.pdf", "idiom" : "universal" } ], diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/floor_map.imageset/floormap.pdf b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/floor_map.imageset/floormap.pdf new file mode 100644 index 000000000..de30cbd6b Binary files /dev/null and b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/floor_map.imageset/floormap.pdf differ diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/floor_map_fill_off.imageset/Contents.json b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/floor_map_fill_off.imageset/Contents.json new file mode 100644 index 000000000..14768b2fb --- /dev/null +++ b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/floor_map_fill_off.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "floormap_fill_off.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/map.imageset/map.pdf b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/floor_map_fill_off.imageset/floormap_fill_off.pdf similarity index 100% rename from app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/map.imageset/map.pdf rename to app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/floor_map_fill_off.imageset/floormap_fill_off.pdf diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/info.imageset/info.pdf b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/info.imageset/info.pdf index 8fabe60d0..9f6382084 100644 Binary files a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/info.imageset/info.pdf and b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/info.imageset/info.pdf differ diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/info_fill_off.imageset/Contents.json b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/info_fill_off.imageset/Contents.json new file mode 100644 index 000000000..78cd4a83f --- /dev/null +++ b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/info_fill_off.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "info_fill_off.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/info_fill_off.imageset/info_fill_off.pdf b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/info_fill_off.imageset/info_fill_off.pdf new file mode 100644 index 000000000..8fabe60d0 Binary files /dev/null and b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/info_fill_off.imageset/info_fill_off.pdf differ diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/badge.imageset/Contents.json b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/stamp.imageset/Contents.json similarity index 79% rename from app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/badge.imageset/Contents.json rename to app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/stamp.imageset/Contents.json index 9f3083259..798efe483 100644 --- a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/badge.imageset/Contents.json +++ b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/stamp.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "badge.pdf", + "filename" : "stamp.pdf", "idiom" : "universal" } ], diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/stamp.imageset/stamp.pdf b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/stamp.imageset/stamp.pdf new file mode 100644 index 000000000..c514bf0dc Binary files /dev/null and b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/stamp.imageset/stamp.pdf differ diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/stamp_fill_off.imageset/Contents.json b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/stamp_fill_off.imageset/Contents.json new file mode 100644 index 000000000..f27e6e3d3 --- /dev/null +++ b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/stamp_fill_off.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stamp_fill_off.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/stamp_fill_off.imageset/stamp_fill_off.pdf b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/stamp_fill_off.imageset/stamp_fill_off.pdf new file mode 100644 index 000000000..2e14167d6 Binary files /dev/null and b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/stamp_fill_off.imageset/stamp_fill_off.pdf differ diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/timetable_fill_off.imageset/Contents.json b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/timetable_fill_off.imageset/Contents.json new file mode 100644 index 000000000..76bbf04b8 --- /dev/null +++ b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/timetable_fill_off.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "timetable_fill_off.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/timetable_fill_off.imageset/timetable_fill_off.pdf b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/timetable_fill_off.imageset/timetable_fill_off.pdf new file mode 100644 index 000000000..8f284fc37 Binary files /dev/null and b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/timetable_fill_off.imageset/timetable_fill_off.pdf differ diff --git a/app-ios/Modules/Sources/Model/TimetableTimeGroupItems.swift b/app-ios/Modules/Sources/Model/TimetableTimeGroupItems.swift index ccc9c6a77..c2542bc88 100644 --- a/app-ios/Modules/Sources/Model/TimetableTimeGroupItems.swift +++ b/app-ios/Modules/Sources/Model/TimetableTimeGroupItems.swift @@ -1,36 +1,17 @@ import shared public struct TimetableTimeGroupItems: Identifiable, Equatable, Hashable { - public struct Duration: Hashable { - public let startsAt: Kotlinx_datetimeInstant - public let endsAt: Kotlinx_datetimeInstant - - var minute: Int { - let startMinute = Int(startsAt.epochSeconds) / 60 - let endMinute = Int(endsAt.epochSeconds) / 60 - - return endMinute - startMinute - } - - public init(startsAt: Kotlinx_datetimeInstant, endsAt: Kotlinx_datetimeInstant) { - self.startsAt = startsAt - self.endsAt = endsAt - } - } - public var id: String { UUID().uuidString } - public var startsAt: Date - public var endsAt: Date - public var minute: Int + public var startsTimeString: String + public var endsTimeString: String public var items: [TimetableItemWithFavorite] - public init(duration: Duration, items: [TimetableItemWithFavorite]) { - self.startsAt = duration.startsAt.toDate() - self.endsAt = duration.endsAt.toDate() - self.minute = duration.minute + public init(startsTimeString: String, endsTimeString: String, items: [TimetableItemWithFavorite]) { + self.startsTimeString = startsTimeString + self.endsTimeString = endsTimeString self.items = items } } diff --git a/app-ios/Modules/Sources/Navigation/RootView.swift b/app-ios/Modules/Sources/Navigation/RootView.swift index 25efc1d74..60decf25d 100644 --- a/app-ios/Modules/Sources/Navigation/RootView.swift +++ b/app-ios/Modules/Sources/Navigation/RootView.swift @@ -34,8 +34,13 @@ public struct RootView: View { Label { Text("Timetable") } icon: { - Assets.Icons.timetable.swiftUIImage - .renderingMode(.template) + if selection == .timeline { + Assets.Icons.timetable.swiftUIImage + .renderingMode(.template) + } else { + Assets.Icons.timetableFillOff.swiftUIImage + .renderingMode(.template) + } } } FloorMapView() @@ -44,8 +49,13 @@ public struct RootView: View { Label { Text("FloorMap") } icon: { - Assets.Icons.map.swiftUIImage - .renderingMode(.template) + if selection == .floorMap { + Assets.Icons.floorMap.swiftUIImage + .renderingMode(.template) + } else { + Assets.Icons.floorMapFillOff.swiftUIImage + .renderingMode(.template) + } } } StampsView() @@ -54,8 +64,13 @@ public struct RootView: View { Label { Text("Stamps") } icon: { - Assets.Icons.badge.swiftUIImage - .renderingMode(.template) + if selection == .stamps { + Assets.Icons.stamp.swiftUIImage + .renderingMode(.template) + } else { + Assets.Icons.stampFillOff.swiftUIImage + .renderingMode(.template) + } } } AboutView( @@ -74,8 +89,13 @@ public struct RootView: View { Label { Text("About") } icon: { - Assets.Icons.info.swiftUIImage - .renderingMode(.template) + if selection == .about { + Assets.Icons.info.swiftUIImage + .renderingMode(.template) + } else { + Assets.Icons.infoFillOff.swiftUIImage + .renderingMode(.template) + } } } } diff --git a/app-ios/Modules/Sources/Stamps/StampsView.swift b/app-ios/Modules/Sources/Stamps/StampsView.swift index 9001cc5df..0a9a47764 100644 --- a/app-ios/Modules/Sources/Stamps/StampsView.swift +++ b/app-ios/Modules/Sources/Stamps/StampsView.swift @@ -23,7 +23,7 @@ public struct StampsView: View { .padding(16) } .background(AssetColors.Surface.surface.swiftUIColor) - .navigationTitle("Badges") + .navigationTitle("Stamps") } } } diff --git a/app-ios/Modules/Sources/Timetable/Bookmark/BookmarkViewModel.swift b/app-ios/Modules/Sources/Timetable/Bookmark/BookmarkViewModel.swift index b9d4bb894..e911e4520 100644 --- a/app-ios/Modules/Sources/Timetable/Bookmark/BookmarkViewModel.swift +++ b/app-ios/Modules/Sources/Timetable/Bookmark/BookmarkViewModel.swift @@ -44,19 +44,17 @@ final class BookmarkViewModel: ObservableObject { } ?? cachedTimetable.contents let timetableTimeGroupItems = timetableContents // .filter { cachedTimetable.bookmarks.contains($0.timetableItem.id) } - .map { - TimetableTimeGroupItems.Duration(startsAt: $0.timetableItem.startsAt, endsAt: $0.timetableItem.endsAt) - } - .map { duration in + .map { content in let items = timetableContents .filter { itemWithFavorite in - itemWithFavorite.timetableItem.startsAt == duration.startsAt && itemWithFavorite.timetableItem.endsAt == duration.endsAt + itemWithFavorite.timetableItem.startsTimeString == content.timetableItem.startsTimeString && itemWithFavorite.timetableItem.endsTimeString == content.timetableItem.endsTimeString } .sorted { $0.timetableItem.room.name.currentLangTitle < $1.timetableItem.room.name.currentLangTitle } return TimetableTimeGroupItems( - duration: duration, + startsTimeString: content.timetableItem.startsTimeString, + endsTimeString: content.timetableItem.endsTimeString, items: items ) } diff --git a/app-ios/Modules/Sources/Timetable/Search/SearchViewModel.swift b/app-ios/Modules/Sources/Timetable/Search/SearchViewModel.swift index b0983e66e..9faa084e4 100644 --- a/app-ios/Modules/Sources/Timetable/Search/SearchViewModel.swift +++ b/app-ios/Modules/Sources/Timetable/Search/SearchViewModel.swift @@ -58,19 +58,17 @@ final class SearchViewModel: ObservableObject { } let timetableContents = cachedTimetable.filtered(filters: state.filters).contents let timetableTimeGroupItems = timetableContents - .map { - TimetableTimeGroupItems.Duration(startsAt: $0.timetableItem.startsAt, endsAt: $0.timetableItem.endsAt) - } - .map { duration in + .map { content in let items = timetableContents .filter { itemWithFavorite in - itemWithFavorite.timetableItem.startsAt == duration.startsAt && itemWithFavorite.timetableItem.endsAt == duration.endsAt + itemWithFavorite.timetableItem.startsTimeString == content.timetableItem.startsTimeString && itemWithFavorite.timetableItem.endsTimeString == content.timetableItem.endsTimeString } .sorted { $0.timetableItem.room.name.currentLangTitle < $1.timetableItem.room.name.currentLangTitle } return TimetableTimeGroupItems( - duration: duration, + startsTimeString: content.timetableItem.startsTimeString, + endsTimeString: content.timetableItem.endsTimeString, items: items ) } diff --git a/app-ios/Modules/Sources/Timetable/SessionTimeView.swift b/app-ios/Modules/Sources/Timetable/SessionTimeView.swift index cdf77ebc4..c2f4f1b1a 100644 --- a/app-ios/Modules/Sources/Timetable/SessionTimeView.swift +++ b/app-ios/Modules/Sources/Timetable/SessionTimeView.swift @@ -1,27 +1,21 @@ +import Model import SwiftUI import Theme -private let formatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateStyle = .none - formatter.timeStyle = .short - return formatter -}() - struct SessionTimeView: View { - var startsAt: Date - var endsAt: Date + var startsTimeString: String + var endsTimeString: String var body: some View { VStack(spacing: 4) { - Text(formatter.string(from: startsAt)) + Text(startsTimeString) .foregroundStyle(AssetColors.Surface.onSurface.swiftUIColor) .font(Font(UIFont.systemFont(ofSize: 16, weight: .bold))) .frame(height: 24) Rectangle() .foregroundColor(AssetColors.Outline.outlineVariant.swiftUIColor) .frame(width: 2, height: 8) - Text(formatter.string(from: endsAt)) + Text(endsTimeString) .foregroundStyle(AssetColors.Secondary.secondary.swiftUIColor) .font(Font(UIFont.systemFont(ofSize: 16, weight: .bold))) .frame(height: 24) @@ -29,6 +23,14 @@ struct SessionTimeView: View { } } - #Preview { - SessionTimeView(startsAt: .distantPast, endsAt: .distantFuture) - } +#if DEBUG +import shared + +#Preview { + SessionTimeView( + startsTimeString: Timetable.companion.fake().contents.first!.timetableItem.startsTimeString, + endsTimeString: Timetable.companion.fake().contents.first!.timetableItem.endsTimeString + ) +} + +#endif diff --git a/app-ios/Modules/Sources/Timetable/TimetableListItemView.swift b/app-ios/Modules/Sources/Timetable/TimetableListItemView.swift index 055a80a7c..ecb9d92c7 100644 --- a/app-ios/Modules/Sources/Timetable/TimetableListItemView.swift +++ b/app-ios/Modules/Sources/Timetable/TimetableListItemView.swift @@ -58,6 +58,7 @@ struct TimetableListItemView: View { } Spacer().frame(height: 16) } + .frame(maxWidth: .infinity, alignment: .leading) Button( action: { // TODO: favorite action diff --git a/app-ios/Modules/Sources/Timetable/TimetableListView.swift b/app-ios/Modules/Sources/Timetable/TimetableListView.swift index 3a72386cb..c1aadd04f 100644 --- a/app-ios/Modules/Sources/Timetable/TimetableListView.swift +++ b/app-ios/Modules/Sources/Timetable/TimetableListView.swift @@ -9,8 +9,8 @@ struct TimetableListView: View { ForEach(timetableTimeGroupItems) { timetableTimeGroupItem in HStack(alignment: .top, spacing: 16) { SessionTimeView( - startsAt: timetableTimeGroupItem.startsAt, - endsAt: timetableTimeGroupItem.endsAt + startsTimeString: timetableTimeGroupItem.startsTimeString, + endsTimeString: timetableTimeGroupItem.endsTimeString ) VStack(spacing: 0) { ForEach(timetableTimeGroupItem.items, id: \.timetableItem.id.value) { timetableItemWithFavorite in @@ -37,10 +37,8 @@ import shared TimetableListView( timetableTimeGroupItems: [ TimetableTimeGroupItems( - duration: .init( - startsAt: Timetable.companion.fake().contents.first!.timetableItem.startsAt, - endsAt: Timetable.companion.fake().contents.first!.timetableItem.endsAt - ), + startsTimeString: Timetable.companion.fake().contents.first!.timetableItem.startsTimeString, + endsTimeString: Timetable.companion.fake().contents.first!.timetableItem.endsTimeString, items: [Timetable.companion.fake().contents.first!] ), ] diff --git a/app-ios/Modules/Sources/Timetable/TimetableViewModel.swift b/app-ios/Modules/Sources/Timetable/TimetableViewModel.swift index 7bd10c720..133a2be42 100644 --- a/app-ios/Modules/Sources/Timetable/TimetableViewModel.swift +++ b/app-ios/Modules/Sources/Timetable/TimetableViewModel.swift @@ -41,19 +41,17 @@ final class TimetableViewModel: ObservableObject { } let timetableTimeGroupItems = cachedTimetable.dayTimetable(droidKaigi2023Day: state.selectedDay) .timetableItems - .map { - TimetableTimeGroupItems.Duration(startsAt: $0.startsAt, endsAt: $0.endsAt) - } - .map { duration in + .map { item in let items = cachedTimetable.contents .filter { itemWithFavorite in - itemWithFavorite.timetableItem.startsAt == duration.startsAt && itemWithFavorite.timetableItem.endsAt == duration.endsAt + itemWithFavorite.timetableItem.startsTimeString == item.startsTimeString && itemWithFavorite.timetableItem.endsTimeString == item.endsTimeString } .sorted { $0.timetableItem.room.name.currentLangTitle < $1.timetableItem.room.name.currentLangTitle } return TimetableTimeGroupItems( - duration: duration, + startsTimeString: item.startsTimeString, + endsTimeString: item.endsTimeString, items: items ) } diff --git a/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/AndroidApplicationPlugin.kt b/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/AndroidApplicationPlugin.kt index 1a9e28a7a..883ad5c6e 100644 --- a/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/AndroidApplicationPlugin.kt +++ b/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/AndroidApplicationPlugin.kt @@ -1,10 +1,7 @@ package io.github.droidkaigi.confsched2023.primitive -import org.gradle.api.JavaVersion import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.jvm.toolchain.JavaLanguageVersion -import org.gradle.kotlin.dsl.dependencies @Suppress("unused") class AndroidApplicationPlugin : Plugin { diff --git a/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/AndroidGradleDsl.kt b/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/AndroidGradleDsl.kt index 986a480fa..b1d793fa3 100644 --- a/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/AndroidGradleDsl.kt +++ b/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/AndroidGradleDsl.kt @@ -73,7 +73,7 @@ fun Project.setupDetekt(extension: DetektExtension) { // parallel processing parallel = true // detekt configuration file - config = files("${project.rootDir}/config/detekt/detekt.yml") + config.setFrom("${project.rootDir}/config/detekt/detekt.yml") // baseline configuration file baseline = file("${project.rootDir}/config/detekt/baseline.xml") // apply your own configuration file on top of the default settings diff --git a/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/KoverPlugin.kt b/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/KoverPlugin.kt index bce450344..1acf05b93 100644 --- a/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/KoverPlugin.kt +++ b/build-logic/src/main/kotlin/io/github/droidkaigi/confsched2023/primitive/KoverPlugin.kt @@ -1,12 +1,35 @@ package io.github.droidkaigi.confsched2023.primitive +import kotlinx.kover.gradle.plugin.dsl.KoverReportExtension import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure class KoverPlugin : Plugin { override fun apply(target: Project) { - with (target) { + with(target) { pluginManager.apply("org.jetbrains.kotlinx.kover") + + configure { + filters { + excludes { + packages( + "com.airbnb.android.showkase", + "dagger.hilt.*", + "hilt_aggregated_deps", + ) + classes( + // classes generated by Hilt + "Hilt_*", + "*_Factory", + "*_HiltModules*", + "*Module_Provide*Factory", + // compose previews + "*Preview*Kt", + ) + } + } + } } } -} \ No newline at end of file +} diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 619601f49..1af8c5358 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -61,7 +61,7 @@ style: ClassOrdering: active: true # Multi-line if statements force curly braces - MandatoryBracesIfStatements: + BracesOnIfStatements: active: true # Multiline for, while... forces curly braces MandatoryBracesLoops: diff --git a/core/designsystem/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/designsystem/preview/Annotations.kt b/core/designsystem/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/designsystem/preview/Annotations.kt index 3993485ea..44acb7abb 100644 --- a/core/designsystem/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/designsystem/preview/Annotations.kt +++ b/core/designsystem/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/designsystem/preview/Annotations.kt @@ -60,25 +60,29 @@ object MultiLanguagePreviewDefinition { object Japanese { const val Name = "Japanese" const val Locale = "ja_JP" + const val Language = "ja" } object English { const val Name = "English" const val Locale = "en_US" + const val Language = "en" } } /** * Annotation for previewing multiple languages. + * + * Note: locale param need to follow [locale qualifier](https://developer.android.com/guide/topics/resources/providing-resources#LocaleQualifier). */ @Preview( name = MultiLanguagePreviewDefinition.Japanese.Name, group = MultiLanguagePreviewDefinition.Group, - locale = MultiLanguagePreviewDefinition.Japanese.Locale, + locale = MultiLanguagePreviewDefinition.Japanese.Language, ) @Preview( name = MultiLanguagePreviewDefinition.English.Name, group = MultiLanguagePreviewDefinition.Group, - locale = MultiLanguagePreviewDefinition.English.Locale, + locale = MultiLanguagePreviewDefinition.English.Language, ) annotation class MultiLanguagePreviews diff --git a/core/testing/build.gradle.kts b/core/testing/build.gradle.kts index 3cb39d1b8..e4d7ceee3 100644 --- a/core/testing/build.gradle.kts +++ b/core/testing/build.gradle.kts @@ -10,16 +10,16 @@ plugins { android.namespace = "io.github.droidkaigi.confsched2023.core.testing" dependencies { - implementation(project(":core:model")) - implementation(project(":core:designsystem")) - implementation(project(":core:data")) - implementation(project(":feature:main")) - implementation(project(":feature:sessions")) - implementation(project(":feature:about")) - implementation(project(":feature:sponsors")) - implementation(project(":feature:floor-map")) - implementation(project(":feature:stamps")) - implementation(project(":feature:staff")) + implementation(projects.core.model) + implementation(projects.core.designsystem) + implementation(projects.core.data) + implementation(projects.feature.main) + implementation(projects.feature.sessions) + implementation(projects.feature.about) + implementation(projects.feature.sponsors) + implementation(projects.feature.floorMap) + implementation(projects.feature.stamps) + implementation(projects.feature.staff) implementation(libs.daggerHiltAndroidTesting) implementation(libs.roborazzi) implementation(libs.kermit) diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/RobotTestRule.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/RobotTestRule.kt index 94e7352b9..9ef4ca353 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/RobotTestRule.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/RobotTestRule.kt @@ -60,6 +60,7 @@ class RobotTestRule( return RuleChain .outerRule(HiltAndroidAutoInjectRule(testInstance)) .around(CoroutinesTestRule()) + .around(TimeZoneTestRule()) .around(object : TestWatcher() { override fun starting(description: Description) { // To see logs in the console diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/TimeZoneTestRule.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/TimeZoneTestRule.kt new file mode 100644 index 000000000..1510bf47d --- /dev/null +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/TimeZoneTestRule.kt @@ -0,0 +1,18 @@ +package io.github.droidkaigi.confsched2023.testing + +import org.junit.rules.TestWatcher +import org.junit.runner.Description +import java.util.TimeZone + +class TimeZoneTestRule : TestWatcher() { + private lateinit var evacuatedTimeZone: TimeZone + + override fun starting(description: Description) { + evacuatedTimeZone = TimeZone.getDefault() + TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo")) + } + + override fun finished(description: Description) { + TimeZone.setDefault(evacuatedTimeZone) + } +} diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 64e2c98a8..b39cafcb9 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -14,10 +14,10 @@ kotlin { sourceSets { commonMain { dependencies { - implementation(project(":core:designsystem")) - implementation(project(":core:data")) + implementation(projects.core.designsystem) + implementation(projects.core.data) implementation(libs.kermit) - api(project(":core:common")) + api(projects.core.common) api(libs.composeImageLoader) } } diff --git a/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/ui/TestEnviroment.kt b/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/ui/TestEnviroment.kt index 74b3d9629..f262a8fd5 100644 --- a/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/ui/TestEnviroment.kt +++ b/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/ui/TestEnviroment.kt @@ -3,6 +3,5 @@ package io.github.droidkaigi.confsched2023.ui import android.os.Build actual fun isTest(): Boolean { - if ("robolectric" == Build.FINGERPRINT) return true - return false + return "robolectric" == Build.FINGERPRINT } diff --git a/feature/about/src/main/java/io/github/droidkaigi/confsched2023/about/AboutStrings.kt b/feature/about/src/main/java/io/github/droidkaigi/confsched2023/about/AboutStrings.kt index 73b19076b..f1e01528b 100644 --- a/feature/about/src/main/java/io/github/droidkaigi/confsched2023/about/AboutStrings.kt +++ b/feature/about/src/main/java/io/github/droidkaigi/confsched2023/about/AboutStrings.kt @@ -5,25 +5,25 @@ import io.github.droidkaigi.confsched2023.designsystem.strings.Strings import io.github.droidkaigi.confsched2023.designsystem.strings.StringsBindings sealed class AboutStrings : Strings(Bindings) { - object Title : AboutStrings() - object Description : AboutStrings() - object DateTitle : AboutStrings() - object DateDescription : AboutStrings() - object PlaceTitle : AboutStrings() - object PlaceDescription : AboutStrings() + data object Title : AboutStrings() + data object Description : AboutStrings() + data object DateTitle : AboutStrings() + data object DateDescription : AboutStrings() + data object PlaceTitle : AboutStrings() + data object PlaceDescription : AboutStrings() class PlaceLink( val url: String = "https://goo.gl/maps/vv9sE19JvRjYKtSP9", ) : AboutStrings() - object CreditsTitle : AboutStrings() - object Staff : AboutStrings() - object Contributor : AboutStrings() - object Sponsor : AboutStrings() - object OthersTitle : AboutStrings() - object CodeOfConduct : AboutStrings() - object License : AboutStrings() - object PrivacyPolicy : AboutStrings() - object AppVersion : AboutStrings() - object LicenceDescription : AboutStrings() + data object CreditsTitle : AboutStrings() + data object Staff : AboutStrings() + data object Contributor : AboutStrings() + data object Sponsor : AboutStrings() + data object OthersTitle : AboutStrings() + data object CodeOfConduct : AboutStrings() + data object License : AboutStrings() + data object PrivacyPolicy : AboutStrings() + data object AppVersion : AboutStrings() + data object LicenceDescription : AboutStrings() private object Bindings : StringsBindings( Lang.Japanese to { item, _ -> diff --git a/feature/about/src/main/java/io/github/droidkaigi/confsched2023/about/component/AboutOthers.kt b/feature/about/src/main/java/io/github/droidkaigi/confsched2023/about/component/AboutOthers.kt index 1f86bd95c..431696ddb 100644 --- a/feature/about/src/main/java/io/github/droidkaigi/confsched2023/about/component/AboutOthers.kt +++ b/feature/about/src/main/java/io/github/droidkaigi/confsched2023/about/component/AboutOthers.kt @@ -1,16 +1,22 @@ package io.github.droidkaigi.confsched2023.about.component import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FileCopy import androidx.compose.material.icons.outlined.Gavel import androidx.compose.material.icons.outlined.PrivacyTip 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.unit.dp import io.github.droidkaigi.confsched2023.about.AboutStrings +import io.github.droidkaigi.confsched2023.designsystem.preview.MultiLanguagePreviews +import io.github.droidkaigi.confsched2023.designsystem.preview.MultiThemePreviews +import io.github.droidkaigi.confsched2023.designsystem.theme.KaigiTheme const val AboutOthersCodeOfConductItemTestTag = "AboutOthersCodeOfConductItem" const val AboutOthersLicenseItemTestTag = "AboutOthersLicenseItem" @@ -70,3 +76,20 @@ fun LazyListScope.aboutOthers( ) } } + +@MultiThemePreviews +@MultiLanguagePreviews +@Composable +internal fun AboutOthersPreview() { + KaigiTheme { + Surface { + LazyColumn { + aboutOthers( + onCodeOfConductItemClick = {}, + onLicenseItemClick = {}, + onPrivacyPolicyItemClick = {}, + ) + } + } + } +} diff --git a/feature/contributors/build.gradle.kts b/feature/contributors/build.gradle.kts index 507fda518..cb53be2d3 100644 --- a/feature/contributors/build.gradle.kts +++ b/feature/contributors/build.gradle.kts @@ -4,6 +4,7 @@ plugins { id("droidkaigi.primitive.kmp.android.hilt") id("droidkaigi.primitive.kmp.ios") id("droidkaigi.primitive.kmp.compose") + id("droidkaigi.primitive.kover") } android.namespace = "io.github.droidkaigi.confsched2023.feature.contributors" diff --git a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableGridItem.kt b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableGridItem.kt index be7beb8c3..382cf9a5f 100644 --- a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableGridItem.kt +++ b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableGridItem.kt @@ -13,9 +13,11 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Error import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Schedule import androidx.compose.material3.Icon @@ -44,6 +46,7 @@ import io.github.droidkaigi.confsched2023.model.TimetableItem import io.github.droidkaigi.confsched2023.model.TimetableItem.Session import io.github.droidkaigi.confsched2023.model.TimetableSpeaker import io.github.droidkaigi.confsched2023.model.fake +import io.github.droidkaigi.confsched2023.sessions.SessionsStrings import io.github.droidkaigi.confsched2023.sessions.SessionsStrings.ScheduleIcon import io.github.droidkaigi.confsched2023.sessions.SessionsStrings.UserIcon import io.github.droidkaigi.confsched2023.sessions.section.TimetableSizes @@ -132,6 +135,27 @@ fun TimetableGridItem( ) } + if (timetableItem is Session) { + timetableItem.message?.let { + Spacer(modifier = Modifier.height(TimetableGridItemSizes.titleToScheduleSpaceHeight)) + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = Icons.Default.Error, + contentDescription = SessionsStrings.ErrorIcon.asString(), + tint = MaterialTheme.colorScheme.errorContainer, + modifier = Modifier.size(TimetableGridItemSizes.scheduleHeight), + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = it.currentLangTitle, + color = MaterialTheme.colorScheme.errorContainer, + fontSize = TimetableGridItemSizes.minTitleFontSize, + lineHeight = TimetableGridItemSizes.minTitleLineHeight, + ) + } + } + } + Spacer( modifier = Modifier .weight(1f) diff --git a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableItemDetailSummaryCard.kt b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableItemDetailSummaryCard.kt index 191a601bb..715dfccc4 100644 --- a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableItemDetailSummaryCard.kt +++ b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableItemDetailSummaryCard.kt @@ -2,20 +2,28 @@ package io.github.droidkaigi.confsched2023.sessions.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Error import androidx.compose.material.icons.outlined.Category import androidx.compose.material.icons.outlined.Language import androidx.compose.material.icons.outlined.Place import androidx.compose.material.icons.outlined.Schedule import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import io.github.droidkaigi.confsched2023.designsystem.preview.MultiLanguagePreviews @@ -26,6 +34,7 @@ import io.github.droidkaigi.confsched2023.model.TimetableItem.Session import io.github.droidkaigi.confsched2023.model.fake import io.github.droidkaigi.confsched2023.model.nameAndFloor import io.github.droidkaigi.confsched2023.sessions.SessionsStrings +import io.github.droidkaigi.confsched2023.sessions.SessionsStrings.ErrorIcon import java.util.Locale @Composable @@ -33,43 +42,64 @@ fun TimetableItemDetailSummaryCard( timetableItem: TimetableItem, modifier: Modifier = Modifier, ) { - val isJapaneseLocale = Locale.getDefault().language == Locale("ja").language + Column(modifier = modifier) { + if (timetableItem is Session) { + timetableItem.message?.let { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = Icons.Default.Error, + contentDescription = ErrorIcon.asString(), + tint = MaterialTheme.colorScheme.error, + modifier = Modifier.size(16.dp), + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = it.currentLangTitle, + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.labelLarge, + ) + } + Spacer(modifier = Modifier.size(20.dp)) + } + } + + val isJapaneseLocale = Locale.getDefault().language == Locale("ja").language - Card( - shape = RoundedCornerShape(12.dp), - modifier = modifier, - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( - 1.dp, + Card( + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + 1.dp, + ), ), - ), - ) { - Column( - verticalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 24.dp), ) { - TimetableItemDetailSummaryCardRow( - leadingIcon = Icons.Outlined.Schedule, - label = SessionsStrings.Date.asString(), - content = timetableItem.formattedDateTimeString, - ) - TimetableItemDetailSummaryCardRow( - leadingIcon = Icons.Outlined.Place, - label = SessionsStrings.Place.asString(), - content = timetableItem.room.nameAndFloor, - ) - TimetableItemDetailSummaryCardRow( - leadingIcon = Icons.Outlined.Language, - label = SessionsStrings.SupportedLanguages.asString(), - content = timetableItem.getSupportedLangString(isJapaneseLocale), - ) - TimetableItemDetailSummaryCardRow( - leadingIcon = Icons.Outlined.Category, - label = SessionsStrings.Category.asString(), - content = timetableItem.category.title.currentLangTitle, - ) + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 24.dp), + ) { + TimetableItemDetailSummaryCardRow( + leadingIcon = Icons.Outlined.Schedule, + label = SessionsStrings.Date.asString(), + content = timetableItem.formattedDateTimeString, + ) + TimetableItemDetailSummaryCardRow( + leadingIcon = Icons.Outlined.Place, + label = SessionsStrings.Place.asString(), + content = timetableItem.room.nameAndFloor, + ) + TimetableItemDetailSummaryCardRow( + leadingIcon = Icons.Outlined.Language, + label = SessionsStrings.SupportedLanguages.asString(), + content = timetableItem.getSupportedLangString(isJapaneseLocale), + ) + TimetableItemDetailSummaryCardRow( + leadingIcon = Icons.Outlined.Category, + label = SessionsStrings.Category.asString(), + content = timetableItem.category.title.currentLangTitle, + ) + } } } } diff --git a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableTab.kt b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableTab.kt index 9910d5258..1234e6a21 100644 --- a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableTab.kt +++ b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableTab.kt @@ -30,7 +30,6 @@ import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex -import io.github.droidkaigi.confsched2023.designsystem.preview.MultiLanguagePreviews import io.github.droidkaigi.confsched2023.designsystem.preview.MultiThemePreviews import io.github.droidkaigi.confsched2023.designsystem.theme.KaigiTheme import io.github.droidkaigi.confsched2023.model.DroidKaigi2023Day @@ -78,7 +77,7 @@ fun TimetableTab( }, selectedContentColor = MaterialTheme.colorScheme.onPrimary, unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = modifier.heightIn(min = minTabHeight), + modifier = modifier.heightIn(min = tabMinHeight, max = tabMaxHeight), ) } @@ -89,7 +88,7 @@ fun TimetableTabIndicator( Box( modifier .zIndex(-1f) - .padding(horizontal = tabIndicatorHorizontalSpacing) + .padding(horizontal = tabIndicatorHorizontalGap / 2) .fillMaxSize() .background( color = MaterialTheme.colorScheme.primary, @@ -115,8 +114,13 @@ fun TimetableTabRow( TabRow( selectedTabIndex = selectedTabIndex, modifier = modifier - .height(maxTabRowHeight - ((maxTabRowHeight - minTabRowHeight) * tabState.tabCollapseProgress)) - .padding(horizontal = tabRowHorizontalSpacing), + .height(tabRowMaxHeight - ((tabRowMaxHeight - tabRowMinHeight) * tabState.tabCollapseProgress)) + .padding( + start = tabRowHorizontalSpacing, + top = tabRowTopSpacing, + end = tabRowHorizontalSpacing, + bottom = tabRowBottomSpacing, + ), divider = {}, indicator = indicator, tabs = tabs, @@ -124,12 +128,13 @@ fun TimetableTabRow( } @Composable -fun rememberTimetableTabState(): TimetableTabState { +fun rememberTimetableTabState(initialScrollOffset: Float = 0.0f): TimetableTabState { val offsetLimit = LocalDensity.current.run { - (maxTabRowHeight - minTabRowHeight).toPx() + (tabRowMaxHeight - tabRowMinHeight).toPx() } return rememberSaveable(saver = TimetableTabState.Saver) { TimetableTabState( + initialScrollOffset = initialScrollOffset, initialOffsetLimit = -offsetLimit, ) } @@ -180,15 +185,19 @@ class TimetableTabState( } } -private val minTabHeight = 32.dp -private val baselineTabHeight = 56.dp -private val maxTabRowHeight = 84.dp -private val minTabRowHeight = 56.dp -private val tabIndicatorHorizontalSpacing = 8.dp -private val tabRowHorizontalSpacing = (maxTabRowHeight - baselineTabHeight) / 2 - tabIndicatorHorizontalSpacing +private val tabMinHeight = 32.dp +private val tabMaxHeight = 56.dp + +private val tabIndicatorHorizontalGap = 8.dp + +private val tabRowHorizontalSpacing = 16.dp - (tabIndicatorHorizontalGap / 2) +private val tabRowTopSpacing = 16.dp +private val tabRowBottomSpacing = 12.dp +private val tabRowMinHeight = tabMinHeight + tabRowTopSpacing + tabRowBottomSpacing +private val tabRowMaxHeight = tabMaxHeight + tabRowTopSpacing + tabRowBottomSpacing @MultiThemePreviews -@MultiLanguagePreviews +// @MultiLanguagePreviews @Composable fun TimetableTabPreview() { KaigiTheme { @@ -202,3 +211,52 @@ fun TimetableTabPreview() { } } } + +@MultiThemePreviews +// @MultiLanguagePreviews +@Composable +fun TimetableTabRowPreview() { + val scrollState = rememberTimetableTabState() + val selectedTabIndex = 0 + KaigiTheme { + Surface { + TimetableTabRow(tabState = scrollState, selectedTabIndex = selectedTabIndex) { + DroidKaigi2023Day.entries.forEachIndexed { index, day -> + TimetableTab( + day = day, + selected = selectedTabIndex == index, + onClick = {}, + scrollState = scrollState, + ) + } + } + } + } +} + +@MultiThemePreviews +// @MultiLanguagePreviews +@Composable +fun CollapsedTimetableTabRowPreview() { + val scrollState = rememberSaveable(saver = TimetableTabState.Saver) { + TimetableTabState( + initialScrollOffset = -1.0f, + initialOffsetLimit = -1.0f, + ) + } + val selectedTabIndex = 0 + KaigiTheme { + Surface { + TimetableTabRow(tabState = scrollState, selectedTabIndex = selectedTabIndex) { + DroidKaigi2023Day.entries.forEachIndexed { index, day -> + TimetableTab( + day = day, + selected = index == selectedTabIndex, + onClick = {}, + scrollState = scrollState, + ) + } + } + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 50d8fc88e..f9894e9b6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -androidGradlePlugin = "8.1.0" +androidGradlePlugin = "8.1.1" # For updating Kotlin and Compose Compiler version, see: # https://github.com/JetBrains/compose-multiplatform/blob/master/VERSIONING.md#kotlin-compatibility # https://developer.android.com/jetpack/androidx/releases/compose-kotlin?#pre-release_kotlin_compatibility @@ -36,6 +36,7 @@ ossLicensesPlugin = "0.10.6" ossLicenses = "17.0.1" detekt = "1.23.1" twitterComposeRule = "0.0.26" +kover = "0.7.3" [libraries] androidGradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } @@ -52,6 +53,7 @@ detektGradlePlugin = { group = "io.gitlab.arturbosch.detekt", name = "detekt-gra detektFormatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" } twitterComposeRule = { module = "com.twitter.compose.rules:detekt", version.ref = "twitterComposeRule" } ossLicensesPlugin = { group = "com.google.android.gms", name = "oss-licenses-plugin", version.ref = "ossLicensesPlugin" } +koverPlugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" } androidDesugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" } @@ -145,7 +147,7 @@ kotlinGradlePlugin = { id = "org.jetbrains.kotlin.android", version.ref = "kotli hiltGradlePlugin = { id = "com.google.dagger.hilt.android", version.ref = "dagger" } roborazziGradlePlugin = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" } kspGradlePlugin = { id = "com.google.devtools.ksp", version.ref = "ksp" } -kotlinxKover = { id = "org.jetbrains.kotlinx.kover", version = "0.7.3" } +kotlinxKover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } detektGradlePlugin = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } ossLicensesPlugin = { id = "com.google.android.gms.oss-licenses-plugin", version.ref = "ossLicensesPlugin" } @@ -162,5 +164,6 @@ plugins = [ "kotlinxSerializationGradlePlugin", "completeKotlinPlugin", "detektGradlePlugin", - "ossLicensesPlugin" + "ossLicensesPlugin", + "koverPlugin" ] diff --git a/settings.gradle.kts b/settings.gradle.kts index 1da645c14..cfb3ff2d0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,9 +1,9 @@ pluginManagement { includeBuild("build-logic") repositories { - gradlePluginPortal() google() mavenCentral() + gradlePluginPortal() } } enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") @@ -12,7 +12,6 @@ dependencyResolutionManagement { repositories { google() mavenCentral() - } } rootProject.name = "conference-app-2023"