• Y
  • List All
  • Feedback
    • This Project
    • All Projects
Profile Account Log out
  • Favorite
  • Project
  • Recent History
Loading...
  • Log in
  • Sign up
ekxkaks / photoslide star
  • Project homeH
  • CodeC
  • IssueI
  • Pull requestP
  • Review R
  • MilestoneM
  • BoardB 1
  • Files
  • Commit
  • Branches
photoslideappsrcmainjavakrcoapp4adphotoslideMainActivity.kt
Download as .zip file
File name
Commit message
Commit date
.idea
phot file load
08-31
app
모듈 분리
09-08
gradle
모듈 분리
09-08
.gitignore
프로잭트 생성 최초 업로드.
08-30
build.gradle.kts
프로잭트 생성 최초 업로드.
08-30
gradle.properties
프로잭트 생성 최초 업로드.
08-30
gradlew
프로잭트 생성 최초 업로드.
08-30
gradlew.bat
프로잭트 생성 최초 업로드.
08-30
settings.gradle.kts
프로잭트 생성 최초 업로드.
08-30
File name
Commit message
Commit date
src
모듈 분리
09-08
.gitignore
프로잭트 생성 최초 업로드.
08-30
build.gradle.kts
모듈 분리
09-08
File name
Commit message
Commit date
androidTest/java/kr/co/app4ad/photoslide
프로잭트 생성 최초 업로드.
08-30
main
모듈 분리
09-08
test/java/kr/co/app4ad/photoslide
프로잭트 생성 최초 업로드.
08-30
File name
Commit message
Commit date
java/kr/co/app4ad/photoslide
모듈 분리
09-08
keepRules
프로잭트 생성 최초 업로드.
08-30
res
phot file load
08-31
AndroidManifest.xml
프로잭트 생성 최초 업로드.
08-30
File name
Commit message
Commit date
ui/theme
프로잭트 생성 최초 업로드.
08-30
MainActivity.kt
모듈 분리
09-08
ekxkaks 09-08 f386275 모듈 분리 UNIX
Raw Open in browser Change history
package kr.co.app4ad.photoslide import android.content.Context import android.net.Uri import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.Crossfade import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.exifinterface.media.ExifInterface import coil.compose.rememberAsyncImagePainter import coil.request.ImageRequest import kr.co.app4ad.photoslide.ui.theme.PhotoSlideTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { PhotoSlideTheme { Surface( modifier = Modifier.fillMaxSize(), color = Color.Black ) { PhotoSlideApp() } } } } } /** * 1. 전체 앱 상태 관리 및 라우팅 담당 (State Holder) */ @Composable fun PhotoSlideApp() { var selectedImageUris by remember { mutableStateOf<List<Uri>>(emptyList()) } var currentIndex by remember { mutableIntStateOf(0) } val photoPickerLauncher = rememberImagePickerLauncher { uris -> if (uris.isNotEmpty()) { selectedImageUris = uris currentIndex = 0 } } Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { if (selectedImageUris.isEmpty()) { EmptyPhotoView( onPickClick = { photoPickerLauncher.launch( PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) ) } ) } else { ActiveSlideView( imageUris = selectedImageUris, currentIndex = currentIndex, onNextClick = { currentIndex = (currentIndex + 1) % selectedImageUris.size }, onChangePhotosClick = { photoPickerLauncher.launch( PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) ) } ) } } } /** * 2. 사진 불러오기 (Picker) 로직 모듈 */ @Composable fun rememberImagePickerLauncher(onUrisSelected: (List<Uri>) -> Unit) = rememberLauncherForActivityResult( contract = ActivityResultContracts.PickMultipleVisualMedia(), onResult = onUrisSelected ) /** * 3. 사진이 없을 때 안내 및 선택 화면 모듈 */ @Composable fun EmptyPhotoView(onPickClick: () -> Unit) { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize() ) { Text( text = "슬라이드쇼에 사용할 사진을 선택해 주세요", color = Color.White, modifier = Modifier.padding(bottom = 16.dp) ) Button(onClick = onPickClick) { Text("사진 여러 장 고르기") } } } /** * 4. 사진 슬라이드 쇼 렌더링, 타이머 프로그레스바 및 전환 애니메이션 모듈 */ @Composable fun ActiveSlideView( imageUris: List<Uri>, currentIndex: Int, onNextClick: () -> Unit, onChangePhotosClick: () -> Unit ) { val slideDuration = 10000L // 사진 한 장당 표시 시간 (3초) val progress = remember { Animatable(0f) } // 사진이 2장 이상일 때, 인덱스가 바뀔 때마다 0부터 1까지 차오르는 타이머 애니메이션 실행 LaunchedEffect(currentIndex, imageUris) { if (imageUris.size > 1) { progress.snapTo(0f) progress.animateTo( targetValue = 1f, animationSpec = tween(durationMillis = slideDuration.toInt(), easing = LinearEasing) ) onNextClick() } } val currentUri = imageUris[currentIndex] BoxWithConstraints( modifier = Modifier .fillMaxSize() .background(Color.Black), contentAlignment = Alignment.Center ) { val maxWidth = maxWidth val maxHeight = maxHeight // Crossfade를 적용하여 사진이 바뀔 때 부드럽게 페이드 인/아웃 되도록 전환 효과 부여 Crossfade( targetState = currentUri, animationSpec = tween(durationMillis = 1000), // 전환에 걸리는 시간 (0.6초) label = "PhotoTransition" ) { uri -> PhotoImageView( uri = uri, maxWidth = maxWidth, maxHeight = maxHeight ) } // 화면 상단에 남은 시간을 시각적으로 보여주는 프로그레스 인디케이터 바 if (imageUris.size > 1) { LinearProgressIndicator( progress = { progress.value }, modifier = Modifier .fillMaxWidth() .height(4.dp) .align(Alignment.TopCenter), color = Color.White, trackColor = Color.Gray.copy(alpha = 0.3f) ) } SlideControlBar( currentIndex = currentIndex, totalSize = imageUris.size, onNextClick = onNextClick, onChangePhotosClick = onChangePhotosClick ) } } /** * 5. 단일 사진 렌더링, getPhotoOrientation 함수 반영 및 오버레이 표시 모듈 */ @Composable fun PhotoImageView( uri: Uri, maxWidth: Dp, maxHeight: Dp ) { val context = LocalContext.current // getPhotoOrientation 함수를 사용하여 회전 각도 추출 var rotation = remember(uri) { getPhotoOrientation(context, uri) } rotation = 0F Log.d ("tag rotation" + "", "rotation = $rotation" ) // EXIF orientation 값을 로그 또는 디버그 표시용으로 읽기 위한 추가 로직 val orientationVal = remember(uri) { try { context.contentResolver.openInputStream(uri)?.use { inputStream -> ExifInterface(inputStream).getAttributeInt( ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL ) } ?: 1 } catch (e: Exception) { 1 } } val painter = rememberAsyncImagePainter( model = ImageRequest.Builder(context) .data(uri) .crossfade(true) .build() ) Box( modifier = Modifier .fillMaxSize() .background(Color.Black), contentAlignment = Alignment.Center ) { val isRotated = rotation == 90f || rotation == 270f Image( painter = painter, contentDescription = "슬라이드 사진", modifier = Modifier .fillMaxSize() .rotate(rotation), contentScale = if (isRotated) ContentScale.Inside else ContentScale.Fit ) // 사진 위에 orientation 값과 회전 각도 표시 Box( modifier = Modifier .align(Alignment.TopStart) .padding(16.dp) .background(Color.Black.copy(alpha = 0.6f)) .padding(horizontal = 8.dp, vertical = 4.dp) ) { Text( text = "Orientation: $orientationVal (Rotation: ${rotation.toInt()}°)", color = Color.White, fontSize = 14.sp ) } } } /** * 5-1 EXIF 정보로 회전각 읽어오기 */ fun getPhotoOrientation(context: Context, uri: Uri): Float { return try { context.contentResolver.openInputStream(uri)?.use { inputStream -> val exif = ExifInterface(inputStream) val orientation = exif.getAttributeInt( ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL ) when (orientation) { ExifInterface.ORIENTATION_ROTATE_90 -> 90f ExifInterface.ORIENTATION_ROTATE_180 -> 180f ExifInterface.ORIENTATION_ROTATE_270 -> 270f else -> 0f } } ?: 0f } catch (e: Exception) { 0f } } /** * 6. 하단 제어 바 (사진 전환 및 재선택 버튼) 모듈 */ @Composable fun SlideControlBar( currentIndex: Int, totalSize: Int, onNextClick: () -> Unit, onChangePhotosClick: () -> Unit ) { Box( modifier = Modifier .fillMaxSize() .padding(16.dp), contentAlignment = Alignment.BottomCenter ) { Row( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically ) { Button(onClick = onChangePhotosClick) { Text("사진 다시 고르기") } Button(onClick = onNextClick) { Text("다음 사진 (${currentIndex + 1}/$totalSize)") } } } }

          
        
    
    
Copyright Yona authors & © NAVER Corp. & NAVER LABS Supported by NAVER CLOUD PLATFORM

or
Sign in with github login with Google Sign in with Google
Reset password | Sign up