diff --git a/.github/workflows/develop_PR_builder.yml b/.github/workflows/develop_PR_builder.yml index 0114fdef..c3a7fe5c 100644 --- a/.github/workflows/develop_PR_builder.yml +++ b/.github/workflows/develop_PR_builder.yml @@ -45,6 +45,7 @@ jobs: KEY_ALIAS: ${{ secrets.SENTRY_DSN }} KEY_PASSWORD: ${{ secrets.SENTRY_DSN }} STORE_PASSWORD: ${{ secrets.SENTRY_DSN }} + FACEBOOK_APP_ID: ${{ secrets.SENTRY_DSN }} run: | echo sentryDsn=\"$SENTRY_DSN\" >> ./local.properties echo kakaoApiKey=$KAKAO_API_KEY >> ./local.properties @@ -53,6 +54,7 @@ jobs: echo keyAlias=$KEY_ALIAS >> ./local.properties echo keyPassword=KEY_PASSWORD >> ./local.properties echo storePassword=$STORE_PASSWORD >> ./local.properties + echo facebookAppId=$FACEBOOK_APP_ID >> ./local.properties - name: Access Firebase Service run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > ./app/google-services.json diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e714103c..cc88876e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,7 +4,9 @@ - + + if (uri != null) { - val intent = albumItem?.let { AddPhotoActivity.getIntent(this, uri.toString(), it) } + val intent = albumItem?.let { AddPhotoActivity.getIntent(this, uri.toString(), it, "PICKER") } photoCountRefreshLauncher.launch(intent) } } diff --git a/app/src/main/java/com/teampophory/pophory/feature/home/add/AddPhotoBottomSheet.kt b/app/src/main/java/com/teampophory/pophory/feature/home/add/AddPhotoBottomSheet.kt index d19033d4..7edc0ad0 100644 --- a/app/src/main/java/com/teampophory/pophory/feature/home/add/AddPhotoBottomSheet.kt +++ b/app/src/main/java/com/teampophory/pophory/feature/home/add/AddPhotoBottomSheet.kt @@ -22,7 +22,6 @@ import com.teampophory.pophory.feature.home.photo.AddPhotoActivity import com.teampophory.pophory.feature.qr.QRActivity class AddPhotoBottomSheet : BottomSheetDialogFragment() { - private val binding by viewBinding(BottomSheetHomeAddPhotoBinding::bind) private val viewModel by activityViewModels() private lateinit var imagePicker: ActivityResultLauncher @@ -44,7 +43,7 @@ class AddPhotoBottomSheet : BottomSheetDialogFragment() { val currentAlbumPosition = viewModel.homeState.value.currentAlbumPosition val albumItem = viewModel.homeState.value.currentAlbums?.getOrNull(currentAlbumPosition) if (uri != null && albumItem != null) { - val intent = AddPhotoActivity.getIntent(context, uri.toString(), albumItem) + val intent = AddPhotoActivity.getIntent(context, uri.toString(), albumItem, "PICKER") addPhotoResultLauncher.launch(intent) } } @@ -57,7 +56,7 @@ class AddPhotoBottomSheet : BottomSheetDialogFragment() { val albumItem = viewModel.homeState.value.currentAlbums?.getOrNull(currentAlbumPosition) if (uriString != null && albumItem != null) { - val intent = AddPhotoActivity.getIntent(context, uriString, albumItem) + val intent = AddPhotoActivity.getIntent(context, uriString, albumItem, "QR") addPhotoResultLauncher.launch(intent) } } diff --git a/app/src/main/java/com/teampophory/pophory/feature/home/model/RegisterNavigationType.kt b/app/src/main/java/com/teampophory/pophory/feature/home/model/RegisterNavigationType.kt new file mode 100644 index 00000000..f2c699f9 --- /dev/null +++ b/app/src/main/java/com/teampophory/pophory/feature/home/model/RegisterNavigationType.kt @@ -0,0 +1,5 @@ +package com.teampophory.pophory.feature.home.model + +enum class RegisterNavigationType { + PICKER, QR, GALLERY +} diff --git a/app/src/main/java/com/teampophory/pophory/feature/home/photo/AddPhotoActivity.kt b/app/src/main/java/com/teampophory/pophory/feature/home/photo/AddPhotoActivity.kt index 539f5b94..8484bb2e 100644 --- a/app/src/main/java/com/teampophory/pophory/feature/home/photo/AddPhotoActivity.kt +++ b/app/src/main/java/com/teampophory/pophory/feature/home/photo/AddPhotoActivity.kt @@ -1,21 +1,27 @@ package com.teampophory.pophory.feature.home.photo import android.app.Activity +import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle import android.util.Size +import android.widget.Toast import androidx.activity.addCallback import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import coil.load import com.google.android.material.datepicker.CalendarConstraints import com.google.android.material.datepicker.DateValidatorPointBackward import com.google.android.material.datepicker.MaterialDatePicker +import com.teampophory.pophory.BuildConfig import com.teampophory.pophory.R +import com.teampophory.pophory.common.bitmap.capture +import com.teampophory.pophory.common.bitmap.saveToDisk import com.teampophory.pophory.common.context.colorOf import com.teampophory.pophory.common.context.snackBar import com.teampophory.pophory.common.context.toast @@ -26,10 +32,12 @@ import com.teampophory.pophory.common.time.systemNow import com.teampophory.pophory.common.view.setOnSingleClickListener import com.teampophory.pophory.common.view.viewBinding import com.teampophory.pophory.databinding.ActivityAddPhotoBinding +import com.teampophory.pophory.feature.home.model.RegisterNavigationType import com.teampophory.pophory.feature.home.store.model.AlbumItem import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import kotlinx.datetime.Instant import java.text.SimpleDateFormat import java.util.Date @@ -63,9 +71,9 @@ class AddPhotoActivity : AppCompatActivity() { private fun loadImageWithAdjustedSize(realImageUri: Uri, adjustedSize: Size) { val (backgroundResource, imageView) = if (adjustedSize.width >= adjustedSize.height) { - Pair(R.drawable.img_background_width, binding.imgHorizontal) + R.drawable.img_background_width to binding.imgHorizontal } else { - Pair(R.drawable.img_background_height, binding.imgVertical) + R.drawable.img_background_height to binding.imgVertical } binding.imgBackground.setImageResource(backgroundResource) imageView.load(realImageUri) { @@ -74,10 +82,10 @@ class AddPhotoActivity : AppCompatActivity() { } private fun initView() { - binding.toolbarAddPhoto.btnBack.setOnClickListener { + binding.btnBack.setOnClickListener { finish() } - binding.toolbarAddPhoto.txtToolbarTitle.text = "사진 추가" + binding.txtToolbarTitle.text = "사진 추가" binding.layoutDate.setOnClickListener { viewModel.onCreatedAtPressed() } @@ -91,6 +99,33 @@ class AddPhotoActivity : AppCompatActivity() { setResult(Activity.RESULT_CANCELED) finish() } + binding.btnShare.setOnSingleClickListener { + lifecycleScope.launch { + val bitmap = binding.layoutImage.capture(this@AddPhotoActivity) + val shareImageUri = bitmap.saveToDisk(this@AddPhotoActivity) + val intent = Intent("com.instagram.share.ADD_TO_STORY").apply { + putExtra("source_application", BuildConfig.FACEBOOK_APP_ID) + type = "image/png" + putExtra("interactive_asset_uri", shareImageUri) + putExtra("top_background_color", "#000000") + putExtra("bottom_background_color", "#000000") + } + grantUriPermission( + "com.instagram.android", + shareImageUri, + Intent.FLAG_GRANT_READ_URI_PERMISSION, + ) + try { + startActivity(intent) + } catch (e: ActivityNotFoundException) { + Toast.makeText( + this@AddPhotoActivity, + "인스타그램 앱이 존재하지 않습니다.", + Toast.LENGTH_SHORT, + ).show() + } + } + } } private fun subscribeEvent() { @@ -112,7 +147,8 @@ class AddPhotoActivity : AppCompatActivity() { .setEnd(Instant.systemNow().toEpochMilliseconds()).build(), ) .setSelection( - currentCreatedAt + TimeZone.getDefault().getOffset(currentCreatedAt), + currentCreatedAt + TimeZone.getDefault() + .getOffset(currentCreatedAt), ) .build() picker.show(supportFragmentManager, "datePicker") @@ -157,18 +193,30 @@ class AddPhotoActivity : AppCompatActivity() { binding.txtStudio.setTextColor(colorOf(com.teampophory.pophory.designsystem.R.color.gray_40)) } }.launchIn(lifecycleScope) + viewModel.type + .flowWithLifecycle(lifecycle) + .onEach { + binding.btnShare.isVisible = it != RegisterNavigationType.PICKER + }.launchIn(lifecycleScope) } companion object { private const val IMAGE_URL_EXTRA = "imageUri" const val ALBUM_ITEM_EXTRA = "albumItem" const val IMAGE_MIME_TYPE = "image/*" + private const val TYPE = "type" @JvmStatic - fun getIntent(context: Context, imageUri: String, albumItem: AlbumItem): Intent = + fun getIntent( + context: Context, + imageUri: String, + albumItem: AlbumItem, + type: String, + ): Intent = Intent(context, AddPhotoActivity::class.java).apply { putExtra(IMAGE_URL_EXTRA, imageUri) putExtra(ALBUM_ITEM_EXTRA, albumItem) + putExtra(TYPE, type) } } } diff --git a/app/src/main/java/com/teampophory/pophory/feature/home/photo/AddPhotoViewModel.kt b/app/src/main/java/com/teampophory/pophory/feature/home/photo/AddPhotoViewModel.kt index d760476b..9e6ff52f 100644 --- a/app/src/main/java/com/teampophory/pophory/feature/home/photo/AddPhotoViewModel.kt +++ b/app/src/main/java/com/teampophory/pophory/feature/home/photo/AddPhotoViewModel.kt @@ -8,6 +8,7 @@ import com.teampophory.pophory.common.time.systemNow import com.teampophory.pophory.data.model.photo.Studio import com.teampophory.pophory.domain.model.S3Image import com.teampophory.pophory.domain.repository.photo.PhotoRepository +import com.teampophory.pophory.feature.home.model.RegisterNavigationType import com.teampophory.pophory.feature.home.photo.model.StudioUiModel import com.teampophory.pophory.feature.home.photo.model.toUiModel import com.teampophory.pophory.feature.home.store.model.AlbumItem @@ -43,6 +44,12 @@ class AddPhotoViewModel @Inject constructor( private var imageRequestBody: RequestBody? = null private var currentImageSize: Size? = null private var currentFileName: String? = null + private val _type = MutableStateFlow( + RegisterNavigationType.valueOf( + savedStateHandle.get("type").orEmpty(), + ), + ) + val type = _type.asStateFlow() private val _createdAt = MutableStateFlow(Instant.systemNow().toEpochMilliseconds()) val createdAt = _createdAt.asStateFlow() private val allStudio = MutableStateFlow>(emptyList()) diff --git a/app/src/main/java/com/teampophory/pophory/feature/home/store/StoreFragment.kt b/app/src/main/java/com/teampophory/pophory/feature/home/store/StoreFragment.kt index 8a4b101c..dc9a1304 100644 --- a/app/src/main/java/com/teampophory/pophory/feature/home/store/StoreFragment.kt +++ b/app/src/main/java/com/teampophory/pophory/feature/home/store/StoreFragment.kt @@ -235,6 +235,7 @@ class StoreFragment : Fragment() { context = requireContext(), imageUri = imageUri.toString(), albumItem = albumItem, + type = "GALLERY", ).let(albumListAddPhotoLauncher::launch) } } diff --git a/app/src/main/res/layout/activity_add_photo.xml b/app/src/main/res/layout/activity_add_photo.xml index 4cd80680..8c4513b6 100644 --- a/app/src/main/res/layout/activity_add_photo.xml +++ b/app/src/main/res/layout/activity_add_photo.xml @@ -5,17 +5,60 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + + + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/toolbar"> + bitmap.compress(format, quality, out) + out.flush() + } +} + +suspend fun scanFilePath(context: Context, filePath: String): Uri? { + return suspendCancellableCoroutine { continuation -> + MediaScannerConnection.scanFile( + context, + arrayOf(filePath), + arrayOf("image/png"), + ) { _, scannedUri -> + if (scannedUri == null) { + continuation.cancel(Exception("File $filePath could not be scanned")) + } else { + continuation.resume(scannedUri) + } + } + } +} + +suspend fun View.capture(activity: Activity) = suspendCancellableCoroutine { continuation -> + val bitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888) + val location = IntArray(2) + getLocationInWindow(location) + PixelCopy.request( + activity.window, + Rect(location[0], location[1], location[0] + width, location[1] + height), + bitmap, + { + if (it == PixelCopy.SUCCESS) { + continuation.resume(bitmap) + } else { + continuation.resumeWithException(Exception("Unable to load bitmap from view")) + } + }, + Handler(Looper.getMainLooper()), + ) +}