messenger-android

Android graphical user interfaces for GNUnet Messenger
Log | Files | Refs | README | LICENSE

AccountListFragment.kt (7176B)


      1 /*
      2    This file is part of GNUnet.
      3    Copyright (C) 2021--2025 GNUnet e.V.
      4 
      5    GNUnet is free software: you can redistribute it and/or modify it
      6    under the terms of the GNU Affero General Public License as published
      7    by the Free Software Foundation, either version 3 of the License,
      8    or (at your option) any later version.
      9 
     10    GNUnet is distributed in the hope that it will be useful, but
     11    WITHOUT ANY WARRANTY; without even the implied warranty of
     12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13    Affero General Public License for more details.
     14 
     15    You should have received a copy of the GNU Affero General Public License
     16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18    SPDX-License-Identifier: AGPL3.0-or-later
     19  */
     20 /*
     21  * @author t3sserakt
     22  * @file GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/account/AccountListFragment.kt
     23  */
     24 
     25 package org.gnunet.gnunetmessenger.ui.account
     26 
     27 import android.os.Bundle
     28 import android.util.Log
     29 import android.view.LayoutInflater
     30 import android.view.View
     31 import android.view.ViewGroup
     32 import android.widget.Button
     33 import android.widget.ProgressBar
     34 import android.widget.TextView
     35 import androidx.core.view.isGone
     36 import androidx.core.view.isVisible
     37 import androidx.fragment.app.Fragment
     38 import androidx.lifecycle.lifecycleScope
     39 import androidx.navigation.fragment.findNavController
     40 import androidx.recyclerview.widget.LinearLayoutManager
     41 import androidx.recyclerview.widget.RecyclerView
     42 import kotlinx.coroutines.Job
     43 import kotlinx.coroutines.launch
     44 import org.gnunet.gnunetmessenger.MainActivity
     45 import org.gnunet.gnunetmessenger.R
     46 import org.gnunet.gnunetmessenger.model.ChatAccount
     47 import org.gnunet.gnunetmessenger.ui.adapters.AccountAdapter
     48 
     49 class AccountListFragment : Fragment() {
     50 
     51     private lateinit var recycler: RecyclerView
     52     private lateinit var createButton: Button
     53     private lateinit var loadingIndicator: ProgressBar
     54     private lateinit var statusText: TextView
     55     private lateinit var adapter: AccountAdapter
     56 
     57     private val accounts = mutableListOf<ChatAccount>()
     58     private var refreshCollectorJob: Job? = null
     59 
     60     companion object {
     61         private const val TAG = "AccountListFragment"
     62     }
     63 
     64     override fun onCreateView(
     65         inflater: LayoutInflater,
     66         container: ViewGroup?,
     67         savedInstanceState: Bundle?
     68     ): View {
     69         val view = inflater.inflate(R.layout.fragment_account_list, container, false)
     70         val activity = requireActivity() as MainActivity
     71 
     72         recycler = view.findViewById(R.id.account_recycler)
     73         createButton = view.findViewById(R.id.btn_create_account)
     74         loadingIndicator = view.findViewById(R.id.account_loading_indicator)
     75         statusText = view.findViewById(R.id.account_status_text)
     76 
     77         adapter = AccountAdapter { selectedAccount ->
     78             viewLifecycleOwner.lifecycleScope.launch {
     79                 try {
     80                     // Multi-handle: spawn-or-reuse a per-account session.
     81                     // Old sessions stay live in the background so a lobby
     82                     // host survives the switch and can complete the pairing
     83                     // handshake when the joiner arrives.
     84                     activity.switchToSession(selectedAccount)
     85                 } catch (t: Throwable) {
     86                     Log.e(TAG, "switchToSession failed", t)
     87                     showError(getString(R.string.account_connect_failed))
     88                     return@launch
     89                 }
     90 
     91                 val action =
     92                     AccountListFragmentDirections.actionAccountListFragmentToAccountOverviewFragment(
     93                         account = selectedAccount
     94                     )
     95                 findNavController().navigate(action)
     96             }
     97         }
     98 
     99         recycler.layoutManager = LinearLayoutManager(context)
    100         recycler.adapter = adapter
    101 
    102         createButton.setOnClickListener {
    103             val action =
    104                 AccountListFragmentDirections.actionAccountListFragmentToCreateAccountFragment()
    105             findNavController().navigate(action)
    106         }
    107 
    108         showLoading(getString(R.string.connecting_to_gnunet))
    109 
    110         return view
    111     }
    112 
    113     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    114         super.onViewCreated(view, savedInstanceState)
    115 
    116         val activity = requireActivity() as MainActivity
    117 
    118         viewLifecycleOwner.lifecycleScope.launch {
    119             try {
    120                 val handle = activity.awaitInitialDataReady()
    121                 Log.d(TAG, "Initial refresh received, loading accounts for handle=${handle.pointer}")
    122 
    123                 createButton.isEnabled = true
    124                 reloadAccounts(showLoading = true)
    125 
    126                 refreshCollectorJob?.cancel()
    127                 refreshCollectorJob = viewLifecycleOwner.lifecycleScope.launch {
    128                     activity.accountRefreshFlow().collect {
    129                         Log.d(TAG, "Account refresh event received")
    130                         reloadAccounts(showLoading = false)
    131                     }
    132                 }
    133             } catch (t: Throwable) {
    134                 Log.e(TAG, "Failed to initialize account list", t)
    135                 showError(getString(R.string.gnunet_connection_failed))
    136             }
    137         }
    138     }
    139 
    140     override fun onDestroyView() {
    141         refreshCollectorJob?.cancel()
    142         refreshCollectorJob = null
    143         super.onDestroyView()
    144     }
    145 
    146     private suspend fun reloadAccounts(showLoading: Boolean) {
    147         val activity = requireActivity() as MainActivity
    148         val gnunetChat = activity.getGnunetChatInstance()
    149         val handle = activity.getChatHandle()
    150 
    151         if (showLoading) {
    152             showLoading(getString(R.string.loading_accounts))
    153         }
    154 
    155         try {
    156             Log.d(TAG, "listAccounts(): handle=${handle.pointer}")
    157             val refreshedAccounts = gnunetChat.listAccounts(handle).map { account ->
    158                 account.key = gnunetChat.getProfileKey(handle)
    159                 account
    160             }
    161 
    162             accounts.clear()
    163             accounts.addAll(refreshedAccounts)
    164             adapter.submitList(accounts.toList())
    165 
    166             loadingIndicator.isGone = true
    167             createButton.isEnabled = true
    168 
    169             if (accounts.isEmpty()) {
    170                 recycler.isGone = true
    171                 statusText.isVisible = true
    172                 statusText.text = getString(R.string.no_accounts_available)
    173             } else {
    174                 statusText.isGone = true
    175                 recycler.isVisible = true
    176             }
    177         } catch (t: Throwable) {
    178             Log.e(TAG, "reloadAccounts failed", t)
    179             showError(getString(R.string.account_list_load_failed))
    180         }
    181     }
    182 
    183     private fun showLoading(message: String) {
    184         loadingIndicator.isVisible = true
    185         statusText.isVisible = true
    186         statusText.text = message
    187         recycler.isGone = true
    188         createButton.isEnabled = false
    189     }
    190 
    191     private fun showError(message: String) {
    192         loadingIndicator.isGone = true
    193         statusText.isVisible = true
    194         statusText.text = message
    195         recycler.isGone = true
    196         createButton.isEnabled = false
    197     }
    198 }