Skip to content

Commit 12a74e6

Browse files
authored
Merge pull request #208 from YAPP-Github/release/1.2.1
[release] v1.2.1
2 parents d7cd407 + b7bb690 commit 12a74e6

115 files changed

Lines changed: 3860 additions & 981 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: GA4 Daily Report
2+
3+
on:
4+
schedule:
5+
- cron: '0 1 * * *' # 매일 10:00 KST
6+
workflow_dispatch:
7+
8+
jobs:
9+
report:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: actions/setup-python@v5
16+
with:
17+
python-version: '3.11'
18+
19+
- name: Install dependencies
20+
run: pip install google-analytics-data requests
21+
22+
- name: Run GA4 daily report
23+
env:
24+
DISCORD_WEBHOOK_URL: ${{ secrets.GA_DISCORD_WEBHOOK_URL }}
25+
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
26+
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
27+
GOOGLE_REFRESH_TOKEN: ${{ secrets.GOOGLE_REFRESH_TOKEN }}
28+
run: python scripts/ga_daily_report.py

app/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ dependencies {
7171
implementation(projects.core.model)
7272
implementation(projects.core.navigation)
7373
implementation(projects.core.ui)
74+
implementation(projects.core.analytics)
7475
implementation(projects.feature.auth.api)
7576
implementation(projects.feature.auth.impl)
7677
implementation(projects.feature.pose.api)
@@ -83,11 +84,12 @@ dependencies {
8384
implementation(projects.feature.mypage.impl)
8485
implementation(projects.feature.photoUpload.api)
8586
implementation(projects.feature.photoUpload.impl)
87+
implementation(projects.feature.selectAlbum.api)
88+
implementation(projects.feature.selectAlbum.impl)
8689

8790
implementation(libs.timber)
8891

8992
implementation(platform(libs.firebase.bom))
90-
implementation(libs.firebase.analytics)
9193
implementation(libs.firebase.crashlytics)
9294

9395
implementation(libs.androidx.activity.compose)

app/src/main/AndroidManifest.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@
2929
<action android:name="android.intent.action.MAIN" />
3030
<category android:name="android.intent.category.LAUNCHER" />
3131
</intent-filter>
32+
<intent-filter>
33+
<action android:name="android.intent.action.SEND" />
34+
<category android:name="android.intent.category.DEFAULT" />
35+
<data android:mimeType="image/*" />
36+
</intent-filter>
37+
<intent-filter>
38+
<action android:name="android.intent.action.SEND_MULTIPLE" />
39+
<category android:name="android.intent.category.DEFAULT" />
40+
<data android:mimeType="image/*" />
41+
</intent-filter>
3242
</activity>
3343

3444
<activity

app/src/main/java/com/neki/android/app/MainActivity.kt

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.neki.android.app
22

3+
import android.content.Intent
34
import android.graphics.Color
45
import android.os.Bundle
56
import android.widget.Toast
@@ -8,7 +9,10 @@ import androidx.activity.SystemBarStyle
89
import androidx.activity.compose.setContent
910
import androidx.activity.enableEdgeToEdge
1011
import androidx.compose.runtime.CompositionLocalProvider
12+
import androidx.compose.runtime.getValue
13+
import androidx.compose.runtime.mutableStateOf
1114
import androidx.compose.runtime.remember
15+
import androidx.compose.runtime.setValue
1216
import androidx.lifecycle.lifecycleScope
1317
import androidx.navigation3.runtime.entryProvider
1418
import com.neki.android.app.main.MainRoute
@@ -28,11 +32,31 @@ import com.neki.android.core.navigation.root.RootNavigator
2832
import com.neki.android.feature.auth.api.AuthNavKey
2933
import com.neki.android.feature.auth.impl.navigation.authEntryProvider
3034
import com.neki.android.feature.photo_upload.api.navigateToQRScan
31-
import com.neki.android.feature.photo_upload.api.navigateToUploadAlbum
35+
import com.neki.android.feature.select_album.api.navigateToSelectAlbum
36+
import android.net.Uri
37+
import androidx.core.content.IntentCompat
3238
import dagger.hilt.android.AndroidEntryPoint
39+
import kotlinx.collections.immutable.ImmutableList
40+
import kotlinx.collections.immutable.persistentListOf
41+
import kotlinx.collections.immutable.toImmutableList
3342
import kotlinx.coroutines.launch
3443
import javax.inject.Inject
3544

45+
private fun Intent.extractShareUriStrings(): ImmutableList<String> {
46+
val uris: List<Uri> = when (action) {
47+
Intent.ACTION_SEND -> {
48+
IntentCompat.getParcelableExtra(this, Intent.EXTRA_STREAM, Uri::class.java)
49+
?.let { listOf(it) } ?: emptyList()
50+
}
51+
Intent.ACTION_SEND_MULTIPLE -> {
52+
IntentCompat.getParcelableArrayListExtra(this, Intent.EXTRA_STREAM, Uri::class.java)
53+
?: emptyList()
54+
}
55+
else -> emptyList()
56+
}
57+
return uris.map { it.toString() }.toImmutableList()
58+
}
59+
3660
@AndroidEntryPoint
3761
class MainActivity : ComponentActivity() {
3862

@@ -54,9 +78,13 @@ class MainActivity : ComponentActivity() {
5478
@Inject
5579
lateinit var authEventManager: AuthEventManager
5680

81+
private var pendingShareUriStrings by mutableStateOf<ImmutableList<String>>(persistentListOf())
82+
5783
override fun onCreate(savedInstanceState: Bundle?) {
5884
super.onCreate(savedInstanceState)
5985

86+
pendingShareUriStrings = intent.extractShareUriStrings()
87+
6088
enableEdgeToEdge(
6189
navigationBarStyle = SystemBarStyle.auto(
6290
lightScrim = Color.TRANSPARENT,
@@ -95,8 +123,9 @@ class MainActivity : ComponentActivity() {
95123
onTabSelected = { mainNavigator.navigate(it) },
96124
onBack = { mainNavigator.goBack() },
97125
navigateToQRScan = mainNavigator::navigateToQRScan,
98-
navigateToUploadAlbumWithGallery = mainNavigator::navigateToUploadAlbum,
99-
navigateToUploadAlbumWithQRScan = mainNavigator::navigateToUploadAlbum,
126+
navigateToSelectAlbum = { action -> mainNavigator.navigateToSelectAlbum(action) },
127+
pendingShareUriStrings = pendingShareUriStrings,
128+
onShareUrisConsumed = { pendingShareUriStrings = persistentListOf() },
100129
)
101130
}
102131
}
@@ -107,6 +136,14 @@ class MainActivity : ComponentActivity() {
107136
observeAuthEvents()
108137
}
109138

139+
override fun onNewIntent(intent: Intent) {
140+
super.onNewIntent(intent)
141+
val uriStrings = intent.extractShareUriStrings()
142+
if (uriStrings.isNotEmpty()) {
143+
pendingShareUriStrings = uriStrings
144+
}
145+
}
146+
110147
private fun observeAuthEvents() {
111148
lifecycleScope.launch {
112149
authEventManager.authEvent.collect { event ->

app/src/main/java/com/neki/android/app/main/MainContract.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.neki.android.app.main
22

33
import android.net.Uri
4+
import com.neki.android.feature.select_album.api.SelectAlbumAction
45
import kotlinx.collections.immutable.ImmutableList
56
import kotlinx.collections.immutable.persistentListOf
67

@@ -18,6 +19,7 @@ sealed interface MainIntent {
1819
data object ClickQRScan : MainIntent
1920
data object ClickGalleryUpload : MainIntent
2021
data class SelectGalleryImage(val uris: List<Uri>) : MainIntent
22+
data class ShareImageReceived(val uris: List<Uri>) : MainIntent
2123
data class QRCodeScanned(val imageUrl: String) : MainIntent
2224
data object DismissSelectWithAlbumDialog : MainIntent
2325
data object ClickUploadWithAlbum : MainIntent
@@ -27,8 +29,7 @@ sealed interface MainIntent {
2729
sealed interface MainSideEffect {
2830
data object NavigateToQRScan : MainSideEffect
2931
data object OpenGallery : MainSideEffect
30-
data class NavigateToUploadAlbumWithGallery(val uriStrings: List<String>) : MainSideEffect
31-
data class NavigateToUploadAlbumWithQRScan(val imageUrl: String) : MainSideEffect
32+
data class NavigateToSelectAlbum(val action: SelectAlbumAction) : MainSideEffect
3233
data class ShowToast(val message: String) : MainSideEffect
3334
data object RefreshArchive : MainSideEffect
3435
}

app/src/main/java/com/neki/android/app/main/MainScreen.kt

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,49 @@
11
package com.neki.android.app.main
22

3-
import androidx.compose.animation.ContentTransform
4-
import androidx.compose.animation.EnterTransition
5-
import androidx.compose.animation.ExitTransition
63
import androidx.activity.compose.rememberLauncherForActivityResult
74
import androidx.activity.result.PickVisualMediaRequest
85
import androidx.activity.result.contract.ActivityResultContracts
6+
import androidx.compose.animation.ContentTransform
7+
import androidx.compose.animation.EnterTransition
8+
import androidx.compose.animation.ExitTransition
99
import androidx.compose.foundation.layout.PaddingValues
1010
import androidx.compose.foundation.layout.fillMaxSize
1111
import androidx.compose.foundation.layout.navigationBarsPadding
1212
import androidx.compose.foundation.layout.padding
1313
import androidx.compose.material3.Scaffold
1414
import androidx.compose.runtime.Composable
15+
import androidx.compose.runtime.LaunchedEffect
1516
import androidx.compose.runtime.getValue
1617
import androidx.compose.runtime.mutableStateOf
1718
import androidx.compose.runtime.remember
1819
import androidx.compose.runtime.snapshots.SnapshotStateList
1920
import androidx.compose.ui.Modifier
2021
import androidx.compose.ui.platform.LocalContext
22+
import androidx.core.net.toUri
2123
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
2224
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2325
import androidx.navigation3.runtime.NavEntry
2426
import androidx.navigation3.runtime.NavKey
2527
import androidx.navigation3.ui.NavDisplay
2628
import com.neki.android.app.main.component.AddPhotoBottomSheet
2729
import com.neki.android.app.main.component.AlbumUploadOption
28-
import com.neki.android.app.ui.BottomNavigationBar
2930
import com.neki.android.app.main.component.SelectWithAlbumDialog
31+
import com.neki.android.app.ui.BottomNavigationBar
3032
import com.neki.android.core.navigation.result.LocalResultEventBus
3133
import com.neki.android.core.navigation.result.ResultEffect
3234
import com.neki.android.core.ui.component.LoadingDialog
3335
import com.neki.android.core.ui.compose.collectWithLifecycle
3436
import com.neki.android.core.ui.toast.NekiToast
3537
import com.neki.android.feature.archive.api.ArchiveNavKey
36-
import com.neki.android.feature.archive.api.ArchiveResult
38+
import com.neki.android.feature.archive.api.PhotoUploadedResult
3739
import com.neki.android.feature.map.api.MapNavKey
3840
import com.neki.android.feature.mypage.api.MyPageNavKey
3941
import com.neki.android.feature.photo_upload.api.PhotoUploadNavKey
4042
import com.neki.android.feature.photo_upload.api.QRScanResult
4143
import com.neki.android.feature.pose.api.PoseNavKey
44+
import com.neki.android.feature.select_album.api.SelectAlbumAction
45+
import kotlinx.collections.immutable.ImmutableList
46+
import kotlinx.collections.immutable.persistentListOf
4247
import timber.log.Timber
4348

4449
@Composable
@@ -50,8 +55,9 @@ fun MainRoute(
5055
onTabSelected: (NavKey) -> Unit,
5156
onBack: () -> Unit,
5257
navigateToQRScan: () -> Unit,
53-
navigateToUploadAlbumWithGallery: (List<String>) -> Unit,
54-
navigateToUploadAlbumWithQRScan: (String) -> Unit,
58+
navigateToSelectAlbum: (SelectAlbumAction) -> Unit,
59+
pendingShareUriStrings: ImmutableList<String> = persistentListOf(),
60+
onShareUrisConsumed: () -> Unit = {},
5561
viewModel: MainViewModel = hiltViewModel(),
5662
) {
5763
val uiState by viewModel.store.uiState.collectAsStateWithLifecycle()
@@ -69,6 +75,13 @@ fun MainRoute(
6975
}
7076
}
7177

78+
LaunchedEffect(pendingShareUriStrings) {
79+
if (pendingShareUriStrings.isNotEmpty()) {
80+
viewModel.store.onIntent(MainIntent.ShareImageReceived(pendingShareUriStrings.map { it.toUri() }))
81+
onShareUrisConsumed()
82+
}
83+
}
84+
7285
ResultEffect<QRScanResult>(resultBus) { result ->
7386
when (result) {
7487
is QRScanResult.QRCodeScanned -> viewModel.store.onIntent(MainIntent.QRCodeScanned(result.imageUrl))
@@ -83,10 +96,9 @@ fun MainRoute(
8396
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly),
8497
)
8598

86-
is MainSideEffect.NavigateToUploadAlbumWithGallery -> navigateToUploadAlbumWithGallery(sideEffect.uriStrings)
87-
is MainSideEffect.NavigateToUploadAlbumWithQRScan -> navigateToUploadAlbumWithQRScan(sideEffect.imageUrl)
99+
is MainSideEffect.NavigateToSelectAlbum -> navigateToSelectAlbum(sideEffect.action)
88100
is MainSideEffect.ShowToast -> nekiToast.showToast(sideEffect.message)
89-
MainSideEffect.RefreshArchive -> resultBus.sendResult<ArchiveResult>(result = ArchiveResult.PhotoUploaded)
101+
MainSideEffect.RefreshArchive -> resultBus.sendResult(result = PhotoUploadedResult, allowDuplicate = false)
90102
}
91103
}
92104

@@ -116,7 +128,6 @@ fun MainScreen(
116128
val shouldShowBottomBar by remember(currentKey) {
117129
mutableStateOf(currentKey in topLevelKeys)
118130
}
119-
120131
Scaffold(
121132
modifier = Modifier
122133
.fillMaxSize()
@@ -153,7 +164,7 @@ fun MainScreen(
153164
)
154165
}
155166

156-
if (uiState.isLoading) {
167+
if (uiState.isLoading && !uiState.isShowSelectWithAlbumDialog) {
157168
LoadingDialog()
158169
}
159170

0 commit comments

Comments
 (0)