Skip to content

Commit

Permalink
Merge branch 'main' of github.com:DroidKaigi/conference-app-2024 into…
Browse files Browse the repository at this point in the history
… feature/446-gridlines
  • Loading branch information
charles-b-stb committed Aug 21, 2024
2 parents 7df2ad7 + 1a5181c commit f9a249a
Show file tree
Hide file tree
Showing 74 changed files with 1,182 additions and 313 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ In addition to the standard features of a conference app, the DroidKaigi 2024 of
- **Contributors**: Discover the contributors behind the app.
...and more!

![image](https://github.com/user-attachments/assets/ffed2cb2-455b-4de8-a9d2-be9ca0842b99)
![image](https://github.com/user-attachments/assets/d1aeccc1-1e8e-475f-9c51-72fb595c6563)

## Try the app

Expand Down
10 changes: 10 additions & 0 deletions app-android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@
android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar" />

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontFamily
Expand Down Expand Up @@ -80,6 +81,7 @@ import io.github.droidkaigi.confsched.sessions.timetableScreenRoute
import io.github.droidkaigi.confsched.settings.settingsScreenRoute
import io.github.droidkaigi.confsched.settings.settingsScreens
import io.github.droidkaigi.confsched.share.ShareNavigator
import io.github.droidkaigi.confsched.share.saveToDisk
import io.github.droidkaigi.confsched.sponsors.sponsorsScreenRoute
import io.github.droidkaigi.confsched.sponsors.sponsorsScreens
import io.github.droidkaigi.confsched.staff.staffScreenRoute
Expand Down Expand Up @@ -134,11 +136,20 @@ private fun KaigiNavHost(
CompositionLocalProvider(
LocalSharedTransitionScope provides this,
) {
val context = LocalContext.current
NavHostWithSharedAxisX(
navController = navController,
startDestination = mainScreenRoute,
) {
mainScreen(windowSize, navController, externalNavController)
mainScreen(
windowSize,
navController,
externalNavController,
onClickShareProfileCard = { shareText, imageBitmap ->
val imageAbsolutePath = imageBitmap.saveToDisk(context)
externalNavController.onShareProfileCardClick(shareText, imageAbsolutePath)
},
)
sessionScreens(
onNavigationIconClick = navController::popBackStack,
onLinkClick = externalNavController::navigate,
Expand Down Expand Up @@ -185,6 +196,7 @@ private fun NavGraphBuilder.mainScreen(
navController: NavHostController,
@Suppress("UnusedParameter")
externalNavController: ExternalNavController,
onClickShareProfileCard: (String, ImageBitmap) -> Unit,
) {
mainScreen(
windowSize = windowSize,
Expand All @@ -197,6 +209,7 @@ private fun NavGraphBuilder.mainScreen(
contentPadding = contentPadding,
)
eventMapScreens(
contentPadding = contentPadding,
onEventMapItemClick = externalNavController::navigate,
)
favoritesScreens(
Expand Down Expand Up @@ -248,7 +261,10 @@ private fun NavGraphBuilder.mainScreen(
}
},
)
profileCardScreen(contentPadding)
profileCardScreen(
contentPadding = contentPadding,
onClickShareProfileCard = onClickShareProfileCard,
)
},
)
}
Expand Down Expand Up @@ -346,6 +362,16 @@ private class ExternalNavController(
)
}

fun onShareProfileCardClick(
text: String,
filePath: String,
) {
shareNavigator.shareTextWithImage(
text = text,
filePath = filePath,
)
}

@Suppress("SwallowedException")
@RequiresApi(Build.VERSION_CODES.R)
private fun navigateToNativeAppApi30(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.droidkaigi.confsched.share

import android.content.Context
import android.graphics.Bitmap.CompressFormat.PNG
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asAndroidBitmap
import kotlinx.datetime.Clock.System
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import java.io.File
import java.io.FileOutputStream

fun ImageBitmap.saveToDisk(context: Context): String {
val timestamp = System.now()
.toLocalDateTime(TimeZone.UTC)
.toString()
.replace(":", "")
.replace(".", "")
val fileName = "shared_image_$timestamp.png"

val cachePath = File(context.cacheDir, "images")
cachePath.mkdirs()
val file = File(cachePath, fileName)
val outputStream = FileOutputStream(file)

this.asAndroidBitmap().compress(PNG, 100, outputStream)
outputStream.flush()
outputStream.close()

return file.absolutePath
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package io.github.droidkaigi.confsched.share
import android.content.ActivityNotFoundException
import android.content.Context
import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import co.touchlab.kermit.Logger
import java.io.File

class ShareNavigator(private val context: Context) {
fun share(text: String) {
Expand All @@ -16,4 +18,19 @@ class ShareNavigator(private val context: Context) {
Logger.e("ActivityNotFoundException Fail startActivity: $e")
}
}

fun shareTextWithImage(text: String, filePath: String) {
try {
val file = File(filePath)
val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)

ShareCompat.IntentBuilder(context)
.setStream(uri)
.setText(text)
.setType("image/png")
.startChooser()
} catch (e: ActivityNotFoundException) {
Logger.e("ActivityNotFoundException Fail startActivity: $e")
}
}
}
4 changes: 4 additions & 0 deletions app-android/src/main/res/xml/file_paths.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="images" path="images/" />
</paths>
6 changes: 5 additions & 1 deletion app-ios/App/App.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
830BFA7C2C74EBF40017A600 /* compose-resources in Resources */ = {isa = PBXBuildFile; fileRef = 830BFA7B2C74EBF40017A600 /* compose-resources */; };
8C31F46B2BF6909A003F1BBA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 8C31F46A2BF6909A003F1BBA /* GoogleService-Info.plist */; };
8C7DACB72BCBCCB0002C298A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8C7DACB02BCBCCB0002C298A /* Preview Assets.xcassets */; };
8C7DACB82BCBCCB0002C298A /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7DACB22BCBCCB0002C298A /* App.swift */; };
Expand All @@ -15,6 +16,7 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
830BFA7B2C74EBF40017A600 /* compose-resources */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "compose-resources"; path = "../../app-ios-shared/build/kotlin-multiplatform-resources/aggregated-resources/iosSimulatorArm64/compose-resources"; sourceTree = "<group>"; };
8C31F46A2BF6909A003F1BBA /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
8C772B152BCBCBCA00F2BADC /* DroidKaigi2024App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DroidKaigi2024App.app; sourceTree = BUILT_PRODUCTS_DIR; };
8C7DACB02BCBCCB0002C298A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -42,6 +44,7 @@
8C772B0C2BCBCBCA00F2BADC = {
isa = PBXGroup;
children = (
830BFA7B2C74EBF40017A600 /* compose-resources */,
C412816C2C149FB500B458D1 /* DroidKaigi2024App-Info.plist */,
8C7DACC22BCBD111002C298A /* App */,
8C7DACC12BCBD0F1002C298A /* app-ios */,
Expand Down Expand Up @@ -152,6 +155,7 @@
8C7DACB92BCBCCB0002C298A /* Assets.xcassets in Resources */,
8C7DACB72BCBCCB0002C298A /* Preview Assets.xcassets in Resources */,
8C31F46B2BF6909A003F1BBA /* GoogleService-Info.plist in Resources */,
830BFA7C2C74EBF40017A600 /* compose-resources in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -173,7 +177,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/zsh;
shellScript = "cd ${SRCROOT}/../..\n./gradlew assembleSharedXCFramework --no-configuration-cache\n";
shellScript = "cd ${SRCROOT}/../..\n./gradlew assembleSharedXCFramework --no-configuration-cache\n./gradlew iosSimulatorArm64AggregateResources --no-configuration-cache\n\nRESOURCE_DIR=\"./app-ios-shared/build/kotlin-multiplatform-resources/aggregated-resources/iosSimulatorArm64\"\n\nmkdir ${RESOURCE_DIR}/compose-resources\ncp -R ${RESOURCE_DIR}/composeResources ${RESOURCE_DIR}/compose-resources\n";
};
/* End PBXShellScriptBuildPhase section */

Expand Down
59 changes: 59 additions & 0 deletions app-ios/Sources/CommonComponents/Timetable/CircularUserIcon.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import Foundation
import SwiftUI

private actor CircularUserIconInMemoryCache {
static let shared = CircularUserIconInMemoryCache()
private init() {}

private var cache: [String: Data] = [:]

func data(urlString: String) -> Data? {
return cache[urlString]
}

func set(data: Data, urlString: String) {
cache[urlString] = data
}
}

public struct CircularUserIcon: View {
let urlString: String
@State private var iconData: Data?

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

public var body: some View {
Group {
if let data = iconData,
let uiImage = UIImage(data: data) {
Image(uiImage: uiImage)
.resizable()
} else {
Circle().stroke(Color.gray)
}
}
.clipShape(Circle())
.task {
if let data = await CircularUserIconInMemoryCache.shared.data(urlString: urlString) {
iconData = data
return
}

guard let url = URL(string: urlString) else {
return
}
let urlRequest = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy)
if let (data, _) = try? await URLSession.shared.data(for: urlRequest) {
iconData = data
await CircularUserIconInMemoryCache.shared.set(data: data, urlString: urlString)
}
}
}
}

#Preview {
CircularUserIcon(urlString: "https://avatars.githubusercontent.com/u/10727543?s=96&v=4")
.frame(width: 32, height: 32)
}
53 changes: 8 additions & 45 deletions app-ios/Sources/CommonComponents/Timetable/RoomTag.swift
Original file line number Diff line number Diff line change
@@ -1,74 +1,37 @@
import SwiftUI
import Theme
import shared
import Model

public struct RoomTag: View {
let roomName: MultiLangText
let roomType: RoomType

public init(_ roomName: MultiLangText) {
self.roomName = roomName
self.roomType = .init(enTitle: roomName.enTitle)
}

public var body: some View {
HStack(spacing: 4) {
roomName.roomType.shape
RoomTypeShape(roomType: roomType)
Text(roomName.currentLangTitle)
.textStyle(.labelMedium)
}
.foregroundStyle(roomName.roomType.theme.primaryColor)
.foregroundStyle(roomType.theme.primaryColor)
.padding(.horizontal, 6)
.overlay(
RoundedRectangle(cornerRadius: 2)
.stroke(roomName.roomType.theme.primaryColor, lineWidth: 1)
.stroke(roomType.theme.primaryColor, lineWidth: 1)
)
}
}

enum ThemeKey {
static let iguana = "iguana"
static let hedgehog = "hedgehog"
static let giraffe = "giraffe"
static let flamingo = "flamingo"
static let jellyfish = "jellyfish"
}


extension MultiLangText {
var roomType: RoomType {
switch enTitle.lowercased() {
case ThemeKey.flamingo: .roomF
case ThemeKey.giraffe: .roomG
case ThemeKey.hedgehog: .roomH
case ThemeKey.iguana: .roomI
case ThemeKey.jellyfish: .roomJ
default: .roomIj
}
}
}

extension RoomType {
public var shape: some View {
Group {
switch self {
case .roomG: Image(.icCircleFill).renderingMode(.template)
case .roomH: Image(.icDiamondFill).renderingMode(.template)
case .roomF: Image(.icSharpDiamondFill).renderingMode(.template)
case .roomI: Image(.icSquareFill).renderingMode(.template)
case .roomJ: Image(.icTriangleFill).renderingMode(.template)
case .roomIj: Image(.icSquareFill).renderingMode(.template)
}
}
.foregroundStyle(theme.primaryColor)
.frame(width: 12, height: 12)
}
}

#Preview {
RoomTag(
MultiLangText(
jaTitle: "Iguana",
enTitle: "Iguana"
currentLangTitle: "Iguana",
enTitle: "Iguana",
jaTitle: "Iguana"
)
)
}
36 changes: 36 additions & 0 deletions app-ios/Sources/CommonComponents/Timetable/RoomTypeShape.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import SwiftUI
import Model

public struct RoomTypeShape: View {
let roomType: RoomType

public init(roomType: RoomType) {
self.roomType = roomType
}

public var body: some View {
Group {
switch roomType {
case .roomG: Image(.icCircleFill).renderingMode(.template)
case .roomH: Image(.icDiamondFill).renderingMode(.template)
case .roomF: Image(.icSharpDiamondFill).renderingMode(.template)
case .roomI: Image(.icSquareFill).renderingMode(.template)
case .roomJ: Image(.icTriangleFill).renderingMode(.template)
case .roomIj: Image(.icSquareFill).renderingMode(.template)
}
}
.foregroundStyle(roomType.theme.primaryColor)
.frame(width: 12, height: 12)
}
}

#Preview {
Group {
RoomTypeShape(roomType: .roomF)
RoomTypeShape(roomType: .roomG)
RoomTypeShape(roomType: .roomH)
RoomTypeShape(roomType: .roomI)
RoomTypeShape(roomType: .roomJ)
RoomTypeShape(roomType: .roomIj)
}
}
Loading

0 comments on commit f9a249a

Please sign in to comment.