Представление Recycler не обновляется при добавлении нового элемента адаптеромAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 Представление Recycler не обновляется при добавлении нового элемента адаптером

Сообщение Anonymous »

Я пытался сделать несколько вещей, чтобы это заработало, но сейчас застрял. У меня есть RecyclerView, который должен обновляться при сканировании или импорте нового документа. Он не обновляется в пользовательском интерфейсе, но журналы показывают, что документы загружены. Вот мой код.
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:")
}
}
}
}
RecentDocumentsAdapter.kt

Код: Выделить всё

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
}
}
}
DocumentViewModel.kt

Код: Выделить всё

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
}
}
Document.kt

Код: Выделить всё

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()
}
}
MainActivity.kt

Код: Выделить всё

    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
Как видно из приведенных выше журналов, документ 4 после сканирования загружается, но только в журналах, а не в пользовательском интерфейсе, только до документа 3.


Подробнее здесь: https://stackoverflow.com/questions/793 ... by-adapter
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»