diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 042874c..2942fa8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -97,6 +97,7 @@
+
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
index cd468ac..1ab76d6 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
@@ -34,6 +34,7 @@ import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommun
import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllViewModel
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment.CreatorCommunityCommentListViewModel
+import kr.co.vividnext.sodalive.explorer.profile.creator_community.write.CreatorCommunityWriteViewModel
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAllViewModel
import kr.co.vividnext.sodalive.explorer.profile.fantalk.UserProfileFantalkAllViewModel
import kr.co.vividnext.sodalive.explorer.profile.follow.UserFollowerListViewModel
@@ -219,6 +220,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { RouletteSettingsViewModel(get()) }
viewModel { CreatorCommunityAllViewModel(get()) }
viewModel { CreatorCommunityCommentListViewModel(get()) }
+ viewModel { CreatorCommunityWriteViewModel(get()) }
}
private val repositoryModule = module {
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt
index 959b29e..3faddb8 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt
@@ -37,6 +37,7 @@ import kr.co.vividnext.sodalive.databinding.ItemCreatorCommunityBinding
import kr.co.vividnext.sodalive.explorer.profile.cheers.UserProfileCheersAdapter
import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity
+import kr.co.vividnext.sodalive.explorer.profile.creator_community.write.CreatorCommunityWriteActivity
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAdapter
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAllViewActivity
import kr.co.vividnext.sodalive.explorer.profile.fantalk.UserProfileFantalkAllViewActivity
@@ -474,7 +475,9 @@ class UserProfileActivity : BaseActivity(
}
private fun setupCreatorCommunityView() {
- binding.layoutCreatorCommunityPost.ivWrite.setOnClickListener { }
+ binding.layoutCreatorCommunityPost.ivWrite.setOnClickListener {
+ startActivity(Intent(applicationContext, CreatorCommunityWriteActivity::class.java))
+ }
binding.layoutCreatorCommunityPost.llAll.setOnClickListener {
startActivity(
Intent(applicationContext, CreatorCommunityAllActivity::class.java).apply {
@@ -706,6 +709,7 @@ class UserProfileActivity : BaseActivity(
binding.layoutCreatorCommunityPost.ivWrite.visibility = View.GONE
}
+ binding.layoutCreatorCommunityPost.llContainer.removeAllViews()
communityPostList.forEachIndexed { index, item ->
val layout = ItemCreatorCommunityBinding.inflate(
LayoutInflater.from(this@UserProfileActivity),
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt
index 4c0043a..40ea926 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt
@@ -5,15 +5,27 @@ import kr.co.vividnext.sodalive.audio_content.comment.ModifyCommentRequest
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.PostCommunityPostLikeRequest
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment.CreateCommunityPostCommentRequest
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
+import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.PUT
+import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query
interface CreatorCommunityApi {
+ @POST("/creator-community")
+ @Multipart
+ fun createCommunityPost(
+ @Part postImage: MultipartBody.Part?,
+ @Part("request") request: RequestBody,
+ @Header("Authorization") authHeader: String
+ ): Single>
+
@GET("/creator-community")
fun getCommunityPostList(
@Query("creatorId") creatorId: Long,
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt
index 27db775..e192173 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt
@@ -3,6 +3,8 @@ package kr.co.vividnext.sodalive.explorer.profile.creator_community
import kr.co.vividnext.sodalive.audio_content.comment.ModifyCommentRequest
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.PostCommunityPostLikeRequest
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.comment.CreateCommunityPostCommentRequest
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
import java.util.TimeZone
class CreatorCommunityRepository(private val api: CreatorCommunityApi) {
@@ -70,4 +72,14 @@ class CreatorCommunityRepository(private val api: CreatorCommunityApi) {
timezone = TimeZone.getDefault().id,
authHeader = token
)
+
+ fun createCommunityPost(
+ postImage: MultipartBody.Part?,
+ request: RequestBody,
+ token: String
+ ) = api.createCommunityPost(
+ postImage = postImage,
+ request = request,
+ authHeader = token
+ )
}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreateCommunityPostRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreateCommunityPostRequest.kt
new file mode 100644
index 0000000..0ad6361
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreateCommunityPostRequest.kt
@@ -0,0 +1,9 @@
+package kr.co.vividnext.sodalive.explorer.profile.creator_community.write
+
+import com.google.gson.annotations.SerializedName
+
+data class CreateCommunityPostRequest(
+ @SerializedName("content") val content: String,
+ @SerializedName("isAdult") val isAdult: Boolean,
+ @SerializedName("isCommentAvailable") val isCommentAvailable: Boolean
+)
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt
new file mode 100644
index 0000000..5748420
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt
@@ -0,0 +1,248 @@
+package kr.co.vividnext.sodalive.explorer.profile.creator_community.write
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.content.ContextCompat
+import coil.load
+import coil.transform.RoundedCornersTransformation
+import com.github.dhaval2404.imagepicker.ImagePicker
+import com.gun0912.tedpermission.PermissionListener
+import com.gun0912.tedpermission.normal.TedPermission
+import com.jakewharton.rxbinding4.widget.textChanges
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.schedulers.Schedulers
+import kr.co.vividnext.sodalive.R
+import kr.co.vividnext.sodalive.base.BaseActivity
+import kr.co.vividnext.sodalive.common.LoadingDialog
+import kr.co.vividnext.sodalive.common.RealPathUtil
+import kr.co.vividnext.sodalive.common.SharedPreferenceManager
+import kr.co.vividnext.sodalive.databinding.ActivityCreatorCommunityWriteBinding
+import kr.co.vividnext.sodalive.extensions.dpToPx
+import org.koin.android.ext.android.inject
+
+class CreatorCommunityWriteActivity : BaseActivity(
+ ActivityCreatorCommunityWriteBinding::inflate
+) {
+
+ private val viewModel: CreatorCommunityWriteViewModel by inject()
+
+ private lateinit var loadingDialog: LoadingDialog
+
+ private val imageResult = registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ val resultCode = result.resultCode
+ val data = result.data
+
+ if (resultCode == RESULT_OK) {
+ val fileUri = data?.data
+
+ if (fileUri != null) {
+ binding.ivContent.background = null
+ binding.ivContent.load(fileUri) {
+ crossfade(true)
+ placeholder(R.drawable.ic_place_holder)
+ transformations(RoundedCornersTransformation(8f.dpToPx()))
+ }
+ viewModel.imageUri = fileUri
+ } else {
+ Toast.makeText(
+ this,
+ "잘못된 파일입니다.\n다시 선택해 주세요.",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ } else if (resultCode == ImagePicker.RESULT_ERROR) {
+ Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ checkPermissions()
+
+ viewModel.getRealPathFromURI = {
+ RealPathUtil.getRealPath(applicationContext, it)
+ }
+
+ bindData()
+ }
+
+ override fun setupView() {
+ loadingDialog = LoadingDialog(this, layoutInflater)
+
+ binding.toolbar.tvBack.text = "게시글 등록"
+ binding.toolbar.tvBack.setOnClickListener { finish() }
+
+ binding.ivPhotoPicker.setOnClickListener {
+ ImagePicker.with(this)
+ .crop()
+ .galleryOnly()
+ .galleryMimeTypes( // Exclude gif images
+ mimeTypes = arrayOf(
+ "image/png",
+ "image/jpg",
+ "image/jpeg"
+ )
+ )
+ .createIntent { imageResult.launch(it) }
+ }
+
+ if (SharedPreferenceManager.isAuth) {
+ binding.llSetAdult.visibility = View.VISIBLE
+ } else {
+ binding.llSetAdult.visibility = View.GONE
+ }
+
+ binding.llCommentNo.setOnClickListener { viewModel.setAvailableComment(false) }
+ binding.llCommentYes.setOnClickListener { viewModel.setAvailableComment(true) }
+ binding.tvCancel.setOnClickListener { finish() }
+ binding.tvUpload.setOnClickListener {
+ viewModel.createCommunityPost { finish() }
+ }
+ }
+
+ private fun checkPermissions() {
+ val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ listOf(Manifest.permission.READ_MEDIA_AUDIO, Manifest.permission.READ_MEDIA_IMAGES)
+ } else {
+ listOf(Manifest.permission.READ_EXTERNAL_STORAGE)
+ }
+
+ TedPermission.create()
+ .setPermissionListener(object : PermissionListener {
+ override fun onPermissionGranted() {
+ }
+
+ override fun onPermissionDenied(deniedPermissions: MutableList?) {
+ finish()
+ }
+ })
+ .setDeniedMessage(R.string.read_storage_permission_denied_message)
+ .setPermissions(*permissions.toTypedArray())
+ .check()
+ }
+
+ @SuppressLint("SetTextI18n")
+ private fun bindData() {
+ compositeDisposable.add(
+ binding.etContent.textChanges().skip(1)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe {
+ binding.tvNumberOfCharacters.text = "${it.length}자"
+ viewModel.content = it.toString()
+ }
+ )
+
+ viewModel.toastLiveData.observe(this) {
+ it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
+ }
+
+ viewModel.isLoading.observe(this) {
+ if (it) {
+ loadingDialog.show(screenWidth)
+ } else {
+ loadingDialog.dismiss()
+ }
+ }
+
+ viewModel.isAvailableCommentLiveData.observe(this) {
+ if (it) {
+ binding.ivCommentYes.visibility = View.VISIBLE
+ binding.tvCommentYes.setTextColor(
+ ContextCompat.getColor(
+ applicationContext,
+ R.color.white
+ )
+ )
+ binding.llCommentYes.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
+
+ binding.ivCommentNo.visibility = View.GONE
+ binding.tvCommentNo.setTextColor(
+ ContextCompat.getColor(
+ applicationContext,
+ R.color.color_80d8ff
+ )
+ )
+ binding.llCommentNo.setBackgroundResource(
+ R.drawable.bg_round_corner_6_7_13181b
+ )
+ } else {
+ binding.ivCommentNo.visibility = View.VISIBLE
+ binding.tvCommentNo.setTextColor(
+ ContextCompat.getColor(
+ applicationContext,
+ R.color.white
+ )
+ )
+ binding.llCommentNo.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
+
+ binding.ivCommentYes.visibility = View.GONE
+ binding.tvCommentYes.setTextColor(
+ ContextCompat.getColor(
+ applicationContext,
+ R.color.color_80d8ff
+ )
+ )
+ binding.llCommentYes
+ .setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b)
+ }
+ }
+
+ if (SharedPreferenceManager.isAuth) {
+ binding.llAgeAll.setOnClickListener {
+ viewModel.setAdult(false)
+ }
+
+ binding.llAge19.setOnClickListener {
+ viewModel.setAdult(true)
+ }
+
+ viewModel.isAdultLiveData.observe(this) {
+ if (it) {
+ binding.ivAgeAll.visibility = View.GONE
+ binding.llAgeAll.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b)
+ binding.tvAgeAll.setTextColor(
+ ContextCompat.getColor(
+ applicationContext,
+ R.color.color_80d8ff
+ )
+ )
+
+ binding.ivAge19.visibility = View.VISIBLE
+ binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
+ binding.tvAge19.setTextColor(
+ ContextCompat.getColor(
+ applicationContext,
+ R.color.white
+ )
+ )
+ } else {
+ binding.ivAge19.visibility = View.GONE
+ binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b)
+ binding.tvAge19.setTextColor(
+ ContextCompat.getColor(
+ applicationContext,
+ R.color.color_80d8ff
+ )
+ )
+
+ binding.ivAgeAll.visibility = View.VISIBLE
+ binding.llAgeAll.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
+ binding.tvAgeAll.setTextColor(
+ ContextCompat.getColor(
+ applicationContext,
+ R.color.white
+ )
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteViewModel.kt
new file mode 100644
index 0000000..df1deaf
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteViewModel.kt
@@ -0,0 +1,136 @@
+package kr.co.vividnext.sodalive.explorer.profile.creator_community.write
+
+import android.net.Uri
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.google.gson.Gson
+import com.orhanobut.logger.Logger
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.schedulers.Schedulers
+import kr.co.vividnext.sodalive.base.BaseViewModel
+import kr.co.vividnext.sodalive.common.SharedPreferenceManager
+import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository
+import okhttp3.MediaType
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.toRequestBody
+import okio.BufferedSink
+import java.io.File
+
+class CreatorCommunityWriteViewModel(private val repository: CreatorCommunityRepository
+): BaseViewModel() {
+ private val _toastLiveData = MutableLiveData()
+ val toastLiveData: LiveData
+ get() = _toastLiveData
+
+ private var _isLoading = MutableLiveData(false)
+ val isLoading: LiveData
+ get() = _isLoading
+
+ private val _isAdultLiveData = MutableLiveData(false)
+ val isAdultLiveData: LiveData
+ get() = _isAdultLiveData
+
+ private val _isAvailableCommentLiveData = MutableLiveData(true)
+ val isAvailableCommentLiveData: LiveData
+ get() = _isAvailableCommentLiveData
+
+ lateinit var getRealPathFromURI: (Uri) -> String?
+
+ var content = ""
+ var imageUri: Uri? = null
+
+ fun setAdult(isAdult: Boolean) {
+ _isAdultLiveData.postValue(isAdult)
+ }
+
+ fun setAvailableComment(isAvailableComment: Boolean) {
+ _isAvailableCommentLiveData.postValue(isAvailableComment)
+ }
+
+ fun createCommunityPost(onSuccess: () -> Unit) {
+ if (!_isLoading.value!! && validateData()) {
+ _isLoading.postValue(true)
+
+ val request = CreateCommunityPostRequest(
+ content = content,
+ isAdult = _isAdultLiveData.value!!,
+ isCommentAvailable = _isAvailableCommentLiveData.value!!
+ )
+
+ val requestJson = Gson().toJson(request)
+
+ val postImage = if (imageUri != null) {
+ val file = File(getRealPathFromURI(imageUri!!))
+ MultipartBody.Part.createFormData(
+ "postImage",
+ file.name,
+ body = object : RequestBody() {
+ override fun contentType(): MediaType {
+ return "image/*".toMediaType()
+ }
+
+ override fun writeTo(sink: BufferedSink) {
+ file.inputStream().use { inputStream ->
+ val buffer = ByteArray(1024)
+ var bytesRead: Int
+ while (inputStream.read(buffer).also { bytesRead = it } != -1) {
+ sink.write(buffer, 0, bytesRead)
+ }
+ }
+ }
+
+ override fun contentLength(): Long {
+ return file.length()
+ }
+ }
+ )
+ } else {
+ null
+ }
+
+ compositeDisposable.add(
+ repository.createCommunityPost(
+ postImage = postImage,
+ request = requestJson.toRequestBody("text/plain".toMediaType()),
+ token = "Bearer ${SharedPreferenceManager.token}"
+ )
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ {
+ if (it.success && it.data != null) {
+ onSuccess()
+ } else {
+ if (it.message != null) {
+ _toastLiveData.postValue(it.message)
+ } else {
+ _toastLiveData.postValue(
+ "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
+ )
+ }
+ }
+ _isLoading.postValue(false)
+ },
+ {
+ _isLoading.postValue(false)
+ it.message?.let { message -> Logger.e(message) }
+ _toastLiveData.postValue(
+ "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
+ )
+ }
+ )
+ )
+ }
+ }
+
+ private fun validateData(): Boolean {
+ if (content.isBlank() || content.length < 5) {
+ _toastLiveData.postValue("내용을 5자 이상 입력해 주세요.")
+ return false
+ }
+
+ return true
+ }
+}
diff --git a/app/src/main/res/drawable-xxhdpi/ic_logo2.png b/app/src/main/res/drawable-xxhdpi/ic_logo2.png
new file mode 100644
index 0000000..c13cfe6
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_logo2.png differ
diff --git a/app/src/main/res/drawable/bg_round_corner_33_3_3bb9f1.xml b/app/src/main/res/drawable/bg_round_corner_33_3_3bb9f1.xml
new file mode 100644
index 0000000..6533c04
--- /dev/null
+++ b/app/src/main/res/drawable/bg_round_corner_33_3_3bb9f1.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_round_corner_6_7_13181b.xml b/app/src/main/res/drawable/bg_round_corner_6_7_13181b.xml
new file mode 100644
index 0000000..12bdf0f
--- /dev/null
+++ b/app/src/main/res/drawable/bg_round_corner_6_7_13181b.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_round_corner_6_7_13181b_3bb9f1.xml b/app/src/main/res/drawable/bg_round_corner_6_7_13181b_3bb9f1.xml
new file mode 100644
index 0000000..282e552
--- /dev/null
+++ b/app/src/main/res/drawable/bg_round_corner_6_7_13181b_3bb9f1.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_round_corner_6_7_3bb9f1.xml b/app/src/main/res/drawable/bg_round_corner_6_7_3bb9f1.xml
new file mode 100644
index 0000000..fb1d9cf
--- /dev/null
+++ b/app/src/main/res/drawable/bg_round_corner_6_7_3bb9f1.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_creator_community_write.xml b/app/src/main/res/layout/activity_creator_community_write.xml
new file mode 100644
index 0000000..9353ca4
--- /dev/null
+++ b/app/src/main/res/layout/activity_creator_community_write.xml
@@ -0,0 +1,346 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+