Homefragment.kt
Код: Выделить всё
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private lateinit var recentDocumentsAdapter: RecentDocumentsAdapter
private lateinit var documentViewModel: DocumentViewModel
private lateinit var progressIndicator: LinearProgressIndicator
private lateinit var analyticsManager: AnalyticsManager
private val isDebug = BuildConfig.DEBUG
private var recentDocumentsVisible = false
private val uiScope = CoroutineScope(Dispatchers.Main)
private val binding get() = _binding!!
companion object {
private const val REQUEST_CODE_IMPORT_PDF = 1001
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val homeViewModel =
ViewModelProvider(this)[HomeViewModel::class.java]
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val textView: TextView = binding.textHome
homeViewModel.text.observe(viewLifecycleOwner) {
textView.text = it
}
// Set up the RecyclerView (Recent Documents)
recentDocumentsAdapter = RecentDocumentsAdapter(requireContext())
binding.recentDocumentsGrid.layoutManager = GridLayoutManager(requireContext(), 3) // 3 columns
binding.recentDocumentsGrid.adapter = recentDocumentsAdapter
documentViewModel = ViewModelProvider(this)[DocumentViewModel::class.java]
analyticsManager = AnalyticsManager.getInstance(requireContext())
setHasOptionsMenu(true) // Enable options menu for this fragment
if (isDebug) {
Timber.plant(Timber.DebugTree())
}
val settingsManager = SettingsManager(requireContext()) // Create an instance here
//We don't plant the crashlytics tree here as we are handling that in the application class
// Timber.plant(CrashlyticsTree(settingsManager))
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
documentViewModel.setAnalyticsManager(requireContext())
PdfImportUtils.setAnalyticsManager(requireContext())
progressIndicator = view.findViewById(R.id.progressIndicator)
// Import PDFs Button click listener
val importPdfsButton = binding.importPdfsButton
importPdfsButton.setOnClickListener {
PdfImportUtils.startPdfImportIntent(this)
try {
analyticsManager.logEvent(eventName = "import_pdf_intent_started")
}catch(e: Exception){
Timber.e(e, "Exception in importPdfsButton:")
}
}
// Post a Runnable to show the progress bar
binding.root.post {
documentViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
progressIndicator.visibility = if (isLoading) View.VISIBLE else View.GONE
}
}
// Trigger loading AFTER setting up the observer
documentViewModel.loadRecentDocuments(requireContext())
documentViewModel.recentDocuments.observe(viewLifecycleOwner) { documents ->
recentDocumentsAdapter.updateRecentDocuments(documents)
recentDocumentsVisible = documents.isNotEmpty()
updateViewVisibility()
}
documentViewModel.documents.observe(viewLifecycleOwner) { documents ->
// Toggle visibility of empty state views
updateViewVisibility()
if (documents.isNullOrEmpty()) {
binding.importPdfsButton.layoutParams.width = // Make button full width
ViewGroup.LayoutParams.MATCH_PARENT
binding.importPdfsButton.textSize = "20".toFloat()
} else {
binding.importPdfsButton.layoutParams.width = // Reset button width
ViewGroup.LayoutParams.WRAP_CONTENT
binding.importPdfsButton.textSize = "16".toFloat()
}
}
}
private fun updateViewVisibility() {
if (documentViewModel.documents.value.isNullOrEmpty() && !recentDocumentsVisible) {
binding.noDocumentsTitleText.visibility = View.VISIBLE
binding.NoDocumentsSubtitleText.visibility = View.VISIBLE
binding.NoDocumentsIcon.visibility = View.VISIBLE
binding.recentDocumentsContainer.visibility = View.GONE
} else {
binding.noDocumentsTitleText.visibility = View.GONE
binding.NoDocumentsSubtitleText.visibility = View.GONE
binding.NoDocumentsIcon.visibility = View.GONE
binding.recentDocumentsContainer.visibility = View.VISIBLE
}
}
@Deprecated("Deprecated in Java")
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
menu.findItem(R.id.action_settings).setVisible(true)
inflater.inflate(R.menu.main, menu)
}
@Deprecated("Deprecated in Java")
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_settings -> {
_binding?.root?.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
val navController = findNavController() // Use findNavController() in Fragment
val settingsManager = SettingsManager(requireContext()) // Get Context in Fragment
val settings = settingsManager.loadSettings()
if (settings.userSignedin) { // User is signed in
navController.navigate(R.id.loggedInSettingsFragment)
} else { // User is not signed in
navController.navigate(R.id.settingsFragment)
}
try {
analyticsManager.logEvent(eventName = "settings_menu_item_clicked")
}catch(e: Exception){
Timber.e(e, "Exception in settings_menu_item_clicked:")
}
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
uiScope.cancel()
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onResume() {
super.onResume()
requireActivity().onBackPressedDispatcher.addCallback(
this, true // Handle even if other fragments have higher priority
) {
onBackPressed()
}
}
@RequiresApi(Build.VERSION_CODES.R)
private fun onBackPressed() {
val materialAlertDialogBuilder = MaterialAlertDialogBuilder(requireContext())
materialAlertDialogBuilder.setTitle("Exit App")
materialAlertDialogBuilder.setMessage("Are you sure you want to quit the app?")
_binding?.root?.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
try {
analyticsManager.logEvent(eventName = "user_confirmed_quit_app")
}catch(e:Exception){
Timber.e(e,"Exception in user_confirmed_quit_app:")
}
materialAlertDialogBuilder.setPositiveButton("Yes") { dialog, _ ->
_binding?.root?.performHapticFeedback(HapticFeedbackConstants.CONFIRM)
Timber.d("User confirmed quitting app")
requireActivity().finish() // Exit the app
dialog.dismiss() // Dismiss the dialog explicitly
}
materialAlertDialogBuilder.setNegativeButton("No") { dialog, _ ->
_binding?.root?.performHapticFeedback(HapticFeedbackConstants.REJECT)
Timber.d("User cancelled quitting app")
try {
analyticsManager.logEvent(eventName = "user_cancelled_quit_app")
}catch (e:Exception){
Timber.e(e,"Exception in user_cancelled_quit_app:")
}
dialog.dismiss() // Dismiss the dialog explicitly
}
materialAlertDialogBuilder.create().show()
}
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PdfImportUtils.REQUEST_CODE_IMPORT_PDF && resultCode == Activity.RESULT_OK) {
val urisToImport = if (data?.clipData != null) {
(0 until data.clipData!!.itemCount).map {
data.clipData!!.getItemAt(it).uri
}
} else {
listOfNotNull(data?.data)
}
//Use a coroutine to import the PDFs asynchronously
uiScope.launch {
progressIndicator.visibility = View.VISIBLE
withContext(Dispatchers.IO){
urisToImport.forEach { uri ->
PdfImportUtils.importPdfFromUri(requireContext(), uri, documentViewModel)
}
}
progressIndicator.visibility = View.GONE
}
try {
analyticsManager.logEvent(eventName = "pdf_imported", params = Bundle().apply {
putString("file_uri", data?.data?.toString() ?: "Multiple Files")
})
}catch(e:Exception){
Timber.e(e, "Exception in pdf_imported:")
}
} else {
try {
analyticsManager.logEvent(eventName = "pdf_import_failed", params = Bundle().apply {
putInt("request_code",requestCode)
putInt("result_code", resultCode)
})
}catch(e: Exception){
Timber.e(e,"Exception in pdf_import_failed:")
}
}
}
}
Код: Выделить всё
class RecentDocumentsAdapter(private val context: Context) :
RecyclerView.Adapter() {
private var recentDocuments: List = emptyList()
private val analyticsManager = AnalyticsManager.getInstance(context)
inner class RecentDocumentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val documentThumbnail: ImageView = itemView.findViewById(R.id.document_thumbnail)
val documentTitle: TextView = itemView.findViewById(R.id.document_title)
init {
// Add click listener to the entire itemView (icon and text)
itemView.setOnClickListener {
try {
val position = bindingAdapterPosition
if (position != RecyclerView.NO_POSITION) {
val document = recentDocuments[position]
analyticsManager.logEvent(eventName = "RecentDocumentsAdapter on click item at position: $position, name: ${document.name}")
openPdfViewer(document)
}
} catch(e: Exception){
Timber.e(e, "Exception in onItemClickListener in RecentDocumentsAdapter:")
}
}
}
private fun openPdfViewer(document: Document) {
try {
val bundle = Bundle()
bundle.putString("filename", document.name)
itemView.findNavController().navigate(R.id.PDFViewFragment, bundle)
analyticsManager.logEvent(eventName = "RecentDocumentsAdapter navigation to PDF viewer with file: ${document.name}")
}catch(e: Exception){
Timber.e(e, "Exception in openPdfViewer in RecentDocumentsAdapter:")
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecentDocumentViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.recent_document_item, parent, false)
return RecentDocumentViewHolder(itemView)
}
override fun onBindViewHolder(holder: RecentDocumentViewHolder, position: Int) {
try {
val document = recentDocuments[position]
holder.documentThumbnail.setImageResource(document.thumbnail) // Load thumbnail
holder.documentTitle.text = document.name
analyticsManager.logEvent(eventName = "RecentDocumentsAdapter Binding item at position: $position, name: ${document.name}")
} catch (e: Exception) {
Timber.e(e, "Exception in onBindViewHolder in RecentDocumentsAdapter:")
}
}
override fun getItemCount(): Int {
try{
analyticsManager.logEvent(eventName = "getItemCount in RecentDocumentsAdapter with document count: ${recentDocuments.size}")
return recentDocuments.size
}catch(e: Exception){
Timber.e(e, "Exception in getItemCount in RecentDocumentsAdapter:")
return recentDocuments.size
}
}
fun updateRecentDocuments(newDocuments: List) {
try {
analyticsManager.logEvent(eventName = "updateRecentDocuments with document count: ${newDocuments.size}")
val diffResult = DiffUtil.calculateDiff(DocumentDiffCallback(recentDocuments, newDocuments))
recentDocuments = newDocuments
diffResult.dispatchUpdatesTo(this)
}catch(e: Exception){
Timber.e(e, "Exception in updateRecentDocuments in RecentDocumentsAdapter:")
}
}
private class DocumentDiffCallback(
private val oldList: List,
private val newList: List
) : DiffUtil.Callback() {
override fun getOldListSize(): Int = oldList.size
override fun getNewListSize(): Int = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition].name == newList[newItemPosition].name
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
return oldItem == newItem
}
}
}
Код: Выделить всё
class DocumentViewModel : ViewModel() {
private val _recentDocuments = MutableLiveData()
val recentDocuments: LiveData = _recentDocuments
private val _isLoading = MutableLiveData()
val isLoading: LiveData = _isLoading
private val _documents = MutableLiveData()
val documents: MutableLiveData = _documents
private lateinit var analyticsManager: AnalyticsManager
fun setAnalyticsManager(context: Context) {
analyticsManager = AnalyticsManager.getInstance(context)
}
fun refreshDocuments(context: Context) {
viewModelScope.launch {
_isLoading.value = true
try {
val documentData = loadAllDocumentsFromStorage(context)
withContext(Dispatchers.Main) {
_documents.value = documentData.first
_recentDocuments.value = documentData.second
}
analyticsManager.logEvent(eventName = "Documents refreshed: ${documentData.first?.size}", params = null)
} catch (e: Exception) {
Timber.e(e, "Exception in refreshDocuments:")
}finally {
_isLoading.value = false
}
}
}
private suspend fun loadDocumentsFromStorage(context: Context): List? {
return withContext(Dispatchers.IO){
try {
val documentsDir = context.filesDir ?: return@withContext null // Handle null context
return@withContext documentsDir.listFiles()
?.filter { it.isFile && it.name.endsWith(".pdf") }
?.sortedByDescending { it.lastModified() } // Sort by last modified date
} catch (e: Exception) {
Timber.e(e, "Exception in loadDocumentsFromStorage:")
return@withContext null
}
}
}
suspend fun loadAllDocumentsFromStorage(context: Context): Pair {
return withContext(Dispatchers.IO) {
try {
val documentsDir = context.filesDir ?: return@withContext Pair(null, emptyList()) // Handle null context
val allDocuments = documentsDir.listFiles()
?.filter { it.isFile && it.name.endsWith(".pdf") }
?.sortedByDescending { it.lastModified() } ?: emptyList()
val recentDocuments = allDocuments
.take(9) // Take only the first 9 (most recent) documents
.map {
val thumbnailPath = R.drawable.ic_pdf
Timber.d("loading document with name ${it.name}")
Document(it.name, it.absolutePath, thumbnailPath, it.lastModified())
}
return@withContext Pair(allDocuments,recentDocuments)
} catch (e: Exception) {
Timber.e(e, "Exception in loadAllDocumentsFromStorage:")
return@withContext Pair(null, emptyList())
}
}
}
fun loadDocuments(context: Context) {
viewModelScope.launch {
_isLoading.value = true
try {
val documentData = loadAllDocumentsFromStorage(context)
withContext(Dispatchers.Main) {
_documents.value = documentData.first
_recentDocuments.value = documentData.second
analyticsManager.logEvent(eventName = "Documents loaded: ${documentData.first?.size}", params = null)
}
} catch (e: Exception) {
Timber.e(e, "Exception in loadDocuments:")
} finally {
_isLoading.value = false
}
}
}
// Load recent documents from storage
fun loadRecentDocuments(context: Context) {
viewModelScope.launch {
_isLoading.value = true // Indicate loading started
try {
val documentData = loadAllDocumentsFromStorage(context)
withContext(Dispatchers.Main) {
_recentDocuments.value = documentData.second
analyticsManager.logEvent(eventName = "Total Recent Documents loaded: ${documentData.second.size}", params = null)
}
} catch (e: Exception) {
Timber.e(e, "Exception in loadRecentDocuments:")
} finally {
_isLoading.value = false // Indicate loading finished
}
}
}
private suspend fun loadRecentDocumentsFromStorage(context: Context): List {
return loadAllDocumentsFromStorage(context).second
}
fun updateRecentDocumentsList(documents: List) {
_recentDocuments.value = documents
}
}
Код: Выделить всё
package com.psavarmattas.paperport.data
data class Document(
val name: String,
val path: String, // Store the file path
val thumbnail: Int, // Thumbnail file path
val timestamp: Long = System.currentTimeMillis() // Add a timestamp for sorting
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Document
if (name != other.name) return false
return true
}
override fun hashCode(): Int {
return name.hashCode()
}
}
Код: Выделить всё
private fun Context.savePdfToAppStorage(pdfUri: Uri) {
try {
// Get the directory for app's files
val appFilesDir = filesDir
// Find the next available filename with auto-increment
val newFileName = createUniqueFileInDirectory(appFilesDir)
// Create the output file path
val outputFile = File(appFilesDir, newFileName)
val inputStream = contentResolver.openInputStream(pdfUri) ?: return
val outputStream = FileOutputStream(outputFile)
inputStream.copyTo(outputStream)
Timber.d("PDF saved successfully to: $outputFile")
Toast.makeText(this, "PDF saved as: $newFileName", Toast.LENGTH_SHORT).show()
uiScope.launch{
documentViewModel.refreshDocuments(this@savePdfToAppStorage)
}
} catch (e: Exception) {
Timber.e(e, "Error saving PDF to App Storage:")
}
}
Код: Выделить всё
2025-01-06 15:18:30.277 7959-7996 EGL_emulation com.psavarmattas.paperport D app_time_stats: avg=13964.90ms min=2.79ms max=627647.50ms count=45
2025-01-06 15:18:30.295 7959-7959 MainActivity$onCreate com.psavarmattas.paperport D Loading Scanner
2025-01-06 15:18:30.297 7959-7959 MainActivity$onCreate com.psavarmattas.paperport D Loading Scanner
2025-01-06 15:18:30.329 7959-7959 MainActivity$onCreate com.psavarmattas.paperport D Scanner launched successfully
2025-01-06 15:18:30.329 7959-7959 MainActivity$onCreate com.psavarmattas.paperport D Scanner launched successfully
2025-01-06 15:18:30.335 7959-7959 FirebaseAppDistribution com.psavarmattas.paperport D NotificationsManager: Activity paused
2025-01-06 15:18:30.381 7959-7959 FirebaseAppDistribution com.psavarmattas.paperport D NotificationsManager: Activity resumed
2025-01-06 15:18:30.678 7959-7959 FirebaseAppDistribution com.psavarmattas.paperport D NotificationsManager: Activity paused
2025-01-06 15:18:32.587 7959-7959 VRI[MainActivity] com.psavarmattas.paperport D visibilityChanged oldVisibility=true newVisibility=false
2025-01-06 15:18:32.604 7959-7959 WindowOnBackDispatcher com.psavarmattas.paperport W sendCancelIfRunning: isInProgress=false callback=androidx.activity.OnBackPressedDispatcher$Api34Impl$createOnBackAnimationCallback$1@1a3bfc4
2025-01-06 15:18:32.726 7959-8457 FA com.psavarmattas.paperport I Application backgrounded at: timestamp_millis: 1736176710680
2025-01-06 15:18:39.465 7959-7959 FirebaseAppDistribution com.psavarmattas.paperport D NotificationsManager: Activity resumed
2025-01-06 15:18:39.685 7959-7959 FirebaseAppDistribution com.psavarmattas.paperport D NotificationsManager: Activity paused
2025-01-06 15:18:39.716 7959-7959 MainActivity com.psavarmattas.paperport D onActivityResult: requestCode=1437889791, resultCode=-1
2025-01-06 15:18:39.721 7959-7959 MainActivity com.psavarmattas.paperport D onActivityResult: requestCode=1437889791, resultCode=-1
2025-01-06 15:18:39.731 7959-7959 MainActivity com.psavarmattas.paperport D Receiving data
2025-01-06 15:18:39.736 7959-7959 MainActivity com.psavarmattas.paperport D Receiving data
2025-01-06 15:18:39.761 7959-7959 MainActivity com.psavarmattas.paperport D Data received
2025-01-06 15:18:39.763 7959-7959 MainActivity com.psavarmattas.paperport D Data received
2025-01-06 15:18:39.787 7959-7959 MainActivity com.psavarmattas.paperport D Retrieved PDF Uri: file:///data/user/0/com.psavarmattas.paperport/cache/mlkit_docscan_ui_client/7636159400400.pdf
2025-01-06 15:18:39.788 7959-7959 MainActivity com.psavarmattas.paperport D Retrieved PDF Uri: file:///data/user/0/com.psavarmattas.paperport/cache/mlkit_docscan_ui_client/7636159400400.pdf
2025-01-06 15:18:39.796 7959-7959 MainActivity com.psavarmattas.paperport D Page Count: 1
2025-01-06 15:18:39.796 7959-7959 MainActivity com.psavarmattas.paperport D Page Count: 1
2025-01-06 15:18:39.811 7959-7959 MainActivity com.psavarmattas.paperport D PDF saved successfully to: /data/user/0/com.psavarmattas.paperport/files/paperport_004.pdf
2025-01-06 15:18:39.812 7959-7959 MainActivity com.psavarmattas.paperport D PDF saved successfully to: /data/user/0/com.psavarmattas.paperport/files/paperport_004.pdf
2025-01-06 15:18:39.826 7959-7959 MainActivity com.psavarmattas.paperport D File saved successfully
2025-01-06 15:18:39.827 7959-7959 MainActivity com.psavarmattas.paperport D File saved successfully
2025-01-06 15:18:39.834 7959-7959 MainActivity com.psavarmattas.paperport D Delegating to fragment: NavHostFragment
2025-01-06 15:18:39.834 7959-7959 MainActivity com.psavarmattas.paperport D Delegating to fragment: NavHostFragment
2025-01-06 15:18:39.836 7959-7959 FirebaseAppDistribution com.psavarmattas.paperport D NotificationsManager: Activity resumed
2025-01-06 15:18:39.852 7959-7959 MainActivity com.psavarmattas.paperport D Auto update disabled
2025-01-06 15:18:39.853 7959-7959 MainActivity com.psavarmattas.paperport D Auto update disabled
2025-01-06 15:18:40.094 7959-8450 DocumentVi...romStorage com.psavarmattas.paperport D loading document with name paperport_004.pdf
2025-01-06 15:18:40.094 7959-8450 DocumentVi...romStorage com.psavarmattas.paperport D loading document with name paperport_004.pdf
2025-01-06 15:18:40.106 7959-8450 DocumentVi...romStorage com.psavarmattas.paperport D loading document with name paperport_003.pdf
2025-01-06 15:18:40.106 7959-8450 DocumentVi...romStorage com.psavarmattas.paperport D loading document with name paperport_003.pdf
2025-01-06 15:18:40.135 7959-8450 DocumentVi...romStorage com.psavarmattas.paperport D loading document with name paperport_002.pdf
2025-01-06 15:18:40.138 7959-8450 DocumentVi...romStorage com.psavarmattas.paperport D loading document with name paperport_002.pdf
2025-01-06 15:18:40.164 7959-8450 DocumentVi...romStorage com.psavarmattas.paperport D loading document with name paperport_001.pdf
2025-01-06 15:18:40.164 7959-8450 DocumentVi...romStorage com.psavarmattas.paperport D loading document with name paperport_001.pdf
2025-01-06 15:18:40.188 7959-7959 AnalyticsManager com.psavarmattas.paperport D Event: Documents refreshed: 4, Params: null
2025-01-06 15:18:40.188 7959-7959 AnalyticsManager com.psavarmattas.paperport D Event: Documents refreshed: 4, Params: null
2025-01-06 15:18:41.092 7959-7959 VRI[GmsDoc...eActivity] com.psavarmattas.paperport D visibilityChanged oldVisibility=true newVisibility=false
2025-01-06 15:18:41.112 7959-7959 WindowOnBackDispatcher com.psavarmattas.paperport W sendCancelIfRunning: isInProgress=false callback=android.app.Activity$$ExternalSyntheticLambda0@990b52
Подробнее здесь: https://stackoverflow.com/questions/793 ... by-adapter
Мобильная версия