Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

๐ŸŽดํ•œ ๋—์— 5์–ต์„ ์•ˆ๋“œ๋กœ๋ฏธ๋‹? ์ซ„๋ฆฌ๋ฉด ์ฃผ๋ฌด์‹œ๋˜์ง€...๐Ÿ’ซ

Notifications You must be signed in to change notification settings

nneaning/meaning_Android

Repository files navigation


๐ŸŒœ ๋ฏธ๋ผํด ๋ชจ๋‹์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋‹น์‹ ์˜ ์˜๋ฏธ์žˆ๋Š” ์•„์นจ, meaning ๐ŸŒ


๐Ÿ‘‡ meaning_Android ๐Ÿ‘‡



๐ŸŽด About US

์ง„์ˆ˜ ํšจ์†ก ํ˜•์ค€

Contact:parkjinsu4755@gmail.com

GitHub:jinsu4755

Contact:gythd1998@gmail.com

GitHub:hyooosong

Contact: leehj1232@naver.com

GitHub:LEE-HYUNGJUN


๐Ÿฆ‚์ง„์ˆ˜

- ํƒ€์ž„์Šคํ…œํ”„ ์นด๋ฉ”๋ผ
- ๋กœ๊ทธ์ธ
- ์˜จ๋ณด๋”ฉ
- ์„œ๋ฒ„ ์—ฐ๊ฒฐ ๋กœ์ง ๊ตฌํ˜„

๐ŸŽ… ํšจ์†ก

- ๊ทธ๋ฃน ํƒญ
- ์บ˜๋ฆฐ๋” ๋ทฐ
- sharedPreferences ์‹ฑ๊ธ€ํ†ค ๊ฐ์ฒด ๊ตฌํ˜„

๐Ÿ‘จ ๐Ÿ‘ง ํ˜•์ค€

- ํ™ˆ ๋ฉ”์ธํŽ˜์ด์ง€
- ์นด๋“œ ๋ทฐ ์• ๋‹ˆ๋ฉ”์ด์…˜
- ๋งˆ์ด ํ”ผ๋“œ, ๊ทธ๋ฃน ํ”ผ๋“œ
- ํ”ผ๋“œ ์ƒ์„ธ๋ณด๊ธฐ ๋ทฐ

๐Ÿ† Meeting Log


๐Ÿ“ List

1. [Service]

2. [Andromeaning Development Environment]

3. [Work Flow]

4. [Dependencies]

5. [Team Role]

  • [Andromeaning Conventions]
  • [Andromeaning Coding Style]
  • [Code Review Guideline]
  • [Git]

6. [meaning Tech Stack]

7. [Packaging]

8. [Main Feature Codes & Methods]


๐Ÿ’ซ Service about meaning

๋ชจ๋“  ๊ฒƒ์€ ๋ฐ”๋€” ์ˆ˜ ์žˆ๊ณ  ๋‚˜ ์—ญ์‹œ ๋ฌด์–ธ๊ฐ€๋ฅผ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ์ƒ์‹œ๊ฐ„์ด ๋‹ฌ๋ผ์ง„๋‹ค๋ฉด, ๋‹น์‹ ๋„ ๋ณ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โ€˜๋‚ดโ€™๊ฐ€ ๋ˆˆ ๋œจ๋Š” ์‹œ๊ฐ„์ด ์•„๋‹Œ, โ€˜ํ•ดโ€™๊ฐ€ ๋œจ๋Š” ์‹œ๊ฐ„๋ถ€ํ„ฐ ํ•˜๋ฃจ๋ฅผ ์‹œ์ž‘ํ•˜๋Š” ๋ฏธ๋ผํด ๋ชจ๋‹.

๋ฏธ๋‹์„ ํ†ตํ•ด ๋ฏธ๋ผํด ๋ชจ๋‹์— ๋„์ „ํ•˜๋ฉฐ ๋‹น์‹ ๋งŒ์˜ ์˜๋ฏธ์žˆ๋Š” ์•„์นจ์„ ๋งŒ๋“ค์–ด ๋‚˜๊ฐ€๋ณด์„ธ์š”.

์ผ์ฐ ์ผ์–ด๋‚˜๋Š” ์Šต๊ด€์œผ๋กœ ํ•˜๋ฃจ๋ฅผ ๊ธธ๊ฒŒ ๋ณด๋‚ด๋ฉด, ์„ฑ์žฅ์˜ ๋ฐœํŒ์„ ๋งˆ๋ จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฏธ๋‹๊ณผ ํ•จ๊ป˜ ์ฒด๊ณ„์ ์ธ ๊ณ„ํš์„ ์„ธ์šฐ๊ณ  ์ด๋ฅผ ๊ทœ์น™์ ์œผ๋กœ ์‹ค์ฒœํ•˜๋ฉด์„œ ์„ฑ์ทจ๊ฐ์„ ์–ป์–ด๋ณด์„ธ์š”.

์„ฑ์žฅ์ง€ํ–ฅ์ ์ธ ๊ทธ๋ฃน์›๊ณผ ๋ชฉํ‘œ๋ฅผ ๊ณต์œ ํ•œ๋‹ค๋ฉด ์šฐ๋ฆฌ๋Š” ํ•จ๊ป˜, ๋” ๋ฉ€๋ฆฌ ๊ฐˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.



๐Ÿ’ซ Development Environment

Android_Studio Kotlin


๐Ÿ’ซ Work Flow

๐Ÿ’ซ Dependencies

Name Gradle
kotlin org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version
Android KTX implementation 'androidx.core:core-ktx:1.3.2
Design androidx.appcompat:appcompat:1.2.0
com.google.android.material:material:1.2.1
androidx.constraintlayout:constraintlayout:2.0.4
androidx.legacy:legacy-support-v4:1.0.0
viewModel init support androidx.activity:activity-ktx:1.1.0
androidx.fragment:fragment-ktx:1.2.5
LiveData and ViewModel (Arch components) androidx.lifecycle:lifecycle-livedata-ktx:2.2.0
androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0
retrofit com.squareup.retrofit2:retrofit:2.9.0
com.squareup.retrofit2:converter-gson:2.9.0
com.squareup.okhttp3:logging-interceptor:4.9.0
Gson com.google.code.gson:gson:2.8.6
CameraX core library using camera2 implementation androidx.camera:camera-core:$camerax_version
androidx.camera:camera-camera2:$camerax_version
CameraX Lifecycle Library androidx.camera:camera-lifecycle:$camerax_version
CameraX View class androidx.camera:camera-view:1.0.0-alpha20
Test junit:junit:4.13.1
androidx.test.ext:junit:1.1.2
androidx.test.espresso:espresso-core:3.3.0
image load com.github.bumptech.glide:glide:4.11.0
com.github.bumptech.glide:compiler:4.11.0
splash lottie com.airbnb.android:lottie:3.5.0

  • Material Design Component ๊ตฌ๊ธ€ Material Design์„ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌํ˜„์ฒด ์ œ๊ณต ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, UI์— ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  • Glide url ํ˜•์‹ ์ด๋ฏธ์ง€๋ฅผ ImageView์— ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  • AAC Lifecycle Live Data, Lifecycle, ViewModel ๊ณผ ๊ฐ™์€ ์ƒ๋ช…์ฃผ๊ธฐ์™€ ์—ฐ๋™๋œ ์ปดํฌ๋„ŒํŠธ๋“ค๊ณผ ํด๋ž˜์Šค ์ œ๊ณต

  • Coroutine ๋น„๋™๊ธฐ ์ž‘์—…์„ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ํƒ€์ž„์Šคํ…œํ”„ ์นด๋ฉ”๋ผ์—์„œ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์‹œ๊ฐ„์˜ ๋ณ€๊ฒฝ์„ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ.

  • Activity, Fragment ktx ViewModel์„ onCreate์—์„œ ์ดˆ๊ธฐํ™” ํ•˜๋Š”๊ฒฝ์šฐ ์—ฌ๋Ÿฌ๋ฒˆ ์ƒ์„ฑํ˜น์€ ์ƒํƒœ ์†์‹ค์„ ๋ง‰๊ธฐ ์œ„ํ•ด lazy delegate ์ž‘์—…์œผ๋กœ viewModel ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์„œ ์‚ฌ์šฉ.

  • Retrofit ์•ˆ๋“œ๋กœ์ด๋“œ REST API ํ†ต์‹  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ. AsyncTask ์—†์ด Background Thread์—์„œ ์‹คํ–‰๋˜๋ฉฐ callback์„ ํ†ตํ•ด Main Thread์—์„œ์˜ UI ์—…๋ฐ์ดํŠธ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œ๊ณต. ์„œ๋ฒ„ ํ†ต์‹ ์„ ์œ„ํ•ด ์‚ฌ์šฉ.

  • CameraX CameraX๋Š” ์นด๋ฉ”๋ผ ์•ฑ ๊ฐœ๋ฐœ์„ ๋” ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์–ด์ง„ Jetpack ์ง€์› ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ํƒ€์ž„์Šคํ…œํ”„ ์นด๋ฉ”๋ผ ๋ถ€๋ถ„์—์„œ ์‚ฌ์šฉ.

  • Lottie Splash ๋ฐ Login ๋ฐฐ๊ฒฝ์œผ๋กœ ์‚ฌ์šฉ


๐Ÿ’ซ Team Role

  • ๐ŸŒฑ Git

    • feat : ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•˜๊ธฐ
    • fix : ๋ฒ„๊ทธ ์ˆ˜์ •ํ•˜๋Š” ๊ฒฝ์šฐ
    • style : ์ƒ‰์ƒ ๋ณ€๊ฒฝ, ํฐํŠธ ๋ณ€๊ฒฝ ๋“ฑ์ด ์žˆ๋Š” ๊ฒฝ์šฐ
    • refactor : ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง ํ•˜๋Š” ๊ฒฝ์šฐ
    • upload : ํŒŒ์ผ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ
    • docs : ๋ฌธ์„œ ์ˆ˜์ •ํ•˜๋Š” ๊ฒฝ์šฐ

    Git issue template

    image

    Git PR template

    image

    Code review

    image

  • ๐ŸŒฑGithub Action & Slack bot

    Slack Bot

    • Github Action ์ž๋™ ๋นŒ๋“œ๊ฐ€ ์„ฑ๊ณตํ•œ ๊ฒฝ์šฐ.
    image
    • Github Action ์ž๋™๋นŒ๋“œ๋Š” ์„ฑ๊ณตํ–ˆ์œผ๋‚˜ ํŒŒ์ผ ์—…๋กœ๋“œ ๊ณผ์ •์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด ๊ฒฝ์šฐ
    image
    • Github Action ์ž๋™ ๋นŒ๋“œ๋„ ์‹คํŒจํ•œ ๊ฒฝ์šฐ
    image

    Github Action

    name: MeaningAndroid Builder
    on:
     push:
     branches: [ develop ]
    defaults:
     run:
     shell: bash
     working-directory: .
    jobs:
     build:
     name: Generate APK
     runs-on: ubuntu-latest
     steps:
     - name: Checkout
     uses: actions/checkout@v2
     
     - name: Gradle cache
     uses: actions/cache@v2
     with:
     path: |
     ~/.gradle/caches
     ~/.gradle/wrapper
     key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
     restore-keys: |
     ${{ runner.os }}-gradle-
     - name: set up JDK 1.8
     uses: actions/setup-java@v1
     with:
     java-version: 1.8
     - name: Change gradlew permissions
     run: chmod +x ./gradlew
     - name: Build with Gradle
     run: ./gradlew assembleDebug
     - name: On Failed, Notify in Slack
     if: ${{ failure() }}
     uses: rtCamp/action-slack-notify@v2
     env:
     SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
     SLACK_TITLE: 'nneaning/Anroid Debug build FailโŒ'
     SLACK_COLOR: '#FF5733'
     MSG_MINIMAL: true
     SLACK_MESSAGE: '์—๋Ÿฌ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”'
     - name: Upload APK
     if: ${{ success() }}
     uses: actions/upload-artifact@v2
     with:
     name: apk
     path: app/build/outputs/apk/debug/
     upload:
     needs: [build]
     name: upload to Slack
     runs-on: ubuntu-latest
     steps: 
     - name: download Article
     uses: actions/download-artifact@v2
     with:
     name: apk
     
     - name: Update Release apk name
     if: ${{ success() }}
     run: |
     mv app-debug.apk ๋ฏธ๋‹-Debug.apk
     echo 'apk=๋ฏธ๋‹-Debug.apk' >> $GITHUB_ENV
     
     - name: Upload APK at Slack
     if: ${{ success() }}
     run: |
     curl -X POST \
     -F file=@$apk \
     -F channels=${{secrets.SLACK_CHANNEL_ID}} \
     -H "Authorization: Bearer ${{secrets.SLACK_BOT_TOKEN}}" \
     https://slack.com/api/files.upload
     
     - name: On Success
     if: ${{ success() }}
     uses: rtCamp/action-slack-notify@v2
     env:
     SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
     SLACK_TITLE: 'nneaning/Anroid Debug build Successโœ…'
     SLACK_COLOR: '#5BFF33'
     MSG_MINIMAL: true
     SLACK_MESSAGE: 'apk ์ƒ์„ฑ ์™„๋ฃŒ! '
     - name: On Success but Fail
     if: ${{ failure() }}
     uses: rtCamp/action-slack-notify@v2
     env:
     SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
     SLACK_TITLE: 'nneaning/Anroid Debug build Successโœ…'
     SLACK_COLOR: '#FFF233'
     MSG_MINIMAL: true
     SLACK_MESSAGE: '๋นŒ๋“œ๋Š” ์™„๋ฃŒ ๋˜์—ˆ์œผ๋‚˜ apk์—…๋กœ๋“œ ์—๋Ÿฌ'
    


๐Ÿ’ซ meaning Tech Stack

MVC์™€ MVVM์˜ ํ˜ผํ•ฉ ์•„ํ‚คํ…์ฒ˜๋กœ ๊ฐœ๋ฐœ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  • ** AAC DataBinding, ViewModel **
private lateinit var binding: ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
 binding.viewModel = loginViewModel
 binding.lifecycleOwner = this
 initView()
}
private val loginViewModel: LoginViewModel by viewModels {
 object : ViewModelProvider.Factory {
 override fun <T : ViewModel?> create(modelClass: Class<T>): T =
 LoginViewModel(MeaningStorage.getInstance(this@LoginActivity)) as T
 }
}
  • Coroutine - ๋น„๋™๊ธฐ ์ž‘์—…

    fun runCurrentTimer() = viewModelScope.launch() {
     while (isEnableTimer) {
     _currentTime.value = SimpleDateFormat(TIME_FORMAT, Locale.KOREA)
     .format(System.currentTimeMillis())
     _currentDate.value = SimpleDateFormat(DATE_FORMAT, Locale.KOREA)
     .format(System.currentTimeMillis())
     delay(10000)
     }
    }
  • CameraX

    private fun startCamera() {
     val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
     cameraProviderFuture.addListener(
     cameraProvideFutureListener(cameraProviderFuture),
     getMainExecutor()
     )
    }
    private fun cameraProvideFutureListener(
     cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
    ) = Runnable {
     val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
     val preview = getCameraPreview()
     val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
     setImageCapture()
     try {
     cameraProvider.unbindAll()
     cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
     } catch (failBindException: Exception) {
     Log.e(TAG, "Use case binding failed", failBindException)
     }
    }
    private fun getCameraPreview(): Preview = Preview.Builder()
     .build()
     .also {
     it.setSurfaceProvider(binding.cameraViewFinder.surfaceProvider)
     }
    private fun setImageCapture() {
     imageCapture = ImageCapture.Builder()
     .build()
    }
    private fun getMainExecutor() = ContextCompat.getMainExecutor(requireContext())
    private fun takePhoto() {
     val imageCapture = imageCapture ?: return
     imageCapture.takePicture(
     getMainExecutor(),
     getImageCapturedCallback()
     )
    }
    private fun getImageCapturedCallback(): TimeStampCameraCallback =
     TimeStampCameraCallback().apply {
     setOnCaptureSuccessListener { imageCaptureEvent(it) }
     }
    private fun imageCaptureEvent(image: Bitmap) {
     cameraViewModel.image = image
     cameraViewModel.isEnableTimer = false
     (requireActivity() as TimeStampCameraActivity).changeFragment(
     CameraResultFragment(),
     null
     )
    }

๐Ÿ’ซ Packaging

๐ŸŒ…meaning.morning
 โ”ฃ ๐Ÿ“‚data
 โ”ฃ ๐Ÿ“‚network
 โ”ƒ โ”ฃ ๐Ÿ“‚request
 โ”ƒ โ”ฃ ๐Ÿ“‚response
 โ”ฃ ๐Ÿ“‚presentation
 โ”ƒ โ”ฃ ๐Ÿ“‚adapter
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“‚feed
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“‚group
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“‚home
 โ”ƒ โ”ฃ ๐Ÿ“‚camera
 โ”ƒ โ”ฃ ๐Ÿ“‚group
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“‚feed
 โ”ƒ โ”ฃ ๐Ÿ“‚home
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“‚card
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“‚feed
 โ”ƒ โ”ฃ ๐Ÿ“‚login
 โ”ƒ โ”— ๐Ÿ“‚onboarding
 โ”—๐Ÿ“‚utils

๐Ÿ’ซ Main Feature Codes & Methods

โœ” sharedPreference ์‹ฑ๊ธ€ํ„ด ์ž‘์„ฑ

object๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ž‘์„ฑํ•˜๊ธฐ.

Multi-Thread Safeํ•˜๋„๋ก ๋งŒ๋“ค๊ธฐ.

SharedPreference์ง€๋งŒ ๋ณด๋‹ค ์ง๊ด€์ ์ธ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜๊ธฐ.

class MeaningStorage(context: Context) {
	/* ... */
 companion object {
 private var instance: MeaningStorage? = null
 
 fun getInstance(context: Context) = instance ?: synchronized(this) {
 instance ?: MeaningStorage(context).apply {
 instance = this
 }
 }
 }
}

โœ” TimeStamp Camera

image image image

  • Camera Permission
 private fun initTimeStampCamera() {
 if (allPermissionGranted()) {
 loadCameraView()
 return
 }
 requestPermission()
 }
 private fun allPermissionGranted() = REQUIRED_PERMISSIONS.all {
 ContextCompat.checkSelfPermission(
 applicationContext,
 it
 ) == PackageManager.PERMISSION_GRANTED
 }
 private fun requestPermission() {
 ActivityCompat.requestPermissions(
 this,
 REQUIRED_PERMISSIONS,
 CameraViewModel.REQUEST_CODE_PERMISSIONS
 )
 }
 override fun onRequestPermissionsResult(
 requestCode: Int,
 permissions: Array<out String>,
 grantResults: IntArray
 ) {
 if (requestCode == CameraViewModel.REQUEST_CODE_PERMISSIONS) {
 permissionResponseEvent()
 }
 }
 private fun permissionResponseEvent() {
 if (allPermissionGranted()) {
 loadCameraView()
 return
 }
 permissionDeniedEvent()
 }
 private fun permissionDeniedEvent() {
 showToast("๊ถŒํ•œ์„ ์Šน์ธํ•˜์ง€ ์•Š์œผ๋ฉด ๋‹น์‹ ์˜ ๋ฏธ๋ผํด ๋ชจ๋‹์„ ๊ธฐ๋กํ•  ์ˆ˜ ์—†์–ด์š”!")
 finish()
 }
 private fun loadCameraView() {
 changeFragment(CameraFragment())
 }
 private fun changeFragment(initFragment: Fragment) {
 val transaction = supportFragmentManager.beginTransaction()
 transaction.apply {
 replace(R.id.fragment_camera, initFragment)
 commit()
 }
 }
 private fun startCamera() {
 val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
 cameraProviderFuture.addListener(
 cameraProvideFutureListener(cameraProviderFuture),
 getMainExecutor()
 )
 }
 private fun cameraProvideFutureListener(
 cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
 ) = Runnable {
 val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
 val preview = getCameraPreview()
 val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
 setImageCapture()
 try {
 cameraProvider.unbindAll()
 cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
 } catch (failBindException: Exception) {
 Log.e(TAG, "Use case binding failed", failBindException)
 }
 }
 private fun getCameraPreview(): Preview = Preview.Builder()
 .build()
 .also {
 it.setSurfaceProvider(binding.cameraViewFinder.surfaceProvider)
 }
 private fun setImageCapture() {
 imageCapture = ImageCapture.Builder()
 .build()
 }
 private fun getMainExecutor() = ContextCompat.getMainExecutor(requireContext())
 private fun takePhoto() {
 val imageCapture = imageCapture ?: return
 imageCapture.takePicture(
 getMainExecutor(),
 getImageCapturedCallback()
 )
 }
 private fun getImageCapturedCallback(): TimeStampCameraCallback =
 TimeStampCameraCallback().apply {
 setOnCaptureSuccessListener { imageCaptureEvent(it) }
 }
 private fun imageCaptureEvent(image: Bitmap) {
 cameraViewModel.image = image
 cameraViewModel.isEnableTimer = false
 /* ... */
 }

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋งŒ๋“ค์–ด์ง„ ์นด๋ฉ”๋ผ๋ฅผ ๋ทฐ๋ชจ๋ธ์— ์ €์žฅํ•˜์—ฌ ๊ฒฐ๊ณผ ์ฐฝ์œผ๋กœ ๋„˜๊ธฐ๊ณ  ๊ฒฐ๊ณผ์ฐฝ์—์„œ๋Š” ํ•ด๋‹น ๋ทฐ๋ฅผ Bitmap์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ €์žฅํ•œ๋‹ค.

class TimeStampImageCreator(private val context: Context) {
 /* ... */
 fun saveOf(viewGroup: ConstraintLayout) {
 val width = viewGroup.width
 val height = viewGroup.height
 removeViewEvent(viewGroup)
 val bitmapBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
 val canvas = Canvas(bitmapBuffer)
 viewGroup.draw(canvas)
 saveImage(bitmapBuffer)
 }
 
 private fun removeViewEvent(viewGroup: ConstraintLayout) {
 viewGroup.apply {
 clearFocus()
 isPressed = false
 invalidate()
 }
 }
 private fun getOutputDirectory(): File {
 val mediaDir = context.externalMediaDirs.firstOrNull()?.let {
 File(it, context.resources.getString(R.string.app_name)).apply {
 mkdirs()
 }
 }
 return if (mediaDir != null && mediaDir.exists()) mediaDir else context.filesDir
 }
 private fun saveImage(bitmapBuffer: Bitmap) {
 photo = getPhotoFile()
 try {
 val outputStream = FileOutputStream(photo)
 bitmapBuffer.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
 outputStream.close()
 galleryAddPicture()
 } catch (errorMessage: FileNotFoundException) {
 errorMessage.stackTrace
 } catch (errorMessage: IOException) {
 errorMessage.stackTrace
 } finally {
 bitmapBuffer.recycle()
 }
 }
 private fun getPhotoFile() = File(
 getOutputDirectory(),
 SimpleDateFormat(
 "yyyy-MM-dd HH:mm:ss",
 Locale.KOREA
 ).format(System.currentTimeMillis()) + ".jpeg"
 )
}

๋งŒ๋“  ํŒŒ์ผ์€ ๊ธ€์“ฐ๊ธฐ ํ™”๋ฉด์œผ๋กœ ๋„˜๊ธด๋‹ค.

โœ” MyFeedPictureAdapter

์•„์ดํ…œ ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๋ถ„๋ฆฌ.

class MyFeedPictureAdapter : RecyclerView.Adapter<MyFeedPictureAdapter.MyFeedPictureViewHolder>() {
 var data = mutableListOf<MyFeedPictureData>()
 private lateinit var itemClickListener : ItemClickListener
 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyFeedPictureViewHolder {
 val binding = FeedItemListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
 return MyFeedPictureViewHolder(binding)
 }
 override fun getItemCount(): Int {
 return data.size
 }
 override fun onBindViewHolder(holder: MyFeedPictureViewHolder, position: Int) {
 holder.onBind(data[position])
 holder.itemView.setOnClickListener {
 itemClickListener.onClick(it,position)
 }
 }
 fun submitData(list : List<MyFeedPictureData>){
 data.addAll(list)
 notifyDataSetChanged()
 }
 class MyFeedPictureViewHolder(val binding: FeedItemListBinding) : RecyclerView.ViewHolder(binding.root) {
 fun onBind(data: MyFeedPictureData) {
 binding.feedItemList = data
 }
 }
 interface ItemClickListener{
 fun onClick(view : View, position: Int)
 }
 fun setItemClickListener(itemClickListener: ItemClickListener){
 this.itemClickListener = itemClickListener
 }
}

๐Ÿ’ซ Layout ๊ด€๋ จ

  • Layout ์‚ฌ์šฉ

    ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์œผ๋กœ ์‚ฌ์šฉ์œผ๋กœ ๋ชจ๋“  ๋ทฐ์˜ ์ตœ์ƒ์œ„๊ฐ€ Layout ํƒœ๊ทธ ์•„๋ž˜ ์žˆ์Œ

  • coordinatorlayout, NestedScrollView ์‚ฌ์šฉ ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋ฐœ์ƒ์‹œ behavior๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ทฐ์˜ ๋ณ€๊ฒฝ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉ.

    - fragment_group.xml
    - activity_my_feed_main.xml
    - activity_group_settting.xml
    
  • ๋‹จ์ˆœ ๋„ํ˜• ์—์…‹ - ์บ˜๋ฆฐ๋” ๋ทฐ ์•„๋ž˜ ์›

image

radius ํ™•์ธ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜์—ฌ ๋””์ž์ด๋„ˆ์—๊ฒŒ ์š”์ฒญํ›„ ์—์…‹์œผ๋กœ ๋ฐ›๊ธฐ๋กœํ•จ

- HomeFragment
  • ์ ˆ๋Œ€ ํฌ๊ธฐ ์ง€์ •

    - feed_item_list.xml
    - dialog_group_recycler.xml
    - dialog_group_detail.xml
    - fragment_home.xml
    
    • feed_item_list : ํ”ผ๋“œ ์•„์ดํ…œ์œผ๋กœ ๋“ค์–ด์˜ฌ ์‚ฌ์ง„ ํฌ๊ธฐ๊ฐ€ ๊ธฐ๊ธฐ๋ณ„๋กœ ๋‹ค๋ฅผ ๊ฒฝ์šฐ๋ฅผ ๋”ฐ๋ผ ์ ˆ๋Œ€ ํฌ๊ธฐ ์ง€์ •
    • dialog : ํ™”๋ฉด ๋น„์œจ์— ๋”ฐ๋ผ๊ฐ€ ์•„๋‹Œ ๋‹ค์ด์–ผ๋กœ๊ทธ ์ฐฝ์˜ ํฌ๊ธฐ ๊ณ ์ •์„ ์œ„ํ•ด์„œ ์‚ฌ์šฉ
    • fragement_home.xml : > ๋ชจ์–‘ ์—์…‹ ํฌ๊ธฐ๊ฐ€ ๋„ˆ๋ฌด ์ž‘๋‹ค๋Š” ์š”์ฒญ์— ์ ˆ๋Œ€ํฌ๊ธฐ๋กœ ์•ฝ๊ฐ„ ํฌ๊ธฐ ์ฆ๊ฐ€ ์ง€์ •.

About

๐ŸŽดํ•œ ๋—์— 5์–ต์„ ์•ˆ๋“œ๋กœ๋ฏธ๋‹? ์ซ„๋ฆฌ๋ฉด ์ฃผ๋ฌด์‹œ๋˜์ง€...๐Ÿ’ซ

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

Languages

AltStyle ใซใ‚ˆใฃใฆๅค‰ๆ›ใ•ใ‚ŒใŸใƒšใƒผใ‚ธ (->ใ‚ชใƒชใ‚ธใƒŠใƒซ) /