728x90
반응형

생각보다 간단했당 ㅎ 문제는 모델 적용한 그 이후인 것 같다

 

1. RecyclerView에서 Click event

2. PostDTO를 parcelable하게

3. RecyclerView click -> DetailActivity로 넘어갈 때 postDTO 넘김

4. DetailActivity에서 각 layer에 data binding

 

[참고]

https://yunaaaas.tistory.com/57

 

[Android/Kotlin] RecyclerView 클릭 이벤트 적용하기

RecyclerView 첫번째 시리즈에 이어 두번째 시리즈인 클릭 리스너를 추가하여 리사이클러뷰 아이템에 각각 클릭 이벤트를 적용하는 방법에 대해 적어보려고 합니다 : ) RecyclerView 만드는 방법에 대

yunaaaas.tistory.com

https://colinch4.github.io/2020-04-01/startactivity/

 

[Kotlin] startactivity, putextra

개발 강좌 블로그

colinch4.github.io

 

반응형
728x90
반응형

AndroidManifest

AndroidMainfest.xml 파일에서 CAMERA Permission 추가한다.

    <uses-permission android:name="android.permission.CAMERA" />

 

 

 

CAMERA PERMISSSION Function

permission 상태를 확인

true면 넘어가고, false면 requestPermission() 함수를 불러온다.

    // 카메라 권한 체크
    private fun checkPermission(): Boolean {
        return (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) ==
                PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(
            this,
            android.Manifest.permission.READ_EXTERNAL_STORAGE
        ) == PackageManager.PERMISSION_GRANTED)
    }

	// 카메라 권한 요청
    private fun requestPermission() {
        ActivityCompat.requestPermissions(
            this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA),
            REQUEST_PERMISSION
        )
    }

    // 권한요청 결과
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Log.d(TAG, "Permission success")
        } else {
            Log.d(TAG, "Fail")
        }
    }

 

 

 

Album에서 Image 가져오기

    override fun onClick(v: View?) {
        when(v?.id){
            R.id.img_content -> {
                // 앨범
                var photoPickIntent = Intent(Intent.ACTION_PICK)
                photoPickIntent.type = "image/*"
                startActivityForResult(photoPickIntent, PICK_IMAGE_FROM_ALBUM)
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (requestCode == PICK_IMAGE_FROM_ALBUM) {
            if (resultCode == Activity.RESULT_OK) {
                photoUri = data?.data
                img_content.setImageURI(photoUri)
            } else {
                finish()
            }
        }
    }
반응형
728x90
반응형

Fragment_found_lost.xml

fragment_found_lost.xml에 FloatingActionButton을 추가한다.

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:src="@drawable/ic_baseline_create"
        android:backgroundTint="@color/purple_500"
        android:contentDescription="write fab"
        app:tint="@color/white"
        app:borderWidth="0dp"
        android:layout_margin="16dp" />

 

 

 

FoundLostFragment.kt

FoundLostFragment.kt의 onViewCreated에 fab를 click하면 UploadActivity를 실행하는 코드를 추가한다.

        fab.setOnClickListener { view ->
            startActivity(Intent(context, UploadActivity::class.java))
        }

 

 

 

 

추가할 것

Fragment에 따른 UploadActivity의 Radio Button

FoundFragment에서 fab를 click하면 UploadActivity의 Radio Button에서 Found 항목이 default로 check, LostFragment에서 fab를 click하면 Lost 항목이 check 되도록 할 것

반응형
728x90
반응형

activity_main.xml


menu.xml

res > menu에 bottomnavigation_menu.xml을 추가한다.

bottomnavigation_menu.xml은 BottomNavigationView에 나타낼 menu 목록을 나타낸다.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/menu_home"
        app:showAsAction="ifRoom"
        android:enabled="true"
        android:icon="@drawable/ic_home"
        android:title="@string/menu_home" />
    <item
        android:id="@+id/menu_found_lost"
        app:showAsAction="ifRoom"
        android:icon="@drawable/ic_pet"
        android:title="@string/menu_foun_lost" />
    <item
        android:id="@+id/menu_chatting"
        app:showAsAction="ifRoom"
        android:enabled="true"
        android:icon="@drawable/ic_chat"
        android:title="@string/menu_chat" />
    <item
        android:id="@+id/menu_alarm"
        app:showAsAction="ifRoom"
        android:enabled="true"
        android:icon="@drawable/ic_alarm"
        android:title="@string/menu_alarm" />
    <item
        android:id="@+id/menu_setting"
        app:showAsAction="ifRoom"
        android:enabled="true"
        android:icon="@drawable/ic_user"
        android:title="@string/menu_setting" />
</menu>

 

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/frame_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:menu="@menu/bottomnavigation_menu"
        app:layout_constraintTop_toBottomOf="@+id/frame_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_alignParentBottom="true" />

</RelativeLayout>

 

 

 

Fragment


java > com.example 폴더 아래에 navigation 패키지를 만든다. navigation 패키지 내부에 New > Fragment > Blank Fragment를 선택하여 HomeFragment.kt와 FoundLostFragment.kt를 생성한다. Fragment.kt를 생성하면 자동으로 res > layout에 fragment.xml이 생성된다.

 

 

fragment_home.xml(fragment_found_lost.xml)

fragment_home.xml은 우선 화면 중앙에 Home이라는 Text만 띄우도록 작성한다. fragment_found_lost.xml도 마찬가지로 FoundLost Text만 띄우도록 작성한다.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".navigation.HomeFragment">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="Home"/>
    
</FrameLayout>

 

 

HomeFragment.kt(FoundLostFragment.kt)

HomeFragment.kt는 onCreateView를 제외한 나머지 코드는 삭제하도록 한다.

package com.example.founddog.navigation

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.founddog.R

class HomeFragment : Fragment(R.layout.fragment_home) {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_home, container, false)
    }
}

 

 

 

MainActivity.kt


package com.example.founddog

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import androidx.navigation.ui.NavigationUI
import com.example.founddog.databinding.FragmentFoundLostBinding
import com.example.founddog.navigation.FoundLostFragment
import com.example.founddog.navigation.HomeFragment
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.navigation.NavigationBarItemView
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    companion object{
        const val TAG: String = "MainActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        supportFragmentManager.beginTransaction().add(frame_content.id, HomeFragment()).commit()
        bottom_navigation.setOnItemSelectedListener {
            Log.d(TAG, "item_id : "+it.itemId)
            replaceFragment(
                when(it.itemId){
                    R.id.menu_home -> HomeFragment()
                    R.id.menu_found_lost -> FoundLostFragment()
                    else -> HomeFragment()
                }
            )
            true
        }
    }

    private fun replaceFragment(fragment: Fragment){
        Log.d(TAG, "fragment "+fragment)
        supportFragmentManager.beginTransaction().replace(frame_content.id, fragment).commit()
    }

}

 

setonnavigationitemselectedlistener deprecated ->  setOnItemSelectedListener

반응형
728x90
반응형

Splash Screen


splash screen는 별 거는 없다. Splash Screen을 구성하는 xml 파일 작성하고 몇 초 딜레이 후 LoginActivity로 이동하도록 한다.

 

 

activity_splash.xml

우선 Splash Screen을 구성하는 activity_splash.xml을 작성한다.

간단하게 보라색 화면에 흰 text로 app name을 화면 중앙에 띄어줬다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SplashActivity"
    android:background="@color/purple_500">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="@string/app_name"
        android:textColor="@color/white"
        android:textSize="50dp"/>

</RelativeLayout>

 

 

NoActionBar

ActionBar가 뜨면 화면이 예쁘지 않기 때문에 ActionBar을 없애주기로 한다.

res > values > themes 에서 themes.xml을 변경하도록 한다. styple을 아래처럼 NoActionBar로 변경해준다.

<style name="Theme.FoundDog" parent="Theme.MaterialComponents.DayNight.NoActionBar">

 

 

Status Bar

res > values > themes 의 themes.xml에서 Status Bar (상태바)도 변경 가능하다. Status Bar의 색상을 변경할 수도 있고 Status Bar를 투명하게 할 수도 있다. 아래 사진의 코드는 Status Bar의 색상을 정하는 부분이다.

temes.xml의 status bar

Status Bar를 투명하게 하면 화면이 Status Bar가 있는 부분까지 채워져서 우선Status Bar를 투명하게 하지 않았다.

Status Bar를 투명하게 하려면 themes.xml에 아래 코드를 추가하면 된다.

<item name="android:windowTranslucentStatus">true</item>

 

 

SplashActivity.kt

package com.example.founddog

import android.content.Intent
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.view.WindowInsets
import android.view.WindowManager

class SplashActivity : AppCompatActivity() {

    private val SPLASH_TIME: Long = 1500

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash)

        // Full Screen
        @Suppress("DEPRECATION")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            window.insetsController?.hide(WindowInsets.Type.statusBars())
        } else {
            window.setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN )
        }

        // LoginActivity 열기
        @Suppress("DEPRECATION")
        Handler().postDelayed(
            {
                startActivity(Intent(this@SplashActivity, LoginActivity::class.java))
                finish()
            },
            SPLASH_TIME
        )
    }
}

 

 

AndroidManifest.xml

앱을 실행하면 SplashActivity가 열리도록 AndroidManifest의 intent-filter 속성을 SplshActivity 밑으로 수정한다.

<activity android:name=".SplashActivity">
       <intent-filter>
             <action android:name="android.intent.action.MAIN" />

             <category android:name="android.intent.category.LAUNCHER" />
       </intent-filter>
</activity>

 

 

 

Login / Register


Firebase 프로젝트에 앱 추가하기

Firebase에서 프로젝트를 생성한 후 Firebase가 시키는대로만 하면 된다.

 

 

Firebase Authentication 시작하기

Firebase auth 공식문서를 참고한다.

만든 Firebase 프로젝트에서 Authentication > 이메일/비밀번호를 사용 설정하고 저장한다.

Firebase Authentication에서 이메일/비밀번호 사용설정

 

Android Studio에서 Firebase Auth를 사용하기 위해 아래 Library를 imiport한다.

implementation 'com.google.firebase:firebase-auth-ktx'

 

 

Login.xml

네 모서리가 둥근 Button을 만들기 위해 res > drawable에 button.xml을 추가한다. 아래는 button.xml 전체 코드이다.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners
        android:radius="@dimen/btn_radius"/>
</shape>

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".LoginActivity">

    <com.google.android.material.textfield.TextInputLayout
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/edt_email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/edt_email"/>
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
        app:passwordToggleEnabled="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/edt_pwd"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPassword"
            android:hint="@string/edt_pwd"/>
    </com.google.android.material.textfield.TextInputLayout>

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/button"
        android:text="@string/btn_login"/>

    <Button
        android:id="@+id/btn_register"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/button"
        android:text="@string/btn_register"/>

</LinearLayout>

 

TextInputLayout을 사용하기 위해 아래 Library를 imiport한다.

implementation 'com.google.android.material:material:1.2.1'

 

 

Register.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".LoginActivity">

    <ImageView
        android:id="@+id/img_clear"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_baseline_clear"/>

    <com.google.android.material.textfield.TextInputLayout
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/edt_email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/edt_email"/>
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
        app:passwordToggleEnabled="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/edt_pwd1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPassword"
            android:hint="@string/edt_pwd"/>
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
        app:passwordToggleEnabled="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/edt_pwd2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPassword"
            android:hint="@string/edt_pwd2"/>
    </com.google.android.material.textfield.TextInputLayout>

    <Button
        android:id="@+id/btn_submit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/button"
        android:text="@string/btn_submit"/>

</LinearLayout>

 

 

LoginActivity.kt

package com.example.founddog

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import kotlinx.android.synthetic.main.activity_login.*

class LoginActivity : AppCompatActivity(), View.OnClickListener {

    companion object{
        const val TAG: String = "LoginActivity"
    }

    lateinit var auth: FirebaseAuth

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)

        auth = Firebase.auth

        btn_login.setOnClickListener(this)
        btn_register.setOnClickListener(this)

    }

    override fun onClick(v: View?) {
        when(v?.id){
            R.id.btn_login -> {
                loginUser()

            }
            R.id.btn_register -> {
                startActivity(Intent(this@LoginActivity, RegisterActivity::class.java))
            }
        }
    }

    private fun loginUser() {
        val email = edt_email.text.toString().trim { it <= ' ' }
        val pwd = edt_pwd.text.toString().trim { it <= ' ' }

        if(validateLoginDetails(email, pwd)) {
            auth.signInWithEmailAndPassword(email, pwd)
                .addOnCompleteListener(this) { task ->
                    if (task.isSuccessful) {
                        // Sign in success, update UI with the signed-in user's information
                        Log.d(TAG, "signInWithEmail:success")

                        startActivity(Intent(this@LoginActivity, MainActivity::class.java))
                        finish()

                    } else {
                        // If sign in fails, display a message to the user.
                        Log.w(TAG, "signInWithEmail:failure", task.exception)
                        Toast.makeText(
                            baseContext, "Authentication failed.",
                            Toast.LENGTH_SHORT
                        ).show()

                    }
                }
        }
    }

    private fun validateLoginDetails(email : String, pwd : String): Boolean {
        return when {
            TextUtils.isEmpty(email) -> {
                Toast.makeText(this,"no email", Toast.LENGTH_SHORT).show()
                false
            }
            TextUtils.isEmpty(pwd) -> {
                Toast.makeText(this,"no email", Toast.LENGTH_SHORT).show()
                false
            }
            else -> {
                true
            }
        }
    }
}

 

 

RegisterActivity.kt

package com.example.founddog

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import android.widget.Toast
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import kotlinx.android.synthetic.main.activity_register.*

class RegisterActivity : AppCompatActivity(), View.OnClickListener {
    companion object{
        const val TAG: String = "RegisterActivity"
    }

    lateinit var auth: FirebaseAuth

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_register)

        auth = Firebase.auth

        btn_submit.setOnClickListener(this)
        img_clear.setOnClickListener(this)

    }

    override fun onClick(v: View?) {
        when(v?.id){
            R.id.btn_submit -> {
                register()
            }
            R.id.img_clear -> {
                finish()
            }
        }
    }

    fun register() {
        val email = edt_email.text.toString().trim { it <= ' ' }
        val pwd1 = edt_pwd1.text.toString().trim { it <= ' ' }
        val pwd2 = edt_pwd2.text.toString().trim { it <= ' ' }

        if(validateRegisterDetails(email, pwd1, pwd2)) {
            auth.createUserWithEmailAndPassword(email, pwd1)
                .addOnCompleteListener(this) { task ->
                    if (task.isSuccessful) {
                        // Sign in success, update UI with the signed-in user's information
                        Log.d(TAG, "createUserWithEmail:success")
                        finish()
                    } else {
                        // If sign in fails, display a message to the user.
                        Log.w(TAG, "createUserWithEmail:failure", task.exception)
                        Toast.makeText(
                            baseContext, "Authentication failed.",
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                }
        }
    }

    private fun validateRegisterDetails(email : String, pwd1 : String, pwd2 : String): Boolean {
        return when {
            TextUtils.isEmpty(email) -> {
                Toast.makeText(this,"no email", Toast.LENGTH_SHORT).show()
                false
            }
            TextUtils.isEmpty(pwd1) -> {
                Toast.makeText(this,"no email", Toast.LENGTH_SHORT).show()
                false
            }
            TextUtils.isEmpty(pwd2) -> {
                Toast.makeText(this,"no email", Toast.LENGTH_SHORT).show()
                false
            }
            pwd1 != pwd2 -> {
                Toast.makeText(this,"no email", Toast.LENGTH_SHORT).show()
                false
            }
            else -> {
                true
            }
        }
    }
}

 

 

 

추가할 것!


activity_login.xml / activity_register.xml

Layout 간의 margin 같은 건 하나도 지정 안 했다.

좀더 이쁘고 보기 편하기 바꿀 것

 

 

Activity의 finish()

activity의 life cycle 공부 후에 activity를 finish()할 타이밍을 정리해서 수정할 것

 

 

Log 출력

오류 같은 거 잘 출력하도록 할 것

 

 

TextInputLayout

email 양식을 틀리게 적거나 비밀번호 오류 날 때 TextInputLayout에서 테두리 color 변경 + 오류 출력하도록 코드 추가할 것

반응형

+ Recent posts