Skip to content

Commit

Permalink
Gsoc 2023 migrating result fragment to compose (#58)
Browse files Browse the repository at this point in the history
* Update for some issue In Deploy to Appetize

* removed Github Token from ci file

* Migrated fragment_result.xml to ResultScreen.kt in compose
  • Loading branch information
narendraanjana09 authored Aug 31, 2023
1 parent bab711c commit 5f2b185
Show file tree
Hide file tree
Showing 18 changed files with 807 additions and 538 deletions.
6 changes: 5 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ dependencies {
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test:runner:$testRunnerVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoCoreVersion"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleRuntime"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleExtensions"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
Expand Down Expand Up @@ -206,6 +207,9 @@ dependencies {
implementation "com.google.accompanist:accompanist-systemuicontroller:$rootProject.accompanist_version"
implementation "com.google.accompanist:accompanist-permissions:$rootProject.accompanist_version"

//compose screenshot
implementation "com.github.SmartToolFactory:Compose-Screenshot:$rootProject.composeScreenShot"

//Coil
implementation("io.coil-kt:coil-compose:$rootProject.coilVersion")

Expand All @@ -215,7 +219,7 @@ dependencies {
//date and time picker
implementation "com.maxkeppeler.sheets-compose-dialogs:core:$rootProject.date_time_picker"
implementation "com.maxkeppeler.sheets-compose-dialogs:calendar:$rootProject.date_time_picker"

//for live data to state
implementation("androidx.compose.runtime:runtime-livedata:$rootProject.composeLiveData")

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ class MainActivity : AppCompatActivity() {
window.addFlags(LayoutParams.FLAG_TRANSLUCENT_STATUS)
supportActionBar?.hide()
}
R.id.resultFragment -> {
supportActionBar?.hide()
}
R.id.settingsFragment
-> {
supportActionBar?.hide()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ constructor(
private var _electionState = mutableStateOf<ElectionModel?>(null)
val electionState: State<ElectionModel?> = _electionState

private var _resultState = mutableStateOf<WinnerDtoModel?>(null)
val resultState: State<WinnerDtoModel?> = _resultState

private var status: AppConstants.Status? = null

fun getElectionDetailsById(id: String) = viewModelScope.launch {
Expand Down Expand Up @@ -204,22 +207,23 @@ constructor(
fun getResult(
id: String?
) {
_getResultResponseStateFlow.value = ResponseUI.loading()
showLoading("Loading result...")
viewModelScope.launch {
try {
val response = electionDetailsUseCases.getResult(id)
hideLoading()
if (!response.isNullOrEmpty())
_getResultResponseStateFlow.value = ResponseUI.success(response[0])
_resultState.value = response[0]
else
_getResultResponseStateFlow.value = ResponseUI.success()
showMessage("Result Not Found!!")
} catch (e: ApiException) {
_getResultResponseStateFlow.value = ResponseUI.error(e.message)
showMessage(e.message!!)
} catch (e: SessionExpirationException) {
sessionExpiredListener.onSessionExpired()
} catch (e: NoInternetException) {
_getResultResponseStateFlow.value = ResponseUI.error(e.message)
showMessage(e.message!!)
} catch (e: Exception) {
_getResultResponseStateFlow.value = ResponseUI.error(e.message)
showMessage(e.message!!)
}
}
}
Expand All @@ -228,42 +232,44 @@ constructor(
context: Context,
bitmap: Bitmap
) {
showLoading("Sharing result...")
viewModelScope.launch {
val btm = withContext(Dispatchers.IO) {
FileUtils.saveBitmap(context, bitmap)
}
btm?.let {
_getShareResponseStateFlow.value = ResponseUI.success(it)
hideLoading()
_uiEventsFlow.emit(UiEvents.ShareFile(it))
} ?: run {
_getShareResponseStateFlow.value =
ResponseUI.error(context.getString(string.something_went_wrong_please_try_again_later))
showMessage(string.something_went_wrong_please_try_again_later)
}
}
}

fun createExcelFile(
context: Context,
winnerDto: WinnerDtoModel,
id: String
) {
if(resultState.value==null){
return
}
showLoading("Creating excel file...")
viewModelScope.launch {
val workbook = withContext(Dispatchers.Default) {
createWorkbook(context, winnerDto)
createWorkbook(context, resultState.value!!)
}
try {
val uri = withContext(Dispatchers.IO) {
writeToExcelFile(context, workbook, id)
}
_getShareResponseStateFlow.value = ResponseUI.success(uri)
hideLoading()
_uiEventsFlow.emit(UiEvents.ShareFile(uri))
} catch (e: FileNotFoundException) {
_getShareResponseStateFlow.value =
ResponseUI.error(context.getString(string.file_not_available))
showMessage(string.file_not_available)
} catch (e: IOException) {
_getShareResponseStateFlow.value =
ResponseUI.error(context.getString(string.cannot_write_file))
showMessage(string.cannot_write_file)
} catch (e: Exception) {
_getShareResponseStateFlow.value =
ResponseUI.error(context.getString(string.something_went_wrong_please_try_again_later))
showMessage(string.something_went_wrong_please_try_again_later)
}
}
}
Expand Down Expand Up @@ -381,5 +387,6 @@ constructor(
object ElectionDeleted:UiEvents()
object InviteVoters:UiEvents()
object ViewResults:UiEvents()
data class ShareFile(val uri: Uri):UiEvents()
}
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
package org.aossie.agoraandroid.ui.fragments.electionDetails

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.app.ActivityCompat
import androidx.core.view.drawToBitmap
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.google.android.material.tabs.TabLayoutMediator
import androidx.navigation.fragment.findNavController
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.aossie.agoraandroid.R
import org.aossie.agoraandroid.R.string
import org.aossie.agoraandroid.data.adapters.ResultViewpagerAdapter
import org.aossie.agoraandroid.databinding.FragmentResultBinding
import org.aossie.agoraandroid.domain.model.WinnerDtoModel
import org.aossie.agoraandroid.ui.fragments.BaseFragment
import org.aossie.agoraandroid.utilities.ResponseUI
import org.aossie.agoraandroid.utilities.hide
import org.aossie.agoraandroid.utilities.show
import org.aossie.agoraandroid.ui.fragments.electionDetails.ElectionDetailsViewModel.UiEvents.ShareFile
import org.aossie.agoraandroid.ui.screens.result.ResultScreen
import org.aossie.agoraandroid.ui.screens.result.ResultScreenEvent.OnBackClick
import org.aossie.agoraandroid.ui.screens.result.ResultScreenEvent.OnExportCSVClick
import org.aossie.agoraandroid.ui.screens.result.ResultScreenEvent.OnShareClick
import org.aossie.agoraandroid.ui.theme.AgoraTheme
import javax.inject.Inject

const val STORAGE_PERMISSION_REQUEST_CODE = 2

/**
* A simple [Fragment] subclass.
*/
Expand All @@ -42,156 +39,69 @@ constructor(
}

private var id: String? = null
private lateinit var winnerDto: WinnerDtoModel
private val chartType: Array<Int> = arrayOf(
string.bar_chart,
string.pie_chart
)
private lateinit var binding: FragmentResultBinding
private lateinit var composeView: ComposeView

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentResultBinding.inflate(inflater)
return binding.root
return ComposeView(requireContext()).also {
composeView = it
}
}

override fun onFragmentInitiated() {

binding.tvNoResult.hide()
electionDetailsViewModel.sessionExpiredListener = this

id = VotersFragmentArgs.fromBundle(
requireArguments()
).id
electionDetailsViewModel.getResult(id)
initListeners()
observeResult()
}
composeView.setContent {

private fun observeResult() {
lifecycleScope.launch {
electionDetailsViewModel.getResultResponseStateFlow.collect { responseUI ->
if (responseUI != null) {
when (responseUI.status) {
ResponseUI.Status.LOADING -> {
binding.resultView.visibility = View.GONE
binding.progressBar.show()
}
ResponseUI.Status.SUCCESS -> {
notify(responseUI.message ?: "")
binding.progressBar.hide()
responseUI.data?.let {
winnerDto = it
initResultView(it)
} ?: run {
binding.resultView.visibility = View.GONE
binding.tvNoResult.text = resources.getString(string.no_result)
binding.tvNoResult.show()
}
}
ResponseUI.Status.ERROR -> {
notify(responseUI.message)
binding.progressBar.hide()
binding.tvNoResult.text = resources.getString(R.string.fetch_result_failed)
binding.tvNoResult.show()
}
else -> {}
}
}
}
}
val progressErrorState by electionDetailsViewModel.progressAndErrorState
val winnerDtoModel by electionDetailsViewModel.resultState

lifecycleScope.launch {
electionDetailsViewModel.getShareResponseStateFlow.collect { responseUI ->
if (responseUI != null) {
when (responseUI.status) {
ResponseUI.Status.LOADING -> {
// Do Nothing
}
ResponseUI.Status.SUCCESS -> {
responseUI.data?.let { uri ->
LaunchedEffect(key1 = electionDetailsViewModel) {
electionDetailsViewModel.uiEventsFlow.collectLatest { event->
when(event) {
is ShareFile -> {
lifecycleScope.launch {
val shareIntent = Intent()
shareIntent.let {
it.action = Intent.ACTION_SEND
it.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
it.setDataAndType(uri, requireContext().contentResolver.getType(uri))
it.putExtra(Intent.EXTRA_STREAM, uri)
it.setDataAndType(event.uri, requireContext().contentResolver.getType(event.uri))
it.putExtra(Intent.EXTRA_STREAM, event.uri)
}
startActivity(Intent.createChooser(shareIntent, getString(string.share_result)))
}
}
ResponseUI.Status.ERROR -> {
notify(responseUI.message)
}
else -> {}
}
}
}
}
}

private fun initListeners() {
binding.shareResult.setOnClickListener {
createImageAndShare()
}
binding.exportResult.setOnClickListener {
if (ActivityCompat.checkSelfPermission(
requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
) {
createExcelFileAndShare()
} else {
askReadStoragePermission()
}
}
}

private fun askReadStoragePermission() {
requestPermissions(
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION_REQUEST_CODE
)
}

private fun createImageAndShare() {
binding.resultView.drawToBitmap()
.let {
electionDetailsViewModel.createImage(requireContext(), it)
}
}

private fun createExcelFileAndShare() {
if (!this::winnerDto.isInitialized) {
notify(getString(string.something_went_wrong_please_try_again_later))
return
}
id?.let {
electionDetailsViewModel.createExcelFile(requireContext(), winnerDto, it)
}
}

private fun initResultView(winner: WinnerDtoModel) {
binding.tvNoResult.hide()
binding.resultView.visibility = View.VISIBLE
binding.textViewWinnerName.text = winner.candidate?.name
binding.viewPager.adapter = ResultViewpagerAdapter(this, chartType, winnerDto)
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
tab.text = getString(chartType[position])
}.attach()
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
if (requestCode == STORAGE_PERMISSION_REQUEST_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
createExcelFileAndShare()
} else {
notify(getString(string.permission_denied))
AgoraTheme() {
ResultScreen(
progressErrorState = progressErrorState,
winnerDto = winnerDtoModel
) { event->
when(event) {
OnBackClick -> {
findNavController().popBackStack()
}
OnExportCSVClick -> {
id?.let {
electionDetailsViewModel.createExcelFile(requireContext(), it)
}
}
is OnShareClick -> {
electionDetailsViewModel.createImage(requireContext(), event.bitmap)
}
}
}
}
}
}
Expand Down
Loading

0 comments on commit 5f2b185

Please sign in to comment.